nordabiz/docs/architecture/02-container-diagram.md
Maciej Pienczyn 23493f0b61 docs: Aktualizacja dokumentacji do Gemini 3 Flash
Zmiana domyślnego modelu w dokumentacji i kodzie:
- gemini-2.5-flash → gemini-3-flash-preview
- gemini-2.5-pro → gemini-3-pro-preview

Zaktualizowane pliki:
- README.md - opis technologii
- docs/architecture/*.md - diagramy i przepływy
- nordabiz_chat.py - fallback model name
- zopk_news_service.py - model dla AI evaluation
- templates/admin/zopk_dashboard.html - wyświetlany model

Zachowano mapowania legacy modeli dla kompatybilności wstecznej.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:19:05 +01:00

29 KiB

Container Diagram (C4 Level 2)

Document Version: 1.0 Last Updated: 2026-01-10 Status: Production LIVE Diagram Type: C4 Model - Level 2 (Containers)


Overview

This diagram shows the major containers (applications and data stores) that make up the Norda Biznes Partner system. It illustrates:

  • What containers exist (web app, database, proxy, external services)
  • How containers communicate with each other
  • Technology choices for each container
  • Network boundaries and protocols

Abstraction Level: Container (C4 Level 2) Audience: Developers, DevOps, Technical Architects Purpose: Understanding system decomposition and technology stack


Container Diagram

graph TB
    %% External actors
    Users["👥 Users<br/>(Members, Visitors, Admins)<br/>Web Browser"]
    Admin["👨‍💼 System Admin<br/>Web Browser + SSH"]

    %% System boundary
    subgraph "Norda Biznes Partner System"
        subgraph "R11-REVPROXY-01 [VM 119 | 10.22.68.250]"
            NPM["🔒 Nginx Proxy Manager<br/>(Reverse Proxy)<br/><br/>Technology: Nginx + Docker<br/>Port: 443 (HTTPS)<br/><br/>Responsibilities:<br/>- SSL/TLS termination<br/>- Request routing<br/>- HTTP→HTTPS redirect<br/>- Let's Encrypt automation"]
        end

        subgraph "NORDABIZ-01 [VM 249 | 10.22.68.249]"
            WebApp["🌐 Flask Web Application<br/>(Application Server)<br/><br/>Technology: Flask 3.0 + Gunicorn<br/>Language: Python 3.9+<br/>Port: 5000<br/><br/>Responsibilities:<br/>- HTTP request handling<br/>- Business logic<br/>- Template rendering<br/>- API endpoints<br/>- Authentication & authorization<br/>- Session management"]

            Database["💾 PostgreSQL Database<br/>(Data Store)<br/><br/>Technology: PostgreSQL 14<br/>Port: 5432 (localhost only)<br/><br/>Responsibilities:<br/>- Persistent data storage<br/>- Full-text search (FTS)<br/>- Fuzzy matching (pg_trgm)<br/>- Data integrity & constraints<br/>- 36 tables (companies, users, etc.)"]

            Scripts["⚙️ Background Scripts<br/>(Batch Jobs)<br/><br/>Technology: Python 3.9+<br/>Execution: Cron / Manual<br/><br/>Responsibilities:<br/>- SEO auditing<br/>- Social media auditing<br/>- Data verification<br/>- Database migrations"]
        end

        subgraph "Service Layer (within Flask)"
            SearchSvc["🔍 Search Service<br/>search_service.py"]
            ChatSvc["💬 Chat Service<br/>nordabiz_chat.py"]
            EmailSvc["📧 Email Service<br/>email_service.py"]
            GeminiSvc["🤖 Gemini Service<br/>gemini_service.py"]
            KRSSvc["🏛️ KRS Service<br/>krs_api_service.py"]
            GBPSvc["📊 GBP Audit Service<br/>gbp_audit_service.py"]
            ITSvc["🖥️ IT Audit Service<br/>it_audit_service.py"]
        end
    end

    %% External systems
    subgraph "External APIs & Services"
        Gemini["🤖 Google Gemini API<br/>gemini-3-flash-preview<br/>REST API (HTTPS)"]
        BraveAPI["🔍 Brave Search API<br/>News & Social Discovery<br/>REST API (HTTPS)"]
        PageSpeed["📊 Google PageSpeed API<br/>SEO & Performance<br/>REST API (HTTPS)"]
        Places["📍 Google Places API<br/>Business Profiles<br/>REST API (HTTPS)"]
        KRS["🏛️ KRS Open API<br/>Company Registry<br/>REST API (HTTPS)"]
        MSGraph["📧 Microsoft Graph API<br/>Email Sending<br/>REST API + OAuth 2.0"]
        ALEO["🌐 ALEO.com<br/>NIP Verification<br/>Web Scraping (Playwright)"]
        Rejestr["🔗 rejestr.io<br/>Company Connections<br/>Web Scraping (Playwright)"]
    end

    %% User interactions
    Users -->|"HTTPS<br/>Port 443"| NPM
    Admin -->|"HTTPS<br/>Port 443"| NPM
    Admin -->|"SSH<br/>Port 22"| WebApp

    %% NPM routing
    NPM -->|"HTTP<br/>Port 5000<br/>(CRITICAL!)"| WebApp

    %% Web app to database
    WebApp -->|"SQL Queries<br/>SQLAlchemy ORM<br/>localhost:5432"| Database
    Scripts -->|"SQL Queries<br/>Direct connection<br/>127.0.0.1:5432"| Database

    %% Service layer connections
    WebApp --> SearchSvc
    WebApp --> ChatSvc
    WebApp --> EmailSvc
    WebApp --> GeminiSvc
    WebApp --> KRSSvc
    WebApp --> GBPSvc
    WebApp --> ITSvc

    SearchSvc --> Database
    ChatSvc --> SearchSvc
    ChatSvc --> GeminiSvc

    %% External API integrations
    GeminiSvc -->|"HTTPS<br/>API Key Auth"| Gemini
    WebApp -->|"HTTPS<br/>API Key Auth"| BraveAPI
    Scripts -->|"HTTPS<br/>API Key Auth"| PageSpeed
    GBPSvc -->|"HTTPS<br/>API Key Auth"| Places
    KRSSvc -->|"HTTPS<br/>Public API"| KRS
    EmailSvc -->|"HTTPS<br/>OAuth 2.0"| MSGraph
    Scripts -->|"HTTPS<br/>Web Scraping"| ALEO
    Scripts -->|"HTTPS<br/>Web Scraping"| Rejestr

    %% Styling
    classDef containerStyle fill:#1168bd,stroke:#0b4884,color:#ffffff,stroke-width:3px
    classDef databaseStyle fill:#438dd5,stroke:#2e6295,color:#ffffff,stroke-width:3px
    classDef serviceStyle fill:#85bbf0,stroke:#5d92c7,color:#000000,stroke-width:2px
    classDef proxyStyle fill:#ff6b6b,stroke:#cc5555,color:#ffffff,stroke-width:3px
    classDef externalStyle fill:#999999,stroke:#666666,color:#ffffff,stroke-width:2px
    classDef personStyle fill:#08427b,stroke:#052e56,color:#ffffff,stroke-width:2px

    class WebApp,Scripts containerStyle
    class Database databaseStyle
    class SearchSvc,ChatSvc,EmailSvc,GeminiSvc,KRSSvc,GBPSvc,ITSvc serviceStyle
    class NPM proxyStyle
    class Gemini,BraveAPI,PageSpeed,Places,KRS,MSGraph,ALEO,Rejestr externalStyle
    class Users,Admin personStyle

Container Descriptions

🔒 Nginx Proxy Manager (NPM)

Location: R11-REVPROXY-01 (VM 119, IP 10.22.68.250) Technology: Nginx + Docker Protocol: HTTPS (Port 443) Purpose: SSL termination and reverse proxy

Responsibilities:

  • Terminate SSL/TLS connections (Let's Encrypt certificates)
  • Route incoming HTTPS requests to backend Flask app
  • Automatically renew SSL certificates
  • Force HTTP→HTTPS redirects
  • Basic security headers (HSTS, CSP)

Critical Configuration:

HTTPS :443 → HTTP 10.22.68.249:5000

⚠️ WARNING: NPM MUST forward to port 5000, NOT port 80!

  • Port 80 on NORDABIZ-01 runs a system nginx that redirects to HTTPS
  • Forwarding to port 80 causes infinite redirect loop (ERR_TOO_MANY_REDIRECTS)
  • See: docs/INCIDENT_REPORT_20260102.md

Verification:

curl -I https://nordabiznes.pl/health
# Expected: HTTP 200 OK

NPM Access:


🌐 Flask Web Application

Location: NORDABIZ-01 (VM 249, IP 10.22.68.249) Technology: Flask 3.0 + Gunicorn WSGI server Language: Python 3.9+ Protocol: HTTP (Port 5000 - internal only) Main File: /var/www/nordabiznes/app.py (13,144 lines)

Responsibilities:

  • Handle HTTP requests from NPM
  • Render HTML templates (Jinja2)
  • Provide REST API endpoints (90+ routes)
  • User authentication (Flask-Login)
  • Session management (Flask sessions)
  • CSRF protection (Flask-WTF)
  • Rate limiting (Flask-Limiter: 200 req/day, 50 req/hour)
  • Business logic orchestration
  • Database ORM interactions (SQLAlchemy)

Technology Stack:

  • Framework: Flask 3.0
  • WSGI Server: Gunicorn (4 workers)
  • Template Engine: Jinja2
  • Forms: Flask-WTF
  • Authentication: Flask-Login
  • ORM: SQLAlchemy 2.0
  • Security: Flask-Limiter, Flask-SeaSurf (CSRF)

Key Modules:

Module Lines Purpose
app.py 13,144 Main application (routes, auth, API)
database.py ~1,500 SQLAlchemy models (36 tables)
search_service.py ~400 Company search (FTS, fuzzy)
nordabiz_chat.py ~800 AI chat engine
gemini_service.py ~500 Google Gemini integration
email_service.py ~200 MS Graph email sender
krs_api_service.py ~300 KRS company verification
gbp_audit_service.py ~600 Google Business Profile audit
it_audit_service.py ~500 IT infrastructure assessment

Service Management:

sudo systemctl status nordabiznes
sudo systemctl restart nordabiznes
sudo journalctl -u nordabiznes -f

Application User: www-data (NOT root or maciejpi)


💾 PostgreSQL Database

Location: NORDABIZ-01 (VM 249, IP 10.22.68.249) Technology: PostgreSQL 14 Protocol: PostgreSQL wire protocol (Port 5432) Access: localhost ONLY (127.0.0.1)

Responsibilities:

  • Store all application data (companies, users, chats, etc.)
  • Full-text search (tsvector, tsquery)
  • Fuzzy matching (pg_trgm extension)
  • Enforce data integrity (foreign keys, constraints)
  • Transaction management
  • Backup and recovery

Database Details:

  • Database Name: nordabiz
  • Application User: nordabiz_app
  • Admin User: postgres
  • Encoding: UTF-8
  • Locale: en_US.UTF-8
  • Total Tables: 36 (via SQLAlchemy)

Key Tables:

Table Records Purpose
companies 80 Member company profiles
users ~50 Registered users
chat_sessions ~500 AI chat conversations
chat_messages ~2000 Chat message history
seo_metrics ~200 SEO audit results
company_social_media ~115 Social media profiles
forum_topics ~30 Forum discussions
company_news ~50 News articles (moderated)

PostgreSQL Extensions:

  • pg_trgm - Trigram similarity (fuzzy search)
  • uuid-ossp - UUID generation
  • pgcrypto - Cryptographic functions

Connection Strings:

# Flask App (from NORDABIZ-01)
DATABASE_URL = 'postgresql://nordabiz_app:***@127.0.0.1:5432/nordabiz'

# Background Scripts (from NORDABIZ-01)
DATABASE_URL = 'postgresql://nordabiz_app:***@127.0.0.1:5432/nordabiz'

⚠️ IMPORTANT: PostgreSQL is configured to reject external connections. Only connections from localhost (127.0.0.1) are allowed.

Development Database:

  • Host: localhost (Mac)
  • Port: 5433 (Docker mapped port)
  • Container: nordabiz-postgres
  • Access: postgresql://nordabiz_app:***@localhost:5433/nordabiz

⚙️ Background Scripts

Location: NORDABIZ-01 (VM 249, IP 10.22.68.249) Directory: /var/www/nordabiznes/scripts/ Technology: Python 3.9+ (same virtualenv as Flask app) Execution: Manual via SSH or Cron jobs

Purpose:

  • Periodic data enrichment (SEO, social media, GBP audits)
  • Database maintenance (migrations, cleanup)
  • External data verification (NIP, KRS)
  • AI quality testing

Key Scripts:

Script Purpose API Dependencies
seo_audit.py Website SEO & performance audit Google PageSpeed
social_media_audit.py Social profile verification Brave Search
gbp_audit.py Google Business Profile audit Google Places
fetch_company_news.py News monitoring (planned) Brave Search + Gemini
verify_all_companies_data.py Data quality report -
fix_krs_verification.py KRS data validation KRS API
import_*.py Company data import ALEO, rejestr.io
run_ai_quality_tests.py AI chat quality tests Gemini

Execution Example:

cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 scripts/seo_audit.py --company-id 26

Cron Jobs (Planned):

# SEO audit - every week
0 2 * * 0 /var/www/nordabiznes/venv/bin/python3 /var/www/nordabiznes/scripts/seo_audit.py --all

# News monitoring - every 6 hours
0 */6 * * * /var/www/nordabiznes/venv/bin/python3 /var/www/nordabiznes/scripts/fetch_company_news.py --all

Service Layer Components

The Flask application uses a service layer pattern to encapsulate business logic and external integrations. Services are Python modules imported by app.py.

🔍 Search Service (search_service.py)

Purpose: Unified company search across multiple methods Used By: /search route, AI chat, API endpoints Database Access: Direct SQL + SQLAlchemy ORM

Search Methods:

  1. NIP/REGON Lookup - Direct identifier match
  2. Synonym Expansion - Keyword mapping (e.g., "strony" → www, web, portal)
  3. PostgreSQL FTS - Full-text search with tsvector
  4. Fuzzy Matching - pg_trgm similarity (handles typos)

Scoring System:

  • Company name match: +10 points
  • Description match: +5 points
  • Services match: +8 points
  • Competencies match: +7 points
  • City match: +3 points

API:

from search_service import search_companies
results = search_companies(db, "strony www", limit=10)
# Returns: List[SearchResult] with company, score, match_type

💬 Chat Service (nordabiz_chat.py)

Purpose: AI-powered chat with company context Dependencies: Gemini Service, Search Service Database Tables: chat_sessions, chat_messages, gemini_usage

Workflow:

  1. User sends message → create/load chat session
  2. Search for relevant companies (max 8 companies)
  3. Build conversation context (last 10 messages + company data)
  4. Send to Gemini API with system prompt
  5. Stream response back to user
  6. Track token usage and costs

Context Limits:

  • Companies in context: 8 (prevents token overflow)
  • Message history: 10 recent messages
  • Max response length: ~1000 tokens

Cost Tracking:

  • Input tokens counted per request
  • Output tokens counted per response
  • Stored in gemini_usage table
  • Cost calculated: $0.075-$1.25 per 1M tokens

🤖 Gemini Service (gemini_service.py)

Purpose: Interface to Google Gemini AI API Models: gemini-3-flash-preview (default), gemini-3-pro-preview (advanced) Authentication: API key from .env

Capabilities:

  • Text generation (chat responses)
  • Image analysis (logo descriptions)
  • Content scoring (news relevance)
  • Streaming responses

API Wrapper Functions:

generate_text(prompt, model="gemini-3-flash-preview")
generate_chat_response(messages, context, stream=True)
analyze_image(image_bytes, prompt)
score_content_relevance(content, company_name)

Error Handling:

  • Rate limit errors → retry with exponential backoff
  • Invalid API key → log error + return fallback response
  • Timeout → 30s default, configurable

📧 Email Service (email_service.py)

Purpose: Send email notifications via Microsoft Graph API Authentication: OAuth 2.0 client credentials Email Provider: Office 365 / Outlook

Use Cases:

  • Email verification during registration
  • Password reset emails
  • Admin notifications
  • User alerts (forum replies, messages)

Configuration:

# .env
MS_GRAPH_CLIENT_ID=...
MS_GRAPH_CLIENT_SECRET=...
MS_GRAPH_TENANT_ID=...
MS_GRAPH_FROM_EMAIL=noreply@nordabiznes.pl

API:

from email_service import send_email
send_email(to="user@example.com", subject="...", body="...")

🏛️ KRS Service (krs_api_service.py)

Purpose: Verify Polish company data from KRS registry API: Polish Ministry of Justice KRS Open API Authentication: Public API (no key required)

Data Retrieved:

  • Company legal verification (KRS number)
  • Corporate structure
  • Board members
  • Share capital
  • Legal form

API:

from krs_api_service import verify_krs
data = verify_krs(krs_number="0000123456")
# Returns: Dict with company details or None

📊 GBP Audit Service (gbp_audit_service.py)

Purpose: Audit Google Business Profile completeness Dependencies: Google Places API, Gemini AI Database Table: gbp_audits

Audit Checks:

  • Profile completeness (name, address, hours, etc.)
  • Photo quantity and quality
  • Review count and average rating
  • Q&A presence
  • Posts activity

AI Recommendations:

  • Uses Gemini to generate improvement suggestions
  • Personalized based on profile gaps
  • Actionable advice

🖥️ IT Audit Service (it_audit_service.py)

Purpose: Assess IT infrastructure maturity Method: Questionnaire-based scoring Database Table: it_audits

Assessment Categories:

  • IT Infrastructure (servers, network, backup)
  • Security (firewall, antivirus, access control)
  • Cloud Services (usage, integration)
  • Digital Tools (CRM, ERP, communication)
  • IT Management (policies, documentation, support)

Scoring:

  • 0-40: Basic level
  • 41-70: Intermediate level
  • 71-100: Advanced level

External API Integrations

All external APIs are called via HTTPS with appropriate authentication.

🤖 Google Gemini AI API

Endpoint: https://generativelanguage.googleapis.com/v1beta/models/* Authentication: API Key (in header: x-goog-api-key) Pricing: $0.075-$1.25 per 1M tokens (varies by model) Rate Limit: No strict limit (cost-limited)

Used For:

  • AI chat responses
  • Image analysis
  • Content relevance scoring
  • Recommendation generation

🔍 Brave Search API

Endpoint: https://api.search.brave.com/res/v1/news/search Authentication: API Key (header: X-Subscription-Token) Pricing: Free tier - 2,000 requests/month Rate Limit: 2,000 req/month

Used For:

  • News monitoring (company mentions)
  • Social media profile discovery
  • Press release detection

📊 Google PageSpeed Insights API

Endpoint: https://www.googleapis.com/pagespeedonline/v5/runPagespeed Authentication: API Key (query param: key) Pricing: Free - 25,000 queries/day Rate Limit: 25,000 req/day

Used For:

  • SEO score calculation
  • Performance metrics (Core Web Vitals)
  • Accessibility auditing
  • Best practices assessment

📍 Google Places API

Endpoint: https://maps.googleapis.com/maps/api/place/* Authentication: API Key Pricing: Pay-per-use (varies by request type) Rate Limit: Quota-based

Used For:

  • Business reviews and ratings
  • Business hours and location
  • Photo gallery
  • Google Maps integration

🏛️ KRS Open API

Endpoint: https://api-krs.ms.gov.pl/ Authentication: None (public API) Pricing: Free Rate Limit: Not strictly enforced

Used For:

  • Company legal verification
  • Corporate structure data
  • Board member information
  • Share capital details

📧 Microsoft Graph API

Endpoint: https://graph.microsoft.com/v1.0/ Authentication: OAuth 2.0 (client credentials) Pricing: Included with Office 365 Rate Limit: Throttling based on tenant

Used For:

  • Email notifications
  • User mailbox access (delegated)

🌐 ALEO.com

Method: Web scraping (Playwright) Authentication: None Rate Limit: Self-imposed (polite scraping) Reliability: Medium (subject to website changes)

Used For:

  • NIP number validation
  • Company legal status check
  • Basic company information

🔗 rejestr.io

Method: Web scraping (Playwright) Authentication: None Rate Limit: Self-imposed Reliability: Medium

Used For:

  • Board member identification
  • Shareholder/ownership structure
  • Cross-company relationships
  • Beneficial owners

Network Flow and Protocols

1. User HTTP Request Flow

User Browser (HTTPS :443)
    │
    ▼
NPM @ 10.22.68.250:443 (SSL termination)
    │
    ├─ Decrypt HTTPS
    ├─ Verify SSL certificate
    └─ Forward as HTTP
    │
    ▼
Flask App @ 10.22.68.249:5000 (HTTP)
    │
    ├─ Authenticate user (session cookie)
    ├─ Authorize request (permissions)
    ├─ Execute business logic
    └─ Query database
    │
    ▼
PostgreSQL @ 127.0.0.1:5432 (local only)
    │
    ├─ Execute SQL query
    ├─ Return result set
    └─ Commit transaction
    │
    ▼
Flask App (render template / JSON)
    │
    ▼
NPM (encrypt response)
    │
    ▼
User Browser (HTTPS response)

2. AI Chat Request Flow

User → Flask /api/chat/<id>/message (POST)
    │
    ▼
Chat Service (nordabiz_chat.py)
    │
    ├─ Load chat session from DB
    ├─ Extract user query
    └─ Search for relevant companies
    │
    ▼
Search Service (search_companies)
    │
    └─ PostgreSQL FTS + fuzzy search
    │
    ▼
Chat Service (build context)
    │
    ├─ Last 10 messages
    ├─ Max 8 company profiles
    └─ System prompt
    │
    ▼
Gemini Service → Google Gemini API (HTTPS)
    │
    ├─ Send context + query
    ├─ Receive streaming response
    └─ Track token usage
    │
    ▼
Chat Service (save to DB)
    │
    ├─ Store user message
    ├─ Store AI response
    └─ Update gemini_usage
    │
    ▼
Flask → User (streaming JSON)

3. SEO Audit Flow

Admin → /admin/seo (trigger audit)
    │
    ▼
Background Script: scripts/seo_audit.py
    │
    ├─ Get company URL from DB
    └─ Call PageSpeed API
    │
    ▼
Google PageSpeed API @ https://www.googleapis.com/...
    │
    ├─ Analyze website
    ├─ Calculate scores (SEO, Performance, etc.)
    └─ Return audit data (JSON)
    │
    ▼
Script (parse results)
    │
    └─ Store in seo_metrics table
    │
    ▼
PostgreSQL (save audit)
    │
    ▼
Admin Dashboard (display results)

Security Boundaries

Network Segmentation

Zone Components Access Level Trust Level
Public Internet User browsers Untrusted Low
DMZ (Proxy) NPM (10.22.68.250) Semi-trusted Medium
App Zone Flask App (10.22.68.249:5000) Trusted High
Data Zone PostgreSQL (127.0.0.1:5432) Highly trusted Critical
External APIs Gemini, Brave, etc. Untrusted Low

Authentication & Authorization

User Authentication:

  • Method: Email/password (bcrypt hashed)
  • Session: Flask-Login (server-side sessions)
  • Cookie: session (encrypted, HttpOnly, SameSite=Lax)

Admin Authorization:

  • Check: current_user.is_authenticated and current_user.is_admin
  • Decorator: @login_required + manual is_admin check

API Authentication:

  • External APIs: API keys in .env (never committed)
  • MS Graph: OAuth 2.0 client credentials

HTTPS/TLS

  • Certificate: Let's Encrypt (auto-renewal via NPM)
  • Protocols: TLS 1.2, TLS 1.3
  • Cipher Suites: Modern (AES-GCM, ChaCha20-Poly1305)
  • HSTS: Enabled (max-age=31536000)

Database Security

  • Access: Localhost only (no external connections)
  • Authentication: Password-based (strong passwords)
  • Encryption: At-rest encryption (OS-level)
  • Backups: Encrypted snapshots (Proxmox Backup Server)

Deployment Configuration

Production Environment

NPM Container (R11-REVPROXY-01):

Proxy Host Configuration:
  Domain: nordabiznes.pl
  Scheme: http
  Forward Hostname: 10.22.68.249
  Forward Port: 5000  # CRITICAL: Must be 5000, not 80!
  Cache Assets: Yes
  Block Common Exploits: Yes
  Websockets Support: No
  SSL:
    Force SSL: Yes
    HTTP/2 Support: Yes
    HSTS Enabled: Yes
    Certificate: Let's Encrypt
    Auto-renewal: Yes

Flask/Gunicorn (NORDABIZ-01):

# /etc/systemd/system/nordabiznes.service
[Unit]
Description=Norda Biznes Partner - Flask Application
After=network.target postgresql.service

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/nordabiznes
Environment="PATH=/var/www/nordabiznes/venv/bin"
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
    --workers 4 \
    --bind 127.0.0.1:5000 \
    --timeout 120 \
    --access-logfile /var/log/nordabiznes/access.log \
    --error-logfile /var/log/nordabiznes/error.log \
    app:app

[Install]
WantedBy=multi-user.target

PostgreSQL (NORDABIZ-01):

# /etc/postgresql/14/main/postgresql.conf
listen_addresses = 'localhost'  # ONLY localhost!
port = 5432
max_connections = 100
shared_buffers = 256MB

# /etc/postgresql/14/main/pg_hba.conf
local   nordabiz   nordabiz_app   md5
host    nordabiz   nordabiz_app   127.0.0.1/32   md5

Development Environment

Local Development (Mac)

Flask Application:

# Run locally on port 5000 or 5001
python3 app.py
# Access: http://localhost:5000

PostgreSQL (Docker):

# Start PostgreSQL container
docker compose up -d

# Connection string
postgresql://nordabiz_app:***@localhost:5433/nordabiz

Environment Variables:

# .env file (NEVER commit!)
DATABASE_URL=postgresql://nordabiz_app:***@localhost:5433/nordabiz
SECRET_KEY=...
GOOGLE_API_KEY=...
BRAVE_SEARCH_API_KEY=...
GOOGLE_PAGESPEED_API_KEY=...

Monitoring and Diagnostics

Health Check Endpoint

# Production
curl -I https://nordabiznes.pl/health
# Expected: HTTP 200 OK

# Development
curl -I http://localhost:5000/health
# Expected: HTTP 200 OK

Logs

Flask Application:

# Systemd journal
sudo journalctl -u nordabiznes -f

# Application logs
tail -f /var/log/nordabiznes/access.log
tail -f /var/log/nordabiznes/error.log

NPM (Nginx Proxy Manager):

# Docker logs
docker logs -f <npm-container-id>

# NPM access logs
docker exec <npm-container-id> tail -f /data/logs/proxy-host-27_access.log

PostgreSQL:

# PostgreSQL logs
sudo tail -f /var/log/postgresql/postgresql-14-main.log

Database Diagnostics

# Connect to database
sudo -u postgres psql nordabiz

# Check active connections
SELECT count(*) FROM pg_stat_activity WHERE datname = 'nordabiz';

# Check table sizes
SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename))
FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

Critical Configuration Warnings

⚠️ NPM Proxy Port Configuration

CRITICAL: NPM MUST forward to port 5000, NOT 80!

Why?

  • Port 80 on NORDABIZ-01 runs system nginx
  • System nginx redirects HTTP → HTTPS
  • NPM → :80 → nginx → HTTPS → NPM (infinite loop!)
  • Results in: ERR_TOO_MANY_REDIRECTS

Correct Configuration:

NPM (10.22.68.250:443) → Flask (10.22.68.249:5000) ✓

Incorrect Configuration (causes loop):

NPM (10.22.68.250:443) → Nginx (10.22.68.249:80) → NPM ✗

Verification After Changes:

curl -I https://nordabiznes.pl/health
# Must return: HTTP 200 OK (not redirect loop)

Incident Report: docs/INCIDENT_REPORT_20260102.md


⚠️ Database Connection Security

PostgreSQL MUST reject external connections:

# postgresql.conf
listen_addresses = 'localhost'  # NOT '*' or '0.0.0.0'!

Scripts MUST use localhost (127.0.0.1):

# CORRECT (from NORDABIZ-01)
DATABASE_URL = 'postgresql://nordabiz_app:***@127.0.0.1:5432/nordabiz'

# INCORRECT (external connection attempt)
DATABASE_URL = 'postgresql://nordabiz_app:***@10.22.68.249:5432/nordabiz'

⚠️ API Keys and Secrets

NEVER commit to git:

  • .env file (must be in .gitignore)
  • API keys in code
  • Database passwords
  • OAuth client secrets

Storage:

  • Production: .env file on NORDABIZ-01
  • Development: .env file on local machine
  • Backup: Secure password manager (not in git!)

Next Level (Deeper Dive)

Same Level (Alternative Views)

Higher Level (Context)

Data Flows


Maintenance Notes

When to Update This Diagram

✏️ UPDATE when:

  • New container added (e.g., Redis cache, Celery worker)
  • Container technology changed (e.g., Flask → FastAPI)
  • New external API integrated
  • Database technology changed (e.g., PostgreSQL → MySQL)
  • Service layer significantly refactored

DON'T UPDATE for:

  • New Flask routes (component-level change)
  • New database tables (schema-level change)
  • Code refactoring within existing containers
  • Infrastructure changes (deployment diagram)

Review Frequency

  • After major releases: Review container boundaries
  • Quarterly: Verify technology versions
  • When onboarding: Ensure diagram matches reality

Glossary

Term Definition
Container Separately deployable/runnable unit (app, database, service)
NPM Nginx Proxy Manager - reverse proxy with SSL termination
Gunicorn Python WSGI HTTP server for running Flask apps
WSGI Web Server Gateway Interface - Python web server standard
ORM Object-Relational Mapping (SQLAlchemy)
FTS Full-Text Search (PostgreSQL feature)
pg_trgm PostgreSQL trigram extension for fuzzy search
Service Layer Business logic abstraction (services/*.py)
Flask-Login Flask extension for user session management
bcrypt Password hashing algorithm
HSTS HTTP Strict Transport Security
DMZ Demilitarized Zone (network security boundary)

Document Status: Complete Diagram Validated: 2026-01-10 Mermaid Syntax: v10.6+ Renders in: GitHub, GitLab, VS Code (with Mermaid extension) Production Verified: 2026-01-10 (via health check)