import type { AuthResponse, PublicUser, SelfUser } from "../../shared/user.ts"; import type { Session } from "../utils/auth/storage.ts"; import type { Category } from "../../shared/category.ts"; import type { ChatMessage } from "../../shared/chat.ts"; import type { Comment } from "../../shared/comment.ts"; import type { Dump } from "../../shared/dump.ts"; import type { Notification } from "../../shared/notification.ts"; import type { Playlist } from "../../shared/playlist.ts"; import { AUTH_STORAGE_KEY } from "../utils/auth/storage.ts"; let seq = 0; const nextId = (prefix: string) => `${prefix}-${++seq}`; /** An unsigned JWT-shaped token carrying `payload`, which the client reads. */ export function makeJwt(payload: object): string { const b64 = (o: unknown) => btoa(JSON.stringify(o)).replace(/=+$/, "").replace(/\+/g, "-").replace( /\//g, "_", ); return `${b64({ alg: "HS256" })}.${b64(payload)}.sig`; } /** A session token; the client only ever reads `exp`. */ export function makeToken( { expiresIn = 3600, sub = "u" }: { expiresIn?: number; sub?: string } = {}, ): string { const exp = Math.floor(Date.now() / 1000) + expiresIn; return makeJwt({ sub, exp, n: ++seq }); } export function makeUser(over: Partial = {}): PublicUser { const id = over.id ?? nextId("user"); return { id, username: over.username ?? `user${seq}`, role: "user", createdAt: new Date("2026-01-01T00:00:00Z"), ...over, }; } export function makeSession(over: Partial = {}): AuthResponse { const base = makeUser(over); const user = { ...base, email: over.email ?? `${base.username}@x.test` }; return { token: makeToken({ sub: user.id }), mediaToken: `media-${user.id}`, user, }; } /** Puts a session in storage, where AuthProvider picks it up on mount. */ export function storeSession(session: Session) { localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(session)); } export function makeDump(over: Partial = {}): Dump { const id = over.id ?? nextId("dump"); return { id, kind: "url", title: `Dump ${id}`, slug: id, userId: "user-owner", ownerUsername: "owner", createdAt: new Date(Date.now() - 60_000), url: `https://example.com/${id}`, voteCount: 0, commentCount: 0, isPrivate: false, ...over, }; } export function makePlaylist(over: Partial = {}): Playlist { const id = over.id ?? nextId("playlist"); return { id, userId: "user-owner", ownerUsername: "owner", title: `Playlist ${id}`, slug: id, isPublic: true, createdAt: new Date("2026-02-01T00:00:00Z"), dumpCount: 0, ...over, }; } export function makeComment(over: Partial = {}): Comment { const id = over.id ?? nextId("comment"); return { id, dumpId: "dump-x", userId: "user-owner", body: `Comment ${id}`, createdAt: new Date(Date.now() - 30_000), deleted: false, likeCount: 0, authorUsername: "owner", ...over, }; } export function makeChatMessage(over: Partial = {}): ChatMessage { const id = over.id ?? nextId("msg"); return { id, userId: "user-other", body: `Message ${id}`, createdAt: new Date(Date.now() - 10_000), authorUsername: "other", ...over, }; } export function makeCategory(over: Partial = {}): Category { const id = over.id ?? nextId("cat"); return { id, kind: "dump", slug: over.slug ?? `cat${seq}`, name: over.name ?? `Category ${seq}`, position: 0, createdAt: new Date("2026-01-01T00:00:00Z"), ...over, }; } export function makeNotification( over: Partial = {}, ): Notification { return { id: over.id ?? nextId("notif"), userId: "user-me", type: "user_followed", data: { followerId: "user-f", followerUsername: "fan" }, read: false, createdAt: new Date(Date.now() - 5_000), ...over, }; } /** A value as it crosses the wire: dates become ISO strings. */ export function wire(value: T): T { return JSON.parse(JSON.stringify(value)); } /** A paginated envelope payload. */ export function page(items: T[], hasMore = false) { return { items: wire(items), total: items.length, hasMore }; }