diff --git a/README.md b/README.md index 14f8663..a7601bd 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,5 @@ Simple Node.js CRUD app that writes to `nubes_test_table` and renders a small HTML UI. If the input is empty, it inserts "Node did it". + +Примечание: таблица создается автоматически, а дубликаты подряд отсекаются по таймауту. diff --git a/server.js b/server.js index 680fdb4..d8c0fea 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,7 @@ const http = require("http"); const { URL } = require("url"); const { Pool } = require("pg"); +// Конфигурация Postgres, DATABASE_URL имеет приоритет. function buildPgConfig() { if (process.env.DATABASE_URL) { return { @@ -20,14 +21,17 @@ function buildPgConfig() { }; } +// Общий пул подключений. const pool = new Pool(buildPgConfig()); +// Таблица для демо создается автоматически. async function ensureTable() { await pool.query( "CREATE TABLE IF NOT EXISTS nubes_test_table (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT NOW())" ); } +// Простой парсер application/x-www-form-urlencoded. function parseForm(body) { return body .split("&") @@ -38,6 +42,7 @@ function parseForm(body) { }, {}); } +// Рендер HTML-страницы с CRUD-формами. function renderPage(rows, error) { const errorHtml = error ? `

${error}

` : ""; const rowsHtml = rows @@ -118,6 +123,7 @@ function renderPage(rows, error) { `; } +// Маршруты: /, /add, /update, /delete, /healthz. async function handleRequest(req, res) { const url = new URL(req.url, `http://${req.headers.host}`); @@ -153,6 +159,7 @@ async function handleRequest(req, res) { await ensureTable(); if (url.pathname === "/add") { const content = form.txt_content || "Node did it"; + // Защита от быстрых дублей подряд. const { rows: lastRows } = await pool.query( "SELECT test_data, created_at FROM nubes_test_table ORDER BY id DESC LIMIT 1" ); diff --git a/src/application.ts b/src/application.ts index 9223e6c..d803e96 100644 --- a/src/application.ts +++ b/src/application.ts @@ -18,23 +18,23 @@ export class Loopback4ExampleGithubApplication extends BootMixin( constructor(options: ApplicationConfig = {}) { super(options); - // Set up the custom sequence + // Подключаем кастомную sequence для обработки запросов. this.sequence(MySequence); - // Set up default home page + // Главная страница из папки public. this.static('/', path.join(__dirname, '../public')); - // Customize @loopback/rest-explorer configuration here + // Включаем REST Explorer. this.configure(RestExplorerBindings.COMPONENT).to({ path: '/explorer', }); this.component(RestExplorerComponent); this.projectRoot = __dirname; - // Customize @loopback/boot Booter Conventions here + // Настройки автозагрузки контроллеров. this.bootOptions = { controllers: { - // Customize ControllerBooter Conventions here + // Ищем контроллеры в папке controllers. dirs: ['controllers'], extensions: ['.controller.js'], nested: true, diff --git a/src/index.ts b/src/index.ts index a295bdc..f7aa6bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import {ApplicationConfig, Loopback4ExampleGithubApplication} from './applicatio export * from './application'; export async function main(options: ApplicationConfig = {}) { + // Создаем и запускаем LoopBack приложение. const app = new Loopback4ExampleGithubApplication(options); await app.boot(); await app.start(); @@ -15,7 +16,7 @@ export async function main(options: ApplicationConfig = {}) { } if (require.main === module) { - // Run the application + // Локальный запуск с базовой конфигурацией REST. const config = { rest: { port: +(process.env.PORT ?? 3000), diff --git a/src/sequence.ts b/src/sequence.ts index 2fe7751..30b28d6 100644 --- a/src/sequence.ts +++ b/src/sequence.ts @@ -1,3 +1,4 @@ import {MiddlewareSequence} from '@loopback/rest'; +// Стандартная sequence без кастомной логики. export class MySequence extends MiddlewareSequence {}