Safe to say these relations are fixed!!

This commit is contained in:
Fergal Moran
2023-12-21 19:57:33 +00:00
parent 3f9ca9f73a
commit 78ea60b7b7
19 changed files with 193 additions and 71 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -14,25 +14,29 @@ CREATE TABLE IF NOT EXISTS "account" (
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "child" (
"id" uuid PRIMARY KEY NOT NULL,
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar(256),
"email" varchar(256),
"phone" varchar(256),
"key" varchar(256),
"parent_id" uuid
"parent_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "device" (
"id" uuid PRIMARY KEY NOT NULL,
"child_id" uuid,
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"device_id" varchar NOT NULL,
"child_id" uuid NOT NULL,
"api_key" varchar NOT NULL,
"pin" integer NOT NULL,
"expires" timestamp DEFAULT now
()
+ interval '1 hour',
CONSTRAINT "device_device_id_unique" UNIQUE("device_id"),
CONSTRAINT "device_api_key_unique" UNIQUE("api_key")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "ping" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"device_id" uuid NOT NULL,
"latitude" double precision NOT NULL,
"longitude" double precision NOT NULL,
@@ -46,7 +50,7 @@ CREATE TABLE IF NOT EXISTS "session" (
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "user" (
"id" uuid PRIMARY KEY NOT NULL,
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text,
"email" text NOT NULL,
"emailVerified" timestamp,

View File

@@ -1,5 +1,5 @@
{
"id": "876770f8-cae4-4913-b299-855783c0b37c",
"id": "7ac914a6-b952-4605-8341-0b7f522e85a5",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "5",
"dialect": "pg",
@@ -110,7 +110,8 @@
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
@@ -118,6 +119,12 @@
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(256)",
"primaryKey": false,
"notNull": false
},
"phone": {
"name": "phone",
"type": "varchar(256)",
@@ -134,7 +141,7 @@
"name": "parent_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
"notNull": true
}
},
"indexes": {},
@@ -150,13 +157,20 @@
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"device_id": {
"name": "device_id",
"type": "varchar",
"primaryKey": false,
"notNull": true
},
"child_id": {
"name": "child_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
"notNull": true
},
"api_key": {
"name": "api_key",
@@ -182,6 +196,13 @@
"foreignKeys": {},
"compositePrimaryKeys": {},
"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,
@@ -195,6 +216,13 @@
"name": "ping",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"device_id": {
"name": "device_id",
"type": "uuid",
@@ -275,7 +303,8 @@
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",

View File

@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1703070994335,
"tag": "0000_thankful_sasquatch",
"when": 1703185817079,
"tag": "0000_watery_zaladane",
"breakpoints": true
}
]

View File

@@ -5,13 +5,14 @@
"scripts": {
"dev2": "next dev",
"dev": "NODE_ENV=development next dev -p 3002 & local-ssl-proxy --key /etc/letsencrypt/live/dev.fergl.ie/privkey.pem --cert /etc/letsencrypt/live/dev.fergl.ie/fullchain.pem --source 3000 --target 3002",
"drizzle:migrate": "node --loader esbuild-register/loader -r esbuild-register ./src/db/migrate.ts",
"drizzle:seed": "node --loader esbuild-register/loader -r esbuild-register ./src/db/seed.ts",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth/drizzle-adapter": "^0.3.11",
"@auth/drizzle-adapter": "^0.3.12",
"@hookform/resolvers": "^3.3.2",
"@microsoft/signalr": "^8.0.0",
"@radix-ui/react-accordion": "^1.1.2",
@@ -42,20 +43,21 @@
"@radix-ui/react-tooltip": "^1.0.7",
"@t3-oss/env-core": "^0.7.1",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^5.14.1",
"@tanstack/react-query-devtools": "^5.14.1",
"@tanstack/react-query": "^5.14.2",
"@tanstack/react-query-devtools": "^5.14.5",
"axios": "^1.6.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"date-fns": "^3.0.0",
"date-fns": "^3.0.5",
"dotenv": "^16.3.1",
"drizzle-orm": "^0.29.1",
"esbuild-register": "^3.5.0",
"generate-api-key": "^1.0.2",
"http-status-codes": "^2.3.0",
"leaflet": "^1.9.4",
"local-ssl-proxy": "^2.0.5",
"lucide-react": "^0.298.0",
"lucide-react": "^0.299.0",
"next": "14.0.4",
"next-auth": "^4.24.5",
"next-themes": "^0.2.1",

View File

@@ -3,7 +3,6 @@ export PGUSER=postgres
export PGPASSWORD=hackme
export PGHOST=localhost
echo Removing migrations
rm -rf drizzle
echo "Dropping db"
@@ -11,9 +10,9 @@ echo "Dropping db"
dropdb -f --if-exists parentgrine
echo "Creating db"
createdb parentgrine
echo "Exiting"
exit
bunx drizzle-kit generate:pg --config=./drizzle.config.ts
bunx drizzle-kit push:pg --config=./drizzle.config.ts
bunx tsx src/db/migrate.ts
# bun run src/db/migrate.ts
bun run ./src/db/scripts/seed.ts

View File

@@ -15,7 +15,9 @@ export async function GET(request: Request) {
const activeChildren = await db.query.child.findMany({
where: eq(child.parentId, session.user.id),
with: {
devices: true,
devices: {
with: { pings: true },
},
},
});

View File

@@ -49,7 +49,7 @@ const POST = async (req: Request, res: Response) => {
.execute();
return Response.json(
{ childId, deviceId, pin, apiKey },
{ status: StatusCodes.CREATED },
{ status: StatusCodes.CREATED }
);
}
return badRequest('Invalid registration request');

View File

@@ -15,6 +15,7 @@ import { Icons } from '../icons';
const AuthHeader = () => {
const { data: session, status } = useSession();
if (status === 'loading') return null;
return !session ? (
<Link
className={cn(buttonVariants({ variant: 'default', size: 'sm' }))}

View File

@@ -12,13 +12,16 @@ interface MainNavProps {
export function MainNav({ items }: MainNavProps) {
return (
<div className='flex gap-6 md:gap-10'>
<Link href='/' className='flex items-center space-x-2'>
<Icons.logo className='h-6 w-6' />
<span className='inline-block font-bold'>{siteConfig.name}</span>
<div className="flex gap-6 md:gap-10">
<Link
href="/"
className="flex items-center space-x-2"
>
<Icons.logo className="h-6 w-6" />
<span className="inline-block font-bold">{siteConfig.name}</span>
</Link>
{items?.length ? (
<nav className='flex gap-6'>
<nav className="flex gap-6">
{items?.map(
(item, index) =>
item.href && (
@@ -27,12 +30,12 @@ export function MainNav({ items }: MainNavProps) {
href={item.href}
className={cn(
'flex items-center text-sm font-medium text-muted-foreground',
item.disabled && 'cursor-not-allowed opacity-80',
item.disabled && 'cursor-not-allowed opacity-80'
)}
>
{item.title}
</Link>
),
)
)}
</nav>
) : null}

View File

@@ -2,7 +2,6 @@ import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
import { env } from '@/env.mjs';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
const client = postgres(env.DATABASE_URL as string);
const db = drizzle(client, { schema });

View File

@@ -1,8 +0,0 @@
import 'dotenv/config';
import db from '.';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
// This will run migrations on the database, skipping the ones already applied
// migrate(db, { migrationsFolder: './drizzle' }).then(() =>
// console.log('DRIZZLE', 'migrated')
// );

View File

@@ -16,7 +16,10 @@ import { relations, sql } from 'drizzle-orm';
//TODO: use this schema once https://github.com/drizzle-team/drizzle-orm/issues/636 is fixed
const authSchema = pgSchema('auth');
export const users = pgTable('user', {
id: uuid('id').notNull().primaryKey(),
id: uuid('id')
.notNull()
.primaryKey()
.default(sql`gen_random_uuid()`),
name: text('name'),
email: text('email').notNull(),
emailVerified: timestamp('emailVerified', { mode: 'date' }),
@@ -74,11 +77,14 @@ export const userRelations = relations(users, ({ many }) => ({
//#region child
export const child = pgTable('child', {
id: uuid('id').primaryKey(),
id: uuid('id')
.primaryKey()
.default(sql`gen_random_uuid()`),
name: varchar('name', { length: 256 }),
email: varchar('email', { length: 256 }),
phone: varchar('phone', { length: 256 }),
apiKey: varchar('key', { length: 256 }),
parentId: uuid('parent_id'),
parentId: uuid('parent_id').notNull(),
});
export const childRelations = relations(child, ({ one, many }) => ({
parent: one(users, {
@@ -88,19 +94,19 @@ export const childRelations = relations(child, ({ one, many }) => ({
devices: many(device),
}));
export const device = pgTable(
'device',
{
id: uuid('id').primaryKey(),
childId: uuid('child_id'),
apiKey: varchar('api_key').notNull().unique(),
//TODO: make the device request/pin a separate table and enforce the expiry
pin: integer('pin').notNull(),
expires: timestamp('expires').default(sql`now
export const device = pgTable('device', {
id: uuid('id')
.primaryKey()
.default(sql`gen_random_uuid()`),
deviceId: varchar('device_id').notNull().unique(),
childId: uuid('child_id').notNull(),
apiKey: varchar('api_key').notNull().unique(),
//TODO: make the device request/pin a separate table and enforce the expiry
pin: integer('pin').notNull(),
expires: timestamp('expires').default(sql`now
()
+ interval '1 hour'`),
}
);
});
export const deviceRelations = relations(device, ({ one, many }) => ({
child: one(child, {
fields: [device.childId],
@@ -110,13 +116,16 @@ export const deviceRelations = relations(device, ({ one, many }) => ({
}));
export const ping = pgTable('ping', {
id: uuid('id')
.primaryKey()
.default(sql`gen_random_uuid()`),
deviceId: uuid('device_id').notNull(),
latitude: doublePrecision('latitude').notNull(),
longitude: doublePrecision('longitude').notNull(),
timestamp: timestamp('timestamp').notNull(),
});
export const pingRelations = relations(ping, ({ one, many }) => ({
child: one(device, {
device: one(device, {
fields: [ping.deviceId],
references: [device.id],
}),

33
src/db/scripts/auth.ts Normal file
View File

@@ -0,0 +1,33 @@
import * as dotenv from 'dotenv';
import * as schema from '@/db/schema';
import db from '..';
const main = async () => {
db.insert(schema.accounts)
.values([
{
userId: '2250f34e-997a-44de-ab8d-beddeda13525',
provider: 'google',
type: 'oauth',
providerAccountId: '112561477626832751929',
access_token: 'FARTS',
expires_at: 9,
token_type: 'Bearer',
scope:
'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
id_token: 'FARTS',
},
])
.execute();
return;
};
console.log('auth', 'Seeding auth');
main().then(
() => {
console.log('auth', 'Seeded auth');
},
(err) => {
console.error('auth', 'Error seeding auth', err);
}
);

51
src/db/scripts/seed.ts Normal file
View File

@@ -0,0 +1,51 @@
import * as dotenv from 'dotenv';
import * as schema from '@/db/schema';
import db from '..';
// dotenv.config({ path: './.env' });
// if (!('DATABASE_URL' in process.env))
// throw new Error('DATABASE_URL not found on .env.development');
const main = async () => {
const seedUsers = [
{
id: '2250f34e-997a-44de-ab8d-beddeda13525',
name: 'Fergal Moran',
email: 'fergal.moran@gmail.com',
emailVerified: new Date(),
},
];
db.insert(schema.users).values(seedUsers).execute();
const seedChildren = [
{
id: '2250f34e-997a-44de-ab8d-beddeda13525',
name: 'Lil Debuggles',
phone: '123 456 789',
email: 'lildebuggles@parentgrine.com',
parentId: seedUsers[0].id,
},
];
db.insert(schema.child).values(seedChildren).execute();
const seedDevices = [
{
deviceId: '373791e3-afe3-49de-b0a2-842a44071585',
childId: seedChildren[0].id,
apiKey: 'nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=',
pin: 1234,
expires: new Date(2065),
},
];
db.insert(schema.device).values(seedDevices).execute();
return;
};
console.log('seed', 'Seeding');
main().then(
() => {
console.log('seed', 'Seeded');
},
(err) => {
console.log('seed', 'Error', err);
}
);

View File

@@ -1,5 +0,0 @@
import * as dotenv from 'dotenv';
dotenv.config({ path: './.env' });
if (!('DATABASE_URL' in process.env))
throw new Error('DATABASE_URL not found on .env.development');

View File

@@ -1,16 +1,19 @@
import db from '@/db';
import { StatusCodes } from 'http-status-codes';
import { ping } from '@/db/schema';
import {
NextApiResponseServerIo,
} from '@/lib/models/types/next-api-response-socket';
import { NextApiResponseServerIo } from '@/lib/models/types/next-api-response-socket';
import { badRequest, notAuthorised } from '@/app/api/responses';
import { NextApiRequest, NextApiResponse } from 'next';
import LocationUpdate from '@/lib/models/location-update';
export default async function POST(req: NextApiRequest, res: NextApiResponseServerIo) {
export default async function POST(
req: NextApiRequest,
res: NextApiResponseServerIo
) {
if (req.method !== 'POST') {
return res.status(StatusCodes.METHOD_NOT_ALLOWED).send({ message: 'Method not allowed' });
return res
.status(StatusCodes.METHOD_NOT_ALLOWED)
.send({ message: 'Method not allowed' });
}
const apiKey = req?.headers['x-api-key'] as string;
const deviceId = req?.headers['x-device-id'] as string;
@@ -19,10 +22,8 @@ export default async function POST(req: NextApiRequest, res: NextApiResponseServ
}
const device = await db.query.device.findFirst({
where: (device, {
and,
eq,
}) => and(eq(device.deviceId, deviceId), eq(device.apiKey, apiKey)),
where: (device, { and, eq }) =>
and(eq(device.deviceId, deviceId), eq(device.apiKey, apiKey)),
});
if (!device) {
@@ -48,7 +49,7 @@ export default async function POST(req: NextApiRequest, res: NextApiResponseServ
}
const location = await db.insert(ping).values({
deviceId: device.deviceId,
deviceId: device.id,
latitude: coordinates.latitude,
longitude: coordinates.longitude,
timestamp: new Date(),
@@ -66,5 +67,4 @@ export default async function POST(req: NextApiRequest, res: NextApiResponseServ
res?.socket?.server?.io?.emit(`ping:${child.id}`, update);
// Send a response
return res.json({});
}

View File

@@ -34,7 +34,7 @@
"**/*.tsx",
".next/types/**/*.ts",
"drizzle.config.js"
],
, ".krud/seed.ts", "src/db/___migrate.ts___" ],
"exclude": [
"node_modules"
]

View File

@@ -7,6 +7,9 @@ declare module 'next-auth' {
interface Session {
user: {
id: string;
name: string;
image: string;
email: string;
};
}
}