1/** Smart HTTP uses the same API middleware and request services as JSON endpoints. */
2import * as Git from "alchemy/Git";
3import * as GitHttp from "alchemy/Git/Http";
4import * as Effect from "effect/Effect";
5import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
6import { AppApi } from "./api.ts";
7import { checkRefChanges } from "./branch-policy.ts";
8
9export const ProtocolLive = HttpApiBuilder.group(AppApi, "protocol", (h) =>
10 Effect.gen(function* () {
11 const git = yield* Git.Engine;
12 const defaults = yield* Git.Handlers;
13 return h
14 .handleRaw("infoRefs", defaults.protocol.infoRefs)
15 .handleRaw("uploadPack", defaults.protocol.uploadPack)
16 .handleRaw("receivePack", ({ params, request }) =>
17 Effect.scoped(
18 Effect.gen(function* () {
19 const repo = yield* git.repositories
20 .get(params)
21 .pipe(Effect.catchTag("StoreError", Effect.die));
22 const push = yield* GitHttp.ReceivePack.decode(request);
23 if (push._tag === "Probe")
24 return GitHttp.ReceivePack.probeResponse();
25 return yield* Effect.gen(function* () {
26 yield* checkRefChanges(repo, push.updates);
27 const prepared = yield* git.preparePush(repo, push.input);
28 // Additional application validation can read prepared.readObject(oid) here.
29 return GitHttp.ReceivePack.response(
30 push,
31 yield* git.commitPush(prepared),
32 );
33 }).pipe(
34 Effect.catchTag("PushDenied", (error) =>
35 Effect.succeed(GitHttp.ReceivePack.reject(push, error.reason)),
36 ),
37 );
38 }),
39 ).pipe(
40 Effect.catchTag("StoreError", (error) =>
41 Effect.succeed(GitHttp.ReceivePack.failure(error.reason)),
42 ),
43 Effect.catchTag(["WireProtocolError", "PackIngestError"], (error) =>
44 Effect.succeed(GitHttp.ReceivePack.failure(error.reason)),
45 ),
46 ),
47 );
48 }),
49);