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 DashboardPage from "@/components/pages/dashboard-page";
import React from 'react';
import DashboardPage from '@/components/pages/dashboard-page';
import { api } from '@/trpc/server';
const Dashboard = () => {
return <DashboardPage />;
const Dashboard = async () => {
const kids = await api.child.mine.query();
return <DashboardPage kids={kids} />;
};
export default Dashboard;

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,19 @@
import React from 'react'
import ChildSelectList from './child-select-list'
import React from 'react';
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 (
<div className='flex flex-row space-x-2 justify-center items-center'>
<ChildSelectList />
<div className="flex flex-row space-x-2 justify-center items-center">
<ChildSelectList kids={kids} />
<AddChildComponent />
</div>
)
}
);
};
export default ChildrenFilter
export default ChildrenFilter;

View File

@@ -1,18 +1,22 @@
import React from "react";
import ChildrenFilter from "@/components/children/children-filter";
import dynamic from "next/dynamic";
import React from 'react';
import ChildrenFilter from '@/components/children/children-filter';
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
//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,
});
return (
<div>
<div className="z-10">
<ChildrenFilter />
<ChildrenFilter kids={kids} />
</div>
<div className="z-0 mt-4">
<Map />

View File

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

View File

@@ -1,37 +1,33 @@
import { z } from "zod";
import { z } from 'zod';
import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from "@/server/api/trpc";
import { child } from "@/server/db/schema";
} from '@/server/api/trpc';
import { child } from '@/server/db/schema';
import { eq } from 'drizzle-orm';
export const childRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),
create: protectedProcedure
.input(z.object({ name: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
await ctx.db.insert(child).values({
const c = {
parentId: ctx.session.user.id,
name,
});
name: input.name,
};
await ctx.db.insert(child).values(c);
}),
getLatest: publicProcedure.query(({ ctx }) => {
return ctx.db.query.child.findFirst({
mine: protectedProcedure.query(async ({ ctx }) => {
return ctx.db.query.child.findMany({
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,
pgSchema,
doublePrecision,
} from "drizzle-orm/pg-core";
import type { AdapterAccount } from "@auth/core/adapters";
import { relations, sql } from "drizzle-orm";
} from 'drizzle-orm/pg-core';
import type { AdapterAccount } from '@auth/core/adapters';
import { relations, sql } from 'drizzle-orm';
//#region auth
//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")
const authSchema = pgSchema('auth');
export const users = pgTable('user', {
id: uuid('id')
.notNull()
.primaryKey()
.default(sql`gen_random_uuid()`),
name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
.default(sql`gen_random_uuid
()`),
name: text('name'),
email: text('email').notNull(),
emailVerified: timestamp('emailVerified', { mode: 'date' }),
image: text('image'),
});
export const accounts = pgTable(
"account",
'account',
{
userId: uuid("userId")
userId: uuid('userId')
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
.references(() => users.id, { onDelete: 'cascade' }),
type: text('type').$type<AdapterAccount['type']>().notNull(),
provider: text('provider').notNull(),
providerAccountId: text('providerAccountId').notNull(),
refresh_token: text('refresh_token'),
access_token: text('access_token'),
expires_at: integer('expires_at'),
token_type: text('token_type'),
scope: text('scope'),
id_token: text('id_token'),
session_state: text('session_state'),
},
(account) => ({
compoundKey: primaryKey(account.provider, account.providerAccountId),
}),
);
export const sessions = pgTable("session", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: uuid("userId")
export const sessions = pgTable('session', {
sessionToken: text('sessionToken').notNull().primaryKey(),
userId: uuid('userId')
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
.references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp('expires', { mode: 'date' }).notNull(),
});
export const verificationTokens = pgTable(
"verification_token",
'verification_token',
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
identifier: varchar('identifier', { length: 255 }).notNull(),
token: varchar('token', { length: 255 }).notNull(),
expires: timestamp('expires', { mode: 'date' }).notNull(),
},
(vt) => ({
compoundKey: primaryKey(vt.identifier, vt.token),
@@ -74,15 +75,16 @@ export const userRelations = relations(users, ({ many }) => ({
}));
//#region child
export const child = pgTable("child", {
id: uuid("id")
export const child = pgTable('child', {
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").notNull(),
.default(sql`gen_random_uuid
()`),
name: varchar('name', { length: 256 }).notNull(),
email: varchar('email', { length: 256 }),
phone: varchar('phone', { length: 256 }),
apiKey: varchar('key', { length: 256 }),
parentId: uuid('parent_id').notNull(),
});
export const childRelations = relations(child, ({ one, many }) => ({
parent: one(users, {
@@ -92,17 +94,18 @@ export const childRelations = relations(child, ({ one, many }) => ({
devices: many(device),
}));
export const device = pgTable("device", {
id: uuid("id")
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(),
.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
()
pin: integer('pin').notNull(),
expires: timestamp('expires').default(sql`now
()
+ interval '1 hour'`),
});
export const deviceRelations = relations(device, ({ one, many }) => ({
@@ -113,14 +116,15 @@ export const deviceRelations = relations(device, ({ one, many }) => ({
pings: many(ping),
}));
export const ping = pgTable("ping", {
id: uuid("id")
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(),
.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 }) => ({
device: one(device, {