draftlink 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +92 -0
- package/bin/draftlink +393 -0
- package/migrations/0000_baseline.sql +51 -0
- package/package.json +45 -0
- package/skills/draftlink/SKILL.md +54 -0
- package/src/assets.ts +4 -0
- package/src/drafts.ts +209 -0
- package/src/index.ts +572 -0
- package/src/ui.ts +696 -0
- package/src/util.ts +119 -0
- package/tsconfig.json +15 -0
- package/wrangler.jsonc +15 -0
package/src/assets.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Draftlink logo + favicon, generated from assets/logo source art.
|
|
2
|
+
// Regenerate: resize assets to 160px / 48px PNG and re-embed base64.
|
|
3
|
+
export const LOGO_PNG = "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";
|
|
4
|
+
export const FAVICON_PNG = "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";
|
package/src/drafts.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { randomId } from "./util";
|
|
2
|
+
|
|
3
|
+
export const STATUS_ORDER = ["active", "done", "archived"] as const;
|
|
4
|
+
export type Status = (typeof STATUS_ORDER)[number];
|
|
5
|
+
|
|
6
|
+
// Keep the last 25 versions per draft; raise if agent churn overflows this.
|
|
7
|
+
const VERSIONS_KEPT = 25;
|
|
8
|
+
|
|
9
|
+
export interface VersionRow {
|
|
10
|
+
id: number;
|
|
11
|
+
created_at: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DraftRow {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
project: string;
|
|
18
|
+
status: string;
|
|
19
|
+
is_public: number;
|
|
20
|
+
created_at: number;
|
|
21
|
+
updated_at: number;
|
|
22
|
+
owner_login?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DraftFullRow extends DraftRow {
|
|
26
|
+
owner_user_id: number;
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type Access = { draft: DraftFullRow; isOwner: boolean; canEdit: boolean };
|
|
31
|
+
|
|
32
|
+
export type Viewer = { id: number; github_id: number } | null;
|
|
33
|
+
|
|
34
|
+
export function validStatus(s: string | null | undefined): Status {
|
|
35
|
+
return STATUS_ORDER.includes(s as Status) ? (s as Status) : "active";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function likePattern(q: string): string {
|
|
39
|
+
return `%${q.replace(/[\\%_]/g, (m) => `\\${m}`)}%`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function resolveAccess(db: D1Database, id: string, viewer: Viewer): Promise<Access | null> {
|
|
43
|
+
const draft = await db
|
|
44
|
+
.prepare("SELECT d.*, u.login AS owner_login FROM drafts d JOIN users u ON u.id = d.owner_user_id WHERE d.id = ?")
|
|
45
|
+
.bind(id)
|
|
46
|
+
.first<DraftFullRow & { owner_login: string }>();
|
|
47
|
+
if (!draft) return null;
|
|
48
|
+
const isOwner = !!viewer && viewer.id === draft.owner_user_id;
|
|
49
|
+
let canEdit = isOwner;
|
|
50
|
+
if (!canEdit && viewer) {
|
|
51
|
+
const g = await db.prepare("SELECT 1 AS x FROM draft_access WHERE draft_id = ? AND github_id = ?").bind(id, viewer.github_id).first();
|
|
52
|
+
canEdit = !!g;
|
|
53
|
+
}
|
|
54
|
+
return { draft, isOwner, canEdit };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function canRead(a: Access): boolean {
|
|
58
|
+
return a.draft.is_public === 1 || a.canEdit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function versionStatements(db: D1Database, draftId: string, body: string, now: number): D1PreparedStatement[] {
|
|
62
|
+
return [
|
|
63
|
+
db.prepare("INSERT INTO draft_versions (draft_id, body, created_at) VALUES (?, ?, ?)").bind(draftId, body, now),
|
|
64
|
+
db
|
|
65
|
+
.prepare(
|
|
66
|
+
"DELETE FROM draft_versions WHERE draft_id = ? AND id NOT IN (SELECT id FROM draft_versions WHERE draft_id = ? ORDER BY created_at DESC, id DESC LIMIT ?)"
|
|
67
|
+
)
|
|
68
|
+
.bind(draftId, draftId, VERSIONS_KEPT),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function listVersions(db: D1Database, draftId: string): Promise<VersionRow[]> {
|
|
73
|
+
const { results } = await db
|
|
74
|
+
.prepare("SELECT id, created_at FROM draft_versions WHERE draft_id = ? ORDER BY created_at DESC, id DESC")
|
|
75
|
+
.bind(draftId)
|
|
76
|
+
.all<VersionRow>();
|
|
77
|
+
return results ?? [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function getVersion(db: D1Database, draftId: string, versionId: number): Promise<{ id: number; body: string; created_at: number } | null> {
|
|
81
|
+
return await db
|
|
82
|
+
.prepare("SELECT id, body, created_at FROM draft_versions WHERE id = ? AND draft_id = ?")
|
|
83
|
+
.bind(versionId, draftId)
|
|
84
|
+
.first<{ id: number; body: string; created_at: number }>();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function create(
|
|
88
|
+
db: D1Database,
|
|
89
|
+
ownerUserId: number,
|
|
90
|
+
input: { title?: string; project?: string; status?: Status; body: string }
|
|
91
|
+
): Promise<string> {
|
|
92
|
+
const id = randomId(12);
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
await db
|
|
95
|
+
.prepare("INSERT INTO drafts (id, owner_user_id, title, project, status, body, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
|
96
|
+
.bind(id, ownerUserId, (input.title ?? "untitled").slice(0, 300), (input.project ?? "misc").slice(0, 100), input.status ?? "active", input.body, now, now)
|
|
97
|
+
.run();
|
|
98
|
+
return id;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface UpdateInput {
|
|
102
|
+
title?: string;
|
|
103
|
+
project?: string;
|
|
104
|
+
status?: Status;
|
|
105
|
+
isPublic?: number;
|
|
106
|
+
body?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function update(db: D1Database, id: string, input: UpdateInput): Promise<void> {
|
|
110
|
+
let previous: string | undefined;
|
|
111
|
+
if (input.body !== undefined) {
|
|
112
|
+
const cur = await db.prepare("SELECT body FROM drafts WHERE id = ?").bind(id).first<{ body: string }>();
|
|
113
|
+
previous = cur?.body;
|
|
114
|
+
}
|
|
115
|
+
const sets = ["updated_at = ?"];
|
|
116
|
+
const args: unknown[] = [Date.now()];
|
|
117
|
+
if (input.title !== undefined) {
|
|
118
|
+
sets.push("title = ?");
|
|
119
|
+
args.push(input.title.slice(0, 300));
|
|
120
|
+
}
|
|
121
|
+
if (input.project !== undefined) {
|
|
122
|
+
sets.push("project = ?");
|
|
123
|
+
args.push(input.project.slice(0, 100));
|
|
124
|
+
}
|
|
125
|
+
if (input.status !== undefined) {
|
|
126
|
+
sets.push("status = ?");
|
|
127
|
+
args.push(input.status);
|
|
128
|
+
}
|
|
129
|
+
if (input.isPublic !== undefined) {
|
|
130
|
+
sets.push("is_public = ?");
|
|
131
|
+
args.push(input.isPublic);
|
|
132
|
+
}
|
|
133
|
+
if (input.body !== undefined) {
|
|
134
|
+
sets.push("body = ?");
|
|
135
|
+
args.push(input.body);
|
|
136
|
+
}
|
|
137
|
+
args.push(id);
|
|
138
|
+
const statements = [db.prepare(`UPDATE drafts SET ${sets.join(", ")} WHERE id = ?`).bind(...args)];
|
|
139
|
+
if (previous !== undefined && previous !== input.body) statements.push(...versionStatements(db, id, previous, Date.now()));
|
|
140
|
+
await db.batch(statements);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function remove(db: D1Database, id: string): Promise<void> {
|
|
144
|
+
await db.batch([
|
|
145
|
+
db.prepare("DELETE FROM draft_access WHERE draft_id = ?").bind(id),
|
|
146
|
+
db.prepare("DELETE FROM draft_versions WHERE draft_id = ?").bind(id),
|
|
147
|
+
db.prepare("DELETE FROM drafts WHERE id = ?").bind(id),
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function grant(db: D1Database, draftId: string, resolved: { github_id: number; login: string }): Promise<void> {
|
|
152
|
+
await db
|
|
153
|
+
.prepare("INSERT INTO draft_access (draft_id, github_id, login, created_at) VALUES (?, ?, ?, ?) ON CONFLICT(draft_id, github_id) DO NOTHING")
|
|
154
|
+
.bind(draftId, resolved.github_id, resolved.login, Date.now())
|
|
155
|
+
.run();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function revoke(db: D1Database, draftId: string, githubId: number): Promise<void> {
|
|
159
|
+
await db.prepare("DELETE FROM draft_access WHERE draft_id = ? AND github_id = ?").bind(draftId, githubId).run();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function listGrants(db: D1Database, draftId: string): Promise<{ github_id: number; login: string }[]> {
|
|
163
|
+
const { results } = await db
|
|
164
|
+
.prepare("SELECT github_id, login FROM draft_access WHERE draft_id = ? ORDER BY created_at")
|
|
165
|
+
.bind(draftId)
|
|
166
|
+
.all<{ github_id: number; login: string }>();
|
|
167
|
+
return results ?? [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function listMine(db: D1Database, userId: number, q: string, filter: string, project: string): Promise<DraftRow[]> {
|
|
171
|
+
const like = likePattern(q);
|
|
172
|
+
const { results } = await db
|
|
173
|
+
.prepare(
|
|
174
|
+
`SELECT id, title, project, status, is_public, created_at, updated_at FROM drafts
|
|
175
|
+
WHERE owner_user_id = ?1 AND (?2 = '%%' OR title LIKE ?2 ESCAPE '\\' OR project LIKE ?2 ESCAPE '\\' OR body LIKE ?2 ESCAPE '\\') AND (?3 = 'all' OR status = ?3)
|
|
176
|
+
AND (?4 = '' OR project = ?4)
|
|
177
|
+
ORDER BY created_at DESC LIMIT 500`
|
|
178
|
+
)
|
|
179
|
+
.bind(userId, like, filter, project)
|
|
180
|
+
.all<DraftRow>();
|
|
181
|
+
return results ?? [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function listShared(db: D1Database, githubId: number, q: string, filter: string, project: string): Promise<DraftRow[]> {
|
|
185
|
+
const like = likePattern(q);
|
|
186
|
+
const { results } = await db
|
|
187
|
+
.prepare(
|
|
188
|
+
`SELECT d.id, d.title, d.project, d.status, d.is_public, d.created_at, d.updated_at, u.login AS owner_login
|
|
189
|
+
FROM drafts d JOIN draft_access a ON a.draft_id = d.id JOIN users u ON u.id = d.owner_user_id
|
|
190
|
+
WHERE a.github_id = ?1 AND (?2 = '%%' OR d.title LIKE ?2 ESCAPE '\\' OR d.project LIKE ?2 ESCAPE '\\' OR d.body LIKE ?2 ESCAPE '\\') AND (?3 = 'all' OR d.status = ?3)
|
|
191
|
+
AND (?4 = '' OR d.project = ?4)
|
|
192
|
+
ORDER BY d.created_at DESC LIMIT 500`
|
|
193
|
+
)
|
|
194
|
+
.bind(githubId, like, filter, project)
|
|
195
|
+
.all<DraftRow>();
|
|
196
|
+
return results ?? [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function listProjects(db: D1Database, userId: number, githubId: number): Promise<string[]> {
|
|
200
|
+
const { results } = await db
|
|
201
|
+
.prepare(
|
|
202
|
+
"SELECT project FROM drafts WHERE owner_user_id = ?1 " +
|
|
203
|
+
"UNION SELECT d.project FROM drafts d JOIN draft_access a ON a.draft_id = d.id WHERE a.github_id = ?2 " +
|
|
204
|
+
"ORDER BY project COLLATE NOCASE"
|
|
205
|
+
)
|
|
206
|
+
.bind(userId, githubId)
|
|
207
|
+
.all<{ project: string }>();
|
|
208
|
+
return (results ?? []).map((row) => row.project);
|
|
209
|
+
}
|