From 65afb5a7214e7e41deea6a96371d6a65d1fa34ee Mon Sep 17 00:00:00 2001 From: JCEEE <0xjceee@proton.me> Date: Fri, 11 Sep 2026 07:40:38 +0100 Subject: [PATCH] update migration of user fields --- frontend/src/lib/server/migrate.ts | 81 ++++++++++++++----- .../src/routes/[fam]/[username]/+page.svelte | 2 +- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/frontend/src/lib/server/migrate.ts b/frontend/src/lib/server/migrate.ts index 77a2e7b..a5305ea 100644 --- a/frontend/src/lib/server/migrate.ts +++ b/frontend/src/lib/server/migrate.ts @@ -407,6 +407,34 @@ async function ensureSchema(): Promise { console.log('[migrate] Schema bootstrapped.'); } +// Add theme + newer auth fields to `users` on installs where the collection +// already exists (fresh installs get them via ensureUsers). Idempotent — +// only adds missing fields. Mirrors ensureFamFields. +async function ensureUserFields(): Promise { + const usersCol = await getCollection('users'); + if (!usersCol) return; + const has = (n: string) => usersCol.fields.some((f: any) => f.name === n); + const needed: any[] = []; + if (!has('pattern')) { + needed.push({ name: 'pattern', type: 'text', required: false }); + } + if (!has('themeSize')) { + needed.push({ name: 'themeSize', type: 'text', required: false }); + } + if (!has('themeOpacity')) { + needed.push({ name: 'themeOpacity', type: 'text', required: false }); + } + if (!has('passwordResetToken')) { + needed.push({ name: 'passwordResetToken', type: 'text', required: false }); + } + if (!has('passwordResetExpiry')) { + needed.push({ name: 'passwordResetExpiry', type: 'text', required: false }); + } + if (needed.length) { + await updateCollection(usersCol.id, { ...usersCol, fields: [...usersCol.fields, ...needed] }); + } +} + // Add the access-gating fields to `fams` on installs where it already exists // (fresh installs get them via SCHEMA_PLAN). Idempotent — only adds missing // fields. @@ -443,6 +471,7 @@ export async function migrate(): Promise { await ensureSchema(); // Runs even when the schema already exists (unlike ensureSchema's early // return) so new platform collections/fields/seed land on existing installs. + await ensureUserFields(); await ensureFamFields(); await ensureBonusFields(); await ensureTemplateFields(); @@ -960,7 +989,7 @@ function addDays(d: Date, n: number): Date { // Each kid's "completion personality" — Juno is reliable, Jackson solid, Jalipo spotty. // Maps assigned chore customName → weekday (0=Sun) → probability of completion. const COMPLETION_PROFILES: Record>> = { - 'Juno': { + Juno: { 0: { default: 0.85 }, // Sun 1: { default: 0.95 }, // Mon 2: { default: 0.9 }, @@ -969,7 +998,7 @@ const COMPLETION_PROFILES: Record> 5: { default: 0.85 }, // Fri 6: { default: 0.8 } // Sat }, - 'Jackson': { + Jackson: { 0: { default: 0.7 }, 1: { default: 0.9 }, 2: { default: 0.85 }, @@ -978,7 +1007,7 @@ const COMPLETION_PROFILES: Record> 5: { default: 0.7 }, 6: { default: 0.6 } }, - 'Jalipo': { + Jalipo: { 0: { default: 0.4 }, 1: { default: 0.75 }, 2: { default: 0.65 }, @@ -1018,12 +1047,18 @@ async function seedDemoCompletions(): Promise { // Fetch members, assigned chores const [membersRes, assignedRes] = await Promise.all([ - fetch(`${PB_ENDPOINT}/api/collections/users/records?filter=${encodeURIComponent(`famId='${famId}' && role='child'`)}&perPage=10`, { - headers: { Authorization: `Bearer ${t}` } - }), - fetch(`${PB_ENDPOINT}/api/collections/assigned_chores/records?filter=famId='${famId}'&perPage=100`, { - headers: { Authorization: `Bearer ${t}` } - }) + fetch( + `${PB_ENDPOINT}/api/collections/users/records?filter=${encodeURIComponent(`famId='${famId}' && role='child'`)}&perPage=10`, + { + headers: { Authorization: `Bearer ${t}` } + } + ), + fetch( + `${PB_ENDPOINT}/api/collections/assigned_chores/records?filter=famId='${famId}'&perPage=100`, + { + headers: { Authorization: `Bearer ${t}` } + } + ) ]); const members: any[] = (await membersRes.json())?.items || []; const assigned: any[] = (await assignedRes.json())?.items || []; @@ -1032,12 +1067,18 @@ async function seedDemoCompletions(): Promise { // Delete existing completions and weekly_history for the demo family const [oldCompsRes, oldHistoryRes] = await Promise.all([ - fetch(`${PB_ENDPOINT}/api/collections/completions/records?filter=famId='${famId}'&perPage=500`, { - headers: { Authorization: `Bearer ${t}` } - }), - fetch(`${PB_ENDPOINT}/api/collections/weekly_history/records?filter=famId='${famId}'&perPage=500`, { - headers: { Authorization: `Bearer ${t}` } - }) + fetch( + `${PB_ENDPOINT}/api/collections/completions/records?filter=famId='${famId}'&perPage=500`, + { + headers: { Authorization: `Bearer ${t}` } + } + ), + fetch( + `${PB_ENDPOINT}/api/collections/weekly_history/records?filter=famId='${famId}'&perPage=500`, + { + headers: { Authorization: `Bearer ${t}` } + } + ) ]); const oldComps: any[] = (await oldCompsRes.json())?.items || []; const oldHistory: any[] = (await oldHistoryRes.json())?.items || []; @@ -1061,7 +1102,10 @@ async function seedDemoCompletions(): Promise { // Track per-member-per-week totals for weekly_history type WeekKey = string; // "YYYY-MM-DD" (Monday) - const weekTotals: Record> = {}; + const weekTotals: Record< + string, + Record + > = {}; for (const member of members) { const profile = COMPLETION_PROFILES[member.name] || COMPLETION_PROFILES['Jalipo']; @@ -1092,10 +1136,7 @@ async function seedDemoCompletions(): Promise { const prob = dayProfile[chore.customName] || dayProfile.default; const rng = demoRng( - famId.length * 1000 + - member.id.length * 100 + - d * 10 + - (chore.id?.charCodeAt(0) || 0) + famId.length * 1000 + member.id.length * 100 + d * 10 + (chore.id?.charCodeAt(0) || 0) ); if (rng() > prob) continue; // missed this day diff --git a/frontend/src/routes/[fam]/[username]/+page.svelte b/frontend/src/routes/[fam]/[username]/+page.svelte index d0c1c74..9695313 100644 --- a/frontend/src/routes/[fam]/[username]/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/+page.svelte @@ -1069,7 +1069,7 @@ {/if} - 0 ? '#f97316' : undefined} class={claimableRewards().length > 0 ? 'claims-card' : ''}> + {#if claimableRewards().length === 0}
{@html checkCircleIcon}No outstanding claims