social components
inlay.at
atproto
components
sdui
1// data/actions.ts
2// Write mutations — all scoped to a verified DID
3import "server-only";
4import { eq } from "drizzle-orm";
5import { getDb, components, canvases, userPreferences } from "./db";
6import type { CanvasRecord } from "./types";
7
8// =============================================================================
9// Component mutations
10// =============================================================================
11
12export async function upsertComponent(
13 uri: string,
14 authorDid: string,
15 record: Record<string, unknown>
16): Promise<void> {
17 const db = getDb();
18 const now = new Date();
19
20 // Verify ownership if record exists
21 const existing = await db.query.components.findFirst({
22 where: eq(components.uri, uri),
23 columns: { authorDid: true },
24 });
25 if (existing && existing.authorDid !== authorDid) {
26 throw new Error("Cannot modify another user's component");
27 }
28
29 await db
30 .insert(components)
31 .values({ uri, cid: "pending", authorDid, record, createdAt: now })
32 .onConflictDoUpdate({
33 target: components.uri,
34 set: { cid: "pending", record, indexedAt: now },
35 });
36}
37
38export async function deleteComponent(
39 uri: string,
40 authorDid: string
41): Promise<Record<string, unknown> | null> {
42 const db = getDb();
43
44 // Verify ownership
45 const existing = await db.query.components.findFirst({
46 where: eq(components.uri, uri),
47 columns: { authorDid: true, record: true },
48 });
49 if (!existing) return null;
50 if (existing.authorDid !== authorDid) {
51 throw new Error("Cannot delete another user's component");
52 }
53
54 await db.delete(components).where(eq(components.uri, uri));
55 return existing.record as Record<string, unknown>;
56}
57
58// =============================================================================
59// Canvas mutations
60// =============================================================================
61
62export async function upsertCanvas(
63 uri: string,
64 authorDid: string,
65 record: CanvasRecord
66): Promise<void> {
67 const db = getDb();
68 const now = new Date();
69
70 const existing = await db.query.canvases.findFirst({
71 where: eq(canvases.uri, uri),
72 columns: { authorDid: true },
73 });
74 if (existing && existing.authorDid !== authorDid) {
75 throw new Error("Cannot modify another user's canvas");
76 }
77
78 await db
79 .insert(canvases)
80 .values({
81 uri,
82 cid: "pending",
83 authorDid,
84 record,
85 createdAt: record.createdAt ? new Date(record.createdAt) : now,
86 })
87 .onConflictDoUpdate({
88 target: canvases.uri,
89 set: { cid: "pending", record, indexedAt: now },
90 });
91}
92
93export async function deleteCanvas(
94 uri: string,
95 authorDid: string
96): Promise<void> {
97 const db = getDb();
98
99 const existing = await db.query.canvases.findFirst({
100 where: eq(canvases.uri, uri),
101 columns: { authorDid: true },
102 });
103 if (!existing) return;
104 if (existing.authorDid !== authorDid) {
105 throw new Error("Cannot delete another user's canvas");
106 }
107
108 await db.delete(canvases).where(eq(canvases.uri, uri));
109}
110
111// =============================================================================
112// User preferences mutations
113// =============================================================================
114
115export async function saveValTownToken(
116 did: string,
117 token: string
118): Promise<void> {
119 const db = getDb();
120 await db
121 .insert(userPreferences)
122 .values({ did, valTownToken: token, updatedAt: new Date() })
123 .onConflictDoUpdate({
124 target: userPreferences.did,
125 set: { valTownToken: token, updatedAt: new Date() },
126 });
127}
128
129export async function clearValTownToken(did: string): Promise<void> {
130 const db = getDb();
131 await db
132 .update(userPreferences)
133 .set({ valTownToken: null, updatedAt: new Date() })
134 .where(eq(userPreferences.did, did));
135}