diff --git a/actions/Schemas/create.ts b/actions/Schemas/create.ts deleted file mode 100644 index 33be2e8..0000000 --- a/actions/Schemas/create.ts +++ /dev/null @@ -1,29 +0,0 @@ -"use server"; - -import { auth } from "@/auth"; -import { prisma } from "@/prisma/prismaClient"; -import { isEmailVerified } from "@/util/isEmailVerified"; -import { redirect } from "next/navigation"; - -export const createSchema = async (form: FormData) => { - const name = form.get("name")?.toString(); - const gsId = form.get("gsId")?.toString(); - - const session = await auth(); - - if (!name || !gsId || !session?.user?.id || !isEmailVerified(session.user.id)) - return; - - const { id } = await prisma.schema.create({ - data: { - name, - schema: "{}", - types: "{}", - version: 0, - gameSystemId: gsId, - authorId: session.user.id, - }, - select: { id: true }, - }); - redirect(`/game-systems/${gsId}/schema/${id}`); -}; diff --git a/actions/Schemas/find.ts b/actions/Schemas/find.ts deleted file mode 100644 index 9771fd8..0000000 --- a/actions/Schemas/find.ts +++ /dev/null @@ -1,20 +0,0 @@ -"use server"; - -import { prisma } from "@/prisma/prismaClient"; - -export const findSchema = async (id: string) => { - const schema = await prisma.schema.findFirst({ - where: { - id, - }, - include: { - gameSystem: { - select: { - id: true, - name: true, - }, - }, - }, - }); - return schema; -}; diff --git a/actions/Schemas/index.ts b/actions/Schemas/index.ts new file mode 100644 index 0000000..b26ef46 --- /dev/null +++ b/actions/Schemas/index.ts @@ -0,0 +1,79 @@ +"use server"; +import { auth } from "@/auth"; +import { isEmailVerified } from "@/util/isEmailVerified"; +import { redirect } from "next/navigation"; +import { prisma } from "@/prisma/prismaClient"; + +export const saveSchemaDb = async (s: Schema) => { + const sesh = await auth(); + if (!sesh?.user?.id) return; + + const { id } = await prisma.schema.upsert({ + // data: { + // ...s, + // }, + create: { + ...s, + version: 0, + authorId: sesh.user.id, + id: undefined, + }, + update: s, + where: { + id: s.id, + }, + select: { + id: true, + }, + }); + redirect(`/game-systems/${s.gameSystemId}/schema/${id}`); +}; + +export const findSchema = async (id: string): Promise => { + const schema = await prisma.schema.findFirst({ + where: { + id, + }, + // include: { + // gameSystem: { + // select: { + // id: true, + // name: true, + // }, + // }, + // }, + select: { + id: true, + name: true, + schema: true, + types: true, + }, + }); + + if (!schema) return null; + + return schema as Schema; +}; + +export const createSchema = async (form: FormData) => { + const name = form.get("name")?.toString(); + const gsId = form.get("gsId")?.toString(); + + const session = await auth(); + + if (!name || !gsId || !session?.user?.id || !isEmailVerified(session.user.id)) + return; + + const { id } = await prisma.schema.create({ + data: { + name, + schema: "{}", + types: "{}", + version: 0, + gameSystemId: gsId, + authorId: session.user.id, + }, + select: { id: true }, + }); + redirect(`/game-systems/${gsId}/schema/${id}`); +}; diff --git a/app/game-systems/[id]/page.tsx b/app/game-systems/[id]/page.tsx index c8ca80f..7b71c2a 100644 --- a/app/game-systems/[id]/page.tsx +++ b/app/game-systems/[id]/page.tsx @@ -1,18 +1,18 @@ import { prisma } from "@/prisma/prismaClient"; import Link from "next/link"; -export default async function GameSystem( - { params: { id } }: { params: { id: string } }, -) { +export default async function GameSystem({ + params: { id }, +}: { + params: { id: string }; +}) { if (!id) throw "HOW DID YOU GET HERE?"; const gameSystem = await prisma.gameSystem.findFirst({ where: { id, }, - select: { - id: true, - name: true, + include: { schemas: { select: { name: true, @@ -26,6 +26,10 @@ export default async function GameSystem( }, }, }, + // select: { + // id: true, + // name: true, + // }, }); return ( diff --git a/auth/index.ts b/auth/index.ts index 6a56246..eab44a6 100644 --- a/auth/index.ts +++ b/auth/index.ts @@ -30,7 +30,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth(async () => { let user = null; const pwHash = await saltAndHashPassword( - credentials.password as string + credentials.password as string, ); user = await prisma.user.findFirst({ where: { diff --git a/components/schema/index.tsx b/components/schema/index.tsx index c0f89a4..67602c9 100644 --- a/components/schema/index.tsx +++ b/components/schema/index.tsx @@ -1,30 +1,45 @@ "use client"; -import { FC, useCallback, useState } from "react"; +import { FC, useCallback, useEffect, useState } from "react"; import AnimatedPageContainer from "@/components/AnimatedPageContainer"; import { TypeEditor } from "./type-editor"; import { useObjectStateWrapper } from "@/hooks/useObjectState"; import { useInput } from "../../hooks/useInput"; -import { useRecoilState, useResetRecoilState } from "recoil"; +import { useRecoilState } from "recoil"; import { SchemaEditAtom } from "@/recoil/atoms/schema"; import { SchemaViewer } from "./schema-viewer"; import { TemplateEditor } from "./template-editor"; import { Icon } from "@/components/Icon"; import { useParams } from "next/navigation"; import { FieldTypes } from "./fieldtypes"; -import { prisma } from "@/prisma/prismaClient"; +import { findSchema, saveSchemaDb } from "@/actions/Schemas/index"; +import { useToast } from "../toast"; export const SchemaBuilder: FC = () => { const [schema, setSchema] = useRecoilState(SchemaEditAtom); - const resetSchema = useResetRecoilState(SchemaEditAtom); + // const resetSchema = useResetRecoilState(SchemaEditAtom); + const { createToast } = useToast(); const { update: updateSchema, bindProperty: bindSchemaProperty } = useObjectStateWrapper(schema, setSchema); - const { schemaId, gameSystemId } = useParams<{ + const { schemaId, id: gameSystemId } = useParams<{ schemaId: string; - gameSystemId?: string; + id: string; }>(); + useEffect(() => { + if (schemaId !== "create" && schemaId !== schema.id) + findSchema(schemaId).then((sc) => { + if (!sc) return; + setSchema(sc); + }); + }, [schema.id, schemaId, setSchema]); + + useEffect(() => { + if (gameSystemId && !schema.gameSystemId) + setSchema((sc) => ({ ...sc, gameSystemId })); + }, [gameSystemId, schema.gameSystemId, setSchema]); + const { value: typeName, bind: bindTypeName, @@ -33,7 +48,7 @@ export const SchemaBuilder: FC = () => { const [pageNumber, setPageNumber] = useState(0); - const [lastSaved, setLastSaved] = useState(schema); + const [lastSaved, _setLastSaved] = useState(schema); const [selectedType, setSelectedType] = useState(""); @@ -49,24 +64,13 @@ export const SchemaBuilder: FC = () => { setPageNumber(0); setSelectedType(""); }, - [resetTypeName, updateSchema] + [resetTypeName, updateSchema], ); const saveSchema = useCallback(async () => { - // "use server"; - // setLastSaved(schema); - // await prisma.schema.upsert({ - // where: { id: schema.id }, - // update: { ...schema }, - // create: { - // name: schema.name, - // schema: schema.schema, - // types: schema.types, - // version: 0, - // gameSystemId, - // }, - // }); - }, [schema, gameSystemId]); + createToast({ msg: "Saving Schema", fading: true }); + await saveSchemaDb(schema); + }, [createToast, schema]); const selectTypeForEdit = useCallback((typeKey: string) => { setSelectedType(typeKey); @@ -82,10 +86,7 @@ export const SchemaBuilder: FC = () => { updateSchema((s) => ({ schema: { ...s.schema, - [schemaFieldName]: { - display: "", - type: FieldTypes.any, - }, + [schemaFieldName]: FieldTypes.any, }, })); resetSchemaFieldName(); @@ -100,7 +101,7 @@ export const SchemaBuilder: FC = () => { }, })); }, - [updateSchema] + [updateSchema], ); const deleteType = useCallback( @@ -111,7 +112,7 @@ export const SchemaBuilder: FC = () => { return { types }; }); }, - [updateSchema] + [updateSchema], ); return ( @@ -141,7 +142,7 @@ export const SchemaBuilder: FC = () => { template={schemaField} update={updateSchemaField} /> - ) + ), )} diff --git a/lib/secret/index.ts b/lib/secret/index.ts index ce1633d..87190a6 100644 --- a/lib/secret/index.ts +++ b/lib/secret/index.ts @@ -15,9 +15,13 @@ export class DHSecretClient { * @param dhBaseUri uri for hosted Dragon's Hoard instance * @param cacheDir path to cache dir */ - constructor(private dhBaseUri: string, private cacheDir: string) { + constructor( + private dhBaseUri: string, + private cacheDir: string, + ) { this.cacheLocation = this.cacheDir.trim().replace(/\/^/, "") + "/.dh_cache"; mkdirSync(this.cacheDir, { recursive: true }); + this.readDiskCache(); this.token = this.fetchToken(); } @@ -42,12 +46,13 @@ export class DHSecretClient { } private readDiskCache() { - const cache = readFileSync(this.cacheLocation, "utf-8"); - - if (!cache) { + try { + const cache = readFileSync(this.cacheLocation, "utf-8"); + this.cache = JSON.parse(cache || "{}"); + } catch { this.cache = {}; - this.writeDiskCache(); - } else this.cache = JSON.parse(cache || "{}"); + this.writeDiskCache().then(this.readDiskCache); + } } private async writeDiskCache() { await writeFile(this.cacheLocation, JSON.stringify(this.cache), "utf-8"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 76b69eb..5830110 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,9 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? -// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init - generator client { provider = "prisma-client-js" } @@ -31,24 +25,53 @@ model Schema { author User @relation(fields: [authorId], references: [id]) authorId String - originalId String? - name String - schema Json - types Json - version Int + name String + + SchemaRevision SchemaRevision[] +} + +model SchemaRevision { + id String @id @default(cuid()) + schemaId String + parentSchema Schema @relation(fields: [schemaId], references: [id]) + + schema Json + types Json + version Int + Publication Publication[] + + @@unique([schemaId, version]) } model Publication { - id String @id @default(cuid()) - schema Schema @relation(fields: [schemaId], references: [id]) - schemaId String - tags Tag[] - author User @relation(fields: [authorId], references: [id]) - authorId String + id String @id @default(cuid()) + schema Schema @relation(fields: [schemaId], references: [id]) + schemaId String + schemaVersion Int + schemaRevision SchemaRevision @relation(fields: [schemaVersion, schemaId], references: [version, schemaId]) + tags Tag[] - name String - data Json - TagsOnPublications TagsOnPublications[] + name String + + TagsOnPublications TagsOnPublications[] + PublicationRevision PublicationRevision[] +} + +model PublicationRevision { + id String @id @default(cuid()) + version String + isFinal Boolean + data Json + + previousId String? + publicationId String + publication Publication @relation(fields: [publicationId], references: [id]) + previousRevision PublicationRevision? @relation(name: "downlineRevisions", fields: [previousId], references: [id]) + downlineRevisions PublicationRevision[] @relation("downlineRevisions") + author User @relation(fields: [authorId], references: [id]) + authorId String + + @@unique([publicationId, version]) } model TagsOnPublications { @@ -80,10 +103,9 @@ model TagsOnTags { } model User { - id String @id @default(cuid()) - schemas Schema[] - gameSystems GameSystem[] - publications Publication[] + id String @id @default(cuid()) + schemas Schema[] + gameSystems GameSystem[] name String? username String? @unique @@ -94,8 +116,9 @@ model User { accounts Account[] sessions Session[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + PublicationRevision PublicationRevision[] } model Account { diff --git a/types.d.ts b/types.d.ts index 318e0d7..e13e924 100644 --- a/types.d.ts +++ b/types.d.ts @@ -31,7 +31,7 @@ type FrontMatter = Record; type SearchFunction = ( s: string, start: number, - end: number + end: number, ) => { start: number; end: number; @@ -53,7 +53,7 @@ type IdentifierRegistration = >( parseFunction: (s: string, rx: RegExp) => IdentifiedToken, renderFunction: TokenRenderer, openTagRx?: RegExp, - closeTagRx?: RegExp + closeTagRx?: RegExp, ) => void; // Schema @@ -76,11 +76,16 @@ type Template = { display: string; }; +type SchemaFields = Record; + +type SchemaTypes = Record; + type Schema = { id: string; name: string; - schema: Record; - types: Record; + schema: SchemaFields; + types: SchemaTypes; + gameSystemId?: string; }; // Input Binder @@ -88,7 +93,7 @@ type InputBinder = { name: string; value: string | number; onChange: ( - e: ChangeEvent + e: ChangeEvent, ) => void; };