Repository navigation
Expand file tree
/
Copy pathdb.ts
More file actions
132 lines (106 loc) · 4.11 KB
/
Copy pathdb.ts
File metadata and controls
132 lines (106 loc) · 4.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Persistence layer built on Deno KV.
//
// Key design follows the doc's privacy principle (section 11/26/27):
// only durable, functionally necessary data is stored permanently.
// Conversation state is stored too, but with a short TTL via `expireIn`
// so idle flows self-clean instead of accumulating forever.
import type {
AccessRequest,
ConversationState,
Destination,
Post,
ScheduledPost,
} from "./types.ts";
const kv = await Deno.openKv();
const CONVERSATION_TTL_MS = 30 * 60 * 1000; // 30 minutes of inactivity
// ---- Destinations ---------------------------------------------------------
export async function addDestination(dest: Destination): Promise<void> {
await kv.set(["destinations", dest.id], dest);
}
export async function removeDestination(id: string): Promise<void> {
await kv.delete(["destinations", id]);
}
export async function listDestinations(): Promise<Destination[]> {
const out: Destination[] = [];
for await (const entry of kv.list<Destination>({ prefix: ["destinations"] })) {
out.push(entry.value);
}
return out;
}
// ---- Posts (published) -----------------------------------------------------
export async function savePost(post: Post): Promise<void> {
await kv.set(["posts", post.id], post);
}
export async function getPost(id: string): Promise<Post | null> {
const res = await kv.get<Post>(["posts", id]);
return res.value;
}
// Every button gets an index entry so "change this button's URL" can find
// every post/message it appears in, across destinations, in one lookup.
export async function indexButton(buttonId: string, postId: string): Promise<void> {
await kv.set(["button_index", buttonId, postId], true);
}
export async function findPostsForButton(buttonId: string): Promise<Post[]> {
const posts: Post[] = [];
for await (const entry of kv.list<boolean>({ prefix: ["button_index", buttonId] })) {
const postId = entry.key[2] as string;
const post = await getPost(postId);
if (post) posts.push(post);
}
return posts;
}
// ---- Scheduled posts --------------------------------------------------------
export async function saveScheduledPost(sp: ScheduledPost): Promise<void> {
await kv.set(["scheduled", sp.id], sp);
}
export async function listDuePosts(now: number): Promise<ScheduledPost[]> {
const due: ScheduledPost[] = [];
for await (const entry of kv.list<ScheduledPost>({ prefix: ["scheduled"] })) {
const sp = entry.value;
if (!sp.published && sp.publishAt <= now) due.push(sp);
}
return due;
}
export async function markScheduledPublished(id: string): Promise<void> {
const res = await kv.get<ScheduledPost>(["scheduled", id]);
if (res.value) {
res.value.published = true;
await kv.set(["scheduled", id], res.value);
}
}
// ---- Access requests ---------------------------------------------------------
export async function saveAccessRequest(req: AccessRequest): Promise<void> {
await kv.set(["access", req.userId], req);
}
export async function getAccessRequest(userId: number): Promise<AccessRequest | null> {
const res = await kv.get<AccessRequest>(["access", userId]);
return res.value;
}
export async function isApprovedUser(userId: number): Promise<boolean> {
const req = await getAccessRequest(userId);
return req?.status === "approved";
}
export async function listPendingRequests(): Promise<AccessRequest[]> {
const out: AccessRequest[] = [];
for await (const entry of kv.list<AccessRequest>({ prefix: ["access"] })) {
if (entry.value.status === "pending") out.push(entry.value);
}
return out;
}
// ---- Conversation state (short-lived, per user) ------------------------------
export async function setConversationState(
userId: number,
state: ConversationState,
): Promise<void> {
await kv.set(["conversation", userId], state, { expireIn: CONVERSATION_TTL_MS });
}
export async function getConversationState(
userId: number,
): Promise<ConversationState | null> {
const res = await kv.get<ConversationState>(["conversation", userId]);
return res.value;
}
export async function clearConversationState(userId: number): Promise<void> {
await kv.delete(["conversation", userId]);
}
export { kv };