Skip to content

Commit 939e671

Browse files
committed
typesafe context in api server
1 parent 35548da commit 939e671

File tree

6 files changed

+99
-106
lines changed

6 files changed

+99
-106
lines changed

apps/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
88
},
99
"dependencies": {
10+
"@hono/zod-validator": "^0.7.3",
1011
"@supabase/supabase-js": "^2.58.0",
1112
"@t3-oss/env-core": "^0.13.8",
1213
"hono": "^4.9.10",

apps/api/src/env.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import type { Context } from "hono";
33
import { env } from "hono/adapter";
44
import { z } from "zod";
55

6-
export const getEnv = (c: Context) =>
6+
import type { Env } from "./types";
7+
8+
export const getEnv = (c: Context<Env>) =>
79
createEnv({
810
server: {
911
PORT: z.coerce.number().default(3000),
@@ -13,6 +15,6 @@ export const getEnv = (c: Context) =>
1315
SUPABASE_URL: z.string().min(1),
1416
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
1517
},
16-
runtimeEnv: env(c),
18+
runtimeEnv: env(c) as Record<string, string | undefined>,
1719
emptyStringAsUndefined: true,
1820
});

apps/api/src/index.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,57 @@
1+
import { zValidator } from "@hono/zod-validator";
12
import { Hono } from "hono";
23
import { proxy } from "hono/proxy";
4+
import { z } from "zod";
35

46
import { getEnv } from "./env";
57
import { supabaseMiddleware } from "./middleware/supabase";
8+
import type { Env } from "./types";
69

7-
const app = new Hono();
10+
const app = new Hono<Env>();
811
app.use("/v1", supabaseMiddleware());
912

1013
app.get("/health", (c) => c.text("OK"));
1114

12-
app.post("/v1/write", async (c) => {
13-
return c.json({ message: "OK" });
14-
});
15+
app.post(
16+
"/v1/write",
17+
zValidator(
18+
"json",
19+
z.discriminatedUnion("operation", [
20+
z.object({
21+
table: z.string(),
22+
row_id: z.string(),
23+
operation: z.literal("delete"),
24+
}),
25+
z.object({
26+
table: z.string(),
27+
row_id: z.string(),
28+
data: z.record(z.string(), z.unknown()),
29+
operation: z.literal("update"),
30+
}),
31+
]),
32+
),
33+
async (c) => {
34+
const supabase = c.get("supabase");
35+
const user = c.get("user");
36+
const body = c.req.valid("json");
37+
38+
// TODO: use RPC / transaction
39+
if (body.operation === "delete") {
40+
await supabase.from(body.table).delete().eq("id", body.row_id);
41+
} else {
42+
await supabase.from(body.table).upsert({
43+
...body.data,
44+
id: body.row_id,
45+
user_id: user.id,
46+
});
47+
}
48+
49+
return c.json({ message: "OK" });
50+
},
51+
);
1552

1653
app.post("/v1/chat/completions", async (c) => {
1754
const { OPENAI_BASE_URL, OPENAI_DEFAULT_MODEL, OPENAI_API_KEY } = getEnv(c);
18-
1955
const data = await c.req.json();
2056

2157
const res = await proxy(

apps/api/src/middleware/supabase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { createClient } from "@supabase/supabase-js";
22
import { createMiddleware } from "hono/factory";
33

44
import { getEnv } from "../env";
5+
import type { Env } from "../types";
56

67
export const supabaseMiddleware = () => {
7-
return createMiddleware(async (c, next) => {
8+
return createMiddleware<Env>(async (c, next) => {
89
const authHeader = c.req.header("Authorization");
910
const token = authHeader?.replace("Bearer ", "");
1011

apps/api/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { SupabaseClient, User } from "@supabase/supabase-js";
2+
3+
export type Variables = {
4+
supabase: SupabaseClient;
5+
user: User;
6+
};
7+
8+
export type Env = {
9+
Variables: Variables;
10+
};

0 commit comments

Comments
 (0)