{"id":1290,"date":"2026-02-15T20:14:29","date_gmt":"2026-02-15T20:14:29","guid":{"rendered":"https:\/\/docs.ai.drawconclusions.org\/?page_id=1290"},"modified":"2026-02-24T12:45:55","modified_gmt":"2026-02-24T12:45:55","slug":"architecture","status":"publish","type":"page","link":"https:\/\/docs.ai.drawconclusions.org\/?page_id=1290","title":{"rendered":"Architecture"},"content":{"rendered":"<p>Note: This document is limited to used software, Docker containers, and high-level structure. It does not describe code-level implementation details.<\/p>\n<h2>Overview<\/h2>\n<ul>\n<li>Two Symfony installs: one frontend, one backend.<\/li>\n<li>Connected via API.<\/li>\n<li>Running with Docker.<\/li>\n<li>Docker configuration (images, Traefik, etc.) is stored under <code>server\/var_www\/docker\/<\/code>.<\/li>\n<li>Database: PostgreSQL.<\/li>\n<li>Reverse proxy: Traefik.<\/li>\n<li>Traefik uses a file provider (server\/var_www\/docker\/traefik\/dynamic.yml) for routing rules.<\/li>\n<li>Admin tool: phpMyAdmin.<\/li>\n<li>phpMyAdmin is routed via Traefik at <code>pma.ai.drawconclusions.org<\/code> and protected by an IP allowlist.<\/li>\n<li>Access via server IP (no domain yet).<\/li>\n<li>Architecture subpages include Routes, Docker Containers, Items, and Relations.<\/li>\n<li>All containers are defined in a single docker-compose file.<\/li>\n<li>Web stack: Nginx + PHP-FPM.<\/li>\n<li>FE PHP-FPM image is built from <code>server\/var_www\/docker\/fe\/Dockerfile<\/code>.<\/li>\n<li>BE PHP-FPM image is built from <code>server\/var_www\/docker\/be\/Dockerfile<\/code>.<\/li>\n<li>Frontend templates: Twig.<\/li>\n<li>Theme switcher in FE stores preference in session.<\/li>\n<li>Frontend scripting: TypeScript.<\/li>\n<li>Frontend build tools: Webpack, webpack-cli, TypeScript, ts-loader, Babel (core + preset-env), babel-loader, css-loader, mini-css-extract-plugin, sass, file-loader, terser-webpack-plugin, clean-webpack-plugin.<\/li>\n<li>Frontend libs: D3, TinyMCE.<\/li>\n<li>Docker Compose file (server): \/var\/www\/docker-compose.yml<\/li>\n<li>Traefik config: server\/var_www\/docker\/traefik\/traefik.yml<\/li>\n<li>Backend API is served under <code>\/api<\/code> (e.g., <code>\/api\/health<\/code>).<\/li>\n<li>Mailpit is used as a local SMTP catch-all for development.<\/li>\n<li>Database convention: UUIDv4 primary keys for all tables (see <code>conventions.md<\/code>, ID: DOC-CONV-UUID).<\/li>\n<li>Docker container names are UpperCamelCase: Traefik, FePhp, FeNginx, BePhp, BeNginx, Db, Mailpit, PhpMyAdmin, wordpress-app (service names remain lowercase in docker-compose).<\/li>\n<\/ul>\n<h2>Canonical Ownership<\/h2>\n<h2>Canonical<\/h2>\n<ul>\n<li>Docker container details live on Docker Containers pages (see <code>components.md<\/code>).<\/li>\n<li>Route details live on route pages (see <code>architecture_routes.md<\/code>).<\/li>\n<li>Table details live on table pages (see <code>tables.md<\/code> and <code>table_*.md<\/code>).<\/li>\n<\/ul>\n<h2>Database<\/h2>\n<h2>Db<\/h2>\n<ul>\n<li>PostgreSQL runs as the <code>db<\/code> service in <code>server\/var_www\/docker-compose.yml<\/code>.<\/li>\n<li>Init SQL lives at <code>server\/var_www\/docker\/postgres\/init.sql<\/code> and creates the <code>health_check<\/code> table.<\/li>\n<li>Init SQL seeds the <code>users<\/code> table with <code>admin@example.com<\/code> and the configured admin email from <code>config.xml<\/code> (<code>admin\/email<\/code>).<\/li>\n<li>Backend API reads DB connection settings from environment variables set in <code>docker-compose.yml<\/code>.<\/li>\n<li>Backend DB check endpoint: <code>\/api\/db-check<\/code> (Traefik strips <code>\/api<\/code> before hitting BE).<\/li>\n<li>Backend login endpoint: <code>\/api\/login<\/code> (expects JSON with email + password).<\/li>\n<li>FE login page posts to the backend login endpoint via <code>BE_API_URL<\/code> (defaults to http:\/\/be-nginx).<\/li>\n<li>Backend mail test endpoint: <code>\/api\/mail-test<\/code> (sends via Mailpit).<\/li>\n<li>Backend test-user endpoint: <code>\/api\/test-user<\/code> (creates a test login user for Playwright).<\/li>\n<li>Backend contact endpoint: <code>\/api\/contact<\/code> stores email rows and sends mail to admin.<\/li>\n<li>Backend register endpoint: <code>\/api\/register<\/code> creates a pending registration and sends a confirmation email.<\/li>\n<li>Backend register confirm endpoint: <code>\/api\/register\/confirm<\/code> creates the user from the pending registration.<\/li>\n<li>Backend roles service provides hardcoded roles (guest, user, admin) and <code>\/api\/roles<\/code>.<\/li>\n<li>Users table includes <code>role<\/code> with allowed values <code>user<\/code> and <code>admin<\/code> (default <code>user<\/code>).<\/li>\n<li>Thoughts core unit stored in <code>thoughts<\/code> table with <code>\/api\/thoughts<\/code> list\/create and <code>\/api\/thoughts\/{id}<\/code> detail.<\/li>\n<li>Password reset flow:<\/li>\n<li><code>\/api\/forgot-password<\/code> stores a reset token and emails a link.<\/li>\n<li><code>\/api\/reset-password<\/code> validates the token, updates the password, clears the token, and returns the email for auto-login.<\/li>\n<\/ul>\n<h2>Diagrams<\/h2>\n<h3>Physical Machines<\/h3>\n<pre><code>graph LR\n  Local[Local Machine] --&gt;|git push\/pull| GitHub[(GitHub Repo)]\n  Local --&gt;|deploy (rsync\/scp)| Droplet[DO Server]\n  Droplet --&gt;|git pull (read-only)| GitHub<\/code><\/pre>\n<h3>Docker Network<\/h3>\n<pre><code>graph TD\n  Internet((Internet)) --&gt; Traefik[Traefik :80]\n  Traefik --&gt;|\/| FE_NGINX[FeNginx]\n  Traefik --&gt;|\/api| BE_NGINX[BeNginx]\n  Traefik --&gt;|docs.ai.drawconclusions.org| WORDPRESS[WordPress]\n  FE_NGINX --&gt; FE_PHP[FePhp]\n  BE_NGINX --&gt; BE_PHP[BePhp]\n  FE_PHP --&gt;|API calls| BE_NGINX\n  BE_PHP --&gt; DB[(PostgreSQL Db)]<\/code><\/pre>\n<h2>Docker Containers<\/h2>\n<ul>\n<li>Frontend Symfony install (folder: fe)<\/li>\n<li>Backend Symfony install (folder: be)<\/li>\n<li>API connection between frontend and backend<\/li>\n<li>PostgreSQL database<\/li>\n<li>Traefik reverse proxy \/ ingress<\/li>\n<li>phpMyAdmin (admin UI)<\/li>\n<\/ul>\n<p class=\"developerdocs-id-search\" aria-hidden=\"true\">DeveloperDoc ID: dd_id_b82bfe7e81<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note: This document is limited to used software, Docker containers, and high-level structure. It does not describe code-level implementation details. Overview Two Symfony installs: one frontend, one backend. Connected via API. Running with Docker. Docker configuration (images, Traefik, etc.) is stored under server\/var_www\/docker\/. Database: PostgreSQL. Reverse proxy: Traefik. Traefik uses a file provider (server\/var_www\/docker\/traefik\/dynamic.yml) for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":1360,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"doc_category":[],"doc_layer":[31],"class_list":["post-1290","page","type-page","status-publish","hentry","layer-dd_id_cffc321500"],"_links":{"self":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1290","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1290"}],"version-history":[{"count":7,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1290\/revisions"}],"predecessor-version":[{"id":3805,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1290\/revisions\/3805"}],"up":[{"embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=\/wp\/v2\/pages\/1360"}],"wp:attachment":[{"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1290"}],"wp:term":[{"taxonomy":"doc_category","embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fdoc_category&post=1290"},{"taxonomy":"doc_layer","embeddable":true,"href":"https:\/\/docs.ai.drawconclusions.org\/index.php?rest_route=%2Fwp%2Fv2%2Fdoc_layer&post=1290"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}