From 192a353943cd0e422ae2773018ccab83c06ddac2 Mon Sep 17 00:00:00 2001 From: Paolo Brasolin <paolo.brasolin@eurac.edu> Date: Wed, 16 Mar 2022 21:00:46 +0100 Subject: [PATCH] feat: #be switch to typebox --- backend/package-lock.json | 46 +++++---------- backend/package.json | 2 +- backend/src/api.ts | 121 +++++++++++++++++++++++++------------- 3 files changed, 96 insertions(+), 73 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 6073480..528119d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,13 +9,13 @@ "version": "0.1.0", "license": "MIT", "dependencies": { + "@sinclair/typebox": "^0.23.4", "@types/sharp": "^0.29.5", "@xmldom/xmldom": "^0.8.1", "axios": "^0.26.0", "fastify": "^3.27.1", "fastify-cors": "^6.0.2", "fastify-swagger": "^5.0.0", - "json-schema-to-ts": "^1.6.5", "knex": "^1.0.3", "pg": "^8.7.3", "sharp": "^0.30.2" @@ -1066,6 +1066,11 @@ "node": ">= 8" } }, + "node_modules/@sinclair/typebox": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.4.tgz", + "integrity": "sha512-0/WqSvpVbCBAV1yPeko7eAczKbs78dNVAaX14quVlwOb2wxfKuXCx91h4NrEfkYK9zEnyVSW4JVI/trP3iS+Qg==" + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1225,7 +1230,8 @@ "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true }, "node_modules/@types/node": { "version": "16.11.25", @@ -5094,15 +5100,6 @@ "node": ">=10" } }, - "node_modules/json-schema-to-ts": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.5.tgz", - "integrity": "sha512-BLUdzgz50XV+NMrSg8+KrUvV7Oh1eb/kLsyPbkWXFTXTloWkAsq7MbAppibE+DyMy4PasS/7bFQPwTIHx35r+A==", - "dependencies": { - "@types/json-schema": "^7.0.6", - "ts-toolbelt": "^6.15.5" - } - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7319,11 +7316,6 @@ "node": ">=0.4.0" } }, - "node_modules/ts-toolbelt": { - "version": "6.15.5", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", - "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -8601,6 +8593,11 @@ "fastq": "^1.6.0" } }, + "@sinclair/typebox": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.23.4.tgz", + "integrity": "sha512-0/WqSvpVbCBAV1yPeko7eAczKbs78dNVAaX14quVlwOb2wxfKuXCx91h4NrEfkYK9zEnyVSW4JVI/trP3iS+Qg==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -8751,7 +8748,8 @@ "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true }, "@types/node": { "version": "16.11.25", @@ -11644,15 +11642,6 @@ "uri-js": "^4.2.2" } }, - "json-schema-to-ts": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.5.tgz", - "integrity": "sha512-BLUdzgz50XV+NMrSg8+KrUvV7Oh1eb/kLsyPbkWXFTXTloWkAsq7MbAppibE+DyMy4PasS/7bFQPwTIHx35r+A==", - "requires": { - "@types/json-schema": "^7.0.6", - "ts-toolbelt": "^6.15.5" - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -13298,11 +13287,6 @@ } } }, - "ts-toolbelt": { - "version": "6.15.5", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", - "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" - }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", diff --git a/backend/package.json b/backend/package.json index 35f939b..b509e7a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,13 +18,13 @@ "watch:test": "jest --watch" }, "dependencies": { + "@sinclair/typebox": "^0.23.4", "@types/sharp": "^0.29.5", "@xmldom/xmldom": "^0.8.1", "axios": "^0.26.0", "fastify": "^3.27.1", "fastify-cors": "^6.0.2", "fastify-swagger": "^5.0.0", - "json-schema-to-ts": "^1.6.5", "knex": "^1.0.3", "pg": "^8.7.3", "sharp": "^0.30.2" diff --git a/backend/src/api.ts b/backend/src/api.ts index 22712a2..628086d 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -1,46 +1,36 @@ import { FastifyPluginCallback } from "fastify"; -import { FromSchema } from "json-schema-to-ts"; +import { Static, Type } from "@sinclair/typebox"; import { connection } from "./db"; // NOTE: see https://www.npmjs.com/package/fastify-plugin for TS plugin definition -const ParamsSchema = { - type: "object", - properties: { - id: { - type: "string", - format: "uuid", - }, - }, - required: ["id"], -} as const; - -const GameSchema = { - type: "object", - properties: { - id: { - type: "string", - format: "uuid", - }, - }, - required: ["id"], -} as const; - -const WordSchema = { - type: "object", - properties: { - id: { type: "string", format: "uuid" }, - image: { type: "string" }, - ocr_confidence: { type: "number", minimum: 0, maximum: 1 }, - ocr_transcript: { type: "string" }, - }, - required: ["id"], -} as const; +const ParamsSchema = Type.Object({ + id: Type.String({ format: "uuid" }), +}); + +type ParamsType = Static<typeof ParamsSchema>; + +const GameSchema = Type.Object({ + id: Type.Readonly(Type.String({ format: "uuid" })), + began_at: Type.Optional(Type.String({ format: "date-time" })), + ended_at: Type.Optional(Type.String({ format: "date-time" })), +}); + +type GameType = Static<typeof GameSchema>; + +const WordSchema = Type.Object({ + id: Type.String({ format: "uuid" }), + image: Type.String(), + ocr_confidence: Type.Number({ minimum: 0, maximum: 1 }), + ocr_transcript: Type.String(), +}); + +type WordType = Static<typeof WordSchema>; const apiPlugin: FastifyPluginCallback = (fastify, options, next) => { fastify.route<{ - Reply: FromSchema<typeof WordSchema>; + Reply: WordType; }>({ method: "GET", url: "/word", @@ -52,7 +42,7 @@ const apiPlugin: FastifyPluginCallback = (fastify, options, next) => { }, handler: async (request, reply) => { // TODO: scope this to game to avoid collisions - const word = await connection<FromSchema<typeof WordSchema>>("words") + const word = await connection<WordType>("words") .whereBetween("ocr_confidence", [0.4, 0.8]) // i.e. needs improvement but it's not trash .whereRaw(`"ocr_transcript" ~ '^[[:alpha:]]+$'`) // i.e. no numbers nor symbols .orderByRaw("RANDOM()") @@ -71,8 +61,8 @@ const apiPlugin: FastifyPluginCallback = (fastify, options, next) => { }); fastify.route<{ - Params: FromSchema<typeof ParamsSchema>; - Reply: FromSchema<typeof GameSchema>; + Params: ParamsType; + Reply: GameType; }>({ method: "GET", url: "/games/:id", @@ -84,15 +74,64 @@ const apiPlugin: FastifyPluginCallback = (fastify, options, next) => { }, }, handler: async (request, reply) => { - const game = await connection<FromSchema<typeof GameSchema>>("games") + const game = await connection<GameType>("games") .where("id", request.params.id) .first(); if (game === undefined) { reply.code(404).send(); } else { - reply.code(200).send({ - id: game.id, - }); + reply.code(200).send(game); + } + }, + }); + + fastify.route<{ + Reply: GameType; + }>({ + method: "POST", + url: "/games", + schema: { + response: { + 200: GameSchema, + }, + }, + handler: async (request, reply) => { + const games = await connection + .table("games") + .insert({}) + .returning<GameType[]>("*"); + + reply.code(200).send(games[0]); + }, + }); + + const GamePatchSchema = Type.Omit(GameSchema, ["id"]); + + fastify.route<{ + Params: ParamsType; + Body: Static<typeof GamePatchSchema>; + Reply: GameType; + }>({ + method: "PATCH", + url: "/games/:id", + schema: { + params: ParamsSchema, + body: GamePatchSchema, + response: { + 200: GameSchema, + }, + }, + handler: async (request, reply) => { + const game = await connection<GameType>("games") + .where("id", request.params.id) + .first(); + if (game === undefined) { + reply.code(404).send(); + } else { + const games = await connection<GameType>("games") + .update(request.body) + .returning("*"); + reply.code(200).send(games[0]); } }, }); -- GitLab