1import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons";
2import { schemas as BarometerSchemas } from "barometer-lexicon";
3import { config } from "./config";
4import { ok as clientOk } from "@atcute/client";
5import { atpClient } from ".";
6import { now as generateTid } from "@atcute/tid";
7import type { AtprotoDid } from "@atcute/lexicons/syntax";
8
9export type CollectionUri<Nsid extends string> =
10 `at://${AtprotoDid}/${Nsid}/${RecordKey}`;
11export type StateUri = CollectionUri<"systems.gaze.barometer.state">;
12export type CheckUri = CollectionUri<"systems.gaze.barometer.check">;
13export type ServiceUri = CollectionUri<"systems.gaze.barometer.service">;
14
15export const getUri = <
16 Collection extends
17 | "systems.gaze.barometer.state"
18 | "systems.gaze.barometer.check"
19 | "systems.gaze.barometer.service",
20>(
21 collection: Collection,
22 rkey: RecordKey,
23): CollectionUri<Collection> => {
24 return `at://${config.repoDid}/${collection}/${rkey}`;
25};
26
27export type Result<T, E> =
28 | {
29 ok: true;
30 value: T;
31 }
32 | {
33 ok: false;
34 error: E;
35 };
36
37export const ok = <T, E>(value: T): Result<T, E> => {
38 return { ok: true, value };
39};
40export const err = <T, E>(error: E): Result<T, E> => {
41 return { ok: false, error };
42};
43
44export const expect = <T, E>(
45 v: Result<T, E>,
46 msg: string = "expected result to not be error",
47) => {
48 if (v.ok) {
49 return v.value;
50 }
51 throw msg;
52};
53
54export const getRecord = async <
55 Collection extends keyof typeof BarometerSchemas,
56>(
57 collection: Collection,
58 rkey: RecordKey,
59): Promise<
60 Result<InferOutput<(typeof BarometerSchemas)[Collection]>, string>
61> => {
62 let maybeRecord = await atpClient.get("com.atproto.repo.getRecord", {
63 params: {
64 collection,
65 repo: config.repoDid,
66 rkey,
67 },
68 });
69 if (!maybeRecord.ok)
70 return err(maybeRecord.data.message ?? maybeRecord.data.error);
71 const maybeTyped = safeParse(
72 BarometerSchemas[collection],
73 maybeRecord.data.value,
74 );
75 if (!maybeTyped.ok) return err(maybeTyped.message);
76 return maybeTyped;
77};
78
79export const putRecord = async <
80 Collection extends keyof typeof BarometerSchemas,
81>(
82 record: InferOutput<(typeof BarometerSchemas)[Collection]>,
83 rkey: RecordKey | null = null,
84) => {
85 return await clientOk(
86 atpClient.post("com.atproto.repo.putRecord", {
87 input: {
88 collection: record["$type"],
89 repo: config.repoDid,
90 record,
91 rkey: rkey ?? generateTid(),
92 },
93 }),
94 );
95};
96
97export const log = {
98 info: console.log,
99 warn: console.warn,
100 error: console.error,
101};
102
103export type Middleware = (
104 req: Bun.BunRequest,
105) => Promise<Bun.BunRequest | Response>;
106
107export const applyMiddleware =
108 <T extends string>(
109 fns: Middleware[],
110 route: Bun.RouterTypes.RouteHandler<T>,
111 ): Bun.RouterTypes.RouteHandler<T> =>
112 async (req, srv) => {
113 for (const fn of fns) {
114 const result = await fn(req);
115 if (result instanceof Response) return result;
116 else req = result;
117 }
118 return route(req, srv);
119 };
120
121type Routes = Record<
122 string,
123 Record<string, Bun.RouterTypes.RouteHandler<string>>
124>;
125export const applyMiddlewareAll = (
126 fns: Middleware[],
127 routes: Routes,
128): Routes => {
129 return Object.fromEntries(
130 Object.entries(routes).map(([path, route]) => {
131 return [
132 path,
133 Object.fromEntries(
134 Object.entries(route).map(([method, handler]) => {
135 return [method, applyMiddleware(fns, handler)];
136 }),
137 ),
138 ];
139 }),
140 );
141};