Child and device onboarding working

This commit is contained in:
Fergal Moran
2023-11-22 18:09:08 +00:00
parent cfbaf2cb32
commit a2fbdaa1fa
26 changed files with 460 additions and 308 deletions

28
.vscode/launch.json vendored Normal file
View File

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

16
.vscode/settings.json vendored
View File

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

BIN
bun.lockb

Binary file not shown.

View File

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

View File

@@ -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 $$;

View File

@@ -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": {},

View File

@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1699562330825,
"tag": "0000_bizarre_firebrand",
"when": 1700413726993,
"tag": "0000_medical_jetstream",
"breakpoints": true
}
]

View File

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

View File

@@ -3,6 +3,7 @@ export PGUSER=postgres
export PGPASSWORD=hackme
export PGHOST=localhost
echo Removing migrations
rm -rf drizzle
echo "Dropping db"

View File

@@ -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' });
}

View File

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

View File

@@ -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';

View File

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

View File

@@ -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 };

View File

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

View File

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

View File

@@ -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 = () => {
<TableCaption>Here are your children.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Name</TableHead>
<TableHead className="">Name</TableHead>
<TableHead>Last seen at</TableHead>
<TableHead className="text-right">Actions</TableHead>
<TableHead className="">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -45,7 +45,12 @@ const ChildrenList = () => {
<TableCell className="font-medium">{child.name}</TableCell>
<TableCell>Douglas</TableCell>
<TableCell className="text-right">
<ConnectDeviceDialog child={child} />
<div className="space-x-1">
<ConnectDeviceDialog child={child} />
<Button>
<Icons.edit className="mr-2 h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}

View File

@@ -37,7 +37,7 @@ const ConnectDeviceDialog: React.FC<ConnectDeviceDialogProps> = ({ child }) => {
<QRCode
size={190}
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
value={`${process.env.NEXT_PUBLIC_BASE_URL}/device/connect?childId=${child.id}`}
value={child.id}
/>
</div>
</div>

View File

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

8
src/db/index.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <SessionProvider>{children}</SessionProvider>
return <SessionProvider>{children}</SessionProvider>;
}

View File

@@ -0,0 +1,5 @@
import * as z from 'zod';
export const connectDeviceSchema = z.object({
childId: z.string().max(50),
});