Initial trpc calls done

This commit is contained in:
Fergal Moran
2023-12-27 20:24:13 +00:00
parent 4ee89aa684
commit cb2c0ad644
17 changed files with 254 additions and 138 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

61
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,61 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_KEEP_WHITESPACES_INSIDE" value="" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="parentgrine@localhost" uuid="da0ccf76-47f7-499f-8f35-82c39250995f">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/parentgrine</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/parentgrine-server.iml" filepath="$PROJECT_DIR$/.idea/parentgrine-server.iml" />
</modules>
</component>
</project>

12
.idea/parentgrine-server.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,9 +1,10 @@
"use client"; import React from 'react';
import React from "react"; import DashboardPage from '@/components/pages/dashboard-page';
import DashboardPage from "@/components/pages/dashboard-page"; import { api } from '@/trpc/server';
const Dashboard = () => { const Dashboard = async () => {
return <DashboardPage />; const kids = await api.child.mine.query();
return <DashboardPage kids={kids} />;
}; };
export default Dashboard; export default Dashboard;

View File

@@ -1,19 +1,18 @@
import { newChildSchema } from '@/lib/validations/child'; import { newChildSchema } from '@/lib/validations/child';
import { getServerAuthSession } from '@/lib/services/auth/config';
import { createApiKey } from '@/lib/services/auth/api'; import { createApiKey } from '@/lib/services/auth/api';
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import {db} from '@/server/db'; import { db } from '@/server/db';
import { child } from '@/server/db/schema'; import { child } from '@/server/db/schema';
import { users } from '@/server/db/schema'; import { users } from '@/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { device } from '@/server/db/schema'; import { getServerAuthSession } from '@/server/auth';
export async function POST(req: Request) { export async function POST(req: Request) {
const session = await getServerAuthSession(); const session = await getServerAuthSession();
if (!session || !session.user?.email) if (!session?.user?.email)
return NextResponse.next({ return NextResponse.next({
statusText: getReasonPhrase(StatusCodes.UNAUTHORIZED), statusText: getReasonPhrase(StatusCodes.UNAUTHORIZED),
status: StatusCodes.UNAUTHORIZED, status: StatusCodes.UNAUTHORIZED,
@@ -36,7 +35,7 @@ export async function POST(req: Request) {
const newChild = await db const newChild = await db
.insert(child) .insert(child)
.values({ .values({
parentId: user[0].id.toString(), parentId: "asdkfjhsa",
name, name,
}) })
.returning(); .returning();

View File

@@ -1,9 +1,9 @@
import { db } from "@/server/db"; import { db } from '@/server/db';
import { child } from "@/server/db/schema"; import { child } from '@/server/db/schema';
import { StatusCodes, getReasonPhrase } from "http-status-codes"; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { NextResponse } from "next/server"; import { NextResponse } from 'next/server';
import { eq } from "drizzle-orm"; import { eq } from 'drizzle-orm';
import { getServerAuthSession } from "@/server/auth"; import { getServerAuthSession } from '@/server/auth';
export async function GET(request: Request) { export async function GET(request: Request) {
const session = await getServerAuthSession(); const session = await getServerAuthSession();

View File

@@ -1,5 +1,5 @@
'use client' 'use client';
import React from 'react' import React from 'react';
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -7,35 +7,25 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select' } from '@/components/ui/select';
import axios from 'axios' import axios from 'axios';
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query';
import { env } from '@/env' import { env } from '@/env';
import ChildModel from '@/lib/models/child';
const ChildSelectList = () => { type ChildSelectListProps = {
const { data, isLoading, isError } = useQuery({ kids: ChildModel[];
queryKey: ['user-children'], }
queryFn: async () => { const ChildSelectList: React.FC<ChildSelectListProps> = ({ kids }) => {
const { data } = await axios.get(
`${env.NEXT_PUBLIC_BASE_URL}/child`,
{
withCredentials: true,
},
)
return data as ChildModel[]
},
})
if (isLoading) return <div>Loading....</div>
if (isError) return <div>Error loading</div>
return ( return (
<Select defaultValue={'____all____'}> <Select defaultValue={'____all____'}>
<SelectTrigger className='w-[180px]'> <SelectTrigger className="w-[180px]">
<SelectValue placeholder='Choose child' /> <SelectValue placeholder="Choose child" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value='____all____'>(All Children)</SelectItem> <SelectItem value="____all____">(All Children)</SelectItem>
{data?.map((r) => ( {kids?.map((r) => (
<SelectItem <SelectItem
key={r.name} key={r.name}
value={r.name.toLowerCase()} value={r.name.toLowerCase()}
@@ -46,7 +36,7 @@ const ChildSelectList = () => {
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
) );
} };
export default ChildSelectList export default ChildSelectList;

View File

@@ -1,15 +1,19 @@
import React from 'react' import React from 'react';
import ChildSelectList from './child-select-list' import ChildSelectList from './child-select-list';
import AddChildComponent from './add-child-component' import AddChildComponent from './add-child-component';
import ChildModel from '@/lib/models/child';
const ChildrenFilter = async () => { type ChildrenFilterProps = {
kids: ChildModel[];
}
const ChildrenFilter: React.FC<ChildrenFilterProps> = async ({ kids }) => {
return ( return (
<div className='flex flex-row space-x-2 justify-center items-center'> <div className="flex flex-row space-x-2 justify-center items-center">
<ChildSelectList /> <ChildSelectList kids={kids} />
<AddChildComponent /> <AddChildComponent />
</div> </div>
) );
} };
export default ChildrenFilter export default ChildrenFilter;

View File

@@ -1,18 +1,22 @@
import React from "react"; import React from 'react';
import ChildrenFilter from "@/components/children/children-filter"; import ChildrenFilter from '@/components/children/children-filter';
import dynamic from "next/dynamic"; import dynamic from 'next/dynamic';
import ChildModel from '@/lib/models/child';
const DashboardPage: React.FC = () => { type DashboardPageProps = {
kids: ChildModel[];
}
const DashboardPage: React.FC<DashboardPageProps> = ({ kids }) => {
//this needs to be a dynamic import //this needs to be a dynamic import
//otherwise it causes window not found errors //otherwise it causes window not found errors
const Map = dynamic(() => import("@/components/maps/main-map"), { const Map = dynamic(() => import('@/components/maps/main-map'), {
ssr: false, ssr: false,
}); });
return ( return (
<div> <div>
<div className="z-10"> <div className="z-10">
<ChildrenFilter /> <ChildrenFilter kids={kids} />
</div> </div>
<div className="z-0 mt-4"> <div className="z-0 mt-4">
<Map /> <Map />

View File

@@ -1,5 +1,5 @@
interface ChildModel { export default interface ChildModel {
id: string; id: string;
name: string; name: string;
recentLocations: Location[]; // recentLocations: Location[];
} }

View File

@@ -1,37 +1,33 @@
import { z } from "zod"; import { z } from 'zod';
import { import {
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,
publicProcedure, publicProcedure,
} from "@/server/api/trpc"; } from '@/server/api/trpc';
import { child } from "@/server/db/schema"; import { child } from '@/server/db/schema';
import { eq } from 'drizzle-orm';
export const childRouter = createTRPCRouter({ export const childRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
create: protectedProcedure create: protectedProcedure
.input(z.object({ name: z.string().min(1) })) .input(z.object({ name: z.string().min(1) }))
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
await ctx.db.insert(child).values({ const c = {
parentId: ctx.session.user.id, parentId: ctx.session.user.id,
name, name: input.name,
}); };
await ctx.db.insert(child).values(c);
}), }),
getLatest: publicProcedure.query(({ ctx }) => { mine: protectedProcedure.query(async ({ ctx }) => {
return ctx.db.query.child.findFirst({ return ctx.db.query.child.findMany({
orderBy: (posts, { asc }) => [asc(child.name)], orderBy: (posts, { asc }) => [asc(child.name)],
where: eq(child.parentId, ctx.session.user.id),
with: {
devices: {
with: { pings: true },
},
},
}); });
}), }),
getSecretMessage: protectedProcedure.query(() => {
return "you can now see this secret message!";
}),
}); });

View File

@@ -8,60 +8,61 @@ import {
varchar, varchar,
pgSchema, pgSchema,
doublePrecision, doublePrecision,
} from "drizzle-orm/pg-core"; } from 'drizzle-orm/pg-core';
import type { AdapterAccount } from "@auth/core/adapters"; import type { AdapterAccount } from '@auth/core/adapters';
import { relations, sql } from "drizzle-orm"; import { relations, sql } from 'drizzle-orm';
//#region auth //#region auth
//TODO: use this schema once https://github.com/drizzle-team/drizzle-orm/issues/636 is fixed //TODO: use this schema once https://github.com/drizzle-team/drizzle-orm/issues/636 is fixed
const authSchema = pgSchema("auth"); const authSchema = pgSchema('auth');
export const users = pgTable("user", { export const users = pgTable('user', {
id: uuid("id") id: uuid('id')
.notNull() .notNull()
.primaryKey() .primaryKey()
.default(sql`gen_random_uuid()`), .default(sql`gen_random_uuid
name: text("name"), ()`),
email: text("email").notNull(), name: text('name'),
emailVerified: timestamp("emailVerified", { mode: "date" }), email: text('email').notNull(),
image: text("image"), emailVerified: timestamp('emailVerified', { mode: 'date' }),
image: text('image'),
}); });
export const accounts = pgTable( export const accounts = pgTable(
"account", 'account',
{ {
userId: uuid("userId") userId: uuid('userId')
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: 'cascade' }),
type: text("type").$type<AdapterAccount["type"]>().notNull(), type: text('type').$type<AdapterAccount['type']>().notNull(),
provider: text("provider").notNull(), provider: text('provider').notNull(),
providerAccountId: text("providerAccountId").notNull(), providerAccountId: text('providerAccountId').notNull(),
refresh_token: text("refresh_token"), refresh_token: text('refresh_token'),
access_token: text("access_token"), access_token: text('access_token'),
expires_at: integer("expires_at"), expires_at: integer('expires_at'),
token_type: text("token_type"), token_type: text('token_type'),
scope: text("scope"), scope: text('scope'),
id_token: text("id_token"), id_token: text('id_token'),
session_state: text("session_state"), session_state: text('session_state'),
}, },
(account) => ({ (account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId), compoundKey: primaryKey(account.provider, account.providerAccountId),
}), }),
); );
export const sessions = pgTable("session", { export const sessions = pgTable('session', {
sessionToken: text("sessionToken").notNull().primaryKey(), sessionToken: text('sessionToken').notNull().primaryKey(),
userId: uuid("userId") userId: uuid('userId')
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp("expires", { mode: "date" }).notNull(), expires: timestamp('expires', { mode: 'date' }).notNull(),
}); });
export const verificationTokens = pgTable( export const verificationTokens = pgTable(
"verification_token", 'verification_token',
{ {
identifier: varchar("identifier", { length: 255 }).notNull(), identifier: varchar('identifier', { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(), token: varchar('token', { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(), expires: timestamp('expires', { mode: 'date' }).notNull(),
}, },
(vt) => ({ (vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token), compoundKey: primaryKey(vt.identifier, vt.token),
@@ -74,15 +75,16 @@ export const userRelations = relations(users, ({ many }) => ({
})); }));
//#region child //#region child
export const child = pgTable("child", { export const child = pgTable('child', {
id: uuid("id") id: uuid('id')
.primaryKey() .primaryKey()
.default(sql`gen_random_uuid()`), .default(sql`gen_random_uuid
name: varchar("name", { length: 256 }), ()`),
email: varchar("email", { length: 256 }), name: varchar('name', { length: 256 }).notNull(),
phone: varchar("phone", { length: 256 }), email: varchar('email', { length: 256 }),
apiKey: varchar("key", { length: 256 }), phone: varchar('phone', { length: 256 }),
parentId: uuid("parent_id").notNull(), apiKey: varchar('key', { length: 256 }),
parentId: uuid('parent_id').notNull(),
}); });
export const childRelations = relations(child, ({ one, many }) => ({ export const childRelations = relations(child, ({ one, many }) => ({
parent: one(users, { parent: one(users, {
@@ -92,17 +94,18 @@ export const childRelations = relations(child, ({ one, many }) => ({
devices: many(device), devices: many(device),
})); }));
export const device = pgTable("device", { export const device = pgTable('device', {
id: uuid("id") id: uuid('id')
.primaryKey() .primaryKey()
.default(sql`gen_random_uuid()`), .default(sql`gen_random_uuid
deviceId: varchar("device_id").notNull().unique(), ()`),
childId: uuid("child_id").notNull(), deviceId: varchar('device_id').notNull().unique(),
apiKey: varchar("api_key").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 //TODO: make the device request/pin a separate table and enforce the expiry
pin: integer("pin").notNull(), pin: integer('pin').notNull(),
expires: timestamp("expires").default(sql`now expires: timestamp('expires').default(sql`now
() ()
+ interval '1 hour'`), + interval '1 hour'`),
}); });
export const deviceRelations = relations(device, ({ one, many }) => ({ export const deviceRelations = relations(device, ({ one, many }) => ({
@@ -113,14 +116,15 @@ export const deviceRelations = relations(device, ({ one, many }) => ({
pings: many(ping), pings: many(ping),
})); }));
export const ping = pgTable("ping", { export const ping = pgTable('ping', {
id: uuid("id") id: uuid('id')
.primaryKey() .primaryKey()
.default(sql`gen_random_uuid()`), .default(sql`gen_random_uuid
deviceId: uuid("device_id").notNull(), ()`),
latitude: doublePrecision("latitude").notNull(), deviceId: uuid('device_id').notNull(),
longitude: doublePrecision("longitude").notNull(), latitude: doublePrecision('latitude').notNull(),
timestamp: timestamp("timestamp").notNull(), longitude: doublePrecision('longitude').notNull(),
timestamp: timestamp('timestamp').notNull(),
}); });
export const pingRelations = relations(ping, ({ one, many }) => ({ export const pingRelations = relations(ping, ({ one, many }) => ({
device: one(device, { device: one(device, {