diff --git a/public/swagger.json b/public/swagger.json index fed8984..3d6a63f 100644 --- a/public/swagger.json +++ b/public/swagger.json @@ -252,6 +252,39 @@ } ] } + }, + "/api/v3/apps/{slug}/version": { + "post": { + "operationId": "CreateVersion", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Device" + }, + "type": "array" + } + } + } + } + }, + "description": "Get list of devices (badges)", + "tags": ["public"], + "security": [], + "parameters": [ + { + "in": "path", + "name": "slug", + "required": true, + "schema": { + "type": "string" + } + } + ] + } } }, "servers": [ diff --git a/src/app.ts b/src/app.ts index 07967a5..16df5ae 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,6 +1,6 @@ import express from "express"; -import { RegisterRoutes } from "./generated/routes.js"; -import openapi from "./openapi.js"; +import { RegisterRoutes } from "./generated/routes"; +import openapi from "./openapi"; import { pinoHttp } from "pino-http"; const app = express(); diff --git a/src/controllers/private-rest.ts b/src/controllers/private-rest.ts new file mode 100644 index 0000000..774781e --- /dev/null +++ b/src/controllers/private-rest.ts @@ -0,0 +1,14 @@ +import { Path, Post, Route, Tags } from "tsoa"; +import { Device } from "../db/models"; + +@Route("/api/v3") +@Tags("public") +export class PrivateRestController { + /** + * Get list of devices (badges) + */ + @Post("/apps/{slug}/version") + public async createVersion(@Path() slug: string): Promise { + throw new Error("Not implemented"); + } +} diff --git a/src/public-rest.test.ts b/src/controllers/public-rest.test.ts similarity index 93% rename from src/public-rest.test.ts rename to src/controllers/public-rest.test.ts index 7c96f36..d38b740 100644 --- a/src/public-rest.test.ts +++ b/src/controllers/public-rest.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from "vitest"; import request from "supertest"; -import app from "./app"; +import app from "../app"; describe("API Routes", () => { test("GET /vitest", async () => { diff --git a/src/public-rest.ts b/src/controllers/public-rest.ts similarity index 75% rename from src/public-rest.ts rename to src/controllers/public-rest.ts index 7aa011b..5cfd218 100644 --- a/src/public-rest.ts +++ b/src/controllers/public-rest.ts @@ -1,6 +1,8 @@ -import pg from "pg"; +import pg, { Pool } from "pg"; import { Get, Path, Query, Res, Route, Tags } from "tsoa"; import type { TsoaResponse } from "tsoa"; +import { getPool } from "../db/connectionPool"; +import { App, AppDetails, Category, Device } from "../db/models"; /** * The code is annotated so that OpenAPI documentation can be generated with tsoa @@ -14,49 +16,22 @@ import type { TsoaResponse } from "tsoa"; * npm run swagger */ -const pool = new pg.Pool({ - host: process.env.POSTGRES_HOST, - database: process.env.POSTGRES_DB, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, - port: 5432, -}); - -interface Device { - name: string; - slug: string; -} - -interface Category { - name: string; - slug: string; -} - -interface App { - name: string; - slug: string; - category_slug: string; - user_name: string; -} - -interface AppDetails { - name: string; - slug: string; - description: string; - category_slug: string; - user_name: string; - devices: string[]; -} - @Route("/api/v3") @Tags("public") -export class RestController { +export class PublicRestController { + private pool: Pool; + public constructor() { + this.pool = getPool(); + } + /** * Get list of devices (badges) */ @Get("/devices") public async getDevices(): Promise { - const result = await pool.query(`select name, slug from badges`); + const result = await this.pool.query( + `select name, slug from badges` + ); return result.rows; } @@ -65,7 +40,7 @@ export class RestController { */ @Get("/categories") public async getCategories(): Promise { - const result = await pool.query( + const result = await this.pool.query( `select name, slug from categories` ); return result.rows; @@ -93,7 +68,7 @@ export class RestController { let result: pg.QueryResult; if (category && !device) { - result = await pool.query( + result = await this.pool.query( `${mainQuery} where c.slug = $3 limit $1 offset $2 @@ -101,7 +76,7 @@ export class RestController { [pageLength ?? null, pageStart ?? 0, category] ); } else if (!category && device) { - result = await pool.query( + result = await this.pool.query( `${mainQuery} ${badgeQuery} where b.slug=$3 limit $1 offset $2 @@ -109,7 +84,7 @@ export class RestController { [pageLength ?? null, pageStart ?? 0, device] ); } else if (category && device) { - result = await pool.query( + result = await this.pool.query( `${mainQuery} ${badgeQuery} where c.slug = $3 and b.slug=$4 limit $1 offset $2 @@ -117,7 +92,7 @@ export class RestController { [pageLength ?? null, pageStart ?? 0, category, device] ); } else { - result = await pool.query( + result = await this.pool.query( `${mainQuery} limit $1 offset $2 `, @@ -135,7 +110,7 @@ export class RestController { @Path() slug: string, @Res() notFoundResponse: TsoaResponse<404, { reason: string }> ): Promise { - const result = await pool.query( + const result = await this.pool.query( `select p.id, p.name, p.slug, p.description, c.slug as category_slug, u.name as user_name from projects p inner join categories c on p.category_id = c.id @@ -145,11 +120,11 @@ export class RestController { ); if (result.rows[0]) { const projectId = result.rows[0].id; - const badgeResult = await pool.query( + const badgeResult = await this.pool.query( `select b.slug from badge_project bp inner join badges b on bp.badge_id=b.id where project_id=$1`, [projectId] ); - const devices = badgeResult.rows.map((badge) => badge.slug); + const devices = badgeResult.rows.map((badge: Device) => badge.slug); const { id, ...resultWithoutId } = result.rows[0]; return { ...resultWithoutId, devices }; } else { diff --git a/src/db/connectionPool.ts b/src/db/connectionPool.ts new file mode 100644 index 0000000..27adbc4 --- /dev/null +++ b/src/db/connectionPool.ts @@ -0,0 +1,14 @@ +import pg from "pg"; +let pool: pg.Pool; +export const getPool = () => { + if (!pool) { + pool = new pg.Pool({ + host: process.env.POSTGRES_HOST, + database: process.env.POSTGRES_DB, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + port: 5432, + }); + } + return pool; +}; diff --git a/src/db/models/index.ts b/src/db/models/index.ts new file mode 100644 index 0000000..0b0f6a5 --- /dev/null +++ b/src/db/models/index.ts @@ -0,0 +1,25 @@ +export interface Device { + name: string; + slug: string; +} + +export interface Category { + name: string; + slug: string; +} + +export interface App { + name: string; + slug: string; + category_slug: string; + user_name: string; +} + +export interface AppDetails { + name: string; + slug: string; + description: string; + category_slug: string; + user_name: string; + devices: string[]; +} diff --git a/src/generated/routes.ts b/src/generated/routes.ts index 9660a2f..643c57b 100644 --- a/src/generated/routes.ts +++ b/src/generated/routes.ts @@ -7,7 +7,9 @@ import { ExpressTemplateService, } from "@tsoa/runtime"; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa -import { RestController } from "./../public-rest.js"; +import { PublicRestController } from "./../controllers/public-rest.js"; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { PrivateRestController } from "./../controllers/private-rest.js"; import type { Request as ExRequest, Response as ExResponse, @@ -79,10 +81,12 @@ export function RegisterRoutes(app: Router) { // ########################################################################################################### app.get( "/api/v3/devices", - ...fetchMiddlewares(RestController), - ...fetchMiddlewares(RestController.prototype.getDevices), + ...fetchMiddlewares(PublicRestController), + ...fetchMiddlewares( + PublicRestController.prototype.getDevices + ), - async function RestController_getDevices( + async function PublicRestController_getDevices( request: ExRequest, response: ExResponse, next: any @@ -99,7 +103,7 @@ export function RegisterRoutes(app: Router) { response, }); - const controller = new RestController(); + const controller = new PublicRestController(); await templateService.apiHandler({ methodName: "getDevices", @@ -117,10 +121,12 @@ export function RegisterRoutes(app: Router) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.get( "/api/v3/categories", - ...fetchMiddlewares(RestController), - ...fetchMiddlewares(RestController.prototype.getCategories), + ...fetchMiddlewares(PublicRestController), + ...fetchMiddlewares( + PublicRestController.prototype.getCategories + ), - async function RestController_getCategories( + async function PublicRestController_getCategories( request: ExRequest, response: ExResponse, next: any @@ -137,7 +143,7 @@ export function RegisterRoutes(app: Router) { response, }); - const controller = new RestController(); + const controller = new PublicRestController(); await templateService.apiHandler({ methodName: "getCategories", @@ -155,10 +161,10 @@ export function RegisterRoutes(app: Router) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.get( "/api/v3/apps", - ...fetchMiddlewares(RestController), - ...fetchMiddlewares(RestController.prototype.getApps), + ...fetchMiddlewares(PublicRestController), + ...fetchMiddlewares(PublicRestController.prototype.getApps), - async function RestController_getApps( + async function PublicRestController_getApps( request: ExRequest, response: ExResponse, next: any @@ -180,7 +186,7 @@ export function RegisterRoutes(app: Router) { response, }); - const controller = new RestController(); + const controller = new PublicRestController(); await templateService.apiHandler({ methodName: "getApps", @@ -198,10 +204,12 @@ export function RegisterRoutes(app: Router) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.get( "/api/v3/apps/:slug", - ...fetchMiddlewares(RestController), - ...fetchMiddlewares(RestController.prototype.getAppDetails), + ...fetchMiddlewares(PublicRestController), + ...fetchMiddlewares( + PublicRestController.prototype.getAppDetails + ), - async function RestController_getAppDetails( + async function PublicRestController_getAppDetails( request: ExRequest, response: ExResponse, next: any @@ -227,7 +235,7 @@ export function RegisterRoutes(app: Router) { response, }); - const controller = new RestController(); + const controller = new PublicRestController(); await templateService.apiHandler({ methodName: "getAppDetails", @@ -243,6 +251,48 @@ export function RegisterRoutes(app: Router) { } ); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post( + "/api/v3/apps/:slug/version", + ...fetchMiddlewares(PrivateRestController), + ...fetchMiddlewares( + PrivateRestController.prototype.createVersion + ), + + async function PrivateRestController_createVersion( + request: ExRequest, + response: ExResponse, + next: any + ) { + const args: Record = { + slug: { in: "path", name: "slug", required: true, dataType: "string" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = templateService.getValidatedArgs({ + args, + request, + response, + }); + + const controller = new PrivateRestController(); + + await templateService.apiHandler({ + methodName: "createVersion", + controller, + response, + next, + validatedArgs, + successStatus: undefined, + }); + } catch (err) { + return next(err); + } + } + ); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/index.ts b/src/index.ts index 253c4c3..72c9df4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ -import app from "./app.js"; +import { config } from "dotenv"; +import app from "./app"; + +config(); const port = 8081; diff --git a/tsconfig.json b/tsconfig.json index 5c6a4fa..b2cab52 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,19 +13,12 @@ "noImplicitOverride": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - // Transpile our TypeScript code to JavaScript - "module": "NodeNext", + "module": "esnext", + "moduleResolution": "node", "sourceMap": true, "outDir": "dist", "lib": ["es2022"] }, - // Include the necessary files for your project - "files": [ - "src/index.ts", - "src/openapi.ts", - "src/public-rest.ts", - "src/generated/routes.ts" - ], - "include": ["src/index.ts"], + "include": ["src/**/*.ts"], "exclude": ["node_modules"] } diff --git a/tsoa.json b/tsoa.json index d555b38..9a9076e 100644 --- a/tsoa.json +++ b/tsoa.json @@ -1,5 +1,6 @@ { - "entryFile": "src/public-rest.ts", + "entryFile": "src/index.ts", + "controllerPathGlobs": ["src/controllers/**/*-rest.ts"], "noImplicitAdditionalProperties": "throw-on-extras", "spec": { "outputDirectory": "public", @@ -10,4 +11,4 @@ "routesDir": "src/generated", "esm": true } -} \ No newline at end of file +}