Files
spoon/packages/backend/convex/boxSettings.ts
T

78 lines
2.5 KiB
TypeScript

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 });
}
},
});