+39
-17
proxy/src/index.ts
+39
-17
proxy/src/index.ts
···
9
9
} from "@atcute/identity-resolver";
10
10
import {
11
11
parseCanonicalResourceUri,
12
-
parseResourceUri,
13
12
type RecordKey,
14
13
} from "@atcute/lexicons/syntax";
15
14
import { config } from "./config";
16
15
import type {} from "@atcute/atproto";
17
-
import { safeParse } from "@atcute/lexicons";
16
+
import { is, safeParse } from "@atcute/lexicons";
18
17
import {
19
18
SystemsGazeBarometerState,
20
19
SystemsGazeBarometerHost,
21
20
SystemsGazeBarometerService,
22
21
SystemsGazeBarometerCheck,
23
22
} from "barometer-lexicon";
24
-
import { expect, getRecord, putRecord } from "./utils";
23
+
import { err, expect, getRecord, ok, putRecord, type Result } from "./utils";
25
24
26
25
interface Check {
27
26
record: SystemsGazeBarometerCheck.Main;
···
75
74
hostname,
76
75
);
77
76
}
77
+
78
+
interface PushRequest {
79
+
serviceName?: string; // service manager service name
80
+
state: SystemsGazeBarometerState.Main;
81
+
}
82
+
83
+
const parsePushRequest = (json: unknown): Result<PushRequest, string> => {
84
+
if (typeof json !== "object" || json === null) {
85
+
return err("invalid request");
86
+
}
87
+
if ("serviceName" in json && typeof json.serviceName !== "string") {
88
+
return err("serviceName is not a string");
89
+
}
90
+
if ("state" in json) {
91
+
const parsed = safeParse(SystemsGazeBarometerState.mainSchema, json.state);
92
+
if (!parsed.ok) {
93
+
return err(`state is invalid: ${parsed.message}`);
94
+
}
95
+
} else {
96
+
return err("state not found");
97
+
}
98
+
return ok(json as PushRequest);
99
+
};
78
100
79
101
const badRequest = <Error extends { msg: string }>(error: Error) => {
80
102
return new Response(JSON.stringify(error), { status: 400 });
···
83
105
routes: {
84
106
"/push": {
85
107
POST: async (req) => {
86
-
const maybeState = safeParse(
87
-
SystemsGazeBarometerState.mainSchema,
88
-
await req.json(),
89
-
);
90
-
if (!maybeState.ok) {
108
+
const maybeData = parsePushRequest(await req.json());
109
+
if (!maybeData.ok) {
91
110
return badRequest({
92
-
msg: `invalid state: ${maybeState.message}`,
93
-
issues: maybeState.issues,
111
+
msg: `invalid request: ${maybeData.error}`,
94
112
});
95
113
}
96
-
const state = maybeState.value;
114
+
const data = maybeData.value;
97
115
98
116
const serviceAtUri = expect(
99
-
parseCanonicalResourceUri(state.forService),
117
+
parseCanonicalResourceUri(data.state.forService),
100
118
);
101
119
let service = services.get(serviceAtUri.rkey);
102
120
if (!service) {
···
105
123
serviceAtUri.rkey,
106
124
);
107
125
if (!serviceRecord.ok) {
108
-
return badRequest({ msg: "service was not found" });
126
+
return badRequest({
127
+
msg: `service was not found or is invalid: ${serviceRecord.error}`,
128
+
});
109
129
}
110
130
service = {
111
131
record: serviceRecord.value,
···
114
134
services.set(serviceAtUri.rkey, service);
115
135
}
116
136
117
-
if (state.generatedBy) {
137
+
if (data.state.generatedBy) {
118
138
const checkAtUri = expect(
119
-
parseCanonicalResourceUri(state.generatedBy),
139
+
parseCanonicalResourceUri(data.state.generatedBy),
120
140
);
121
141
let check = service.checks.get(checkAtUri.rkey);
122
142
if (!check) {
···
125
145
checkAtUri.rkey,
126
146
);
127
147
if (!checkRecord.ok) {
128
-
return badRequest({ msg: "check record not found" });
148
+
return badRequest({
149
+
msg: `check record not found or is invalid: ${checkRecord.error}`,
150
+
});
129
151
}
130
152
check = {
131
153
record: checkRecord.value,
···
134
156
}
135
157
}
136
158
137
-
const result = await putRecord(state);
159
+
const result = await putRecord(data.state);
138
160
return new Response(
139
161
JSON.stringify({ cid: result.cid, uri: result.uri }),
140
162
);
+11
-7
proxy/src/utils.ts
+11
-7
proxy/src/utils.ts
···
1
1
import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons";
2
2
import { schemas as BarometerSchemas } from "barometer-lexicon";
3
3
import { config } from "./config";
4
-
import { ok } from "@atcute/client";
4
+
import { ok as clientOk } from "@atcute/client";
5
5
import { now as generateTid } from "@atcute/tid";
6
6
import { atpClient } from ".";
7
7
···
14
14
ok: false;
15
15
error: E;
16
16
};
17
+
18
+
export const ok = <T, E>(value: T): Result<T, E> => {
19
+
return { ok: true, value };
20
+
};
21
+
export const err = <T, E>(error: E): Result<T, E> => {
22
+
return { ok: false, error };
23
+
};
17
24
18
25
export const expect = <T, E>(
19
26
v: Result<T, E>,
···
41
48
},
42
49
});
43
50
if (!maybeRecord.ok) {
44
-
return {
45
-
ok: false,
46
-
error: maybeRecord.data.message ?? maybeRecord.data.error,
47
-
};
51
+
return err(maybeRecord.data.message ?? maybeRecord.data.error);
48
52
}
49
53
const maybeTyped = safeParse(
50
54
BarometerSchemas[collection],
51
55
maybeRecord.data.value,
52
56
);
53
57
if (!maybeTyped.ok) {
54
-
return { ok: false, error: maybeTyped.message };
58
+
return err(maybeTyped.message);
55
59
}
56
60
return maybeTyped;
57
61
};
···
62
66
record: InferOutput<(typeof BarometerSchemas)[Collection]>,
63
67
rkey?: RecordKey,
64
68
) => {
65
-
return await ok(
69
+
return await clientOk(
66
70
atpClient.post("com.atproto.repo.putRecord", {
67
71
input: {
68
72
collection: record["$type"],