Monorepo for Aesthetic.Computer
aesthetic.computer
1// Hearts, 26.02.25
2// Toggle and count hearts for any content type.
3// Used by chat-manager (session server) and the chat-heart Netlify function.
4
5export async function ensureIndexes(db) {
6 const collection = db.collection("hearts");
7 try {
8 await collection.createIndex(
9 { type: 1, for: 1, user: 1 },
10 { unique: true, background: true, name: "hearts_type_for_user_unique" },
11 );
12 await collection.createIndex(
13 { type: 1, for: 1 },
14 { background: true, name: "hearts_type_for" },
15 );
16 } catch {
17 // Indexes already exist — safe to ignore
18 }
19}
20
21// Toggle a heart. Returns { hearted: bool, count: number }.
22export async function toggleHeart(db, { user, type, for: forId }) {
23 const collection = db.collection("hearts");
24 let hearted;
25 try {
26 await collection.insertOne({ user, type, for: forId, when: new Date() });
27 hearted = true;
28 } catch (err) {
29 if (err.code === 11000) {
30 await collection.deleteOne({ user, type, for: forId });
31 hearted = false;
32 } else {
33 throw err;
34 }
35 }
36 const count = await collection.countDocuments({ type, for: forId });
37 return { hearted, count };
38}
39
40// Bulk-count hearts for a set of ids within a type.
41// Returns { [forId]: count } — missing ids have count 0.
42export async function countHearts(db, type, forIds) {
43 if (!forIds.length) return {};
44 const collection = db.collection("hearts");
45 const results = await collection
46 .aggregate([
47 { $match: { type, for: { $in: forIds } } },
48 { $group: { _id: "$for", count: { $sum: 1 } } },
49 ])
50 .toArray();
51 return Object.fromEntries(results.map((r) => [r._id, r.count]));
52}