Add pings and seeds

This commit is contained in:
Fergal Moran
2024-01-29 19:25:27 +00:00
parent 88adbdcdc6
commit 40bfb085c6
19 changed files with 276 additions and 78 deletions

View File

@@ -9,5 +9,6 @@
".vscode": true,
".next": true,
"node_modules": true
}
},
"workbench.colorTheme": "poimandres-noitalics"
}

BIN
bun.lockb

Binary file not shown.

View File

@@ -11,6 +11,7 @@
"start": "next start",
"db:generate": "drizzle-kit generate:pg",
"db:migrate": "tsx src/server/db/migrate.ts",
"db:seed": "tsx src/server/db/seed/seed.ts && tsx src/server/db/seed/auth.ts",
"db:drop": "drizzle-kit drop",
"db:pull": "drizzle-kit introspect:pg",
"db:studio": "drizzle-kit studio",
@@ -19,6 +20,7 @@
"dependencies": {
"@auth/core": "^0.22.0",
"@auth/drizzle-adapter": "^0.3.16",
"@faker-js/faker": "^8.4.0",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
@@ -110,4 +112,4 @@
"ct3aMetadata": {
"initVersion": "7.25.2"
}
}
}

18
scripts/reset.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
export PGUSER=postgres
export PGPASSWORD=hackme
export PGHOST=localhost
echo Removing migrations
rm -rf drizzle
echo "Dropping db"
dropdb -f --if-exists kidarr
echo "Creating db"
createdb kidarr
bun db:generate
bun db:migrate
# bun run src/db/migrate.ts
bun db:seed

View File

@@ -1,5 +1,5 @@
'use client';
import React from 'react';
"use client";
import React from "react";
import {
Select,
SelectContent,
@@ -7,15 +7,15 @@ import {
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import type ChildModel from '@/lib/models/child';
} from "@/components/ui/select";
import { type Child } from "@/server/db/schema/children";
type ChildSelectListProps = {
kids: ChildModel[];
}
const ChildSelectList: React.FC<ChildSelectListProps> = ({ kids }) => {
children: Child[];
};
const ChildSelectList: React.FC<ChildSelectListProps> = ({ children: kids }) => {
return (
<Select defaultValue={'____all____'}>
<Select defaultValue={"____all____"}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Choose child" />
</SelectTrigger>
@@ -23,10 +23,7 @@ const ChildSelectList: React.FC<ChildSelectListProps> = ({ kids }) => {
<SelectGroup>
<SelectItem value="____all____">(All Children)</SelectItem>
{kids?.map((r) => (
<SelectItem
key={r.id}
value={r.id}
>
<SelectItem key={r.id} value={r.id}>
{r.name}
</SelectItem>
))}

View File

@@ -1,15 +1,19 @@
import React from "react";
"use client";
import React, { useEffect } from "react";
import ChildSelectList from "./child-select-list";
import { type CompleteChild } from "@/server/db/schema/children";
import { type Child } from "@/server/db/schema/children";
type ChildrenFilterProps = {
kids: CompleteChild[];
children: Child[];
};
const ChildrenFilter: React.FC<ChildrenFilterProps> = async ({ kids }) => {
const ChildrenFilter: React.FC<ChildrenFilterProps> = ({ children }) => {
useEffect(() => {
console.log("ChildrenFilter: useEffect", children);
}, [children]);
return (
<div className="flex flex-row items-center justify-center space-x-2">
<ChildSelectList kids={kids} />
<ChildSelectList children={children} />
{/* <AddChildComponent /> */}
</div>
);

View File

@@ -27,7 +27,25 @@ const MainMap: React.FC<MainMapProps> = ({ kids }) => {
zoom={10}
scrollWheelZoom={true}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<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}
childName={kid.name}
avatar={kid.avatar}
deviceName={device.name}
latitude={ping.latitude}
longitude={ping.longitude}
timestamp={ping.timestamp}
/>
)),
),
)}
</>
</TileLayer>
</MapContainer>
</div>
);

View File

@@ -11,7 +11,7 @@ const DashboardPage = async () => {
return (
<div>
<div className="flex flex-row justify-between">
<ChildrenFilter kids={children} />
<ChildrenFilter children={children} />
<MapViewTypeSelector />
</div>
<div className="mt-4">

View File

@@ -1,24 +1,36 @@
import { db } from "@/server/db/index";
import { eq, and } from "drizzle-orm";
import { getUserAuth } from "@/lib/auth/utils";
import { db } from "@/server/db";
import {
type ChildId,
childIdSchema,
children,
} from "@/server/db/schema/children";
import { devices } from "@/server/db/schema/devices";
import { pings } from "@/server/db/schema/pings";
export const getChildren = async () => {
const { session } = await getUserAuth();
const c = await db
.select()
.from(children)
.where(eq(children.userId, session?.user.id!))
.innerJoin(devices, eq(devices.childId, children.id))
.innerJoin(pings, eq(pings.deviceId, devices.id))
.orderBy(children.name);
return { children: c.map((c) => ({ ...c, devices: [] })) };
console.log("queries", "getChildren", session?.user.id!);
const c = await db.query.children.findMany({
where: (children, { eq }) => eq(children.userId, session?.user.id!),
orderBy: (children) => children.name,
with: {
devices: {
with: {
pings: true,
},
},
},
});
console.log("queries", "gotChildren", c);
return { children: c };
// const c = await db
// .select()
// .from(children)
// .where(eq(children.userId, session?.user.id!))
// .innerJoin(devices, eq(devices.childId, children.id))
// .innerJoin(pings, eq(pings.deviceId, devices.id))
// .orderBy(children.name);
// return c;
};
export const getChildById = async (id: ChildId) => {

View File

@@ -2,7 +2,7 @@ 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 { env } from "@/env.mjs";
import GoogleProvider from "next-auth/providers/google";
declare module "next-auth" {
@@ -32,14 +32,13 @@ export const authOptions: NextAuthOptions = {
},
},
providers: [
GoogleProvider({
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
})
}),
],
};
export const getUserAuth = async () => {
const session = await getServerSession(authOptions);
return { session } as AuthSession;
@@ -47,6 +46,5 @@ export const getUserAuth = async () => {
export const checkAuth = async () => {
const { session } = await getUserAuth();
if (!session) redirect("/api/auth/signin");
if (!session) redirect("/signin");
};

View File

@@ -2,7 +2,6 @@ import { getChildById, getChildren } from "@/lib/api/children/queries";
import {
publicProcedure,
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import {
childIdSchema,

View File

@@ -8,6 +8,9 @@ import {
verificationTokens,
children,
devices,
childrenRelations,
deviceRelations,
pingRelations,
} from "@/server/db/schema/_root";
export const client = postgres(env.DATABASE_URL);
@@ -19,5 +22,8 @@ export const db = drizzle(client, {
accounts,
sessions,
verificationTokens,
childrenRelations,
deviceRelations,
pingRelations,
},
});

View File

@@ -1,26 +1,23 @@
import { env } from "@/env.mjs";
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const runMigrate = async () => {
if (!env.DATABASE_URL) {
throw new Error("DATABASE_URL is not defined");
}
const connection = postgres(env.DATABASE_URL, { max: 1 });
const db = drizzle(connection);
const connection = postgres(env.DATABASE_URL, { max: 1 });
const db = drizzle(connection);
console.log("⏳ Running migrations...");
const start = Date.now();
await migrate(db, { migrationsFolder: 'src/server/db/migrations' });
await migrate(db, { migrationsFolder: "src/server/db/migrations" });
const end = Date.now();
@@ -33,4 +30,4 @@ runMigrate().catch((err) => {
console.error("❌ Migration failed");
console.error(err);
process.exit(1);
});
});

View File

@@ -1,6 +1,16 @@
import { users, accounts, sessions, verificationTokens } from "./auth"
import { children } from "./children";
import { devices } from "./devices";
import { pings } from "./pings";
export { pings, devices, children, users, accounts, sessions, verificationTokens }
import { users, accounts, sessions, verificationTokens } from "./auth";
import { children, childrenRelations } from "./children";
import { deviceRelations, devices } from "./devices";
import { pingRelations, pings } from "./pings";
export {
pings,
devices,
children,
users,
accounts,
sessions,
verificationTokens,
childrenRelations,
deviceRelations,
pingRelations,
};

View File

@@ -6,6 +6,8 @@ import { users } from "@/server/db/schema/auth";
import { type getChildren } from "@/lib/api/children/queries";
import { randomUUID } from "crypto";
import { relations } from "drizzle-orm";
import { devices } from "./devices";
export const children = pgTable("children", {
id: varchar("id", { length: 191 })
@@ -18,6 +20,9 @@ export const children = pgTable("children", {
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
});
export const childrenRelations = relations(children, ({ many }) => ({
devices: many(devices),
}));
// Schema for children - used to validate API requests
export const insertChildSchema = createInsertSchema(children);

View File

@@ -1,38 +1,56 @@
import { varchar, pgTable } from "drizzle-orm/pg-core";
import {
varchar,
pgTable,
PgColumn,
PgTableWithColumns,
} from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
import { children } from "./children"
import { children } from "./children";
import { users } from "@/server/db/schema/auth";
import { type getDevices } from "@/lib/api/devices/queries";
import { randomUUID } from "crypto";
import { relations } from "drizzle-orm";
import { pings } from "./pings";
export const devices = pgTable('devices', {
id: varchar("id", { length: 191 }).primaryKey().$defaultFn(() => randomUUID()),
export const devices = pgTable("devices", {
id: varchar("id", { length: 191 })
.primaryKey()
.$defaultFn(() => randomUUID()),
name: varchar("name", { length: 256 }).notNull(),
deviceId: varchar("device_id", { length: 256 }).notNull(),
childId: varchar("child_id", { length: 256 }).references(() => children.id, { onDelete: "cascade" }).notNull(),
userId: varchar("user_id", { length: 256 }).references(() => users.id, { onDelete: "cascade" }).notNull(),
childId: varchar("child_id", { length: 256 })
.references(() => children.id, { onDelete: "cascade" })
.notNull(),
userId: varchar("user_id", { length: 256 })
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
});
export const deviceRelations = relations(devices, ({ one, many }) => ({
child: one(children, {
fields: [devices.childId],
references: [children.id],
}),
pings: many(pings),
}));
// Schema for devices - used to validate API requests
export const insertDeviceSchema = createInsertSchema(devices);
export const insertDeviceParams = createSelectSchema(devices, {
childId: z.coerce.string().min(1)
}).omit({
childId: z.coerce.string().min(1),
}).omit({
id: true,
userId: true
userId: true,
});
export const updateDeviceSchema = createSelectSchema(devices);
export const updateDeviceParams = createSelectSchema(devices,{
childId: z.coerce.string().min(1)
}).omit({
userId: true
export const updateDeviceParams = createSelectSchema(devices, {
childId: z.coerce.string().min(1),
}).omit({
userId: true,
});
export const deviceIdSchema = updateDeviceSchema.pick({ id: true });
@@ -43,7 +61,8 @@ export type NewDevice = z.infer<typeof insertDeviceSchema>;
export type NewDeviceParams = z.infer<typeof insertDeviceParams>;
export type UpdateDeviceParams = z.infer<typeof updateDeviceParams>;
export type DeviceId = z.infer<typeof deviceIdSchema>["id"];
// this type infers the return from getDevices() - meaning it will include any joins
export type CompleteDevice = Awaited<ReturnType<typeof getDevices>>["devices"][number];
// this type infers the return from getDevices() - meaning it will include any joins
export type CompleteDevice = Awaited<
ReturnType<typeof getDevices>
>["devices"][number];

View File

@@ -1,9 +1,4 @@
import {
real,
timestamp,
varchar,
pgTable,
} from "drizzle-orm/pg-core";
import { real, timestamp, varchar, pgTable } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
import { devices } from "./devices";
@@ -11,6 +6,7 @@ import { users } from "@/server/db/schema/auth";
import { type getPings } from "@/lib/api/pings/queries";
import { randomUUID } from "crypto";
import { relations } from "drizzle-orm";
export const pings = pgTable("pings", {
id: varchar("id", { length: 191 })
@@ -26,7 +22,12 @@ export const pings = pgTable("pings", {
.references(() => users.id, { onDelete: "cascade" })
.notNull(),
});
export const pingRelations = relations(pings, ({ one }) => ({
device: one(devices, {
fields: [pings.deviceId],
references: [devices.id],
}),
}));
// Schema for pings - used to validate API requests
export const insertPingSchema = createInsertSchema(pings);

View File

@@ -0,0 +1,31 @@
import { db } from "..";
import { accounts } from "../schema/auth";
const runAuthSeed = async () => {
console.log("🔐", "Seeding auth");
await db
.insert(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();
console.log("🌱", "Seeded auth");
process.exit(0);
};
runAuthSeed().catch((err) => {
console.error("❌ Auth seeding failed");
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { db } from "..";
import { faker } from "@faker-js/faker";
import { users } from "../schema/auth";
import { children } from "../schema/children";
import { devices } from "../schema/devices";
import { pings } from "../schema/pings";
console.log("seed", "Seeding");
const runSeed = async () => {
console.log("👯", "Seeding users");
const seedUsers = [
{
id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: "Fergal Moran",
email: "fergal.moran@gmail.com",
emailVerified: new Date(),
},
];
await db.insert(users).values(seedUsers).execute();
const seedChildren = [
{
id: "2250f34e-997a-44de-ab8d-beddeda13525",
name: "Lil Debuggles",
phone: "123 456 789",
email: "lildebuggles@kidarr.com",
avatar: faker.image.avatar(),
userId: seedUsers[0]?.id!,
},
];
await db.insert(children).values(seedChildren).execute();
const seedDevices = [
{
id: "5af79a30-df27-4646-9d9f-77e19b4191c1",
name: "Not an iPhone",
deviceId: "373791e3-afe3-49de-b0a2-842a44071585",
childId: seedChildren[0]?.id!,
userId: seedUsers[0]?.id!,
deviceName: "Not an iPhone",
apiKey: "nQhXtqemsWjzBpbDxlIV2qtDx9xxO4oZVBJADdhJLfA=",
pin: 1234,
expires: new Date(2065),
},
];
await db.insert(devices).values(seedDevices).execute();
const seedPings = [
{
deviceId: seedDevices[0]?.id!,
userId: seedUsers[0]?.id!,
latitude: 51.903614,
longitude: -8.468399,
timestamp: new Date(),
},
{
deviceId: seedDevices[0]?.id!,
userId: seedUsers[0]?.id!,
latitude: 51.8985,
longitude: -8.4756,
timestamp: new Date(),
},
{
deviceId: seedDevices[0]?.id!,
userId: seedUsers[0]?.id!,
latitude: 51.93588161110811,
longitude: -8.495129534566756,
timestamp: new Date(),
},
];
await db.insert(pings).values(seedPings).execute();
console.log("🌱", "Seeded users");
process.exit(0);
};
runSeed().catch((err) => {
console.error("❌ Seeding failed");
console.error(err);
process.exit(1);
});