From 46f2f0b07ef394922ff697dc9ac448d3835169f6 Mon Sep 17 00:00:00 2001 From: AppFactory Bot Date: Fri, 31 Jul 2026 14:42:26 +0000 Subject: [PATCH] Initial Node.js TypeScript service --- .dockerignore | 3 + AGENTS.md | 353 ++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 15 +++ README.md | 8 ++ package.json | 16 +++ src/index.ts | 35 +++++ tsconfig.json | 10 ++ 7 files changed, 440 insertions(+) create mode 100644 .dockerignore create mode 100644 AGENTS.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ac33e3c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.git/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ad89362 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,353 @@ +# AGENTS.md + +Tento repozitář obsahuje aplikaci běžící v AppFactory. + +Tento soubor je určený pro AI asistenty, vývojáře a automatizované nástroje, které budou aplikaci upravovat. + +## Kontext AppFactory + +Aplikace běží jako Docker container spravovaný AppFactory. + +AppFactory zajišťuje: + +- vytvoření Gitea repozitáře +- webhook z Gitea do AppFactory +- build Docker image +- deploy containeru +- reverse proxy přes Caddy +- runtime variables a secrets přes environment variables +- monitoring přes health endpoint + +Aplikační repozitář nemá měnit infrastrukturu AppFactory. + +## Veřejná URL a reverse proxy + +Aplikace neběží v rootu domény. + +Veřejná URL aplikace má tvar: + +```text +https://services.csbot.cz/apps/ +``` + +Příklady: + +```text +https://services.csbot.cz/apps/test-dotnet-api +https://services.csbot.cz/apps/microsoft-365-service +``` + +Aplikace musí počítat s tím, že běží za reverse proxy. + +Nikdy nehardcoduj veřejnou doménu. + +Nikdy nehardcoduj `/apps/` do business logiky, pokud framework nabízí lepší mechanismus, například: + +- `ROOT_PATH` +- `PathBase` +- `basePath` +- OpenAPI `servers` +- Swagger route prefix +- framework-specific proxy/base URL nastavení + +## Caddy routing + +AppFactory Caddy routuje aplikace přes: + +```text +/apps/ +``` + +Typicky platí: + +```text +veřejný request: +GET /apps//contacts + +aplikace uvnitř containeru často vidí: +GET /contacts +``` + +Důvodem je použití reverse proxy route typu `handle_path`, která prefix `/apps/` odstraní před předáním do containeru. + +Aplikace proto musí být napsaná tak, aby: + +- správně obsloužila interní routy +- správně generovala dokumentaci pro veřejnou proxy cestu +- Swagger UI testování používalo veřejnou cestu s `/apps/` + +## Povinné endpointy + +Každá služba musí poskytovat: + +```text +GET /health +GET /docs +``` + +Z veřejné URL musí být dostupné jako: + +```text +GET /apps//health +GET /apps//docs +``` + +`/health` musí vracet HTTP 200, pokud je aplikace schopná přijímat provoz. + +`/docs` musí poskytovat Swagger/OpenAPI dokumentaci nebo obdobnou interaktivní dokumentaci API. + +Pokud služba není HTTP API, musí i tak poskytovat minimální HTTP health endpoint. + +## Swagger / OpenAPI pravidla + +Swagger UI a OpenAPI definice musí respektovat AppFactory reverse proxy prefix. + +Pokud aplikace běží veřejně na: + +```text +https://services.csbot.cz/apps/ +``` + +pak Swagger UI musí při testování endpointů volat stejný prefix. + +Špatně: + +```text +GET https://services.csbot.cz/contacts +``` + +Správně: + +```text +GET https://services.csbot.cz/apps//contacts +``` + +Po každé úpravě API je povinné ověřit: + +1. `/health` funguje +2. `/docs` funguje +3. Swagger UI se načte +4. Swagger UI `Try it out` volá endpointy přes `/apps/` +5. OpenAPI JSON obsahuje správný server/base path +6. Nově přidané endpointy jsou ve Swagger dokumentaci + +Pokud framework generuje OpenAPI `servers`, musí obsahovat proxy prefix. + +Příklad: + +```json +{ + "servers": [ + { + "url": "/apps/" + } + ] +} +``` + +Nesmí vzniknout stav, kdy Swagger UI vypadá správně, ale tlačítko `Try it out` volá endpointy bez `/apps/`. + +## ROOT_PATH / PathBase + +AppFactory může aplikaci předávat environment variable: + +```text +ROOT_PATH=/apps/ +``` + +Použití závisí na frameworku. + +### .NET + +V ASP.NET Core použij `UsePathBase`, pokud template nebo aplikace používá `ROOT_PATH`. + +Typicky: + +```csharp +var rootPath = Environment.GetEnvironmentVariable("ROOT_PATH"); + +if (!string.IsNullOrWhiteSpace(rootPath)) +{ + app.UsePathBase(rootPath); +} +``` + +Swagger/OpenAPI ale musí být nakonfigurovaný tak, aby `servers` odpovídaly proxy prefixu. + +Nestačí pouze přidat endpoint `/docs`. + +Je nutné ověřit i Swagger `Try it out`. + +### Python / FastAPI + +FastAPI typicky používá `root_path`. + +Aplikace musí zajistit, že dokumentace a OpenAPI schema respektují proxy prefix. + +### Node.js / Express + +Express aplikace musí počítat s reverse proxy prefixem. + +Pokud se používá Swagger UI, musí být OpenAPI `servers` nebo Swagger konfigurace nastavené tak, aby testovací requesty šly přes `/apps/`. + +## Variables a secrets + +AppFactory spravuje variables a secrets přes portál. + +Portál ukládá hodnoty do DB a generuje runtime `.env` soubor aplikace. + +Aplikace je čte jako environment variables. + +Příklady: + +### .NET + +```csharp +var value = Environment.GetEnvironmentVariable("MY_VARIABLE"); +var optionalValue = builder.Configuration["OPTIONAL_VARIABLE"] ?? "default"; +``` + +### Python + +```python +import os + +value = os.getenv("MY_VARIABLE") +``` + +### Node.js + +```js +const value = process.env.MY_VARIABLE; +``` + +Secrets se nikdy nesmí: + +- commitovat do repository +- zapisovat do README +- vypisovat do logu +- vracet z běžných endpointů +- zobrazovat ve Swagger příkladech +- ukládat do zdrojového kódu +- hardcodovat + +Testovací endpointy, které vrací variables nebo secrets, se smí používat pouze dočasně pro ověření a musí být odstraněny před produkčním použitím. + +## Docker a port + +Aplikace musí poslouchat na portu definovaném AppFactory šablonou nebo metadaty aplikace. + +Port neměň bez odpovídající úpravy AppFactory konfigurace. + +Aplikace musí poslouchat na všech rozhraních containeru: + +```text +0.0.0.0 +``` + +Ne pouze na: + +```text +localhost +``` + +Dockerfile musí být deterministický a nesmí vyžadovat ruční zásahy v containeru. + +Ruční změny provedené přímo v běžícím containeru nejsou trvalé. + +## Co AI nesmí měnit v aplikačním repozitáři + +AI nesmí z aplikačního repozitáře měnit: + +- AppFactory deploy mechanismus +- Caddy konfiguraci +- Gitea webhooky +- Registry konfiguraci +- Backup/restore skripty +- Secrets storage +- Systémové soubory serveru +- AppFactory core služby +- AppFactory tools skripty + +Pokud je potřeba změnit infrastrukturu, musí se to řešit v příslušném AppFactory repozitáři, ne v repozitáři konkrétní aplikace. + +## Pravidla pro úpravy aplikace + +Před úpravou si vždy přečti: + +- `README.md` +- `AGENTS.md` +- `Dockerfile` +- hlavní vstupní soubor aplikace +- existující konfiguraci Swagger/OpenAPI +- způsob práce s environment variables + +Po úpravě ověř minimálně: + +- `/health` +- `/docs` +- upravovaný endpoint +- Swagger UI +- Swagger `Try it out` přes `/apps/` +- že container stále startuje +- že se nezměnil port bez úpravy metadat +- že secrets nejsou v logu ani ve zdrojovém kódu +- ideálně průběžně generuj dokumentaci .md do složky documentation v hlavním adresáři projektu. + +Každá změna musí zachovat kompatibilitu s AppFactory reverse proxy. + +Pokud přidáváš nový endpoint, dokumentace se musí aktualizovat současně. + +Pokud upravuješ request/response modely, Swagger/OpenAPI musí odpovídat skutečnému chování aplikace. + +## Secrets v parametrech +- Variables jako ClientId apod., které by neměly jít přes normální requesty se budou předávat jako X-ClientId v hlavičce. Nezapomeň takové přidat do swagger dokumentace, když budou nutné. + - pokud se bude předávat jinak, např. jako vnitřní secret, není potřeba. Vždy se na to programátora zeptej. + + +## Zakázané zkratky + +Nedělej tyto věci: + +- nepřidávej endpoint, který funguje jen lokálně, ale ne přes `/apps/` +- neopravuj Swagger tak, že bude fungovat pouze na root doméně +- nevypínej Swagger kvůli proxy problému +- nevypínej health check +- nevypínej validaci secrets tím, že je začneš logovat +- nepřepisuj Dockerfile na jiný port bez úpravy AppFactory metadat +- nepřidávej hardcoded URL produkční domény do business logiky + +## Doporučený postup po změně + +Po změně aplikace ověř veřejně: + +```text +GET https://services.csbot.cz/apps//health +GET https://services.csbot.cz/apps//docs +``` + +A přes Swagger UI ověř, že testování endpointů volá URL ve tvaru: + +```text +https://services.csbot.cz/apps// +``` + +ne: + +```text +https://services.csbot.cz/ +``` + +## Shrnutí pro AI + +Nejdůležitější pravidla: + +- aplikace běží za `/apps/` +- `/health` je povinný +- `/docs` se Swaggerem je povinný +- Swagger `Try it out` musí používat `/apps/` +- OpenAPI musí mít správný base path/server +- secrets nikdy nelogovat ani necommitovat +- konfiguraci číst z environment variables +- neměnit AppFactory infrastrukturu z aplikačního repozitáře +- po každé úpravě ověř reverse proxy chování diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..413527e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-slim AS build +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM node:20-slim +WORKDIR /app +ENV PORT=3000 +EXPOSE 3000 +COPY package*.json ./ +RUN npm install --omit=dev +COPY --from=build /app/dist ./dist +CMD ["npm", "start"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..a384fd7 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# csbot-prototype + +Node.js TypeScript služba vytvořená přes CSBot Services Portal. + +## Endpointy + +- GET / +- GET /health diff --git a/package.json b/package.json new file mode 100644 index 0000000..4815544 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "csbot-prototype", + "version": "1.0.0", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "express": "^4.18.3" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.11.30", + "typescript": "^5.4.0" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ddd2e40 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,35 @@ +import express from "express"; + +const app = express(); +const port = Number(process.env.PORT || 3000); +const rootPath = process.env.ROOT_PATH || ""; + +app.get("/", (_req, res) => { + res.json({ + name: "csbot-prototype", + service: "csbot-prototype", + status: "ok" + }); +}); + +app.get("/health", (_req, res) => { + res.json({ status: "ok" }); +}); + +if (rootPath) { + app.get(rootPath, (_req, res) => { + res.json({ + name: "csbot-prototype", + service: "csbot-prototype", + status: "ok" + }); + }); + + app.get(rootPath + "/health", (_req, res) => { + res.json({ status: "ok" }); + }); +} + +app.listen(port, "0.0.0.0", () => { + console.log("csbot-prototype listening on port " + port); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..81a634e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true + } +}