From 2a6bee9cffe624a763fc2d25756432c262d6ba56 Mon Sep 17 00:00:00 2001 From: Demian Parkhomenko <95881717+DemianParkhomenko@users.noreply.github.com> Date: Sat, 23 Sep 2023 20:37:44 +0300 Subject: [PATCH] Add Prisma and new folder structure --- .prettierrc | 2 +- api/.eslintrc.json | 16 +- api/city.js | 1 - api/country.js | 14 - api/user.js | 35 +- config.js | 20 -- config/index.js | 22 ++ db.js | 53 --- db/data.sql | 25 -- db/install.sql | 5 - db/setup.sh | 3 - db/structure.sql | 41 --- hash.js | 14 - lib/db/crud.js | 35 ++ lib/db/start.js | 14 + {logger => lib/logger}/pino.js | 24 +- {logger => lib/logger}/winston.js | 22 +- lib/main.js | 39 +++ lib/static/server.js | 21 ++ lib/transport/fastify.js | 39 +++ .../const.js => lib/transport/headers.js | 2 +- lib/transport/http.js | 30 ++ lib/transport/ws.js | 35 ++ lib/utils/freeze.js | 12 + lib/utils/load.js | 13 + main.js | 31 -- package-lock.json | 303 +++++++++++++++++- package.json | 10 +- .../20230923140354_init/migration.sql | 11 + prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 14 + static.js | 23 -- static/client.js | 76 ++--- transport/fastify.js | 40 --- transport/http.js | 32 -- transport/ws.js | 37 --- tsconfig.json | 13 + 37 files changed, 690 insertions(+), 440 deletions(-) delete mode 100644 api/city.js delete mode 100644 api/country.js delete mode 100644 config.js create mode 100644 config/index.js delete mode 100644 db.js delete mode 100644 db/data.sql delete mode 100644 db/install.sql delete mode 100755 db/setup.sh delete mode 100644 db/structure.sql delete mode 100644 hash.js create mode 100644 lib/db/crud.js create mode 100644 lib/db/start.js rename {logger => lib/logger}/pino.js (70%) rename {logger => lib/logger}/winston.js (75%) create mode 100644 lib/main.js create mode 100644 lib/static/server.js create mode 100644 lib/transport/fastify.js rename transport/const.js => lib/transport/headers.js (98%) create mode 100644 lib/transport/http.js create mode 100644 lib/transport/ws.js create mode 100644 lib/utils/freeze.js create mode 100644 lib/utils/load.js delete mode 100644 main.js create mode 100644 prisma/migrations/20230923140354_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma delete mode 100644 static.js delete mode 100644 transport/fastify.js delete mode 100644 transport/http.js delete mode 100644 transport/ws.js create mode 100644 tsconfig.json diff --git a/.prettierrc b/.prettierrc index 9d313aa..eb0e50a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "semi": false, + "semi": true, "trailingComma": "es5", "singleQuote": true } diff --git a/api/.eslintrc.json b/api/.eslintrc.json index 99dc244..eea00ed 100644 --- a/api/.eslintrc.json +++ b/api/.eslintrc.json @@ -2,11 +2,19 @@ "parserOptions": { "sourceType": "module" }, - "rules": { - "strict": ["error", "never"] - }, "globals": { + "api": "readonly", "db": "readonly", - "common": "readonly" + "crud": "readonly", + "common": "readonly", + "domain": "readonly", + "lib": "readonly", + "context": "readonly", + "config": "readonly", + "metarhia": "readonly" + }, + "rules": { + "strict": ["error", "never"], + "id-denylist": [2, "api", "db", "common", "console"] } } diff --git a/api/city.js b/api/city.js deleted file mode 100644 index ac34953..0000000 --- a/api/city.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ({ db }) => db('city') diff --git a/api/country.js b/api/country.js deleted file mode 100644 index eab9709..0000000 --- a/api/country.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = ({ db }) => { - const country = db('country') - - return { - async read(id) { - return await country.read(id) - }, - - async find(mask) { - const sql = 'SELECT * from country where name like $1' - return await country.query(sql, [mask]) - }, - } -} diff --git a/api/user.js b/api/user.js index 953b044..22afba7 100644 --- a/api/user.js +++ b/api/user.js @@ -1,28 +1,7 @@ -module.exports = ({ db, common }) => { - const user = db('users') - - return { - async read(id) { - return await user.read(id, ['id', 'login']) - }, - - async create({ login, password }) { - const passwordHash = await common.hash(password) - return await user.create({ login, password: passwordHash }) - }, - - async update(id, { login, password }) { - const passwordHash = await common.hash(password) - return await user.update(id, { login, password: passwordHash }) - }, - - async delete(id) { - return await user.delete(id) - }, - - async find(mask) { - const sql = 'SELECT login from users where login like $1' - return await user.query(sql, [mask]) - }, - } -} +({ + ...crud('user'), + read: async () => { + console.warn('🚨 This is a warning'); + return await await db.user.findMany(); + }, +}); diff --git a/config.js b/config.js deleted file mode 100644 index 6c04130..0000000 --- a/config.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - db: { - port: 5432, - database: 'example', - user: 'marcus', - password: 'marcus', - }, - api: { - port: 8001, - transport: 'ws', - }, - static: { - port: 8000, - }, - load: { - timeout: 5000, - displayErrors: false, - }, - logger: { name: 'pino', fsPath: './log', console: true }, -} diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..654cebc --- /dev/null +++ b/config/index.js @@ -0,0 +1,22 @@ +module.exports = { + db: { + url: 'postgresql://marcus:marcus@localhost:5432/example', + }, + api: { + path: './api', + port: 8001, + transport: 'ws', + }, + static: { + port: 8000, + }, + load: { + timeout: 5000, + displayErrors: false, + }, + logger: { name: 'winston', fsPath: './logs', console: true }, + sandbox: { + timeout: 5000, + displayErrors: false, + }, +}; diff --git a/db.js b/db.js deleted file mode 100644 index c618d53..0000000 --- a/db.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict' - -const pg = require('pg') - -const crud = (pool) => (table) => ({ - async query(sql, args) { - return await pool.query(sql, args) - }, - - async read(id, fields = ['*']) { - const names = fields.join(', ') - const sql = `SELECT ${names} FROM ${table}` - if (!id) return pool.query(sql) - return await pool.query(`${sql} WHERE id = $1`, [id]) - }, - - async create({ ...record }) { - const keys = Object.keys(record) - const nums = new Array(keys.length) - const data = new Array(keys.length) - let i = 0 - for (const key of keys) { - data[i] = record[key] - nums[i] = `$${++i}` - } - const fields = '"' + keys.join('", "') + '"' - const params = nums.join(', ') - const sql = `INSERT INTO "${table}" (${fields}) VALUES (${params})` - return await pool.query(sql, data) - }, - - async update(id, { ...record }) { - const keys = Object.keys(record) - const updates = new Array(keys.length) - const data = new Array(keys.length) - let i = 0 - for (const key of keys) { - data[i] = record[key] - updates[i] = `${key} = $${++i}` - } - const delta = updates.join(', ') - const sql = `UPDATE ${table} SET ${delta} WHERE id = $${++i}` - data.push(id) - return await pool.query(sql, data) - }, - - async delete(id) { - const sql = `DELETE FROM ${table} WHERE id = $1` - return await pool.query(sql, [id]) - }, -}) - -module.exports = (config) => crud(new pg.Pool(config)) diff --git a/db/data.sql b/db/data.sql deleted file mode 100644 index ae70ae9..0000000 --- a/db/data.sql +++ /dev/null @@ -1,25 +0,0 @@ -INSERT INTO "users" ("login", "password") VALUES - ('admin', 'ypMEd9FwvtlOjcvH94iICQ==:V6LnSOVwXzENxeLCJk59Toadea7oaA1IxYulAOtKkL9tBxjEPOw085vYalEdLDoe8xbrXQlhh7QRGzrSe8Bthw=='), - ('marcus', 'dpHw0OUNBz76nuqrXZbeYQ==:wpvUVgi8Yp9rJ0yZyBWecaWP2EL/ahpxZY74KOVfhAYbAZSq6mWqjsQwtCvIPcSKZqUVpVb13JcSciB2fA+6Tg=='), - ('user', 'r8zb8AdrlPSh5wNy6hqOxg==:HyO5rvOFLtwzU+OZ9qFi3ADXlVccDJWGSfUS8mVq43spJ6sxyliUdW3i53hOPdkFAtDn3EAQMttOlIoJap1lTQ=='), - ('iskandar', 'aqX1O4bKXiwC/Jh2EKNIYw==:bpE4TARNg09vb2Libn1c00YRxcvoklB9zVSbD733LwQQFUuAm7WHP85PbZXwEbbeOVPIFHgflR4cvEmvYkr76g=='); - --- Examples login/password --- admin/123456 --- marcus/marcus --- user/nopassword --- iskandar/zulqarnayn - -INSERT INTO "country" ("name") VALUES - ('Soviet Union'), - ('People''s Republic of China'), - ('Vietnam'), - ('Cuba'); - -INSERT INTO "city" ("name", "country") VALUES - ('Beijing', 2), - ('Wuhan', 2), - ('Kiev', 1), - ('Havana', 4), - ('Hanoi', 3), - ('Kaliningrad', 1); diff --git a/db/install.sql b/db/install.sql deleted file mode 100644 index 29687ca..0000000 --- a/db/install.sql +++ /dev/null @@ -1,5 +0,0 @@ -DROP DATABASE IF EXISTS example; -DROP USER IF EXISTS marcus; -CREATE USER marcus WITH PASSWORD 'marcus'; -CREATE DATABASE example OWNER marcus; - diff --git a/db/setup.sh b/db/setup.sh deleted file mode 100755 index e23b5fa..0000000 --- a/db/setup.sh +++ /dev/null @@ -1,3 +0,0 @@ -psql -f install.sql -U postgres -PGPASSWORD=marcus psql -d example -f structure.sql -U marcus -PGPASSWORD=marcus psql -d example -f data.sql -U marcus diff --git a/db/structure.sql b/db/structure.sql deleted file mode 100644 index bb9c8da..0000000 --- a/db/structure.sql +++ /dev/null @@ -1,41 +0,0 @@ -CREATE TABLE "users" ( - "id" bigint generated always as identity, - "login" varchar NOT NULL, - "password" varchar NOT NULL -); - -ALTER TABLE "users" ADD CONSTRAINT pkUsers PRIMARY KEY (id); -CREATE UNIQUE INDEX akUsersLogin ON "users" (login); - -CREATE TABLE "session" ( - "id" bigint generated always as identity, - "user" integer NOT NULL, - "token" varchar(64) NOT NULL, - "ip" varchar(45) NOT NULL, - "data" text -); - -ALTER TABLE "session" ADD CONSTRAINT pkSession PRIMARY KEY (id); -CREATE UNIQUE INDEX akSession ON "session" (token); -ALTER TABLE "session" ADD CONSTRAINT fkSessionUserId FOREIGN KEY ("user") REFERENCES "users" (id) ON DELETE CASCADE; - -CREATE TABLE "country" ( - "id" bigint generated always as identity, - "name" varchar NOT NULL -); - -ALTER TABLE "country" ADD CONSTRAINT "pkCountry" PRIMARY KEY ("id"); - -CREATE UNIQUE INDEX "akCountry" ON "country" ("name"); - -CREATE TABLE "city" ( - "id" bigint generated always as identity, - "name" varchar NOT NULL, - "country" bigint NOT NULL -); - -ALTER TABLE "city" ADD CONSTRAINT "pkCity" PRIMARY KEY ("id"); - -CREATE UNIQUE INDEX "akCity" ON "city" ("name"); - -ALTER TABLE "city" ADD CONSTRAINT "fkCityCountry" FOREIGN KEY ("country") REFERENCES "country" ("id") ON DELETE CASCADE; diff --git a/hash.js b/hash.js deleted file mode 100644 index 9eab216..0000000 --- a/hash.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict' - -const crypto = require('node:crypto') - -const hash = (password) => - new Promise((resolve, reject) => { - const salt = crypto.randomBytes(16).toString('base64') - crypto.scrypt(password, salt, 64, (err, result) => { - if (err) reject(err) - resolve(salt + ':' + result.toString('base64')) - }) - }) - -module.exports = hash diff --git a/lib/db/crud.js b/lib/db/crud.js new file mode 100644 index 0000000..5c14c81 --- /dev/null +++ b/lib/db/crud.js @@ -0,0 +1,35 @@ +const { PrismaClient } = require('@prisma/client'); + +const prisma = new PrismaClient(); + +const crud = (model) => ({ + async read(id) { + if (!id) { + return await prisma[model].findMany(); + } + return prisma[model].findUnique({ + where: { id }, + }); + }, + + async create(record) { + return prisma[model].create({ + data: record, + }); + }, + + async update(id, record) { + return prisma[model].update({ + where: { id }, + data: record, + }); + }, + + async delete(id) { + return prisma[model].delete({ + where: { id }, + }); + }, +}); + +module.exports = crud; diff --git a/lib/db/start.js b/lib/db/start.js new file mode 100644 index 0000000..2ec0547 --- /dev/null +++ b/lib/db/start.js @@ -0,0 +1,14 @@ +'use strict'; + +/** + * @typedef { import("@prisma/client").User} User + */ +const { PrismaClient } = require('@prisma/client'); + +const start = async (config) => { + const prisma = new PrismaClient({ datasourceUrl: config.url }); + await prisma.$connect(); + return prisma; +}; + +module.exports = start; diff --git a/logger/pino.js b/lib/logger/pino.js similarity index 70% rename from logger/pino.js rename to lib/logger/pino.js index 4aff66c..54e25d6 100644 --- a/logger/pino.js +++ b/lib/logger/pino.js @@ -1,12 +1,12 @@ -const pino = require('pino') +const pino = require('pino'); module.exports = (config) => { - const targets = [] + const targets = []; if (config.console) { targets.push({ level: 'trace', target: 'pino-pretty', - }) + }); } if (config.fsPath) { targets.push({ @@ -15,26 +15,26 @@ module.exports = (config) => { options: { destination: `${config.fsPath}/${config.name}/combined.log`, }, - }) + }); } const transport = pino.transport({ targets, - }) - const logger = pino(transport) + }); + const logger = pino(transport); return { log(...args) { // TODO: issue https://github.com/pinojs/pino-pretty/issues/455 - logger.info(...args) + logger.info(...args); }, info(...args) { - logger.info(...args) + logger.info(...args); }, warn(...args) { - logger.warn(...args) + logger.warn(...args); }, error(...args) { - logger.error(...args) + logger.error(...args); }, - } -} + }; +}; diff --git a/logger/winston.js b/lib/logger/winston.js similarity index 75% rename from logger/winston.js rename to lib/logger/winston.js index fbe17ca..4e65289 100644 --- a/logger/winston.js +++ b/lib/logger/winston.js @@ -1,7 +1,7 @@ -const winston = require('winston') +const winston = require('winston'); module.exports = (config) => { - const transports = [] + const transports = []; if (config.console) { transports.push( new winston.transports.Console({ @@ -10,33 +10,33 @@ module.exports = (config) => { winston.format.simple() ), }) - ) + ); } if (config.fsPath) { transports.push( new winston.transports.File({ filename: `${config.fsPath}/${config.name}/combined.log`, }) - ) + ); } const logger = winston.createLogger({ format: winston.format.json(), level: 'verbose', transports, - }) + }); return { log(...args) { - logger.verbose(...args) + logger.verbose(...args); }, info(...args) { - logger.info(...args) + logger.info(...args); }, warn(...args) { - logger.warn(...args) + logger.warn(...args); }, error(...args) { - logger.error(...args) + logger.error(...args); }, - } -} + }; +}; diff --git a/lib/main.js b/lib/main.js new file mode 100644 index 0000000..ee0df50 --- /dev/null +++ b/lib/main.js @@ -0,0 +1,39 @@ +'use strict'; + +const fsp = require('node:fs/promises'); +const path = require('node:path'); +const config = require('../config/index.js'); +const crud = require('./db/crud.js'); +const load = require('./utils/load.js')(config.sandbox); +const deepFreeze = require('./utils/freeze.js'); +const staticServer = require('./static/server.js'); +const server = require(`./transport/${config.api.transport}.js`); +const console = require(`./logger/${config.logger.name}.js`)(config.logger); +console.info(`📝 Logger ${config.logger.name} started`); +const startDb = require('./db/start.js'); + +const start = async () => { + const db = await startDb(config.db); + console.info('✅ Connected to database'); + const sandbox = { + console: deepFreeze(console), + //? Can not be frozen because of prisma + db, + crud: deepFreeze(crud), + }; + const apiPath = path.join(process.cwd(), config.api.path); + const routing = {}; + const files = await fsp.readdir(apiPath); + for (const fileName of files) { + if (!fileName.endsWith('.js')) continue; + const filePath = path.join(apiPath, fileName); + const serviceName = path.basename(fileName, '.js'); + routing[serviceName] = await load(filePath, sandbox); + } + staticServer('./static', config.static.port, console); + console.info(`📦 Static server on ${config.static.port}`); + server(routing, config.api.port, console); + console.info(`🚀 API server no ${config.api.port}`); +}; + +start(); diff --git a/lib/static/server.js b/lib/static/server.js new file mode 100644 index 0000000..5adf9ec --- /dev/null +++ b/lib/static/server.js @@ -0,0 +1,21 @@ +'use strict'; + +const http = require('node:http'); +const path = require('node:path'); +const fs = require('node:fs'); + +module.exports = (root, port) => { + http + .createServer(async (req, res) => { + const url = req.url === '/' ? '/index.html' : req.url; + const filePath = path.join(root, url); + try { + const data = await fs.promises.readFile(filePath); + res.end(data); + } catch (err) { + res.statusCode = 404; + res.end('"File is not found"'); + } + }) + .listen(port); +}; diff --git a/lib/transport/fastify.js b/lib/transport/fastify.js new file mode 100644 index 0000000..bd9cba7 --- /dev/null +++ b/lib/transport/fastify.js @@ -0,0 +1,39 @@ +const fastify = require('fastify')({ + logger: false, +}); +const cors = require('@fastify/cors'); +const { HEADERS } = require('./headers.js'); + +const setup = async (routing) => { + const services = Object.keys(routing); + for (const serviceName of services) { + const service = routing[serviceName]; + const methods = Object.keys(service); + for (const methodName of methods) { + const path = `/${serviceName}/${methodName}`; + fastify.post(path, async (request, reply) => { + const entity = routing[serviceName]; + if (!entity) return reply.code(404).send('Not found'); + const handler = entity[methodName]; + if (!handler) return reply.code(404).send('Not found'); + const result = await handler(...request.body); + reply.code(200).send(result); + }); + } + } +}; + +const start = async (port) => { + try { + await fastify.listen({ port }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +module.exports = async (routing, port) => { + await fastify.register(cors, HEADERS); + await setup(routing); + await start(port); +}; diff --git a/transport/const.js b/lib/transport/headers.js similarity index 98% rename from transport/const.js rename to lib/transport/headers.js index 2cecdcc..e507e69 100644 --- a/transport/const.js +++ b/lib/transport/headers.js @@ -5,4 +5,4 @@ module.exports = { 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Max-Age': 600, }, -} +}; diff --git a/lib/transport/http.js b/lib/transport/http.js new file mode 100644 index 0000000..16bbbc7 --- /dev/null +++ b/lib/transport/http.js @@ -0,0 +1,30 @@ +'use strict'; + +const http = require('node:http'); +const { HEADERS } = require('./headers.js'); + +const parseBody = async (req) => { + const buffer = []; + for await (const chunk of req) buffer.push(chunk); + const data = Buffer.concat(buffer).toString(); + return JSON.parse(data); +}; + +module.exports = (routing, port, console) => { + http + .createServer(async (req, res) => { + res.writeHead(200, HEADERS); + if (req.method !== 'POST') return void res.end('"Not found"'); + const { url, socket } = req; + const [name, method] = url.substring(1).split('/'); + const entity = routing[name]; + if (!entity) return void res.end('Not found'); + const handler = entity[method]; + if (!handler) return void res.end('Not found'); + const body = await parseBody(req); + console.log(`${socket.remoteAddress} ${method} ${url}`); + const result = await handler(...body); + res.end(JSON.stringify(result)); + }) + .listen(port); +}; diff --git a/lib/transport/ws.js b/lib/transport/ws.js new file mode 100644 index 0000000..5528bc0 --- /dev/null +++ b/lib/transport/ws.js @@ -0,0 +1,35 @@ +'use strict'; + +const { Server } = require('ws'); + +module.exports = (routing, port, console) => { + const ws = new Server({ port }); + + ws.on('connection', (connection, req) => { + const ip = req.socket.remoteAddress; + connection.on('message', async (message) => { + const obj = JSON.parse(message); + const { name, method, args = [] } = obj; + const entity = routing[name]; + if (!entity) { + connection.send('"Not found"', { binary: false }); + return; + } + const handler = entity[method]; + if (!handler) { + connection.send('"Not found"', { binary: false }); + return; + } + const json = JSON.stringify(args); + const parameters = json.substring(1, json.length - 1); + console.log(`${ip} ${name}.${method}(${parameters})`); + try { + const result = await handler(...args); + connection.send(JSON.stringify(result), { binary: false }); + } catch (err) { + console.error(err); + connection.send('"Server error"', { binary: false }); + } + }); + }); +}; diff --git a/lib/utils/freeze.js b/lib/utils/freeze.js new file mode 100644 index 0000000..9e15c75 --- /dev/null +++ b/lib/utils/freeze.js @@ -0,0 +1,12 @@ +const deepFreeze = (obj) => { + const propNames = Reflect.ownKeys(obj); + for (const name of propNames) { + const value = obj[name]; + const next = + (value && typeof value === 'object') || typeof value === 'function'; + if (next) deepFreeze(value); + } + return Object.freeze(obj); +}; + +module.exports = deepFreeze; diff --git a/lib/utils/load.js b/lib/utils/load.js new file mode 100644 index 0000000..1de6dde --- /dev/null +++ b/lib/utils/load.js @@ -0,0 +1,13 @@ +'use strict'; + +const fs = require('node:fs').promises; +const vm = require('node:vm'); + +module.exports = (options) => async (filePath, sandbox) => { + const src = await fs.readFile(filePath, 'utf8'); + const code = `'use strict';\n${src}`; + const script = new vm.Script(code); + const context = vm.createContext(Object.freeze({ ...sandbox })); + const exported = script.runInContext(context, options); + return exported; +}; diff --git a/main.js b/main.js deleted file mode 100644 index e2adb60..0000000 --- a/main.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const fsp = require('node:fs').promises -const path = require('node:path') -const config = require('./config.js') -const server = require(`./transport/${config.api.transport}.js`) -const staticServer = require('./static.js') -const db = require('./db.js')(config.db) -const hash = require('./hash.js') -const logger = require(`./logger/${config.logger.name}.js`) -const console = logger(config.logger) - -const injection = { - console: Object.freeze(console), - db: Object.freeze(db), - common: Object.freeze({ hash }), -} -const apiPath = path.join(process.cwd(), './api') -const routing = {} - -;(async () => { - const files = await fsp.readdir(apiPath) - for (const fileName of files) { - if (!fileName.endsWith('.js')) continue - const filePath = path.join(apiPath, fileName) - const serviceName = path.basename(fileName, '.js') - routing[serviceName] = require(filePath)(injection) - } - staticServer('./static', config.static.port, console) - server(routing, config.api.port, console) -})() diff --git a/package-lock.json b/package-lock.json index 23d53db..c09f3ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@fastify/cors": "^8.3.0", + "@prisma/client": "^5.3.1", "fastify": "^4.23.2", "pg": "^8.8.0", "pino": "^8.15.1", @@ -19,10 +20,14 @@ }, "devDependencies": { "@flydotio/dockerfile": "^0.4.10", + "@types/node": "^20.6.3", "eslint": "^8.49.0", "husky": "^8.0.3", "lint-staged": "^14.0.1", - "prettier": "3.0.3" + "nodemon": "^3.0.1", + "prettier": "3.0.3", + "prisma": "^5.3.1", + "typescript": "^5.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -264,11 +269,55 @@ "node": ">= 8" } }, + "node_modules/@prisma/client": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.3.1.tgz", + "integrity": "sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" + }, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.3.1.tgz", + "integrity": "sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz", + "integrity": "sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w==" + }, + "node_modules/@types/node": { + "version": "20.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.3.tgz", + "integrity": "sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==", + "dev": true + }, "node_modules/@types/triple-beam": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -409,6 +458,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -467,6 +529,15 @@ } ] }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -545,6 +616,45 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -1286,6 +1396,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1480,6 +1604,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -1532,6 +1662,18 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1928,6 +2070,88 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/nodemon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-run-path": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", @@ -2312,6 +2536,22 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prisma": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.3.1.tgz", + "integrity": "sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.3.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -2337,6 +2577,12 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2394,6 +2640,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -2631,6 +2889,18 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -2830,6 +3100,18 @@ "node": ">=12" } }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -2862,6 +3144,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index eec4a38..a17ba2b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "type": "commonjs", "scripts": { - "start": "node index.js", + "start": "node ./lib/main.js", + "dev": "nodemon ./lib/main.js", "test": "echo no test specified", "lint:check": "eslint .", "lint:write": "eslint --fix .", @@ -22,6 +23,7 @@ "license": "MIT", "dependencies": { "@fastify/cors": "^8.3.0", + "@prisma/client": "^5.3.1", "fastify": "^4.23.2", "pg": "^8.8.0", "pino": "^8.15.1", @@ -31,9 +33,13 @@ }, "devDependencies": { "@flydotio/dockerfile": "^0.4.10", + "@types/node": "^20.6.3", "eslint": "^8.49.0", "husky": "^8.0.3", "lint-staged": "^14.0.1", - "prettier": "3.0.3" + "nodemon": "^3.0.1", + "prettier": "3.0.3", + "prisma": "^5.3.1", + "typescript": "^5.2.2" } } diff --git a/prisma/migrations/20230923140354_init/migration.sql b/prisma/migrations/20230923140354_init/migration.sql new file mode 100644 index 0000000..b6a8924 --- /dev/null +++ b/prisma/migrations/20230923140354_init/migration.sql @@ -0,0 +1,11 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "name" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..7299f5b --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,14 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? +} diff --git a/static.js b/static.js deleted file mode 100644 index ca0b9d0..0000000 --- a/static.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict' - -const http = require('node:http') -const path = require('node:path') -const fs = require('node:fs') - -module.exports = (root, port, console) => { - http - .createServer(async (req, res) => { - const url = req.url === '/' ? '/index.html' : req.url - const filePath = path.join(root, url) - try { - const data = await fs.promises.readFile(filePath) - res.end(data) - } catch (err) { - res.statusCode = 404 - res.end('"File is not found"') - } - }) - .listen(port) - - console.log(`Static on http://localhost:${port}`) -} diff --git a/static/client.js b/static/client.js index 8dcaa11..654544c 100644 --- a/static/client.js +++ b/static/client.js @@ -1,63 +1,57 @@ -'use strict' +'use strict'; + +const url = new URL('ws://localhost:8001'); +const structure = { + user: { + create: ['record'], + read: ['id'], + update: ['id', 'record'], + delete: ['id'], + find: ['mask'], + }, +}; const createRequestWs = (socket) => async (serviceName, methodName, args) => new Promise((resolve) => { - const packet = { name: serviceName, method: methodName, args } - socket.send(JSON.stringify(packet)) + const packet = { name: serviceName, method: methodName, args }; + socket.send(JSON.stringify(packet)); socket.onmessage = (event) => { - const data = JSON.parse(event.data) - resolve(data) - } - }) + const data = JSON.parse(event.data); + resolve(data); + }; + }); const createRequestHttp = (url) => async (serviceName, methodName, args) => { - const path = `${serviceName}/${methodName}` + const path = `${serviceName}/${methodName}`; const response = await fetch(new URL(path, url), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(args), - }) - if (response.ok) return await response.json() - throw new Error(`${response.url} ${response.statusText}`) -} + }); + if (response.ok) return await response.json(); + throw new Error(`${response.url} ${response.statusText}`); +}; const requests = { 'ws:': (url) => createRequestWs(new WebSocket(url)), 'http:': (url) => createRequestHttp(url), -} +}; const scaffold = (url, structure) => { - const api = {} - const request = requests[url.protocol](url) - const services = Object.keys(structure) - if (!request) throw new Error(`Unknown protocol ${url.protocol}`) + const api = {}; + const request = requests[url.protocol](url); + const services = Object.keys(structure); + if (!request) throw new Error(`Unknown protocol ${url.protocol}`); for (const serviceName of services) { - api[serviceName] = {} - const service = structure[serviceName] - const methods = Object.keys(service) + api[serviceName] = {}; + const service = structure[serviceName]; + const methods = Object.keys(service); for (const methodName of methods) { api[serviceName][methodName] = (...args) => - request(serviceName, methodName, args) + request(serviceName, methodName, args); } } - return api -} + return api; +}; -//? FE usage, move to config -const url = new URL('ws://localhost:8001') -const structure = { - user: { - create: ['record'], - read: ['id'], - update: ['id', 'record'], - delete: ['id'], - find: ['mask'], - }, - country: { - read: ['id'], - delete: ['id'], - find: ['mask'], - }, -} -const api = scaffold(url, structure) -console.log(api) +globalThis.api = scaffold(url, structure); diff --git a/transport/fastify.js b/transport/fastify.js deleted file mode 100644 index cacfe42..0000000 --- a/transport/fastify.js +++ /dev/null @@ -1,40 +0,0 @@ -const fastify = require('fastify')({ - logger: false, -}) -const cors = require('@fastify/cors') -const { HEADERS } = require('./const.js') - -const setup = async (routing) => { - const services = Object.keys(routing) - for (const serviceName of services) { - const service = routing[serviceName] - const methods = Object.keys(service) - for (const methodName of methods) { - const path = `/${serviceName}/${methodName}` - fastify.post(path, async (request, reply) => { - const entity = routing[serviceName] - if (!entity) return reply.code(404).send('Not found') - const handler = entity[methodName] - if (!handler) return reply.code(404).send('Not found') - const result = await handler(...request.body) - reply.code(200).send(result.rows) - }) - } - } -} - -const start = async (port, console) => { - try { - await fastify.listen({ port }) - } catch (err) { - fastify.log.error(err) - process.exit(1) - } - console.log(`Fastify API on http://localhost:${port}`) -} - -module.exports = async (routing, port, console) => { - await fastify.register(cors, HEADERS) - await setup(routing) - await start(port, console) -} diff --git a/transport/http.js b/transport/http.js deleted file mode 100644 index ded27f8..0000000 --- a/transport/http.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict' - -const http = require('node:http') -const { HEADERS } = require('./const.js') - -const parseBody = async (req) => { - const buffer = [] - for await (const chunk of req) buffer.push(chunk) - const data = Buffer.concat(buffer).toString() - return JSON.parse(data) -} - -module.exports = (routing, port, console) => { - http - .createServer(async (req, res) => { - res.writeHead(200, HEADERS) - if (req.method !== 'POST') return void res.end('"Not found"') - const { url, socket } = req - const [name, method] = url.substring(1).split('/') - const entity = routing[name] - if (!entity) return void res.end('Not found') - const handler = entity[method] - if (!handler) return void res.end('Not found') - const body = await parseBody(req) - console.log(`${socket.remoteAddress} ${method} ${url}`) - const result = await handler(...body) - res.end(JSON.stringify(result.rows)) - }) - .listen(port) - - console.log(`API on http://localhost:${port}`) -} diff --git a/transport/ws.js b/transport/ws.js deleted file mode 100644 index abd4fce..0000000 --- a/transport/ws.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' - -const { Server } = require('ws') - -module.exports = (routing, port, console) => { - const ws = new Server({ port }) - - ws.on('connection', (connection, req) => { - const ip = req.socket.remoteAddress - connection.on('message', async (message) => { - const obj = JSON.parse(message) - const { name, method, args = [] } = obj - const entity = routing[name] - if (!entity) { - connection.send('"Not found"', { binary: false }) - return - } - const handler = entity[method] - if (!handler) { - connection.send('"Not found"', { binary: false }) - return - } - const json = JSON.stringify(args) - const parameters = json.substring(1, json.length - 1) - console.log(`${ip} ${name}.${method}(${parameters})`) - try { - const result = await handler(...args) - connection.send(JSON.stringify(result.rows), { binary: false }) - } catch (err) { - console.error(err) - connection.send('"Server error"', { binary: false }) - } - }) - }) - - console.log(`API on ws://localhost:${port}`) -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..726daab --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ESNext", + "moduleResolution": "node", + "strict": true, + "baseUrl": ".", + "preserveWatchOutput": true, + "allowJs": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["*", "**/*"] +}