mirror of
https://github.com/fergalmoran/kidarr-server.git
synced 2026-02-19 14:27:00 +00:00
Add pings and seeds
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -9,5 +9,6 @@
|
||||
".vscode": true,
|
||||
".next": true,
|
||||
"node_modules": true
|
||||
}
|
||||
},
|
||||
"workbench.colorTheme": "poimandres-noitalics"
|
||||
}
|
||||
|
||||
@@ -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
18
scripts/reset.sh
Executable 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
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { getChildById, getChildren } from "@/lib/api/children/queries";
|
||||
import {
|
||||
publicProcedure,
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
} from "@/server/api/trpc";
|
||||
import {
|
||||
childIdSchema,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
31
src/server/db/seed/auth.ts
Normal file
31
src/server/db/seed/auth.ts
Normal 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);
|
||||
});
|
||||
80
src/server/db/seed/seed.ts
Normal file
80
src/server/db/seed/seed.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user