mirror of
https://github.com/fergalmoran/kidarr-server.git
synced 2025-12-22 01:07:47 +00:00
Fix routing modes
This commit is contained in:
3
.idea/inspectionProfiles/Project_Default.xml
generated
3
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,5 +2,8 @@
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="UpdateDependencyToLatestVersion" enabled="true" level="WARNING" enabled_by_default="true" editorAttributes="WARNING_ATTRIBUTES">
|
||||
<scope name="Project Files" level="WARNING" enabled="true" editorAttributes="WARNING_ATTRIBUTES" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -11,6 +11,7 @@
|
||||
"node_modules": true
|
||||
},
|
||||
"workbench.colorTheme": "poimandres-noitalics",
|
||||
"editor.fontFamily": "DejaVu Sans Mono",
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <DashboardPage />;
|
||||
const { children } = await api.children.getChildren.query();
|
||||
return <DashboardPage children={children} />;
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
|
||||
@@ -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<MainMapProps> = ({ kids }) => {
|
||||
|
||||
const _renderMarker = (ping: Ping, device: Device, child: Child) => {
|
||||
return (
|
||||
<MapMarker
|
||||
key={ping.id}
|
||||
deviceId={device.id}
|
||||
childName={child.name}
|
||||
avatar={child.avatar}
|
||||
deviceName={device.name}
|
||||
latitude={ping.latitude}
|
||||
longitude={ping.longitude}
|
||||
timestamp={ping.timestamp}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const MainMap: React.FC<MainMapProps> = ({ kids, mode }) => {
|
||||
return (
|
||||
<div>
|
||||
<MapContainer
|
||||
@@ -30,18 +39,12 @@ const MainMap: React.FC<MainMapProps> = ({ kids }) => {
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
{kids?.map((kid) =>
|
||||
kid.devices?.map((device) =>
|
||||
device.pings.map((ping) => (
|
||||
<MapMarker
|
||||
key={ping.id}
|
||||
deviceId={device.id}
|
||||
childName={kid.name}
|
||||
avatar={kid.avatar}
|
||||
deviceName={device.name}
|
||||
latitude={ping.latitude}
|
||||
longitude={ping.longitude}
|
||||
timestamp={ping.timestamp}
|
||||
/>
|
||||
)),
|
||||
mode === "route"
|
||||
? device.pings.map(
|
||||
(ping) =>
|
||||
device.pings.length > 0 && _renderMarker(ping, device, kid),
|
||||
)
|
||||
: _renderMarker(getLatestPing(device.pings)!, device, kid),
|
||||
),
|
||||
)}
|
||||
</MapContainer>
|
||||
|
||||
@@ -47,7 +47,6 @@ const MapMarker: React.FC<MapMarkerProps> = ({
|
||||
usePingSocket({
|
||||
deviceId: deviceId,
|
||||
locationUpdate: (ping) => {
|
||||
console.log("MainMap", "locationUpdate", ping);
|
||||
setPosition([ping.location.latitude, ping.location.longitude]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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<MapViewTypeSelectorProps> = ({
|
||||
currentView,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={currentView}
|
||||
onValueChange={(value) => setCurrentView(value)}
|
||||
onValueChange={(value) =>
|
||||
onChange(value === "latest" ? "latest" : "route")
|
||||
}
|
||||
>
|
||||
<ToggleGroupItem value="location">
|
||||
<ToggleGroupItem value="latest">
|
||||
<Icons.location className="mr-1 h-4 w-4" />
|
||||
Location
|
||||
</ToggleGroupItem>
|
||||
|
||||
@@ -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<DashboardPageProps> = ({ 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 () => {
|
||||
<div>
|
||||
<div className="flex flex-row justify-between">
|
||||
<ChildrenFilter children={children} />
|
||||
<MapViewTypeSelector />
|
||||
<MapViewTypeSelector
|
||||
currentView={mode}
|
||||
onChange={(mode) => setMode(mode)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Map kids={children} />
|
||||
<Map kids={children} mode={mode} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
57
src/env.js
57
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,
|
||||
});
|
||||
|
||||
36
src/env.mjs
36
src/env.mjs
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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 };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
72
src/server/auth.ts
Normal file
72
src/server/auth.ts
Normal file
@@ -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);
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user