[READ-ONLY] Mirror of https://github.com/flo-bit/svelte-cloudflare-statusphere.
statusphere.atmo.tools
atproto
cloudflare-workers
oauth
svelte
2.3 kB
85 lines
1import { error } from '@sveltejs/kit';
2import { command, getRequestEvent } from '$app/server';
3import * as v from 'valibot';
4import { permissions } from '../settings';
5
6// Validate collection format and check against allowed list from settings
7const collectionSchema = v.pipe(
8 v.string(),
9 v.regex(/^[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*\.[a-zA-Z][a-zA-Z0-9-]*$/),
10 v.check(
11 (c) => permissions.collections.some((allowed) => c === allowed || allowed.startsWith(c + '?')),
12 'Collection not in allowed list'
13 )
14);
15
16// AT Protocol rkey: TID, 'self', or other valid record keys (alphanumeric, dash, underscore, dot)
17const rkeySchema = v.optional(v.pipe(v.string(), v.regex(/^[a-zA-Z0-9._:~-]{1,512}$/)));
18
19export const putRecord = command(
20 v.object({
21 collection: collectionSchema,
22 rkey: rkeySchema,
23 record: v.record(v.string(), v.unknown())
24 }),
25 async (input) => {
26 const { locals } = getRequestEvent();
27 if (!locals.client || !locals.did) error(401, 'Not authenticated');
28
29 const response = await locals.client.post('com.atproto.repo.putRecord', {
30 input: {
31 collection: input.collection as `${string}.${string}.${string}`,
32 repo: locals.did,
33 rkey: input.rkey || 'self',
34 record: input.record
35 }
36 });
37
38 return response.data;
39 }
40);
41
42export const deleteRecord = command(
43 v.object({
44 collection: collectionSchema,
45 rkey: rkeySchema
46 }),
47 async (input) => {
48 const { locals } = getRequestEvent();
49 if (!locals.client || !locals.did) error(401, 'Not authenticated');
50
51 const response = await locals.client.post('com.atproto.repo.deleteRecord', {
52 input: {
53 collection: input.collection as `${string}.${string}.${string}`,
54 repo: locals.did,
55 rkey: input.rkey || 'self'
56 }
57 });
58
59 return { ok: response.ok };
60 }
61);
62
63export const uploadBlob = command(
64 v.object({
65 blob: v.instance(Blob)
66 }),
67 async (input) => {
68 const { locals } = getRequestEvent();
69 if (!locals.client || !locals.did) error(401, 'Not authenticated');
70
71 const response = await locals.client.post('com.atproto.repo.uploadBlob', {
72 params: { repo: locals.did },
73 input: input.blob
74 });
75
76 if (!response.ok) error(500, 'Upload failed');
77
78 return response.data.blob as {
79 $type: 'blob';
80 ref: { $link: string };
81 mimeType: string;
82 size: number;
83 };
84 }
85);