import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; const savedMediaValidator = v.object({ _id: v.id("savedMedia"), _creationTime: v.number(), userId: v.id("userProfiles"), url: v.string(), kind: v.string(), filename: v.string(), mimeType: v.optional(v.string()), width: v.optional(v.number()), height: v.optional(v.number()), size: v.optional(v.number()), encryptionKey: v.string(), encryptionIv: v.string(), savedAt: v.number(), }); /** * Save (favorite) an attachment to the user's media library. Idempotent * per (userId, url) — re-saving the same media just updates the * existing row's filename / metadata in case it changed. */ export const save = mutation({ args: { userId: v.id("userProfiles"), url: v.string(), kind: v.string(), filename: v.string(), mimeType: v.optional(v.string()), width: v.optional(v.number()), height: v.optional(v.number()), size: v.optional(v.number()), encryptionKey: v.string(), encryptionIv: v.string(), }, returns: v.null(), handler: async (ctx, args) => { const existing = await ctx.db .query("savedMedia") .withIndex("by_user_and_url", (q) => q.eq("userId", args.userId).eq("url", args.url), ) .unique(); if (existing) { await ctx.db.patch(existing._id, { kind: args.kind, filename: args.filename, mimeType: args.mimeType, width: args.width, height: args.height, size: args.size, encryptionKey: args.encryptionKey, encryptionIv: args.encryptionIv, }); return null; } await ctx.db.insert("savedMedia", { userId: args.userId, url: args.url, kind: args.kind, filename: args.filename, mimeType: args.mimeType, width: args.width, height: args.height, size: args.size, encryptionKey: args.encryptionKey, encryptionIv: args.encryptionIv, savedAt: Date.now(), }); return null; }, }); /** * Remove a saved-media entry by (userId, url). No-op if not present. */ export const remove = mutation({ args: { userId: v.id("userProfiles"), url: v.string(), }, returns: v.null(), handler: async (ctx, args) => { const existing = await ctx.db .query("savedMedia") .withIndex("by_user_and_url", (q) => q.eq("userId", args.userId).eq("url", args.url), ) .unique(); if (existing) await ctx.db.delete(existing._id); return null; }, }); /** * List the user's saved media in reverse-chron order (newest first). */ export const list = query({ args: { userId: v.id("userProfiles"), }, returns: v.array(savedMediaValidator), handler: async (ctx, args) => { const items = await ctx.db .query("savedMedia") .withIndex("by_user", (q) => q.eq("userId", args.userId)) .order("desc") .collect(); return items; }, });