diff --git a/projects/admin/src/app.tsx b/projects/admin/src/app.tsx index fe9e0f9..d2552f4 100644 --- a/projects/admin/src/app.tsx +++ b/projects/admin/src/app.tsx @@ -1,8 +1,9 @@ -import { Admin, ListGuesser, Resource } from "react-admin"; +import { Admin, Resource } from "react-admin"; import { dataProvider } from "./data-provider/data-provider"; -import { UserEdit, UserList } from "./users"; +import { UserEdit, UserList } from "./resources/users"; import { LoginPage } from "./login"; import { authProvider } from "./auth-provider"; +import { EventCreate, EventEdit, EventList } from "./resources/events"; export const App = () => { return ( @@ -17,7 +18,12 @@ export const App = () => { edit={UserEdit} recordRepresentation="email" /> - + ); }; diff --git a/projects/admin/src/auth-provider.ts b/projects/admin/src/auth-provider.ts index 8190abb..0561659 100644 --- a/projects/admin/src/auth-provider.ts +++ b/projects/admin/src/auth-provider.ts @@ -57,6 +57,10 @@ export const authProvider: AuthProvider = { logout: async () => { try { + const signedIn = await trpc.validateSession.query(); + if (!signedIn) { + return; + } await trpc.authentication.signOut.mutate(); } catch (error) { console.error(error); diff --git a/projects/admin/src/data-provider/create.ts b/projects/admin/src/data-provider/create.ts index 8b3e79e..9f8fe4f 100644 --- a/projects/admin/src/data-provider/create.ts +++ b/projects/admin/src/data-provider/create.ts @@ -5,8 +5,9 @@ import { z } from "zod"; const createEventSchema = z.object({ name: z.string(), - startsAt: z.string().datetime(), - endsAt: z.string().datetime(), + startsAt: z.date().transform((date) => date.toISOString()), + endsAt: z.date().transform((date) => date.toISOString()), + marathonTypes: z.array(z.enum(["ONLINE", "ONSITE"])), }); export const create = async ( diff --git a/projects/admin/src/data-provider/get-list.ts b/projects/admin/src/data-provider/get-list.ts index 9358024..f750374 100644 --- a/projects/admin/src/data-provider/get-list.ts +++ b/projects/admin/src/data-provider/get-list.ts @@ -6,7 +6,7 @@ export const getList = async ( resource: string, params: GetListParams, ): Promise => { - const query = { + const input = { order: lowercase(params.sort.order), orderBy: params.sort.field, skip: (params.pagination.page - 1) * params.pagination.perPage, @@ -14,9 +14,21 @@ export const getList = async ( }; switch (resource) { case "users": { - const res = await trpc.admin.users.list.query(query); + const res = await trpc.admin.users.list.query(input); return { data: res.data, total: res.count }; } + case "events": { + const res = await trpc.admin.events.list.query(input); + return { + data: res.data.map((item) => ({ + ...item, + marathonTypes: item.eventMarathonTypes.map((type) => ({ + name: type.marathonType, + })), + })), + total: res.count, + }; + } default: throw new Error(`unknown resource ${resource}`); } diff --git a/projects/admin/src/data-provider/get-one.ts b/projects/admin/src/data-provider/get-one.ts index 150070c..3d3adb8 100644 --- a/projects/admin/src/data-provider/get-one.ts +++ b/projects/admin/src/data-provider/get-one.ts @@ -16,6 +16,17 @@ export const getOne = async ( const data = await trpc.admin.users.get.query({ id }); return { data }; } + case "events": { + const data = await trpc.admin.events.get.query({ id }); + return { + data: { + ...data, + marathonTypes: data.eventMarathonTypes.map( + (type) => type.marathonType, + ), + }, + }; + } default: throw new Error(`unknown resource ${resource}`); } diff --git a/projects/admin/src/resources/events.tsx b/projects/admin/src/resources/events.tsx new file mode 100644 index 0000000..7420956 --- /dev/null +++ b/projects/admin/src/resources/events.tsx @@ -0,0 +1,71 @@ +import { + Create, + DateTimeInput, + SimpleForm, + TextInput, + required, + BooleanField, + Datagrid, + DateField, + List, + TextField, + CheckboxGroupInput, + ArrayField, + SingleFieldList, + ChipField, + Edit, + BooleanInput, +} from "react-admin"; + +export const EventCreate = () => { + return ( + + + + + + + + + ); +}; + +export const EventList = () => ( + + + + + + + + + + + + + +); + +export const EventEdit = () => ( + + + + + + + + + +); diff --git a/projects/admin/src/users.tsx b/projects/admin/src/resources/users.tsx similarity index 100% rename from projects/admin/src/users.tsx rename to projects/admin/src/resources/users.tsx diff --git a/projects/server/prisma/migrations/20240110142600_/migration.sql b/projects/server/prisma/migrations/20240110142600_/migration.sql new file mode 100644 index 0000000..ea949cd --- /dev/null +++ b/projects/server/prisma/migrations/20240110142600_/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "username" DROP NOT NULL; diff --git a/projects/server/prisma/seed.ts b/projects/server/prisma/seed.ts index a2b09da..ee4fbc5 100644 --- a/projects/server/prisma/seed.ts +++ b/projects/server/prisma/seed.ts @@ -1,4 +1,4 @@ -import { PrismaClient, Role } from "@prisma/client"; +import { MarathonType, PrismaClient, Role } from "@prisma/client"; const prisma = new PrismaClient(); @@ -20,3 +20,19 @@ await prisma.user.create({ username: "dev-normal", }, }); + +await prisma.event.create({ + data: { + name: "RTA in Japan Sample 1995", + startsAt: new Date("1995-08-08T12:00:00+0900"), + endsAt: new Date("1995-08-15T18:00:00+0900"), + eventMarathonTypes: { + createMany: { + data: [ + { marathonType: MarathonType.ONLINE }, + { marathonType: MarathonType.ONSITE }, + ], + }, + }, + }, +}); diff --git a/projects/server/src/routes/admin/events.ts b/projects/server/src/routes/admin/events.ts index 814aab3..27a9e2f 100644 --- a/projects/server/src/routes/admin/events.ts +++ b/projects/server/src/routes/admin/events.ts @@ -4,6 +4,13 @@ import { adminProcedure, router } from "../../trpc.js"; import { listSchema } from "./utils.js"; import { MarathonType } from "@prisma/client"; +const createEventSchema = z.object({ + name: z.string(), + startsAt: z.string().datetime(), + endsAt: z.string().datetime(), + marathonTypes: z.array(z.enum([MarathonType.ONLINE, MarathonType.ONSITE])), +}); + export const eventsRouter = router({ list: adminProcedure.input(listSchema).query(async ({ input }) => { const data = await prisma.event.findMany({ @@ -15,6 +22,9 @@ export const eventsRouter = router({ endsAt: input.orderBy === "endsAt" ? input.order : undefined, published: input.orderBy === "published" ? input.order : undefined, }, + include: { + eventMarathonTypes: true, + }, }); const count = await prisma.event.count(); return { data, count }; @@ -26,6 +36,9 @@ export const eventsRouter = router({ where: { id: input.id, }, + include: { + eventMarathonTypes: true, + }, }); if (!event) { throw new Error("event not found"); @@ -33,19 +46,7 @@ export const eventsRouter = router({ return event; }), create: adminProcedure - .input( - z.object({ - name: z.string(), - startsAt: z - .string() - .datetime() - .transform((date) => new Date(date)), - endsAt: z - .string() - .datetime() - .transform((date) => new Date(date)), - }), - ) + .input(createEventSchema) .mutation(async ({ input }) => { const event = await prisma.event.create({ data: { @@ -54,6 +55,12 @@ export const eventsRouter = router({ endsAt: input.endsAt, }, }); + await prisma.eventMarathonType.createMany({ + data: input.marathonTypes.map((type) => ({ + eventId: event.id, + marathonType: type, + })), + }); return event; }), update: adminProcedure @@ -83,6 +90,7 @@ export const eventsRouter = router({ published: input.data.published, }, }); + // TODO: update event types return event; }), delete: adminProcedure