From a2fbdaa1fa66e2d0f5fae293a3dd6d88b9420560 Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Wed, 22 Nov 2023 18:09:08 +0000 Subject: [PATCH] Child and device onboarding working --- .vscode/launch.json | 28 ++ .vscode/settings.json | 16 +- bun.lockb | Bin 254374 -> 260454 bytes drizzle.config.ts | 12 +- ...rebrand.sql => 0000_medical_jetstream.sql} | 56 ++-- drizzle/meta/0000_snapshot.json | 284 +++++++++--------- drizzle/meta/_journal.json | 4 +- package.json | 19 +- scripts/reset.sh | 1 + src/app/api/child/create/route.ts | 48 ++- src/app/api/child/ping/route.ts | 41 +++ src/app/api/child/route.ts | 4 +- src/app/api/device/connect/route.ts | 58 ++++ src/app/api/device/ping/route.ts | 34 +++ src/app/api/responses/index.ts | 10 + src/components/children/child-select-list.tsx | 2 +- src/components/children/children-list.tsx | 13 +- .../children/connect-device-dialog.tsx | 2 +- src/components/icons.tsx | 3 + src/db/index.ts | 8 + src/db/{schema/auth.ts => schema.ts} | 46 ++- src/db/schema/child.ts | 57 ---- src/db/schema/index.ts | 7 - src/lib/services/auth/config.ts | 2 +- src/lib/services/auth/provider.tsx | 8 +- src/lib/validations/connect-device.ts | 5 + 26 files changed, 460 insertions(+), 308 deletions(-) create mode 100644 .vscode/launch.json rename drizzle/{0000_bizarre_firebrand.sql => 0000_medical_jetstream.sql} (80%) create mode 100644 src/app/api/child/ping/route.ts create mode 100644 src/app/api/device/ping/route.ts create mode 100644 src/app/api/responses/index.ts create mode 100644 src/db/index.ts rename src/db/{schema/auth.ts => schema.ts} (54%) delete mode 100644 src/db/schema/child.ts delete mode 100644 src/db/schema/index.ts create mode 100644 src/lib/validations/connect-device.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2727a18 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "bun dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "bun dev", + "serverReadyAction": { + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 77478c2..f2a0327 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,7 @@ "**/.DS_Store": true, "**/Thumbs.db": true }, - "workbench.colorTheme": "Pink Cat Boo", + "workbench.colorTheme": "Monokai Classic", "workbench.iconTheme": "vscode-icontheme-nomo-dark-macos", "workbench.colorCustomizations": { "activityBar.activeBackground": "#ab307e", @@ -33,5 +33,17 @@ "titleBar.inactiveBackground": "#83256199", "titleBar.inactiveForeground": "#e7e7e799" }, - "peacock.color": "#832561" + "peacock.color": "#832561", + "sqltools.connections": [ + { + "previewLimit": 50, + "server": "localhost", + "port": 5432, + "driver": "PostgreSQL", + "name": "localhost", + "username": "postgres", + "password": "hackme", + "database": "parentgrine" + } + ] } diff --git a/bun.lockb b/bun.lockb index ba78f913308a0e490f3d9830f043d9a67dfb5acf..b30d2317c5b580e6889110f65ebc591a4781177d 100755 GIT binary patch delta 52206 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 I#_Q<*AAY+;n*aa+ delta 48921 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 J6Y3e${0H429{~UW diff --git a/drizzle.config.ts b/drizzle.config.ts index 43d26a9..36b5421 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,13 +1,13 @@ -import type { Config } from "drizzle-kit"; -import 'dotenv/config' +import type { Config } from 'drizzle-kit'; +import 'dotenv/config'; if (!process.env.DATABASE_URL) { - throw new Error("DATABASE_URL is missing"); + throw new Error('DATABASE_URL is missing'); } export default { - schema: "./src/db/schema/*", - out: "./drizzle", - driver: "pg", + schema: './src/db', + out: './drizzle', + driver: 'pg', dbCredentials: { connectionString: process.env.DATABASE_URL as string, }, diff --git a/drizzle/0000_bizarre_firebrand.sql b/drizzle/0000_medical_jetstream.sql similarity index 80% rename from drizzle/0000_bizarre_firebrand.sql rename to drizzle/0000_medical_jetstream.sql index d753b09..bfcd2a8 100644 --- a/drizzle/0000_bizarre_firebrand.sql +++ b/drizzle/0000_medical_jetstream.sql @@ -13,6 +13,25 @@ CREATE TABLE IF NOT EXISTS "account" ( CONSTRAINT account_provider_providerAccountId PRIMARY KEY("provider","providerAccountId") ); --> statement-breakpoint +CREATE TABLE IF NOT EXISTS "child" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "parent_id" uuid NOT NULL, + "name" varchar(256), + "phone" varchar(256), + "key" varchar(256) +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "device" ( + "child_id" uuid NOT NULL, + "pin" integer NOT NULL, + "device_id" varchar NOT NULL, + "api_key" varchar NOT NULL, + "expires" timestamp DEFAULT now() + interval '1 hour', + CONSTRAINT device_child_id_device_id PRIMARY KEY("child_id","device_id"), + CONSTRAINT "device_device_id_unique" UNIQUE("device_id"), + CONSTRAINT "device_api_key_unique" UNIQUE("api_key") +); +--> statement-breakpoint CREATE TABLE IF NOT EXISTS "session" ( "sessionToken" text PRIMARY KEY NOT NULL, "userId" uuid NOT NULL, @@ -34,41 +53,12 @@ CREATE TABLE IF NOT EXISTS "verificationToken" ( CONSTRAINT verificationToken_identifier_token PRIMARY KEY("identifier","token") ); --> statement-breakpoint -CREATE TABLE IF NOT EXISTS "child" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "parent_id" uuid NOT NULL, - "name" varchar(256), - "phone" varchar(256), - "key" varchar(256) -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "child_devices" ( - "child_id" uuid NOT NULL, - "pin" integer, - "api_key" varchar, - "expires" timestamp DEFAULT now() + interval '1 hour', - CONSTRAINT child_devices_child_id_pin PRIMARY KEY("child_id","pin") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "locations" ( - "uuid1" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "longitude" numeric, - "latitude" numeric, - "user_id" uuid -); ---> statement-breakpoint DO $$ BEGIN ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint DO $$ BEGIN ALTER TABLE "child" ADD CONSTRAINT "child_parent_id_user_id_fk" FOREIGN KEY ("parent_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION @@ -76,7 +66,13 @@ EXCEPTION END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "child_devices" ADD CONSTRAINT "child_devices_child_id_child_id_fk" FOREIGN KEY ("child_id") REFERENCES "child"("id") ON DELETE cascade ON UPDATE no action; + ALTER TABLE "device" ADD CONSTRAINT "device_child_id_child_id_fk" FOREIGN KEY ("child_id") REFERENCES "child"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index c2680dd..08cebeb 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,8 +1,8 @@ { + "id": "31902de1-3b11-4911-8297-18ddfbb762fa", + "prevId": "00000000-0000-0000-0000-000000000000", "version": "5", "dialect": "pg", - "id": "95fbc031-2db5-4391-aa2c-f48c8829eda6", - "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "account": { "name": "account", @@ -102,6 +102,139 @@ }, "uniqueConstraints": {} }, + "child": { + "name": "child", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "child_parent_id_user_id_fk": { + "name": "child_parent_id_user_id_fk", + "tableFrom": "child", + "tableTo": "user", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "device": { + "name": "device", + "schema": "", + "columns": { + "child_id": { + "name": "child_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pin": { + "name": "pin", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "api_key": { + "name": "api_key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now() + interval '1 hour'" + } + }, + "indexes": {}, + "foreignKeys": { + "device_child_id_child_id_fk": { + "name": "device_child_id_child_id_fk", + "tableFrom": "device", + "tableTo": "child", + "columnsFrom": [ + "child_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "device_child_id_device_id": { + "name": "device_child_id_device_id", + "columns": [ + "child_id", + "device_id" + ] + } + }, + "uniqueConstraints": { + "device_device_id_unique": { + "name": "device_device_id_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id" + ] + }, + "device_api_key_unique": { + "name": "device_api_key_unique", + "nullsNotDistinct": false, + "columns": [ + "api_key" + ] + } + } + }, "session": { "name": "session", "schema": "", @@ -219,153 +352,6 @@ } }, "uniqueConstraints": {} - }, - "child": { - "name": "child", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "parent_id": { - "name": "parent_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(256)", - "primaryKey": false, - "notNull": false - }, - "phone": { - "name": "phone", - "type": "varchar(256)", - "primaryKey": false, - "notNull": false - }, - "key": { - "name": "key", - "type": "varchar(256)", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "child_parent_id_user_id_fk": { - "name": "child_parent_id_user_id_fk", - "tableFrom": "child", - "tableTo": "user", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "child_devices": { - "name": "child_devices", - "schema": "", - "columns": { - "child_id": { - "name": "child_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "pin": { - "name": "pin", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "api_key": { - "name": "api_key", - "type": "varchar", - "primaryKey": false, - "notNull": false - }, - "expires": { - "name": "expires", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now() + interval '1 hour'" - } - }, - "indexes": {}, - "foreignKeys": { - "child_devices_child_id_child_id_fk": { - "name": "child_devices_child_id_child_id_fk", - "tableFrom": "child_devices", - "tableTo": "child", - "columnsFrom": [ - "child_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "child_devices_child_id_pin": { - "name": "child_devices_child_id_pin", - "columns": [ - "child_id", - "pin" - ] - } - }, - "uniqueConstraints": {} - }, - "locations": { - "name": "locations", - "schema": "", - "columns": { - "uuid1": { - "name": "uuid1", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "longitude": { - "name": "longitude", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "latitude": { - "name": "latitude", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} } }, "enums": {}, diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 83ed5cf..e1a71c9 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "5", - "when": 1699562330825, - "tag": "0000_bizarre_firebrand", + "when": 1700413726993, + "tag": "0000_medical_jetstream", "breakpoints": true } ] diff --git a/package.json b/package.json index e1d4712..9f2f72f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint": "next lint" }, "dependencies": { - "@auth/drizzle-adapter": "^0.3.5", + "@auth/drizzle-adapter": "^0.3.6", "@hookform/resolvers": "^3.3.2", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", @@ -38,9 +38,9 @@ "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", - "@tanstack/react-query": "^5.8.1", - "@tanstack/react-query-devtools": "^5.8.1", - "axios": "^1.6.1", + "@tanstack/react-query": "^5.8.4", + "@tanstack/react-query-devtools": "^5.8.4", + "axios": "^1.6.2", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", @@ -52,9 +52,10 @@ "leaflet": "^1.9.4", "local-ssl-proxy": "^2.0.5", "lucide-react": "^0.292.0", - "next": "14.0.2", - "next-auth": "^4.24.4", + "next": "14.0.3", + "next-auth": "^4.24.5", "next-themes": "^0.2.1", + "pg": "^8.11.3", "postgres": "^3.4.3", "react": "^18", "react-day-picker": "^8.9.1", @@ -72,11 +73,11 @@ "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10", - "drizzle-kit": "^0.20.1", + "drizzle-kit": "^0.20.4", "eslint": "^8", - "eslint-config-next": "14.0.2", + "eslint-config-next": "14.0.3", "postcss": "^8", - "prettier": "3.0.3", + "prettier": "3.1.0", "tailwindcss": "^3", "typescript": "^5" } diff --git a/scripts/reset.sh b/scripts/reset.sh index 63518cf..5c717d7 100755 --- a/scripts/reset.sh +++ b/scripts/reset.sh @@ -3,6 +3,7 @@ export PGUSER=postgres export PGPASSWORD=hackme export PGHOST=localhost + echo Removing migrations rm -rf drizzle echo "Dropping db" diff --git a/src/app/api/child/create/route.ts b/src/app/api/child/create/route.ts index 759dc97..36ca131 100644 --- a/src/app/api/child/create/route.ts +++ b/src/app/api/child/create/route.ts @@ -4,19 +4,20 @@ import { createApiKey } from '@/lib/services/auth/api'; import { NextResponse } from 'next/server'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import db from '@/db/schema'; +import db from '@/db'; -import { child, childDevices } from '@/db/schema/child'; -import { users } from '@/db/schema/auth'; +import { child } from '@/db/schema'; +import { users } from '@/db/schema'; import { eq } from 'drizzle-orm'; +import { device } from '@/db/schema'; export async function POST(req: Request) { const session = await getServerAuthSession(); if (!session || !session.user?.email) - return NextResponse.json( - { error: getReasonPhrase(StatusCodes.UNAUTHORIZED) }, - { status: StatusCodes.UNAUTHORIZED } - ); + return NextResponse.next({ + statusText: getReasonPhrase(StatusCodes.UNAUTHORIZED), + status: StatusCodes.UNAUTHORIZED, + }); const body = await req.json(); const user = await db @@ -25,10 +26,10 @@ export async function POST(req: Request) { .where(eq(users.email, session.user.email)); if (!user) { - return NextResponse.json( - { error: `Unable to find user id for ${session.user.email}` }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); + return NextResponse.next({ + statusText: `Unable to find user id for ${session.user.email}`, + status: StatusCodes.INTERNAL_SERVER_ERROR, + }); } const { name } = newChildSchema.parse(body); @@ -40,26 +41,11 @@ export async function POST(req: Request) { }) .returning(); if (!newChild) { - return NextResponse.json( - { error: `Error inserting child` }, - { status: StatusCodes.INTERNAL_SERVER_ERROR } - ); + return NextResponse.next({ + statusText: 'Error inserting child', + status: StatusCodes.INTERNAL_SERVER_ERROR, + }); } - let done = false; - let pin = 0; - while (!done) { - pin = Math.floor(1000 + Math.random() * 9000); - const exists = await db - .selectDistinct() - .from(childDevices) - .where(eq(childDevices.childId, newChild[0].id)); - done = exists.length === 0; - } - const apiKey = createApiKey(); - await db - .insert(childDevices) - .values({ childId: newChild[0].id, pin, apiKey: apiKey }) - .execute(); - return NextResponse.json({ status: 'success', pin: pin }); + return NextResponse.json({ status: 'success' }); } diff --git a/src/app/api/child/ping/route.ts b/src/app/api/child/ping/route.ts new file mode 100644 index 0000000..804a94e --- /dev/null +++ b/src/app/api/child/ping/route.ts @@ -0,0 +1,41 @@ +import db from '@/db'; +import { StatusCodes } from 'http-status-codes'; +import { headers } from 'next/headers'; +import { eq } from 'drizzle-orm'; +import { device, child } from '@/db/schema'; +export async function POST(req: Request) { + const headersList = headers(); + const apiKey = headersList.get('X-Auth-ApiKey'); + + if (!apiKey) { + return new Response('Unauthorized', { + status: StatusCodes.UNAUTHORIZED, + statusText: 'Unauthorized', + }); + } + + const childId = await db + .selectDistinct() + .from(device) + .where(eq(device.apiKey, apiKey)); + if (!childId) { + return new Response('Unauthorized', { + status: StatusCodes.UNAUTHORIZED, + statusText: 'Unauthorized', + }); + } + const pinger = await db + .selectDistinct() + .from(child) + .where(eq(child.id, childId[0].childId)); + if (!pinger) { + return new Response('Unauthorized', { + status: StatusCodes.UNAUTHORIZED, + statusText: 'Unauthorized', + }); + } + return new Response('pong', { + status: StatusCodes.OK, + statusText: 'OK', + }); +} diff --git a/src/app/api/child/route.ts b/src/app/api/child/route.ts index 74f5c5c..cc85b52 100644 --- a/src/app/api/child/route.ts +++ b/src/app/api/child/route.ts @@ -1,5 +1,5 @@ -import db from '@/db/schema'; -import { child } from '@/db/schema/child'; +import db from '@/db'; +import { child } from '@/db/schema'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { NextResponse } from 'next/server'; import { getServerAuthSession } from '@/lib/services/auth/config'; diff --git a/src/app/api/device/connect/route.ts b/src/app/api/device/connect/route.ts index e69de29..e0d70b8 100644 --- a/src/app/api/device/connect/route.ts +++ b/src/app/api/device/connect/route.ts @@ -0,0 +1,58 @@ +import db from '@/db' +import { eq } from 'drizzle-orm' +import { StatusCodes } from 'http-status-codes' +import { child, device } from '@/db/schema' +import { createApiKey } from '@/lib/services/auth/api' +import { badRequest } from '@/app/api/responses' + +const POST = async (req: Request, res: Response) => { + if (req.method === 'POST') { + const url = new URL(req.url) + const { deviceId, childId } = await req.json() + console.log('route', 'childId', childId) + console.log('route', 'deviceId', deviceId) + + if (!childId || !deviceId) { + return badRequest('Invalid registration request') + } + + const childToRegister = ( + await db.selectDistinct().from(child).where(eq(child.id, childId)) + )[0] + + if (!childToRegister) { + return badRequest('Invalid registration request') + } + + let done = false + let pin = 2021 + while (!done) { + pin = Math.floor(1000 + Math.random() * 9000) + console.log('route', 'device/connect', 'checking for PIN', pin) + const exists = await db + .selectDistinct() + .from(device) + .where(eq(device.pin, pin)) + console.log('route', 'exists', exists) + done = exists.length === 0 + } + + const apiKey = createApiKey() + await db + .insert(device) + .values({ + childId: childToRegister.id, + deviceId: deviceId, + pin, + apiKey: apiKey, + }) + .execute() + return Response.json( + { childId, pin, apiKey }, + { status: StatusCodes.CREATED }, + ) + } + return badRequest('Invalid registration request') +} + +export { POST } diff --git a/src/app/api/device/ping/route.ts b/src/app/api/device/ping/route.ts new file mode 100644 index 0000000..b477acb --- /dev/null +++ b/src/app/api/device/ping/route.ts @@ -0,0 +1,34 @@ +import { StatusCodes } from 'http-status-codes'; +import { notAuthorised, badRequest } from '../../responses'; +import db from '@/db'; +import { device } from '@/db/schema'; +import { eq } from 'drizzle-orm'; + +const POST = async (req: Request, res: Response) => { + const apiKey = req.headers.get('x-api-key'); + if (!apiKey) { + return notAuthorised(); + } + + const user = await db.query.device.findFirst({ + where: (device, { eq }) => eq(device.apiKey, apiKey), + with: { + child: true, + }, + }); + + const { coordinates } = await req.json(); + + // Check if coordinates exist in the headers + if (!coordinates) { + return badRequest('Invalid coordinates'); + } + + // Process the coordinates + // ... + + // Send a response + return Response.json({}, { status: StatusCodes.CREATED }); +}; + +export { POST }; diff --git a/src/app/api/responses/index.ts b/src/app/api/responses/index.ts new file mode 100644 index 0000000..5cd6bad --- /dev/null +++ b/src/app/api/responses/index.ts @@ -0,0 +1,10 @@ +import { StatusCodes } from 'http-status-codes'; + +export const notAuthorised = () => + new Response('Not authorised', { + status: StatusCodes.BAD_REQUEST, + }); +export const badRequest = (message: string) => + new Response(message, { + status: StatusCodes.BAD_REQUEST, + }); diff --git a/src/components/children/child-select-list.tsx b/src/components/children/child-select-list.tsx index c373046..e8d1342 100644 --- a/src/components/children/child-select-list.tsx +++ b/src/components/children/child-select-list.tsx @@ -17,7 +17,7 @@ const ChildSelectList = () => { queryKey: ['user-children'], queryFn: async () => { const { data } = await axios.get( - `${process.env.NEXT_PUBLIC_BASE_URL}/api/child`, + `${process.env.NEXT_PUBLIC_BASE_URL}/child`, { withCredentials: true, } diff --git a/src/components/children/children-list.tsx b/src/components/children/children-list.tsx index b991ad1..83f7ccb 100644 --- a/src/components/children/children-list.tsx +++ b/src/components/children/children-list.tsx @@ -19,7 +19,7 @@ const ChildrenList = () => { queryKey: ['user-children'], queryFn: async () => { const { data } = await axios.get( - `${process.env.NEXT_PUBLIC_BASE_URL}/api/child`, + `${process.env.NEXT_PUBLIC_BASE_URL}/child`, { withCredentials: true, } @@ -34,9 +34,9 @@ const ChildrenList = () => { Here are your children. - Name + Name Last seen at - Actions + Actions @@ -45,7 +45,12 @@ const ChildrenList = () => { {child.name} Douglas - +
+ + +
))} diff --git a/src/components/children/connect-device-dialog.tsx b/src/components/children/connect-device-dialog.tsx index e0e60db..6f1fa60 100644 --- a/src/components/children/connect-device-dialog.tsx +++ b/src/components/children/connect-device-dialog.tsx @@ -37,7 +37,7 @@ const ConnectDeviceDialog: React.FC = ({ child }) => { diff --git a/src/components/icons.tsx b/src/components/icons.tsx index faf81bd..3d9b5fb 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -17,6 +17,8 @@ import { Save, Copy, Cable, + Edit, + Edit2, } from 'lucide-react'; export type Icon = LucideIcon; @@ -27,6 +29,7 @@ export const Icons = { connect: Cable, chevronRight: ChevronRight, copy: Copy, + edit: Edit, sun: SunMedium, login: LogIn, mobile: TabletSmartphone, diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..936304f --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,8 @@ +import { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; +import * as schema from './schema'; + +const client = postgres(process.env.DATABASE_URL as string); +const db = drizzle(client, { schema }); + +export default db; diff --git a/src/db/schema/auth.ts b/src/db/schema.ts similarity index 54% rename from src/db/schema/auth.ts rename to src/db/schema.ts index 0603d9b..d604992 100644 --- a/src/db/schema/auth.ts +++ b/src/db/schema.ts @@ -5,8 +5,12 @@ import { integer, pgTable, uuid, + varchar, } from 'drizzle-orm/pg-core'; import type { AdapterAccount } from '@auth/core/adapters'; +import { relations, sql } from 'drizzle-orm'; + +//#region auth export const users = pgTable('user', { id: uuid('id').notNull().primaryKey(), @@ -34,7 +38,9 @@ export const accounts = pgTable( session_state: text('session_state'), }, (account) => ({ - compoundKey: primaryKey(account.provider, account.providerAccountId), + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), }) ); @@ -54,6 +60,42 @@ export const verificationTokens = pgTable( expires: timestamp('expires', { mode: 'date' }).notNull(), }, (vt) => ({ - compoundKey: primaryKey(vt.identifier, vt.token), + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), }) ); +//#endregion auth + +//#region child +export const child = pgTable('child', { + id: uuid('id') + .default(sql`gen_random_uuid()`) + .primaryKey(), + parentId: uuid('parent_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + name: varchar('name', { length: 256 }), + phone: varchar('phone', { length: 256 }), + apiKey: varchar('key', { length: 256 }), +}); + +export const childDevices = relations(child, ({ many }) => ({ + devices: many(device), +})); +//#endregion child +//#region device +export const device = pgTable( + 'device', + { + childId: uuid('child_id') + .notNull() + .references(() => child.id, { onDelete: 'cascade' }), + pin: integer('pin').notNull(), + deviceId: varchar('device_id').notNull().unique(), + apiKey: varchar('api_key').notNull().unique(), + expires: timestamp('expires').default(sql`now() + interval '1 hour'`), + }, + (device) => ({ + composePk: primaryKey({ columns: [device.childId, device.deviceId] }), + }) +); +//#endregion device diff --git a/src/db/schema/child.ts b/src/db/schema/child.ts deleted file mode 100644 index f34067e..0000000 --- a/src/db/schema/child.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - integer, - numeric, - pgEnum, - pgTable, - primaryKey, - serial, - text, - timestamp, - uniqueIndex, - uuid, - varchar, -} from 'drizzle-orm/pg-core'; -import { relations, sql } from 'drizzle-orm'; -import { users } from './auth'; - -export const child = pgTable('child', { - id: uuid('id') - .default(sql`gen_random_uuid()`) - .primaryKey(), - parentId: uuid('parent_id') - .notNull() - .references(() => users.id, { onDelete: 'cascade' }), - name: varchar('name', { length: 256 }), - phone: varchar('phone', { length: 256 }), - apiKey: varchar('key', { length: 256 }), -}); -export const locations = pgTable('locations', { - id: uuid('uuid1').defaultRandom().primaryKey(), - longitude: numeric('longitude'), - latitude: numeric('latitude'), - userId: uuid('user_id'), -}); - -export const childDevices = pgTable( - 'child_devices', - { - childId: uuid('child_id') - .notNull() - .references(() => child.id, { onDelete: 'cascade' }), - pin: integer('pin'), - apiKey: varchar('api_key'), - expires: timestamp('expires').default(sql`now() + interval '1 hour'`), - }, - (childPIN) => ({ - composePk: primaryKey(childPIN.childId, childPIN.pin), - }) -); -export const childLocations = relations(child, ({ many }) => ({ - locations: many(locations), -})); -export const locationsRelations = relations(locations, ({ one }) => ({ - author: one(child, { - fields: [locations.userId], - references: [child.id], - }), -})); diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts deleted file mode 100644 index 48628b1..0000000 --- a/src/db/schema/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js"; -import postgres from "postgres"; - -// for query purposes -const queryClient = postgres(process.env.DATABASE_URL as string); -const db: PostgresJsDatabase = drizzle(queryClient); -export default db; diff --git a/src/lib/services/auth/config.ts b/src/lib/services/auth/config.ts index be90497..38f565f 100644 --- a/src/lib/services/auth/config.ts +++ b/src/lib/services/auth/config.ts @@ -1,7 +1,7 @@ import { getServerSession, type AuthOptions } from 'next-auth'; import GoogleProvider from 'next-auth/providers/google'; import { DrizzleAdapter } from '@auth/drizzle-adapter'; -import db from '@/db/schema'; +import db from '@/db'; const authOptions: AuthOptions = { adapter: DrizzleAdapter(db), diff --git a/src/lib/services/auth/provider.tsx b/src/lib/services/auth/provider.tsx index dffd619..bfda253 100644 --- a/src/lib/services/auth/provider.tsx +++ b/src/lib/services/auth/provider.tsx @@ -1,12 +1,12 @@ -'use client' +'use client'; -import { SessionProvider } from 'next-auth/react' -import { ReactNode } from 'react' +import { SessionProvider } from 'next-auth/react'; +import { ReactNode } from 'react'; export default function NextAuthProvider({ children, }: { children: ReactNode; }) { - return {children} + return {children}; } diff --git a/src/lib/validations/connect-device.ts b/src/lib/validations/connect-device.ts new file mode 100644 index 0000000..cae18e4 --- /dev/null +++ b/src/lib/validations/connect-device.ts @@ -0,0 +1,5 @@ +import * as z from 'zod'; + +export const connectDeviceSchema = z.object({ + childId: z.string().max(50), +});