import { v } from 'convex/values'; import { internalMutation, internalQuery, query } from './_generated/server'; import { deriveHomeUsername, getRequiredUserId } from './model'; // Per-user box account settings. The password itself is write-only from the // client's perspective: browsers only ever learn whether one is set; the // plaintext flows exclusively to the worker via boxSettingsNode. export const hasMine = query({ args: {}, handler: async (ctx) => { const ownerId = await getRequiredUserId(ctx); const row = await ctx.db .query('boxSettings') .withIndex('by_owner', (q) => q.eq('ownerId', ownerId)) .unique(); return { hasPassword: Boolean(row?.boxPasswordEncrypted) }; }, }); // Resolve the authed caller's owner id + home username (same derivation as // userEnvironment.getMine) for the Node action, which cannot touch ctx.db. export const resolveOwnerInternal = internalQuery({ args: {}, handler: async (ctx) => { const ownerId = await getRequiredUserId(ctx); const [user, settings] = await Promise.all([ ctx.db.get(ownerId), ctx.db .query('userEnvironment') .withIndex('by_owner', (q) => q.eq('ownerId', ownerId)) .unique(), ]); return { ownerId, username: settings?.homeUsername ?? deriveHomeUsername(user?.name), }; }, }); // Worker-facing lookup at box creation. `.first()` (not `.unique()`): derived // usernames are not guaranteed globally unique, and a throw here would brick // box creation for both colliding users. export const getByUsernameInternal = internalQuery({ args: { username: v.string() }, handler: async (ctx, args) => await ctx.db .query('boxSettings') .withIndex('by_username', (q) => q.eq('username', args.username)) .first(), }); export const upsertMineInternal = internalMutation({ args: { ownerId: v.id('users'), username: v.string(), boxPasswordEncrypted: v.optional(v.string()), }, handler: async (ctx, args) => { const existing = await ctx.db .query('boxSettings') .withIndex('by_owner', (q) => q.eq('ownerId', args.ownerId)) .unique(); const patch = { username: args.username, // Explicit undefined clears the field on patch (password removed). boxPasswordEncrypted: args.boxPasswordEncrypted, updatedAt: Date.now(), }; if (existing) { await ctx.db.patch(existing._id, patch); } else { await ctx.db.insert('boxSettings', { ownerId: args.ownerId, ...patch }); } }, });