Skip to content
File

Blob: src/worker/db/d1/dal/namespaces.ts

typescript78 lines
1import { and, eq } from "drizzle-orm";
2 
3import type { Db } from "@/worker/db/d1/client";
4import {
5 namespaceMemberships,
6 type NamespaceMembershipRow,
7 type NewNamespaceMembershipRow,
8} from "@/worker/db/d1/schema/namespaceMemberships";
9import {
10 namespaces,
11 type NamespaceRow,
12 type NewNamespaceRow,
13} from "@/worker/db/d1/schema/namespaces";
14 
15export async function findNamespaceBySlug(db: Db, slug: string): Promise<NamespaceRow | undefined> {
16 const rows = await db.select().from(namespaces).where(eq(namespaces.slug, slug)).limit(1);
17 return rows[0];
18}
19 
20export async function findNamespaceById(
21 db: Db,
22 namespaceId: string
23): Promise<NamespaceRow | undefined> {
24 const rows = await db.select().from(namespaces).where(eq(namespaces.id, namespaceId)).limit(1);
25 return rows[0];
26}
27 
28// Attempt to claim an unused namespace slug for a user. Returns the inserted
29// row on success; returns undefined when another row already holds the slug
30// (race lost or pre-existing). Callers must NOT treat undefined as failure
31// of the surrounding sign-in flow.
32export async function claimNamespace(
33 db: Db,
34 row: NewNamespaceRow
35): Promise<NamespaceRow | undefined> {
36 const inserted = await db
37 .insert(namespaces)
38 .values(row)
39 .onConflictDoNothing({ target: namespaces.slug })
40 .returning();
41 return inserted[0];
42}
43 
44export async function insertMembershipIfMissing(
45 db: Db,
46 row: NewNamespaceMembershipRow
47): Promise<void> {
48 await db.insert(namespaceMemberships).values(row).onConflictDoNothing();
49}
50 
51// Namespaces a user is a member of, ordered by slug for stable rendering.
52export async function listNamespacesForUser(db: Db, userId: string): Promise<NamespaceRow[]> {
53 const rows = await db
54 .select({ ns: namespaces })
55 .from(namespaceMemberships)
56 .innerJoin(namespaces, eq(namespaceMemberships.namespaceId, namespaces.id))
57 .where(eq(namespaceMemberships.userId, userId));
58 return rows.map((row) => row.ns).sort((a, b) => a.slug.localeCompare(b.slug));
59}
60 
61export async function findMembership(
62 db: Db,
63 namespaceId: string,
64 userId: string
65): Promise<NamespaceMembershipRow | undefined> {
66 const rows = await db
67 .select()
68 .from(namespaceMemberships)
69 .where(
70 and(
71 eq(namespaceMemberships.namespaceId, namespaceId),
72 eq(namespaceMemberships.userId, userId)
73 )
74 )
75 .limit(1);
76 return rows[0];
77}