feat(server): auth status, attachments list, user preferences
GET auth/status ({ hasUsers }), GET attachments?entity=&entityId= (owner
scoped, 404 for foreign targets) and GET/PATCH me/preferences (ratingScale,
defaultVisibility in users.preferences).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d547355e5c
commit
d204775235
@@ -1,4 +1,4 @@
|
||||
import { listAttachmentLinks, readAttachment, requireLinkTarget, storeAttachment, unlinkAttachment } from "@sttracker/core/attachments";
|
||||
import { listAttachmentLinks, listAttachments, readAttachment, requireLinkTarget, storeAttachment, unlinkAttachment } from "@sttracker/core/attachments";
|
||||
import { NotFoundError } from "@sttracker/core/errors";
|
||||
import type { AttachmentEntity } from "@sttracker/db/schema";
|
||||
import { z } from "zod";
|
||||
@@ -18,6 +18,18 @@ export function safeFilename(name: string): string {
|
||||
}
|
||||
|
||||
export const attachmentRoutes = [
|
||||
route({
|
||||
method: "GET",
|
||||
path: "attachments",
|
||||
summary: "Attachments linked to one of your records (oldest first), each with its links.",
|
||||
auth: "any:read",
|
||||
query: z.object({ entity: fields.shape.entity, entityId: z.string().min(1) }),
|
||||
handler: async ({ deps, user, query }) => {
|
||||
await requireLinkTarget(deps.db, user.id, query.entity as AttachmentEntity, query.entityId);
|
||||
const atts = await listAttachments(deps.db, user.id, query.entity as AttachmentEntity, query.entityId);
|
||||
return Promise.all(atts.map(async (a) => ({ ...a, links: await listAttachmentLinks(deps.db, user.id, a.id) })));
|
||||
},
|
||||
}),
|
||||
route({
|
||||
method: "POST",
|
||||
path: "attachments",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createAppToken, createInvite, listAppTokens, login, logout, registerUser, revokeAppToken } from "@sttracker/core/auth";
|
||||
import { createAppToken, createInvite, hasUsers, listAppTokens, login, logout, registerUser, revokeAppToken } from "@sttracker/core/auth";
|
||||
import { getPreferences, RATING_SCALES, updatePreferences } from "@sttracker/core/preferences";
|
||||
import { NotFoundError } from "@sttracker/core/errors";
|
||||
import { z } from "zod";
|
||||
import { clearSessionCookie, json, noContent, route, sessionCookie } from "../http.ts";
|
||||
import { visibility } from "./schemas.ts";
|
||||
|
||||
const credentials = z.object({
|
||||
username: z.string().min(1).max(64),
|
||||
@@ -9,6 +11,13 @@ const credentials = z.object({
|
||||
});
|
||||
|
||||
export const authRoutes = [
|
||||
route({
|
||||
method: "GET",
|
||||
path: "auth/status",
|
||||
summary: "Whether any account exists yet (the web app shows first-run registration when not).",
|
||||
auth: "none",
|
||||
handler: async ({ deps }) => ({ hasUsers: await hasUsers(deps.db) }),
|
||||
}),
|
||||
route({
|
||||
method: "POST",
|
||||
path: "auth/register",
|
||||
@@ -41,6 +50,21 @@ export const authRoutes = [
|
||||
},
|
||||
}),
|
||||
route({ method: "GET", path: "me", summary: "The signed-in user.", auth: "any:read", handler: async ({ user }) => user }),
|
||||
route({
|
||||
method: "GET",
|
||||
path: "me/preferences",
|
||||
summary: "Your preferences: rating scale and default visibility.",
|
||||
auth: "any:read",
|
||||
handler: async ({ deps, user }) => getPreferences(deps.db, user.id),
|
||||
}),
|
||||
route({
|
||||
method: "PATCH",
|
||||
path: "me/preferences",
|
||||
summary: "Update your preferences (partial).",
|
||||
auth: "any:write",
|
||||
body: z.object({ ratingScale: z.enum(RATING_SCALES), defaultVisibility: visibility }).partial().strict(),
|
||||
handler: async ({ deps, user, body }) => updatePreferences(deps.db, user.id, body as Parameters<typeof updatePreferences>[2]),
|
||||
}),
|
||||
route({
|
||||
method: "GET",
|
||||
path: "me/tokens",
|
||||
|
||||
Reference in New Issue
Block a user