diff --git a/apps/aris-backend/package.json b/apps/aris-backend/package.json index e0702d8..e33baf2 100644 --- a/apps/aris-backend/package.json +++ b/apps/aris-backend/package.json @@ -14,10 +14,10 @@ "@aris/source-weatherkit": "workspace:*", "@hono/trpc-server": "^0.3", "@trpc/server": "^11", + "arktype": "^2.1.29", "better-auth": "^1", "hono": "^4", - "pg": "^8", - "zod": "^3" + "pg": "^8" }, "devDependencies": { "@types/pg": "^8" diff --git a/apps/aris-backend/src/location/router.ts b/apps/aris-backend/src/location/router.ts new file mode 100644 index 0000000..a76ad88 --- /dev/null +++ b/apps/aris-backend/src/location/router.ts @@ -0,0 +1,33 @@ +import { TRPCError } from "@trpc/server" +import { type } from "arktype" + +import { UserNotFoundError } from "../lib/error.ts" +import type { TRPC } from "../trpc/router.ts" +import type { LocationService } from "./service.ts" + +const locationInput = type({ + lat: "number", + lng: "number", + accuracy: "number", + timestamp: "Date", +}) + +export function createLocationRouter(t: TRPC, { locationService }: { locationService: LocationService }) { + return t.router({ + update: t.procedure.input(locationInput).mutation(({ input, ctx }) => { + try { + locationService.updateUserLocation(ctx.user.id, { + lat: input.lat, + lng: input.lng, + accuracy: input.accuracy, + timestamp: input.timestamp, + }) + } catch (error) { + if (error instanceof UserNotFoundError) { + throw new TRPCError({ code: "NOT_FOUND", message: error.message }) + } + throw error + } + }), + }) +} diff --git a/apps/aris-backend/src/server.ts b/apps/aris-backend/src/server.ts index 287c8ca..ff40f3e 100644 --- a/apps/aris-backend/src/server.ts +++ b/apps/aris-backend/src/server.ts @@ -2,22 +2,33 @@ import { trpcServer } from "@hono/trpc-server" import { Hono } from "hono" import { registerAuthHandlers } from "./auth/http.ts" +import { LocationService } from "./location/service.ts" import { createContext } from "./trpc/context.ts" -import { appRouter } from "./trpc/router.ts" +import { createTRPCRouter } from "./trpc/router.ts" -const app = new Hono() +function main() { + const locationService = new LocationService() -app.get("/health", (c) => c.json({ status: "ok" })) + const trpcRouter = createTRPCRouter({ locationService }) -registerAuthHandlers(app) + const app = new Hono() -app.use( - "/trpc/*", - trpcServer({ - router: appRouter, - createContext, - }), -) + app.get("/health", (c) => c.json({ status: "ok" })) + + registerAuthHandlers(app) + + app.use( + "/trpc/*", + trpcServer({ + router: trpcRouter, + createContext, + }), + ) + + return app +} + +const app = main() export default { port: 3000, diff --git a/apps/aris-backend/src/trpc/router.ts b/apps/aris-backend/src/trpc/router.ts index 2b99062..74d60d5 100644 --- a/apps/aris-backend/src/trpc/router.ts +++ b/apps/aris-backend/src/trpc/router.ts @@ -1,5 +1,47 @@ -import { router } from "./trpc.ts" +import { initTRPC, TRPCError } from "@trpc/server" -export const appRouter = router({}) +import { createLocationRouter } from "../location/router.ts" +import type { LocationService } from "../location/service.ts" +import type { Context } from "./context.ts" -export type AppRouter = typeof appRouter +interface AuthedContext { + user: NonNullable + session: NonNullable +} + +function createTRPC() { + const t = initTRPC.context().create() + + const isAuthed = t.middleware(({ ctx, next }) => { + if (!ctx.user || !ctx.session) { + throw new TRPCError({ code: "UNAUTHORIZED" }) + } + return next({ + ctx: { + user: ctx.user, + session: ctx.session, + }, + }) + }) + + return { + router: t.router, + procedure: t.procedure.use(isAuthed), + } +} + +export type TRPC = ReturnType + +export interface TRPCRouterDeps { + locationService: LocationService +} + +export function createTRPCRouter({ locationService }: TRPCRouterDeps) { + const t = createTRPC() + + return t.router({ + location: createLocationRouter(t, { locationService }), + }) +} + +export type TRPCRouter = ReturnType diff --git a/apps/aris-backend/src/trpc/trpc.ts b/apps/aris-backend/src/trpc/trpc.ts deleted file mode 100644 index 368c742..0000000 --- a/apps/aris-backend/src/trpc/trpc.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { initTRPC, TRPCError } from "@trpc/server" - -import type { Context } from "./context.ts" - -const t = initTRPC.context().create() - -export const router = t.router -export const publicProcedure = t.procedure - -const isAuthed = t.middleware(({ ctx, next }) => { - if (!ctx.user || !ctx.session) { - throw new TRPCError({ code: "UNAUTHORIZED" }) - } - return next({ - ctx: { - user: ctx.user, - session: ctx.session, - }, - }) -}) - -export const protectedProcedure = t.procedure.use(isAuthed) diff --git a/bun.lock b/bun.lock index 78740e9..c3085e8 100644 --- a/bun.lock +++ b/bun.lock @@ -22,10 +22,10 @@ "@aris/source-weatherkit": "workspace:*", "@hono/trpc-server": "^0.3", "@trpc/server": "^11", + "arktype": "^2.1.29", "better-auth": "^1", "hono": "^4", "pg": "^8", - "zod": "^3", }, "devDependencies": { "@types/pg": "^8", @@ -204,12 +204,6 @@ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@better-auth/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "better-call/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], } }