feat(backend): boxSettings table + box password actions
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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 });
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user