Monorepo for Aesthetic.Computer aesthetic.computer
at main 340 lines 8.7 kB view raw
1// plugins.mjs, 26.02.12 2// Manage Max for Live plugin versions and downloads 3// Collection: aesthetic.plugins 4// 5// Code Prefix: 6// & - M4L Plugins (e.g., &np123a for notepat v1.2.3) 7 8// Schema: 9// { 10// _id: ObjectId, 11// code: string, // Unique short code (6 chars, e.g., "np123a") 12// device: { 13// name: string, // "notepat", "pedal", "metronome" 14// displayName: string, // "AC 🟪 Notepat" 15// category: "instrument" | "effect" | "midi", 16// icon: string, // Emoji or icon identifier 17// }, 18// version: { 19// major: number, 20// minor: number, 21// patch: number, 22// string: string, // "1.2.3" 23// }, 24// m4l: { 25// downloadUrl: string, // "https://assets.aesthetic.computer/m4l/..." 26// fileName: string, // "AC 🟪 notepat (aesthetic.computer).amxd" 27// fileSize: number, // Bytes 28// checksum: string, // SHA256 29// }, 30// metadata: { 31// description: string, 32// piece: string, // AC piece name (e.g., "notepat") 33// width: number, 34// height: number, 35// releaseNotes: string, 36// }, 37// stats: { 38// downloads: number, 39// views: number, 40// }, 41// createdAt: Date, 42// updatedAt: Date, 43// publishedAt: Date, 44// deprecated: boolean, 45// } 46 47// Generate a short unique code for plugins 48function generateCode(deviceName, version, length = 6) { 49 const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; 50 // Use first 2 chars of device name + version numbers + random chars 51 const prefix = deviceName.substring(0, 2); 52 const versionStr = version.string.replace(/\./g, ''); 53 let code = prefix + versionStr; 54 55 // Pad with random chars if needed 56 while (code.length < length) { 57 code += chars[Math.floor(Math.random() * chars.length)]; 58 } 59 60 return code.substring(0, length); 61} 62 63/** 64 * Create a new plugin record in the database 65 * @param {Object} db - Database connection 66 * @param {Object} pluginData - Plugin data to insert 67 * @returns {Object} Created plugin document 68 */ 69export async function createPlugin(db, pluginData) { 70 const plugins = db.collection('plugins'); 71 72 // Generate unique code if not provided 73 if (!pluginData.code) { 74 pluginData.code = generateCode( 75 pluginData.device.name, 76 pluginData.version 77 ); 78 79 // Ensure uniqueness 80 let exists = await plugins.findOne({ code: pluginData.code }); 81 while (exists) { 82 pluginData.code = generateCode( 83 pluginData.device.name, 84 pluginData.version 85 ); 86 exists = await plugins.findOne({ code: pluginData.code }); 87 } 88 } 89 90 // Set timestamps 91 const now = new Date(); 92 pluginData.createdAt = now; 93 pluginData.updatedAt = now; 94 95 // Initialize stats if not provided 96 if (!pluginData.stats) { 97 pluginData.stats = { downloads: 0, views: 0 }; 98 } 99 100 // Insert into database 101 const result = await plugins.insertOne(pluginData); 102 pluginData._id = result.insertedId; 103 104 console.log(`✅ Created plugin: ${pluginData.device.displayName} v${pluginData.version.string} (${pluginData.code})`); 105 106 return pluginData; 107} 108 109/** 110 * Get the latest version of each plugin 111 * @param {Object} db - Database connection 112 * @returns {Array} Array of latest plugin versions 113 */ 114export async function getLatestPlugins(db) { 115 const plugins = db.collection('plugins'); 116 117 // Aggregation pipeline to get latest version of each device 118 const pipeline = [ 119 // Filter out deprecated plugins 120 { $match: { deprecated: { $ne: true } } }, 121 122 // Sort by version descending 123 { $sort: { 124 "device.name": 1, 125 "version.major": -1, 126 "version.minor": -1, 127 "version.patch": -1 128 }}, 129 130 // Group by device name and take first (highest version) 131 { 132 $group: { 133 _id: "$device.name", 134 latestPlugin: { $first: "$$ROOT" } 135 } 136 }, 137 138 // Replace root with the plugin document 139 { $replaceRoot: { newRoot: "$latestPlugin" } }, 140 141 // Sort by device name for consistent ordering 142 { $sort: { "device.name": 1 } }, 143 144 // Project only needed fields (exclude MongoDB _id) 145 { 146 $project: { 147 _id: 0, 148 code: 1, 149 device: 1, 150 version: 1, 151 m4l: 1, 152 metadata: 1, 153 stats: 1, 154 publishedAt: 1 155 } 156 } 157 ]; 158 159 const result = await plugins.aggregate(pipeline).toArray(); 160 return result; 161} 162 163/** 164 * Get a specific plugin by code 165 * @param {Object} db - Database connection 166 * @param {string} code - Plugin code 167 * @returns {Object|null} Plugin document or null 168 */ 169export async function getPluginByCode(db, code) { 170 const plugins = db.collection('plugins'); 171 return await plugins.findOne({ code }, { projection: { _id: 0 } }); 172} 173 174/** 175 * Get all versions of a specific device 176 * @param {Object} db - Database connection 177 * @param {string} deviceName - Device name (e.g., "notepat") 178 * @returns {Array} Array of all versions sorted by version descending 179 */ 180export async function getAllVersions(db, deviceName) { 181 const plugins = db.collection('plugins'); 182 183 const versions = await plugins.find( 184 { "device.name": deviceName }, 185 { projection: { _id: 0 } } 186 ) 187 .sort({ 188 "version.major": -1, 189 "version.minor": -1, 190 "version.patch": -1 191 }) 192 .toArray(); 193 194 return versions; 195} 196 197/** 198 * Increment download counter for a plugin 199 * @param {Object} db - Database connection 200 * @param {string} code - Plugin code 201 * @returns {Object} Update result 202 */ 203export async function incrementDownloads(db, code) { 204 const plugins = db.collection('plugins'); 205 206 const result = await plugins.updateOne( 207 { code }, 208 { 209 $inc: { "stats.downloads": 1 }, 210 $set: { updatedAt: new Date() } 211 } 212 ); 213 214 return result; 215} 216 217/** 218 * Increment view counter for a plugin 219 * @param {Object} db - Database connection 220 * @param {string} code - Plugin code 221 * @returns {Object} Update result 222 */ 223export async function incrementViews(db, code) { 224 const plugins = db.collection('plugins'); 225 226 const result = await plugins.updateOne( 227 { code }, 228 { 229 $inc: { "stats.views": 1 }, 230 $set: { updatedAt: new Date() } 231 } 232 ); 233 234 return result; 235} 236 237/** 238 * Mark a plugin as deprecated 239 * @param {Object} db - Database connection 240 * @param {string} code - Plugin code 241 * @returns {Object} Update result 242 */ 243export async function deprecatePlugin(db, code) { 244 const plugins = db.collection('plugins'); 245 246 const result = await plugins.updateOne( 247 { code }, 248 { 249 $set: { 250 deprecated: true, 251 updatedAt: new Date() 252 } 253 } 254 ); 255 256 return result; 257} 258 259/** 260 * Update plugin metadata 261 * @param {Object} db - Database connection 262 * @param {string} code - Plugin code 263 * @param {Object} updates - Fields to update 264 * @returns {Object} Update result 265 */ 266export async function updatePlugin(db, code, updates) { 267 const plugins = db.collection('plugins'); 268 269 const result = await plugins.updateOne( 270 { code }, 271 { 272 $set: { 273 ...updates, 274 updatedAt: new Date() 275 } 276 } 277 ); 278 279 return result; 280} 281 282/** 283 * Search plugins by category 284 * @param {Object} db - Database connection 285 * @param {string} category - Plugin category ("instrument", "effect", "midi") 286 * @returns {Array} Array of matching plugins 287 */ 288export async function getPluginsByCategory(db, category) { 289 const plugins = db.collection('plugins'); 290 291 // Get latest version of each device in category 292 const pipeline = [ 293 { $match: { 294 "device.category": category, 295 deprecated: { $ne: true } 296 }}, 297 { $sort: { 298 "device.name": 1, 299 "version.major": -1, 300 "version.minor": -1, 301 "version.patch": -1 302 }}, 303 { 304 $group: { 305 _id: "$device.name", 306 latestPlugin: { $first: "$$ROOT" } 307 } 308 }, 309 { $replaceRoot: { newRoot: "$latestPlugin" } }, 310 { $sort: { "device.name": 1 } }, 311 { $project: { _id: 0 } } 312 ]; 313 314 return await plugins.aggregate(pipeline).toArray(); 315} 316 317/** 318 * Create indexes for the plugins collection 319 * This should be run once during setup 320 * @param {Object} db - Database connection 321 */ 322export async function createIndexes(db) { 323 const plugins = db.collection('plugins'); 324 325 await plugins.createIndex({ code: 1 }, { unique: true }); 326 await plugins.createIndex({ "device.name": 1 }); 327 await plugins.createIndex({ "device.category": 1 }); 328 await plugins.createIndex({ publishedAt: -1 }); 329 await plugins.createIndex({ 330 "version.major": 1, 331 "version.minor": 1, 332 "version.patch": 1 333 }); 334 await plugins.createIndex({ updatedAt: -1 }); 335 await plugins.createIndex({ deprecated: 1 }); 336 337 console.log("✅ Created indexes for plugins collection"); 338} 339 340export { generateCode };