78 lines
2.5 KiB
TypeScript
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 });
|
|
}
|
|
},
|
|
});
|