1import * as Schema from "effect/Schema";
2import * as HttpApi from "effect/http-api/HttpApi";
3import * as HttpApiEndpoint from "effect/http-api/HttpApiEndpoint";
4import * as HttpApiGroup from "effect/http-api/HttpApiGroup";
5import * as HttpApiSchema from "effect/http-api/HttpApiSchema";
6
7// ─── Domain types ────────────────────────────────────────────────────
8
9export class Metadata extends Schema.Class<Metadata>("Metadata")({
10 description: Schema.String,
11 topics: Schema.Array(Schema.String),
12 stars: Schema.Number,
13 createdAt: Schema.Number,
14}) {}
15
16export class RepoInfo extends Schema.Class<RepoInfo>("RepoInfo")({
17 id: Schema.String,
18 name: Schema.String,
19 description: Schema.NullOr(Schema.String),
20 defaultBranch: Schema.String,
21 remote: Schema.String,
22 status: Schema.String,
23 readOnly: Schema.Boolean,
24 createdAt: Schema.String,
25 updatedAt: Schema.String,
26 lastPushAt: Schema.NullOr(Schema.String),
27 metadata: Schema.NullOr(Metadata),
28}) {}
29
30export class CreateRepoResponse extends Schema.Class<CreateRepoResponse>(
31 "CreateRepoResponse",
32)({
33 name: Schema.String,
34 remote: Schema.String,
35 token: Schema.String,
36 defaultBranch: Schema.String,
37}) {}
38
39export class CloneToken extends Schema.Class<CloneToken>("CloneToken")({
40 id: Schema.String,
41 plaintext: Schema.String,
42 scope: Schema.Literals(["read", "write"]),
43 expiresAt: Schema.String,
44}) {}
45
46// ─── Errors ─────────────────────────────────────────────────────────
47
48export class RepoNotFound extends Schema.TaggedErrorClass<RepoNotFound>()(
49 "RepoNotFound",
50 { name: Schema.String },
51) {}
52
53export class RepoConflict extends Schema.TaggedErrorClass<RepoConflict>()(
54 "RepoConflict",
55 { message: Schema.String },
56) {}
57
58// ─── Path / payload schemas ──────────────────────────────────────────
59
60const RepoNameParam = Schema.Struct({ name: Schema.String });
61
62const CreateRepoPayload = Schema.Struct({
63 name: Schema.String,
64 description: Schema.optional(Schema.String),
65});
66
67const UpdateRepoPayload = Schema.Struct({
68 description: Schema.optional(Schema.String),
69 topics: Schema.optional(Schema.Array(Schema.String)),
70});
71
72const CloneTokenPayload = Schema.Struct({
73 scope: Schema.optional(Schema.Literals(["read", "write"])),
74 ttl: Schema.optional(Schema.Number),
75});
76
77// ─── Endpoints ──────────────────────────────────────────────────────
78
79export const createRepo = HttpApiEndpoint.post("createRepo", "/repos", {
80 payload: CreateRepoPayload,
81 success: CreateRepoResponse,
82 error: RepoConflict,
83});
84
85export const getRepo = HttpApiEndpoint.get("getRepo", "/repos/:name", {
86 params: RepoNameParam,
87 success: RepoInfo,
88 error: RepoNotFound,
89});
90
91export const updateRepo = HttpApiEndpoint.patch("updateRepo", "/repos/:name", {
92 params: RepoNameParam,
93 payload: UpdateRepoPayload,
94 success: Metadata,
95 error: RepoNotFound,
96});
97
98export const deleteRepo = HttpApiEndpoint.delete("deleteRepo", "/repos/:name", {
99 params: RepoNameParam,
100 success: HttpApiSchema.NoContent,
101 error: RepoNotFound,
102});
103
104export const starRepo = HttpApiEndpoint.post("starRepo", "/repos/:name/star", {
105 params: RepoNameParam,
106 success: Metadata,
107 error: RepoNotFound,
108});
109
110export const cloneToken = HttpApiEndpoint.post(
111 "cloneToken",
112 "/repos/:name/clone-token",
113 {
114 params: RepoNameParam,
115 payload: CloneTokenPayload,
116 success: CloneToken,
117 error: RepoNotFound,
118 },
119);
120
121export class ReposGroup extends HttpApiGroup.make("repos")
122 .add(createRepo)
123 .add(getRepo)
124 .add(updateRepo)
125 .add(deleteRepo)
126 .add(starRepo)
127 .add(cloneToken) {}
128
129export class RepoApi extends HttpApi.make("RepoApi").add(ReposGroup) {}