Merge branch 'task/17' into task/18
This commit is contained in:
commit
5c837a6679
7 files changed
+140
-30
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import type { Db } from "@sttracker/db/db";
|
||||
import type { Executor } from "./history.ts";
|
||||
import { fromJson, toJson } from "@sttracker/db/json";
|
||||
import { nowIso } from "@sttracker/db/ids";
|
||||
import type { Visibility } from "@sttracker/db/schema";
|
||||
@@ -16,15 +17,20 @@ export interface Preferences {
|
||||
|
||||
export const DEFAULT_PREFERENCES: Preferences = { ratingScale: "5half", defaultVisibility: "private" };
|
||||
|
||||
async function readRaw(db: Db, userId: string): Promise<Record<string, unknown>> {
|
||||
const u = await db.k.selectFrom("users").select("preferences").where("id", "=", userId).executeTakeFirst();
|
||||
async function readRaw(k: Executor, userId: string, lock = false): Promise<Record<string, unknown>> {
|
||||
let q = k.selectFrom("users").select("preferences").where("id", "=", userId);
|
||||
if (lock) q = q.forUpdate();
|
||||
const u = await q.executeTakeFirst();
|
||||
if (!u) throw new NotFoundError("user");
|
||||
return fromJson<Record<string, unknown>>(u.preferences, {});
|
||||
}
|
||||
|
||||
/** The user's preferences; missing or invalid stored values fall back to the defaults. */
|
||||
export async function getPreferences(db: Db, userId: string): Promise<Preferences> {
|
||||
const raw = await readRaw(db, userId);
|
||||
return toPreferences(await readRaw(db.k, userId));
|
||||
}
|
||||
|
||||
function toPreferences(raw: Record<string, unknown>): Preferences {
|
||||
return {
|
||||
ratingScale: (RATING_SCALES as readonly unknown[]).includes(raw.ratingScale) ? (raw.ratingScale as RatingScale) : DEFAULT_PREFERENCES.ratingScale,
|
||||
defaultVisibility: (VISIBILITIES as readonly unknown[]).includes(raw.defaultVisibility)
|
||||
@@ -37,8 +43,12 @@ export async function getPreferences(db: Db, userId: string): Promise<Preference
|
||||
export async function updatePreferences(db: Db, userId: string, patch: Partial<Preferences>): Promise<Preferences> {
|
||||
if (patch.ratingScale !== undefined && !RATING_SCALES.includes(patch.ratingScale)) throw new ValidationError("ratingScale", "invalid");
|
||||
if (patch.defaultVisibility !== undefined && !VISIBILITIES.includes(patch.defaultVisibility)) throw new ValidationError("defaultVisibility", "invalid");
|
||||
const raw = await readRaw(db, userId);
|
||||
const next = { ...raw, ...Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined)) };
|
||||
await db.k.updateTable("users").set({ preferences: toJson(next), updated_at: nowIso() }).where("id", "=", userId).execute();
|
||||
return getPreferences(db, userId);
|
||||
// Read-modify-write in one transaction, with the user row locked on Postgres (SQLite runs one
|
||||
// connection), so concurrent updates of different keys don't drop each other.
|
||||
return db.k.transaction().execute(async (tx) => {
|
||||
const raw = await readRaw(tx, userId, db.dialect === "postgres");
|
||||
const next = { ...raw, ...Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined)) };
|
||||
await tx.updateTable("users").set({ preferences: toJson(next), updated_at: nowIso() }).where("id", "=", userId).execute();
|
||||
return toPreferences(next);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user