From 4841e0520880cccc237a7875eac6c21d98f27963 Mon Sep 17 00:00:00 2001
From: Paolo Brasolin <paolo.brasolin@eurac.edu>
Date: Wed, 16 Mar 2022 14:15:10 +0100
Subject: [PATCH] feat: #be sketch cleanly typed API

---
 backend/src/api.ts   | 103 +++++++++++++++++++++++++++++++++++++++++++
 backend/src/index.ts |   3 ++
 2 files changed, 106 insertions(+)
 create mode 100644 backend/src/api.ts

diff --git a/backend/src/api.ts b/backend/src/api.ts
new file mode 100644
index 0000000..c524fad
--- /dev/null
+++ b/backend/src/api.ts
@@ -0,0 +1,103 @@
+import { FastifyPluginCallback } from "fastify";
+import { FromSchema } from "json-schema-to-ts";
+
+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 apiPlugin: FastifyPluginCallback = (fastify, options, next) => {
+  fastify.route<{
+    Reply: FromSchema<typeof WordSchema>;
+  }>({
+    method: "GET",
+    url: "/word",
+    schema: {
+      response: {
+        200: WordSchema,
+        404: {}, // TODO: JSend error
+      },
+    },
+    handler: async (request, reply) => {
+      // TODO: scope this to game to avoid collisions
+      const word = await connection<FromSchema<typeof WordSchema>>("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()")
+        .first();
+      if (word === undefined) {
+        reply.code(404);
+      } else {
+        reply.send({
+          id: word.id,
+          image: word.image,
+          ocr_confidence: word.ocr_confidence,
+          ocr_transcript: word.ocr_transcript,
+        });
+      }
+    },
+  });
+
+  fastify.route<{
+    Params: FromSchema<typeof ParamsSchema>;
+    Reply: FromSchema<typeof GameSchema>;
+  }>({
+    method: "GET",
+    url: "/games/:id",
+    schema: {
+      params: ParamsSchema,
+      response: {
+        200: GameSchema,
+        404: {}, // TODO: JSend error
+      },
+    },
+    handler: async (request, reply) => {
+      const game = await connection<FromSchema<typeof GameSchema>>("games")
+        .where("id", request.params.id)
+        .first();
+      if (game === undefined) {
+        reply.code(404);
+      } else {
+        reply.send({
+          id: game.id,
+        });
+      }
+    },
+  });
+
+  next();
+};
+
+export default apiPlugin;
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 4a91473..e0134ae 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -48,6 +48,9 @@ server.get("/", function (request, reply) {
   reply.code(200).send("Hello, World!");
 });
 
+import apiRoutes from "./api";
+server.register(apiRoutes, { prefix: "api" });
+
 server.route({
   method: "POST",
   url: "/GetImage",
-- 
GitLab