Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342// Database, 23.07.09.17.33// Manages database connections to MongoDB.// And has application-specific queries.
import { MongoClient, ObjectId, ServerApiVersion } from "mongodb";
let client;let clientPromise; // For connection reuse in serverless
async function connect() { // Read environment variables at runtime, not at module load time const mongoDBConnectionString = process.env.MONGODB_CONNECTION_STRING; const mongoDBName = process.env.MONGODB_NAME; // Validate environment variables if (!mongoDBConnectionString) { throw new Error('MONGODB_CONNECTION_STRING environment variable is not set'); } if (!mongoDBName) { throw new Error('MONGODB_NAME environment variable is not set'); }
// Reuse existing connection if available (critical for serverless) if (client) { try { // Verify connection is still alive await client.db().admin().ping(); console.log(`♻️ Reusing existing MongoDB connection`); return { db: client.db(mongoDBName), disconnect }; } catch (e) { // Connection dead, clear it console.log(`⚠️ Existing connection dead, reconnecting...`); client = null; clientPromise = null; } }
// If a connection is in progress, wait for it if (clientPromise) { try { client = await clientPromise; return { db: client.db(mongoDBName), disconnect }; } catch (e) { clientPromise = null; } }
// Try multiple connection strategies // TLS is controlled by the connection string (mongodb+srv:// defaults to TLS, // mongodb:// defaults to no TLS). Don't force tls: true here. const strategies = [ { name: 'standard', options: { serverSelectionTimeoutMS: 10000, connectTimeoutMS: 10000, socketTimeoutMS: 45000, maxPoolSize: 10, minPoolSize: 1, } }, { name: 'direct', options: { directConnection: true, serverSelectionTimeoutMS: 5000, connectTimeoutMS: 5000, socketTimeoutMS: 30000, maxPoolSize: 10, minPoolSize: 1, } }, { name: 'minimal', options: { serverSelectionTimeoutMS: 5000, connectTimeoutMS: 5000, maxPoolSize: 10, minPoolSize: 1, } } ];
let lastError; for (const strategy of strategies) { try { const newClient = new MongoClient(mongoDBConnectionString, strategy.options); clientPromise = newClient.connect(); client = await clientPromise;
console.log(`✅ MongoDB connected successfully (${strategy.name})`); const db = client.db(mongoDBName); return { db, disconnect }; } catch (error) { console.log(`❌ Connection failed (${strategy.name}):`, error.message); lastError = error; clientPromise = null; if (client) { try { await client.close(); } catch (e) { /* ignore */ } client = null; } } } // All strategies failed throw lastError;}
async function disconnect() { // In serverless, don't actually close - let connection be reused // The connection will be cleaned up when the Lambda container is recycled // if (client) await client.close?.();}
// Re-usable calls to the application.async function moodFor(sub, database) { const collection = database.db.collection("moods"); const record = ( await collection.find({ user: sub }).sort({ when: -1 }).limit(1).toArray() )[0]; // Most recent mood or `undefined` if none are found. if (record?.mood) { record.mood = record.mood.replaceAll("\\n", "\n"); // Replace \\n -> \n. } return record;}
// Get all moods with handles included.// 🗒️ There will be no `\n` filtering here, so it should happen on client rendering.async function allMoods(database, handles = null) { const collection = database.db.collection("moods"); const matchStage = { deleted: { $ne: true } };
// Refactored to support comma-separated list of handles if (handles) { const handleList = handles .split(",") .map((h) => h.trim().replace(/^@/, "")) .filter(Boolean); matchStage["handleInfo.handle"] = { $in: handleList }; }
const pipeline = [ { $lookup: { from: "@handles", localField: "user", foreignField: "_id", as: "handleInfo", }, }, { $unwind: "$handleInfo" }, { $match: matchStage, }, { $project: { _id: 0, mood: 1, when: 1, handle: { $concat: ["@", "$handleInfo.handle"] }, atproto: 1, }, }, { $sort: { when: -1 } }, ];
const records = await collection.aggregate(pipeline).toArray(); return records;}
/** * Get a single mood by handle and ATProto rkey (for permalinks) * @param {Object} database - MongoDB connection * @param {string} handle - Handle like "@jeffrey" or "jeffrey" * @param {string} rkey - ATProto record key * @returns {Promise<Object|null>} Mood object or null */async function getMoodByRkey(database, handle, rkey) { const collection = database.db.collection("moods");
// Normalize handle (remove @ if present) const normalizedHandle = handle.replace(/^@/, "");
const pipeline = [ { $lookup: { from: "@handles", localField: "user", foreignField: "_id", as: "handleInfo", }, }, { $unwind: "$handleInfo" }, { $match: { "handleInfo.handle": normalizedHandle, "atproto.rkey": rkey, deleted: { $ne: true }, }, }, { $project: { _id: 0, mood: 1, when: 1, handle: { $concat: ["@", "$handleInfo.handle"] }, atproto: 1, bluesky: 1, }, }, ];
const records = await collection.aggregate(pipeline).toArray(); return records[0] || null;}
export { connect, ObjectId, moodFor, allMoods, getMoodByRkey };
// Demo code from MongoDB's connection page: (23.08.15.19.59)
// Create a MongoClient with a MongoClientOptions object to set the Stable API version// const client = new MongoClient(uri, {// serverApi: {// version: ServerApiVersion.v1,// strict: true,// deprecationErrors: true,// }// });
// async function run() {// try {// // Connect the client to the server (optional starting in v4.7)// await client.connect();// // Send a ping to confirm a successful connection// await client.db("admin").command({ ping: 1 });// console.log("Pinged your deployment. You successfully connected to MongoDB!");// } finally {// // Ensures that the client will close when you finish/error// await client.close();// }// }// run().catch(console.dir);
// 🧻🖌️ Paintings media migrations:
import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3";
async function listAndSaveMedia(mediaType) { if (!["painting", "piece"].includes(mediaType)) { throw new Error( 'Invalid media type provided. Expected "painting" or "piece"', ); }
const s3User = new S3Client({ endpoint: "https://" + process.env.USER_ENDPOINT, region: "us-east-1", credentials: { accessKeyId: process.env.ART_KEY, secretAccessKey: process.env.ART_SECRET, }, });
const { db, disconnect } = await connect(); const collection = db.collection(mediaType + "s"); // Adjust collection name based on media type await collection.createIndex({ user: 1 }); await collection.createIndex({ when: 1 }); await collection.createIndex({ slug: 1 }); await collection.createIndex({ slug: 1, user: 1 }, { unique: true });
// Iterate through each user's top-level directory const userDirectories = await listDirectories({ s3: s3User, bucket: process.env.USER_SPACE_NAME, });
for (const dir of userDirectories) { // Check for `auth0` prefix and list its subdirectories if (dir.startsWith("auth0")) { console.log("Dir:", dir);
const subDirectories = await listDirectories( { s3: s3User, bucket: process.env.USER_SPACE_NAME }, dir, );
// Check if the media type subdirectory exists if (subDirectories.includes(dir + mediaType + "/")) { const files = await listFiles( { s3: s3User, bucket: process.env.USER_SPACE_NAME }, dir + mediaType + "/", );
for (const file of files) { const extension = mediaType === "painting" ? ".png" : ".mjs"; // TODO: Eventually add other media types...
if (file.endsWith(extension)) { const slug = file.split("/").pop().replace(extension, ""); const user = dir.split("/")[0]; const existingRecord = await collection.findOne({ slug, user }); if (!existingRecord) { const record = { slug, user, when: new Date(), }; await collection.insertOne(record); console.log(`✅ Added ${mediaType} entry for:`, slug); } else { console.log(`⚠️ ${mediaType} already exists for:`, slug); } } } } } }
disconnect();}
// Helper function to list directories for a given S3 clientasync function listDirectories(client, prefix = "") { const params = { Bucket: client.bucket, Delimiter: "/", Prefix: prefix }; const response = await client.s3.send(new ListObjectsV2Command(params)); return response.CommonPrefixes ? response.CommonPrefixes.map((prefix) => prefix.Prefix) : [];}
// Helper function to list files for a given S3 client and prefixasync function listFiles(client, prefix) { const params = { Bucket: client.bucket, Prefix: prefix }; const response = await client.s3.send(new ListObjectsV2Command(params)); return response.Contents.map((file) => file.Key);}
export { listAndSaveMedia };