Merge pull request #20 from kennethnym/feat/location-router

feat(backend): add location router with tRPC factory pattern
This commit is contained in:
2026-01-25 23:00:03 +00:00
committed by GitHub
6 changed files with 104 additions and 46 deletions

View File

@@ -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"

View File

@@ -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
}
}),
})
}

View File

@@ -2,8 +2,14 @@ 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"
function main() {
const locationService = new LocationService()
const trpcRouter = createTRPCRouter({ locationService })
const app = new Hono()
@@ -14,11 +20,16 @@ registerAuthHandlers(app)
app.use(
"/trpc/*",
trpcServer({
router: appRouter,
router: trpcRouter,
createContext,
}),
)
return app
}
const app = main()
export default {
port: 3000,
fetch: app.fetch,

View File

@@ -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<Context["user"]>
session: NonNullable<Context["session"]>
}
function createTRPC() {
const t = initTRPC.context<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<typeof createTRPC>
export interface TRPCRouterDeps {
locationService: LocationService
}
export function createTRPCRouter({ locationService }: TRPCRouterDeps) {
const t = createTRPC()
return t.router({
location: createLocationRouter(t, { locationService }),
})
}
export type TRPCRouter = ReturnType<typeof createTRPCRouter>

View File

@@ -1,22 +0,0 @@
import { initTRPC, TRPCError } from "@trpc/server"
import type { Context } from "./context.ts"
const t = initTRPC.context<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)

View File

@@ -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=="],
}
}