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