From 5f6222ed6b2febbea5314d6d34e5daf56f0a23de Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Wed, 31 Jan 2024 19:46:44 +0000 Subject: [PATCH] Fix routing modes --- .idea/inspectionProfiles/Project_Default.xml | 3 + .vscode/settings.json | 1 + bun.lockb | Bin 270653 -> 270653 bytes drizzle.config.ts | 6 +- package.json | 2 +- src/app/(app)/dashboard/page.tsx | 4 +- src/components/maps/main-map.tsx | 53 +++++++------ src/components/maps/map-marker.tsx | 1 - src/components/maps/map-viewtype-selector.tsx | 16 +++- src/components/pages/dashboard-page.tsx | 18 +++-- src/env.js | 57 +++++++------- src/env.mjs | 36 --------- src/lib/api/children/mutations.ts | 45 ++++++----- src/lib/api/devices/mutations.ts | 50 +++++++----- src/lib/api/pings/queries.ts | 12 ++- src/lib/auth/utils.ts | 5 +- src/lib/helpers/location/ping.ts | 6 +- src/pages/api/socket/ping.ts | 3 - src/server/api/trpc.ts | 31 ++++---- src/server/auth.ts | 72 ++++++++++++++++++ src/server/db/index.ts | 2 +- src/server/db/migrate.ts | 2 +- 22 files changed, 258 insertions(+), 167 deletions(-) delete mode 100644 src/env.mjs create mode 100644 src/server/auth.ts diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 03d9549..d4aa674 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,5 +2,8 @@ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4001a19..f68cb08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "node_modules": true }, "workbench.colorTheme": "poimandres-noitalics", + "editor.fontFamily": "DejaVu Sans Mono", "sqltools.connections": [ { "previewLimit": 50, diff --git a/bun.lockb b/bun.lockb index d9d298b266f487e7e9fd4d8e80500000799c39d1..d40a4ac1cc561a8e811c195ebb92dcf549e8662d 100755 GIT binary patch delta 347 zcmV-h0i^!D!VtZ}5Rfh)-bJw+PFRM|MwYF8-c0Vn(tzEM6b+bzafDSF?9bT@u}+#( zlcYB&lMpKivlvv+837KHktipV5Gx3?7;ey50*eBZktimU5T6ON8OKy*K!-O+JgP%> zo?8*bYSnb*XKC$LGTa;_)$eyiaA-8yGl2B!ZVgj$B3L1NQ`6%QLmw{1`XAbLQKIV|mbe&CMXvRo`}p=fa|Dh08J zLTi@0q^F}E0v10o=o^Bn{rw_2?O>u{G*V*??VAP58!w|E`|#R3NcoyPzG delta 347 zcmV-h0i^!D!VtZ}5Rfh)_$bcejNKuq{h6(?@{8+0GGY1U8ne$O4j!zmzu(lpu}+#( zlVn#YlMpKivlvv+837HGktipV5Gx3?7;ey50*V5YktimU5T6ON8OKy*Kp~L)kOMT> z1+^Uz(>&>O-g-r9_PX!NYw(A?R-Z0L5K%dBFYv|F+K{IimOv?oYoconK(Wwq^^oX8 znzd5xsJE>w0e~YwZfec00*pEnH(JJ@nX#q|?wzn*ncq(@EL#xWuiH<>{7U{@l~yS| zs_gwk>iWPMB>%ZOg1}-78<8$h5SL3)w{1`XAbLQtgph=giED<216&xQUR*H<9*{6) zZCBGptK}J$Yu#pP4bzWuo|@|wCl&hb@j97?z*u3h@g8S(BK>Ow`g&B_6Z-~%u& tG`Cr+0>0q^Ft-?u10o=oVO;|vw_L3Q>2R0Prvo9EAP58!w|E`|#R7-(pR)h} diff --git a/drizzle.config.ts b/drizzle.config.ts index cab1b8b..8d625d4 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,5 +1,5 @@ import type { Config } from "drizzle-kit"; -import { env } from "@/env.mjs"; +import { env } from "@/env"; export default { schema: "./src/server/db/schema", @@ -7,5 +7,5 @@ export default { driver: "pg", dbCredentials: { connectionString: env.DATABASE_URL, - } -} satisfies Config; \ No newline at end of file + }, +} satisfies Config; diff --git a/package.json b/package.json index 398b582..dea921b 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "leaflet": "^1.9.4", "local-ssl-proxy": "^2.0.5", "lucide-react": "^0.314.0", - "next": "^14.0.4", + "next": "^14.1.0", "next-auth": "^4.24.5", "next-themes": "^0.2.1", "postgres": "^3.4.3", diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 279597b..69610c5 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -1,10 +1,12 @@ import DashboardPage from "@/components/pages/dashboard-page"; import { checkAuth } from "@/lib/auth/utils"; import React from "react"; +import { api } from "@/trpc/server"; const Dashboard = async () => { await checkAuth(); - return ; + const { children } = await api.children.getChildren.query(); + return ; }; export default Dashboard; diff --git a/src/components/maps/main-map.tsx b/src/components/maps/main-map.tsx index 680594e..716f0c4 100644 --- a/src/components/maps/main-map.tsx +++ b/src/components/maps/main-map.tsx @@ -1,24 +1,33 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ "use client"; import "leaflet/dist/leaflet.css"; -import React, { useEffect, useState } from "react"; -import { - MapContainer, - Marker, - Popup, - TileLayer, - Circle, - Polyline, -} from "react-leaflet"; -import { usePingSocket } from "@/lib/hooks/use-ping-socket"; +import React from "react"; +import { MapContainer, TileLayer } from "react-leaflet"; import MapMarker from "@/components/maps/map-marker"; +import { Child, type CompleteChild } from "@/server/db/schema/children"; +import { Ping } from "@/server/db/schema/pings"; +import { Device } from "@/server/db/schema/devices"; import { getLatestPing } from "@/lib/helpers/location/ping"; -import { type CompleteChild } from "@/server/db/schema/children"; type MainMapProps = { kids: CompleteChild[]; + mode: "latest" | "route"; }; -const MainMap: React.FC = ({ kids }) => { + +const _renderMarker = (ping: Ping, device: Device, child: Child) => { + return ( + + ); +}; +const MainMap: React.FC = ({ kids, mode }) => { return (
= ({ kids }) => { {kids?.map((kid) => kid.devices?.map((device) => - device.pings.map((ping) => ( - - )), + mode === "route" + ? device.pings.map( + (ping) => + device.pings.length > 0 && _renderMarker(ping, device, kid), + ) + : _renderMarker(getLatestPing(device.pings)!, device, kid), ), )} diff --git a/src/components/maps/map-marker.tsx b/src/components/maps/map-marker.tsx index abe1995..25a387c 100644 --- a/src/components/maps/map-marker.tsx +++ b/src/components/maps/map-marker.tsx @@ -47,7 +47,6 @@ const MapMarker: React.FC = ({ usePingSocket({ deviceId: deviceId, locationUpdate: (ping) => { - console.log("MainMap", "locationUpdate", ping); setPosition([ping.location.latitude, ping.location.longitude]); }, }); diff --git a/src/components/maps/map-viewtype-selector.tsx b/src/components/maps/map-viewtype-selector.tsx index 6ad6bb6..f1a2f56 100644 --- a/src/components/maps/map-viewtype-selector.tsx +++ b/src/components/maps/map-viewtype-selector.tsx @@ -3,15 +3,23 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Icons } from "@/components/icons"; import React from "react"; -export const MapViewTypeSelector = () => { - const [currentView, setCurrentView] = React.useState("location"); +type MapViewTypeSelectorProps = { + currentView: "latest" | "route"; + onChange: (mode: "latest" | "route") => void; +}; +export const MapViewTypeSelector: React.FC = ({ + currentView, + onChange, +}) => { return ( setCurrentView(value)} + onValueChange={(value) => + onChange(value === "latest" ? "latest" : "route") + } > - + Location diff --git a/src/components/pages/dashboard-page.tsx b/src/components/pages/dashboard-page.tsx index d93a900..6d3b5b3 100644 --- a/src/components/pages/dashboard-page.tsx +++ b/src/components/pages/dashboard-page.tsx @@ -1,10 +1,15 @@ +"use client"; +import React from "react"; import dynamic from "next/dynamic"; -import { api } from "@/trpc/server"; import ChildrenFilter from "../children/children-filter"; import { MapViewTypeSelector } from "../maps/map-viewtype-selector"; +import { CompleteChild } from "@/server/db/schema/children"; -const DashboardPage = async () => { - const { children } = await api.children.getChildren.query(); +type DashboardPageProps = { + children: CompleteChild[]; +}; +const DashboardPage: React.FC = ({ children }) => { + const [mode, setMode] = React.useState<"latest" | "route">("latest"); const Map = dynamic(() => import("@/components/maps/main-map"), { ssr: false, }); @@ -12,10 +17,13 @@ const DashboardPage = async () => {
- + setMode(mode)} + />
- +
); diff --git a/src/env.js b/src/env.js index 5c2f937..1d91ba3 100644 --- a/src/env.js +++ b/src/env.js @@ -2,39 +2,36 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; export const env = createEnv({ - /** - * Specify your server-side environment variables schema here. This way you can ensure the app - * isn't built with invalid env vars. - */ server: { - NODE_ENV: z.enum(["development", "test", "production"]), - }, + NODE_ENV: z + .enum(["development", "test", "production"]) + .default("development"), + DATABASE_URL: z.string().min(1), - /** - * Specify your client-side environment variables schema here. This way you can ensure the app - * isn't built with invalid env vars. To expose them to the client, prefix them with - * `NEXT_PUBLIC_`. - */ + NEXTAUTH_SECRET: + process.env.NODE_ENV === "production" + ? z.string().min(1) + : z.string().min(1).optional(), + NEXTAUTH_URL: z.preprocess( + // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL + // Since NextAuth.js automatically uses the VERCEL_URL if present. + (str) => process.env.VERCEL_URL ?? str, + // VERCEL_URL doesn't include `https` so it cant be validated as a URL + process.env.VERCEL_URL ? z.string().min(1) : z.string().url(), + ), + GOOGLE_CLIENT_ID: z.string().min(1), + GOOGLE_CLIENT_SECRET: z.string().min(1), + }, client: { - // NEXT_PUBLIC_CLIENTVAR: z.string(), + // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), }, - - /** - * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. - * middlewares) or client-side so we need to destruct manually. - */ - runtimeEnv: { - NODE_ENV: process.env.NODE_ENV, - // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, + // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually + // runtimeEnv: { + // DATABASE_URL: process.env.DATABASE_URL, + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, + // }, + // For Next.js >= 13.4.4, you only need to destructure client variables: + experimental__runtimeEnv: { + // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, }, - /** - * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially - * useful for Docker builds. - */ - skipValidation: !!process.env.SKIP_ENV_VALIDATION, - /** - * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and - * `SOME_VAR=''` will throw an error. - */ - emptyStringAsUndefined: true, }); diff --git a/src/env.mjs b/src/env.mjs deleted file mode 100644 index 484d66e..0000000 --- a/src/env.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { z } from "zod"; - -export const env = createEnv({ - server: { - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), - DATABASE_URL: z.string().min(1), - - NEXTAUTH_SECRET: process.env.NODE_ENV === "production" - ? z.string().min(1) - : z.string().min(1).optional(), - NEXTAUTH_URL: z.preprocess( - // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL - // Since NextAuth.js automatically uses the VERCEL_URL if present. - (str) => process.env.VERCEL_URL ?? str, - // VERCEL_URL doesn't include `https` so it cant be validated as a URL - process.env.VERCEL_URL ? z.string().min(1) : z.string().url() - ), - GOOGLE_CLIENT_ID: z.string().min(1), - GOOGLE_CLIENT_SECRET: z.string().min(1), - }, - client: { - // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), - }, - // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually - // runtimeEnv: { - // DATABASE_URL: process.env.DATABASE_URL, - // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, - // }, - // For Next.js >= 13.4.4, you only need to destructure client variables: - experimental__runtimeEnv: { - // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY, - }, -}); diff --git a/src/lib/api/children/mutations.ts b/src/lib/api/children/mutations.ts index 51ba174..08e84a8 100644 --- a/src/lib/api/children/mutations.ts +++ b/src/lib/api/children/mutations.ts @@ -1,21 +1,24 @@ import { db } from "@/server/db/index"; import { and, eq } from "drizzle-orm"; -import { - ChildId, - NewChildParams, - UpdateChildParams, +import { + type ChildId, + type NewChildParams, + type UpdateChildParams, updateChildSchema, - insertChildSchema, + insertChildSchema, children, - childIdSchema + childIdSchema, } from "@/server/db/schema/children"; import { getUserAuth } from "@/lib/auth/utils"; export const createChild = async (child: NewChildParams) => { const { session } = await getUserAuth(); - const newChild = insertChildSchema.parse({ ...child, userId: session?.user.id! }); + const newChild = insertChildSchema.parse({ + ...child, + userId: session?.user.id!, + }); try { - const [c] = await db.insert(children).values(newChild).returning(); + const [c] = await db.insert(children).values(newChild).returning(); return { child: c }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -27,13 +30,18 @@ export const createChild = async (child: NewChildParams) => { export const updateChild = async (id: ChildId, child: UpdateChildParams) => { const { session } = await getUserAuth(); const { id: childId } = childIdSchema.parse({ id }); - const newChild = updateChildSchema.parse({ ...child, userId: session?.user.id! }); + const newChild = updateChildSchema.parse({ + ...child, + userId: session?.user.id!, + }); try { - const [c] = await db - .update(children) - .set(newChild) - .where(and(eq(children.id, childId!), eq(children.userId, session?.user.id!))) - .returning(); + const [c] = await db + .update(children) + .set(newChild) + .where( + and(eq(children.id, childId), eq(children.userId, session?.user.id!)), + ) + .returning(); return { child: c }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -46,8 +54,12 @@ export const deleteChild = async (id: ChildId) => { const { session } = await getUserAuth(); const { id: childId } = childIdSchema.parse({ id }); try { - const [c] = await db.delete(children).where(and(eq(children.id, childId!), eq(children.userId, session?.user.id!))) - .returning(); + const [c] = await db + .delete(children) + .where( + and(eq(children.id, childId), eq(children.userId, session?.user.id!)), + ) + .returning(); return { child: c }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -55,4 +67,3 @@ export const deleteChild = async (id: ChildId) => { throw { error: message }; } }; - diff --git a/src/lib/api/devices/mutations.ts b/src/lib/api/devices/mutations.ts index 64cca10..d84844b 100644 --- a/src/lib/api/devices/mutations.ts +++ b/src/lib/api/devices/mutations.ts @@ -1,21 +1,24 @@ import { db } from "@/server/db/index"; import { and, eq } from "drizzle-orm"; -import { - DeviceId, - NewDeviceParams, - UpdateDeviceParams, +import { + type DeviceId, + type NewDeviceParams, + type UpdateDeviceParams, updateDeviceSchema, - insertDeviceSchema, + insertDeviceSchema, devices, - deviceIdSchema + deviceIdSchema, } from "@/server/db/schema/devices"; import { getUserAuth } from "@/lib/auth/utils"; export const createDevice = async (device: NewDeviceParams) => { const { session } = await getUserAuth(); - const newDevice = insertDeviceSchema.parse({ ...device, userId: session?.user.id! }); + const newDevice = insertDeviceSchema.parse({ + ...device, + userId: session?.user.id!, + }); try { - const [d] = await db.insert(devices).values(newDevice).returning(); + const [d] = await db.insert(devices).values(newDevice).returning(); return { device: d }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -24,16 +27,24 @@ export const createDevice = async (device: NewDeviceParams) => { } }; -export const updateDevice = async (id: DeviceId, device: UpdateDeviceParams) => { +export const updateDevice = async ( + id: DeviceId, + device: UpdateDeviceParams, +) => { const { session } = await getUserAuth(); const { id: deviceId } = deviceIdSchema.parse({ id }); - const newDevice = updateDeviceSchema.parse({ ...device, userId: session?.user.id! }); + const newDevice = updateDeviceSchema.parse({ + ...device, + userId: session?.user.id!, + }); try { - const [d] = await db - .update(devices) - .set(newDevice) - .where(and(eq(devices.id, deviceId!), eq(devices.userId, session?.user.id!))) - .returning(); + const [d] = await db + .update(devices) + .set(newDevice) + .where( + and(eq(devices.id, deviceId), eq(devices.userId, session?.user.id!)), + ) + .returning(); return { device: d }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -46,8 +57,12 @@ export const deleteDevice = async (id: DeviceId) => { const { session } = await getUserAuth(); const { id: deviceId } = deviceIdSchema.parse({ id }); try { - const [d] = await db.delete(devices).where(and(eq(devices.id, deviceId!), eq(devices.userId, session?.user.id!))) - .returning(); + const [d] = await db + .delete(devices) + .where( + and(eq(devices.id, deviceId), eq(devices.userId, session?.user.id!)), + ) + .returning(); return { device: d }; } catch (err) { const message = (err as Error).message ?? "Error, please try again"; @@ -55,4 +70,3 @@ export const deleteDevice = async (id: DeviceId) => { throw { error: message }; } }; - diff --git a/src/lib/api/pings/queries.ts b/src/lib/api/pings/queries.ts index 6263f33..57d8c1d 100644 --- a/src/lib/api/pings/queries.ts +++ b/src/lib/api/pings/queries.ts @@ -2,18 +2,22 @@ import { db } from "@/server/db/index"; import { eq, and } from "drizzle-orm"; import { getUserAuth } from "@/lib/auth/utils"; import { type PingId, pingIdSchema, pings } from "@/server/db/schema/pings"; -import { devices } from "@/server/db/schema/devices"; export const getPings = async () => { const { session } = await getUserAuth(); - const p = await db.select({ ping: pings, device: devices }).from(pings).leftJoin(devices, eq(pings.deviceId, devices.id)).where(eq(pings.userId, session?.user.id!)); + const p = await db.query.pings.findMany({ + where: (pings, { eq }) => eq(pings.userId, session?.user.id!), + }); return { pings: p }; }; export const getPingById = async (id: PingId) => { const { session } = await getUserAuth(); const { id: pingId } = pingIdSchema.parse({ id }); - const [p] = await db.select().from(pings).where(and(eq(pings.id, pingId), eq(pings.userId, session?.user.id!))).leftJoin(devices, eq(pings.deviceId, devices.id)); + const p = await db.query.pings.findFirst({ + where: (pings, { eq, and }) => + and(eq(pings.id, pingId), eq(pings.userId, session?.user.id!)), + }); + return { ping: p }; }; - diff --git a/src/lib/auth/utils.ts b/src/lib/auth/utils.ts index f8c57fe..a471437 100644 --- a/src/lib/auth/utils.ts +++ b/src/lib/auth/utils.ts @@ -2,8 +2,9 @@ import { db } from "@/server/db/index"; import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { DefaultSession, getServerSession, NextAuthOptions } from "next-auth"; import { redirect } from "next/navigation"; -import { env } from "@/env.mjs"; import GoogleProvider from "next-auth/providers/google"; +import { Adapter } from "next-auth/adapters"; +import { env } from "@/env"; declare module "next-auth" { interface Session { @@ -24,7 +25,7 @@ export type AuthSession = { }; export const authOptions: NextAuthOptions = { - adapter: DrizzleAdapter(db), + adapter: DrizzleAdapter(db) as Adapter, callbacks: { session: ({ session, user }) => { session.user.id = user.id; diff --git a/src/lib/helpers/location/ping.ts b/src/lib/helpers/location/ping.ts index c03724c..cf439eb 100644 --- a/src/lib/helpers/location/ping.ts +++ b/src/lib/helpers/location/ping.ts @@ -1,6 +1,8 @@ -import type PingModel from "@/lib/models/ping"; +import { type CompletePing } from "@/server/db/schema/pings"; -export const getLatestPing = (pings: PingModel[]): PingModel | undefined => { +export const getLatestPing = ( + pings: CompletePing[], +): CompletePing | undefined => { if (pings && pings.length !== 0) { return pings.reduce((ping, current) => ping.timestamp > current.timestamp ? ping : current, diff --git a/src/pages/api/socket/ping.ts b/src/pages/api/socket/ping.ts index 22b6d2a..1ef13fb 100644 --- a/src/pages/api/socket/ping.ts +++ b/src/pages/api/socket/ping.ts @@ -1,11 +1,8 @@ -import { db } from "@/server/db"; import { StatusCodes } from "http-status-codes"; import { type NextApiResponseServerIo } from "@/lib/models/types/next-api-response-socket"; import { type NextApiRequest } from "next"; -import type LocationUpdate from "@/lib/models/location-update"; import { badRequest, notAuthorised } from "@/lib/api/responses"; import { getDeviceById } from "@/lib/api/devices/queries"; -import { getChildById } from "@/lib/api/children/queries"; import { createPing } from "@/lib/api/pings/mutations"; type PingRequest = { diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 213e30b..b3b7093 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -6,10 +6,14 @@ * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will * need to use are documented accordingly near the end. */ -import { initTRPC } from "@trpc/server"; + +import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; +import { db } from "@/server/db"; +import { getServerAuthSession } from "../auth"; + /** * 1. CONTEXT * @@ -23,7 +27,11 @@ import { ZodError } from "zod"; * @see https://trpc.io/docs/server/context */ export const createTRPCContext = async (opts: { headers: Headers }) => { + const session = await getServerAuthSession(); + return { + db, + session, ...opts, }; }; @@ -72,8 +80,15 @@ export const createTRPCRouter = t.router; */ export const publicProcedure = t.procedure; -/** Reusable middleware that enforces users are logged in before running the procedure. */ -const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { +/** + * Protected (authenticated) procedure + * + * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies + * the session is valid and guarantees `ctx.session.user` is not null. + * + * @see https://trpc.io/docs/procedures + */ +export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.session || !ctx.session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } @@ -84,13 +99,3 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { }, }); }); - -/** - * Protected (authenticated) procedure - * - * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies - * the session is valid and guarantees `ctx.session.user` is not null. - * - * @see https://trpc.io/docs/procedures - */ -export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); diff --git a/src/server/auth.ts b/src/server/auth.ts new file mode 100644 index 0000000..6047a6d --- /dev/null +++ b/src/server/auth.ts @@ -0,0 +1,72 @@ +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { + getServerSession, + type DefaultSession, + type NextAuthOptions, +} from "next-auth"; +import { type Adapter } from "next-auth/adapters"; +import DiscordProvider from "next-auth/providers/discord"; + +import { env } from "@/env"; +import { db } from "@/server/db"; + +/** + * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` + * object and keep type safety. + * + * @see https://next-auth.js.org/getting-started/typescript#module-augmentation + */ +declare module "next-auth" { + interface Session extends DefaultSession { + user: { + id: string; + // ...other properties + // role: UserRole; + } & DefaultSession["user"]; + } + + // interface User { + // // ...other properties + // // role: UserRole; + // } +} + +/** + * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. + * + * @see https://next-auth.js.org/configuration/options + */ +export const authOptions: NextAuthOptions = { + callbacks: { + session: ({ session, user }) => ({ + ...session, + user: { + ...session.user, + id: user.id, + }, + }), + }, + adapter: DrizzleAdapter(db) as Adapter, + providers: [ + DiscordProvider({ + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ + ], +}; + +/** + * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. + * + * @see https://next-auth.js.org/configuration/nextjs + */ +export const getServerAuthSession = () => getServerSession(authOptions); diff --git a/src/server/db/index.ts b/src/server/db/index.ts index 5e0e35a..e0bef4d 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -1,6 +1,6 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; -import { env } from "@/env.mjs"; +import { env } from "@/env"; import { users, accounts, diff --git a/src/server/db/migrate.ts b/src/server/db/migrate.ts index b9afa86..880c016 100644 --- a/src/server/db/migrate.ts +++ b/src/server/db/migrate.ts @@ -1,4 +1,4 @@ -import { env } from "@/env.mjs"; +import { env } from "@/env"; import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator";