propose new chore update fix

This commit is contained in:
JCEEE
2026-09-30 08:12:30 +01:00
parent d14a154dcb
commit 7de3c8ca62
+34 -13
View File
@@ -30,9 +30,16 @@ export function pbUser(event: RequestEvent) {
// Superuser PB client (memoized). Reserved for server-only privileged
// operations that must bypass collection rules: creating child users, minting
// OTP-login tokens, and verifying OTPs against the superuser-only otp.
//
// NOTE: the auth token expires (prod PB showed ~36h). A stale memoized client
// goes out UNAUTHENTICATED, and PocketBase answers unauthenticated getOne()
// calls with 404 "resource wasn't found" — which the toggle path misreads as
// CHORE_GONE for every chore. So always re-auth when the stored token is no
// longer valid instead of reusing a dead client.
let superClient: PocketBase | null = null;
export async function createSuperClient() {
if (superClient) return superClient;
if (superClient?.authStore.isValid) return superClient;
superClient = null;
const pb = new PocketBase(PB_ENDPOINT);
pb.autoCancellation(false);
await pb.collection('_superusers').authWithPassword(String(PB_EMAIL), String(PB_PASSWORD));
@@ -40,29 +47,43 @@ export async function createSuperClient() {
return pb;
}
// Run a superuser op, retrying once with a freshly-authenticated client when
// the first attempt hits an auth-shaped failure. A locally-valid token can
// still be dead server-side (restart/revoked secret rotation), and PB
// surfaces that as 401/403 — or as 404 when a viewRule then denies access.
async function withSuperRetry<T>(op: (pb: PocketBase) => Promise<T>): Promise<T> {
try {
return await op(await createSuperClient());
} catch (e: any) {
const status = e?.status;
if (status === 401 || status === 403 || status === 404) {
superClient = null;
return await op(await createSuperClient());
}
throw e;
}
}
// Superuser CRUD facade, built on the memoized SDK superuser client. All
// server PB access (authenticated user + superuser) lives in this one module.
export const pbAdmin = {
async getList(collection: string, filter = '') {
const pb = await createSuperClient();
const options: { filter?: string } = {};
if (filter) options.filter = filter;
return pb.collection(collection).getFullList(options);
return withSuperRetry((pb) => {
const options: { filter?: string } = {};
if (filter) options.filter = filter;
return pb.collection(collection).getFullList(options);
});
},
async getOne(collection: string, id: string) {
const pb = await createSuperClient();
return pb.collection(collection).getOne(id);
return withSuperRetry((pb) => pb.collection(collection).getOne(id));
},
async create(collection: string, data: Record<string, unknown>) {
const pb = await createSuperClient();
return pb.collection(collection).create(data);
return withSuperRetry((pb) => pb.collection(collection).create(data));
},
async update(collection: string, id: string, data: Record<string, unknown>) {
const pb = await createSuperClient();
return pb.collection(collection).update(id, data);
return withSuperRetry((pb) => pb.collection(collection).update(id, data));
},
async remove(collection: string, id: string) {
const pb = await createSuperClient();
return pb.collection(collection).delete(id);
return withSuperRetry((pb) => pb.collection(collection).delete(id));
}
};