diff --git a/src/client/app.tsx b/src/client/app.tsx index 6b90721..5f0c34d 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -1,5 +1,4 @@ import { css } from "@emotion/css"; -import { Center, Cover, Stack } from "@firebox/components"; import { Pico8Console } from "./pico8-client/Pico8Console"; import testcarts from "./testcarts"; diff --git a/src/database/migrations/4-fourth-migration.sql b/src/database/migrations/4-fourth-migration.sql index a348f5b..b471a8e 100644 --- a/src/database/migrations/4-fourth-migration.sql +++ b/src/database/migrations/4-fourth-migration.sql @@ -4,10 +4,11 @@ DROP TABLE releases; CREATE TABLE releases ( id text, - picobook_version int, repo text, + author text, slug text, version text, carts json, - title text, + manifest json, + created_at time ); \ No newline at end of file diff --git a/src/server/api/getRelease.ts b/src/server/api/getRelease.ts new file mode 100644 index 0000000..ca89ccd --- /dev/null +++ b/src/server/api/getRelease.ts @@ -0,0 +1,42 @@ +import { Type } from "@sinclair/typebox"; +import { FirRouteInput, FirRouteOptions } from "../util/routewrap.ts"; +import { getRelease, getReleases } from "../dbal/dbal.ts"; + +const method = "GET"; +const url = "/api/release"; + +const payloadT = Type.Any(); + +const handler = async ({payload}: FirRouteInput) => { + const {author, slug, version} = payload; + + if (typeof author !== "string") { + return { + release: null, + versions: [], + }; + } + if (typeof slug !== "string") { + return { + release: null, + versions: [], + }; + } + + const release = await getRelease({author, slug, version}); + const releases = await getReleases({author, slug}); + + const versions = releases.map(r => r.version); + + return { + release, + versions, + } +}; + +export default { + method, + url, + payloadT, + handler, +} as const satisfies FirRouteOptions; \ No newline at end of file diff --git a/src/server/api/release.ts b/src/server/api/release.ts index 071876d..31f9248 100644 --- a/src/server/api/release.ts +++ b/src/server/api/release.ts @@ -1,11 +1,12 @@ import { Type } from "@sinclair/typebox"; -import { TypeCompiler } from "@sinclair/typebox/compiler"; import { FirRouteInput, FirRouteOptions } from "../util/routewrap"; import {git} from "../util/git.ts"; import { randomUUID } from "crypto"; import path from "path"; import {fileURLToPath} from 'url'; import { getCarts } from "../util/carts.ts"; +import { getRelease, insertRelease } from "../dbal/dbal.ts"; +import { ManifestType } from "../types.ts"; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const reposPath = path.resolve(__dirname, "..", "..", "..", "repos"); @@ -14,19 +15,6 @@ const url = "/api/release"; const payloadT = Type.Any(); -const manifestT = Type.Object({ - picobook_version: Type.Number(), - id: Type.String(), - version: Type.String(), - carts: Type.Array(Type.String()), - repo: Type.String(), - title: Type.Optional(Type.String()), - author: Type.Optional(Type.String()), - readme: Type.Optional(Type.String()), -}); - -const ManifestType = TypeCompiler.Compile(manifestT) - const handler = async ({payload}: FirRouteInput) => { const {manifest, token} = payload; @@ -34,6 +22,11 @@ const handler = async ({payload}: FirRouteInput) => { return false; } + const release = await getRelease({author: manifest.author, slug: manifest.id, version: manifest.version}); + if (release) { + return false; + } + const uuid = randomUUID(); const repoPath = path.join(reposPath, uuid); @@ -45,6 +38,11 @@ const handler = async ({payload}: FirRouteInput) => { const carts = await getCarts(repoPath, manifest.carts); + insertRelease({ + manifest, + carts, + }); + console.log({ manifest, carts, diff --git a/src/server/dbal/dbal.ts b/src/server/dbal/dbal.ts index e5e66c3..eb14324 100644 --- a/src/server/dbal/dbal.ts +++ b/src/server/dbal/dbal.ts @@ -1 +1,89 @@ -// Database Access Layer stuff goes here \ No newline at end of file +// Database Access Layer stuff goes here + +// Database Access Layer stuff goes here +import { v4 as uuidv4 } from 'uuid'; +import { db, sql } from "../../database/db" +import { JsonValue } from '@firebox/tsutil'; +import { PicobookManifest } from '../types'; + +export type DbRelease = { + id: string; + slug: string; + repo: string; + version: string; + carts: {name: string; rom: number[]}[]; + author: string; + manifest: PicobookManifest; +} + +const compareVersions = (a: string, b: string) => { + const [a1, a2] = a.split(".").map(x => Number(x)); + const [b1, b2] = b.split(".").map(x => Number(x)); + if (a1 !== b1) { + return a1 - b1; + } else { + return a2 - b2; + } +} + +const compareByVersion = (a: DbRelease, b: DbRelease) => compareVersions(a.version, b.version); + +export const getReleases = async (where: { + author: string; + slug: string; + version?: string; +}): Promise => { + const {author, slug, version} = where; + if (!version) { + const rows = await db.query(sql` + SELECT * from releases + WHERE + slug = ${slug} AND + author = ${author} + `); + return rows; + } else { + const rows = await db.query(sql` + SELECT * from releases + WHERE + slug = ${slug} AND + author = ${author} AND + version = ${version} + `); + return rows; + } +} + +export const getRelease = async (where: { + author: string; + slug: string; + version?: string; +}) => { + const {version} = where; + const releases = await getReleases(where); + if (version) { + if (releases.length === 1) { + return releases[0]; + } else { + return null; + } + } else { + if (releases.length < 1) { + return null; + } + releases.sort(compareByVersion); + return releases[releases.length-1]; + } +} + +export const insertRelease = async (props: {manifest: PicobookManifest, carts: {name: string; rom: number[]}[]}) => { + const {manifest, carts} = props; + const {id: slug, author, repo, version} = manifest; + const id = uuidv4(); + const now = new Date(); + await db.query(sql` + INSERT INTO chats (id, slug, repo, version, author, carts, manifest, created_at) + VALUES (${id}, ${slug}, ${repo}, ${version}, ${author} ${carts}, ${manifest}, ${now}) + `); + return id; +} diff --git a/src/server/types.ts b/src/server/types.ts new file mode 100644 index 0000000..12a6944 --- /dev/null +++ b/src/server/types.ts @@ -0,0 +1,17 @@ +import { Static, Type } from "@sinclair/typebox"; +import { TypeCompiler } from "@sinclair/typebox/compiler"; + +export const manifestT = Type.Object({ + picobook_version: Type.Number(), + id: Type.String(), + version: Type.String(), + carts: Type.Array(Type.String()), + repo: Type.String(), + author: Type.String(), + title: Type.Optional(Type.String()), + readme: Type.Optional(Type.String()), +}); + +export const ManifestType = TypeCompiler.Compile(manifestT); + +export type PicobookManifest = Static; \ No newline at end of file