feat(admin): Add permanent delete for archived companies + update release notes
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Workflow: Active → Archive → Permanent Delete (hard delete from DB). Only ADMIN role can permanently delete, and only archived companies. FK cleanup across 35+ tables before deletion. Also adds 4 missing items to v1.25.0 release notes: - Strefa RADA simplified (removed documents section) - Korzyści commission column visibility - Company hard-delete feature - User delete FK cascade fix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
932ee9c2b0
commit
85e14bb4bf
527
CLAUDE.md
527
CLAUDE.md
@ -27,6 +27,7 @@ nordabiz/
|
||||
├── DEVELOPMENT.md # Szczegóły deweloperskie
|
||||
├── ROADMAP.md # Plan rozwoju, monetyzacja
|
||||
├── CREDENTIALS.md # Zasady zarządzania credentials
|
||||
├── CLAUDE-REFERENCE.md # Szczegóły operacyjne (DR, ZOPK, Remotion, cron)
|
||||
└── INCIDENT_REPORT_*.md
|
||||
```
|
||||
|
||||
@ -38,6 +39,7 @@ nordabiz/
|
||||
| `docs/DEVELOPMENT.md` | SearchService, Chatbot, Testy AI, SEO, News |
|
||||
| `docs/ROADMAP.md` | Plan rozwoju, priorytety, monetyzacja |
|
||||
| `docs/CREDENTIALS.md` | Zarządzanie hasłami i kluczami API |
|
||||
| `docs/CLAUDE-REFERENCE.md` | Szczegóły operacyjne przeniesione z CLAUDE.md |
|
||||
|
||||
**⚠️ WAŻNE:** Przed zmianami w NPM/proxy przeczytaj `docs/architecture/08-critical-configurations.md`
|
||||
|
||||
@ -59,51 +61,30 @@ nordabiz/
|
||||
- **Uruchomienie:** `python3 app.py`
|
||||
- **Docker DB:** `docker compose up -d` (jeśli nie działa)
|
||||
|
||||
### Staging (NOWE - 2026-02-02)
|
||||
### Staging
|
||||
- **Serwer:** NORDABIZ-STAGING-01 (VM 248, IP 10.22.68.248)
|
||||
- **Baza:** PostgreSQL `nordabiz_staging` na 10.22.68.248:5432
|
||||
- **Domena:** staging.nordabiznes.pl
|
||||
- **NPM Proxy Host ID:** 44
|
||||
- **SSL:** Let's Encrypt (ważny do 2026-05-03)
|
||||
|
||||
**Cel:** Testowanie zmian przed wdrożeniem na produkcję (E2E, integration tests)
|
||||
|
||||
**Weryfikacja:**
|
||||
```bash
|
||||
curl -I https://staging.nordabiznes.pl/health
|
||||
ssh maciejpi@10.22.68.248
|
||||
```
|
||||
- **Domena:** staging.nordabiznes.pl (NPM Proxy Host ID: 44)
|
||||
- **Weryfikacja:** `curl -I https://staging.nordabiznes.pl/health`
|
||||
|
||||
### Production
|
||||
- **Serwer:** NORDABIZ-01 (VM 249, IP 10.22.68.249)
|
||||
- **Baza:** PostgreSQL na 10.22.68.249:5432
|
||||
- **Reverse Proxy:** NPM na R11-REVPROXY-01 (VM 119, IP 10.22.68.250)
|
||||
- **Domena:** nordabiznes.pl (DNS w OVH)
|
||||
- **SSL:** Let's Encrypt (auto-renewal)
|
||||
- **Domena:** nordabiznes.pl (DNS w OVH, SSL Let's Encrypt)
|
||||
|
||||
### NPM Proxy Configuration (KRYTYCZNE!)
|
||||
|
||||
**Proxy Host ID:** 27
|
||||
**Forward Port:** 5000 (NIE 80!)
|
||||
**Proxy Host ID:** 27 | **Forward Port:** 5000 (NIE 80!)
|
||||
|
||||
```
|
||||
PRAWIDŁOWA KONFIGURACJA:
|
||||
NPM (10.22.68.250) → Backend (10.22.68.249:5000) ✓
|
||||
|
||||
BŁĘDNA KONFIGURACJA (powoduje pętlę przekierowań):
|
||||
NPM (10.22.68.250) → Backend (10.22.68.249:80) ✗
|
||||
NPM (10.22.68.250) → Backend (10.22.68.249:80) ✗ (pętla przekierowań!)
|
||||
```
|
||||
|
||||
**UWAGA:** Na serwerze 10.22.68.249 działa nginx na porcie 80 który przekierowuje na HTTPS.
|
||||
Flask/Gunicorn działa na porcie 5000. Przy edycji proxy hosta ZAWSZE sprawdź czy port = 5000!
|
||||
Na serwerze .249 nginx na porcie 80 przekierowuje na HTTPS. Flask/Gunicorn na porcie 5000. **ZAWSZE** sprawdź port po edycji NPM!
|
||||
|
||||
**Weryfikacja po zmianach NPM:**
|
||||
```bash
|
||||
curl -I https://nordabiznes.pl/health
|
||||
# Oczekiwany: HTTP 200
|
||||
```
|
||||
|
||||
**Raport incydentu:** `docs/INCIDENT_REPORT_20260102.md`
|
||||
**Weryfikacja:** `curl -I https://nordabiznes.pl/health` | **Incydent:** `docs/INCIDENT_REPORT_20260102.md`
|
||||
|
||||
## Git & Deployment
|
||||
|
||||
@ -111,21 +92,8 @@ curl -I https://nordabiznes.pl/health
|
||||
|
||||
| Remote | URL | Cel |
|
||||
|--------|-----|-----|
|
||||
| **origin** (GitHub) | `git@github.com:pienczyn/nordabiz.git` | Cloud backup, CI/CD ready |
|
||||
| **inpi** (Gitea) | `git@10.22.68.180:maciejpi/nordabiz.git` | Wewnętrzny backup, deploy source |
|
||||
|
||||
**Konta:** GitHub: `pienczyn`, Gitea: `maciejpi`
|
||||
|
||||
### Workflow Deployment
|
||||
|
||||
```
|
||||
┌─────────┐ git push ┌─────────┐ git pull ┌─────────┐ git pull ┌─────────┐
|
||||
│ DEV │ ────────────► │ Gitea │ ◄──────────── │ STAGING │ ◄──────────── │ PROD │
|
||||
│ (Mac) │ │ (INPI) │ │ (test) │ po testach │ │
|
||||
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||||
│ │
|
||||
└──── git push ────► GitHub (CI/CD) ───────────────┘
|
||||
```
|
||||
| **origin** (GitHub) | `git@github.com:pienczyn/nordabiz.git` | Cloud backup, CI/CD |
|
||||
| **inpi** (Gitea) | `git@10.22.68.180:maciejpi/nordabiz.git` | Wewnętrzny, deploy source |
|
||||
|
||||
### Procedura wdrażania (WAŻNE!)
|
||||
|
||||
@ -137,206 +105,81 @@ git push origin master && git push inpi master
|
||||
|
||||
# 2. STAGING: Wdrożenie i test
|
||||
ssh maciejpi@10.22.68.248 "cd /var/www/nordabiznes && sudo -u www-data git pull && sudo systemctl restart nordabiznes"
|
||||
curl -s https://staging.nordabiznes.pl/health
|
||||
# ⚠️ OBOWIĄZKOWO: Test manualny nowej funkcjonalności na staging!
|
||||
|
||||
# 3. PROD: Pull zmiany (DOPIERO PO WERYFIKACJI STAGING!)
|
||||
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && sudo -u www-data git pull"
|
||||
|
||||
# 4. PROD: Uruchom migracje SQL (jeśli są)
|
||||
# 4. PROD: Migracje SQL (jeśli są)
|
||||
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && /var/www/nordabiznes/venv/bin/python3 scripts/run_migration.py database/migrations/XXX_nazwa.sql"
|
||||
|
||||
# 5. PROD: Restart serwisu
|
||||
# 5. PROD: Restart + weryfikacja
|
||||
ssh maciejpi@10.22.68.249 "sudo systemctl restart nordabiznes"
|
||||
|
||||
# 6. Weryfikacja produkcji
|
||||
curl -sI https://nordabiznes.pl/health | head -3
|
||||
```
|
||||
|
||||
**⚠️ UWAGI KRYTYCZNE:**
|
||||
|
||||
1. **Migracje SQL** - NIE używaj `psql` bezpośrednio. Użyj `scripts/run_migration.py`
|
||||
|
||||
2. **Uprawnienia logów** - Jeśli błąd `Permission denied: /var/log/nordabiznes/*`:
|
||||
```bash
|
||||
ssh maciejpi@10.22.68.249 "sudo chown -R maciejpi:maciejpi /var/log/nordabiznes/"
|
||||
```
|
||||
|
||||
2. **Uprawnienia logów** - `sudo chown -R maciejpi:maciejpi /var/log/nordabiznes/`
|
||||
3. **502 po restarcie** - Poczekaj 3-5 sekund i sprawdź ponownie
|
||||
|
||||
4. **Git pull** - Używaj `sudo -u www-data git pull` (www-data ma klucze SSH)
|
||||
5. **SSH timeout** - NIE oznacza że komenda nie została wykonana! Sprawdź `ps aux | grep <skrypt>`
|
||||
|
||||
**Uruchamianie skryptów na produkcji (KRYTYCZNE!):**
|
||||
- SSH timeout NIE oznacza że komenda nie została wykonana!
|
||||
- ZAWSZE sprawdź czy poprzedni proces nie działa: `ps aux | grep <skrypt>`
|
||||
- Dla długich operacji używaj `nohup` lub `screen`
|
||||
- Incydent: `docs/INCIDENT_REPORT_20260115.md`
|
||||
**Skrypty Python z dostępem do bazy (WAŻNE!):**
|
||||
|
||||
**Uruchamianie skryptów Python z dostępem do bazy (WAŻNE!):**
|
||||
|
||||
Skrypty Python wymagające dostępu do bazy danych MUSZĄ mieć ustawiony `DATABASE_URL`.
|
||||
Plik `.env` NIE jest automatycznie wczytywany przez `source .env` - to nie działa w kontekście SSH!
|
||||
`.env` NIE jest wczytywany przez `source .env` w kontekście SSH!
|
||||
|
||||
```bash
|
||||
# ✅ PRAWIDŁOWO - ustaw DATABASE_URL bezpośrednio:
|
||||
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && \
|
||||
DATABASE_URL='postgresql://nordabiz_app:HASŁO@127.0.0.1:5432/nordabiz' \
|
||||
/var/www/nordabiznes/venv/bin/python3 skrypt.py"
|
||||
|
||||
# ✅ PRAWIDŁOWO - pobierz DATABASE_URL z .env:
|
||||
# ✅ PRAWIDŁOWO:
|
||||
ssh maciejpi@10.22.68.249 "cd /var/www/nordabiznes && \
|
||||
DATABASE_URL=\$(grep DATABASE_URL .env | cut -d'=' -f2) \
|
||||
/var/www/nordabiznes/venv/bin/python3 skrypt.py"
|
||||
|
||||
# ❌ BŁĘDNIE - source .env nie działa przez SSH:
|
||||
# ❌ BŁĘDNIE:
|
||||
ssh maciejpi@10.22.68.249 "source .env && python3 skrypt.py"
|
||||
```
|
||||
|
||||
**Hasło do bazy (produkcja):** W pliku `/var/www/nordabiznes/.env` (DATABASE_URL)
|
||||
|
||||
## Auto Claude - Rozwiązywanie problemów
|
||||
|
||||
### Pliki stanu (NIE COMMITOWAĆ!)
|
||||
- `.auto-claude-security.json`, `.auto-claude-status`, `.auto-claude/`
|
||||
- Są w `.gitignore` + pre-commit hook automatycznie je usuwa ze staging
|
||||
|
||||
### Konflikty merge z Auto Claude
|
||||
|
||||
```bash
|
||||
# 1. Sprawdź konflikt
|
||||
git status
|
||||
|
||||
# 2. Usuń pliki Auto Claude z merge
|
||||
git rm .auto-claude-security.json .auto-claude-status
|
||||
|
||||
# 3. Dokończ merge
|
||||
git commit -m "Merge branch 'feature' - resolve Auto Claude file conflicts"
|
||||
```
|
||||
|
||||
### Worktrees
|
||||
|
||||
```bash
|
||||
git worktree list # Lista aktywnych
|
||||
git worktree remove .auto-claude/worktrees/tasks/<task-id> # Usuń nieaktualny
|
||||
git branch -d auto-claude/<task-id> # Usuń branch
|
||||
```
|
||||
|
||||
## Konwencje danych
|
||||
|
||||
### Identyfikatory firm
|
||||
- **Slug:** kebab-case, np. `pixlab-sp-z-o-o`
|
||||
- **NIP:** 10 cyfr bez myślników
|
||||
- **REGON:** 9 lub 14 cyfr
|
||||
- **KRS:** 10 cyfr (tylko spółki)
|
||||
|
||||
### Kategorie firm
|
||||
`IT`, `Construction`, `Services`, `Production`, `Trade`, `Other`
|
||||
|
||||
### Poziomy jakości danych
|
||||
`basic`, `enhanced`, `complete`
|
||||
- **NIP:** 10 cyfr bez myślników | **REGON:** 9 lub 14 cyfr | **KRS:** 10 cyfr (tylko spółki)
|
||||
- **Kategorie:** `IT`, `Construction`, `Services`, `Production`, `Trade`, `Other`
|
||||
- **Jakość danych:** `basic`, `enhanced`, `complete`
|
||||
|
||||
## Ważne zasady
|
||||
|
||||
### Bezpieczeństwo
|
||||
### Bezpieczeństwo & Credentials
|
||||
- NIE edytuj bezpośrednio bazy produkcyjnej PostgreSQL
|
||||
- Testuj zmiany na DEV (Docker: localhost:5433) przed wdrożeniem
|
||||
- Klucze API i hasła tylko w `.env` (nigdy w kodzie)
|
||||
- Klucze API i hasła **tylko w `.env`** (nigdy w kodzie!) — `docs/CREDENTIALS.md`
|
||||
- Rate limiting: 200 req/dzień, 50 req/godzinę
|
||||
|
||||
**Panel bezpieczeństwa:** `/admin/security`
|
||||
- 2FA, CSRF, HTTPS, GeoIP Blocking, Rate Limiting, Account Lockout, Audit Log
|
||||
|
||||
### Zarządzanie credentials (KRYTYCZNE!)
|
||||
|
||||
**NIGDY nie umieszczaj haseł w kodzie źródłowym!**
|
||||
|
||||
```python
|
||||
# ✅ PRAWIDŁOWO:
|
||||
DATABASE_URL = os.getenv('DATABASE_URL')
|
||||
|
||||
# ❌ BŁĘDNIE:
|
||||
DATABASE_URL = 'postgresql://user:password123@localhost/db'
|
||||
```
|
||||
|
||||
- Wszystkie sekrety w `.env` (nigdy w `.py`, `.sh`, `.js`)
|
||||
- Fallback jako placeholder: `CHANGE_ME` (nie prawdziwe hasło!)
|
||||
|
||||
**Pełne zasady:** `docs/CREDENTIALS.md`
|
||||
- Panel bezpieczeństwa: `/admin/security`
|
||||
|
||||
### Deployment
|
||||
- Przed wdrożeniem: `python -m py_compile app.py`
|
||||
- SSH do NORDABIZ-01: `ssh maciejpi@10.22.68.249` (ZAWSZE jako maciejpi!)
|
||||
- Ścieżka aplikacji: `/var/www/nordabiznes`
|
||||
- Restart: `sudo systemctl restart nordabiznes`
|
||||
- **ZAWSZE** aktualizuj historię zmian (`release_notes` w app.py)
|
||||
- SSH: `ssh maciejpi@10.22.68.249` (ZAWSZE jako maciejpi!)
|
||||
- Ścieżka: `/var/www/nordabiznes` | Restart: `sudo systemctl restart nordabiznes`
|
||||
- **ZAWSZE** aktualizuj `release_notes` w app.py
|
||||
|
||||
### Szablony Jinja2 - WAŻNE!
|
||||
- Blok `{% block extra_js %}` w `base.html` jest już wewnątrz `<script>`
|
||||
- **NIE DODAWAJ** własnych tagów `<script>` w `extra_js`!
|
||||
- Prawidłowo: `{% block extra_js %}function foo() {...}{% endblock %}`
|
||||
- `{% block extra_js %}` w `base.html` jest **wewnątrz** `<script>` — **NIE DODAWAJ** własnych tagów `<script>`!
|
||||
|
||||
### Uprawnienia PostgreSQL
|
||||
- Po utworzeniu tabel: `GRANT ALL ON TABLE ... TO nordabiz_app`
|
||||
- Po utworzeniu sekwencji: `GRANT USAGE, SELECT ON SEQUENCE ... TO nordabiz_app`
|
||||
|
||||
## Infrastruktura testowa (NOWE - 2026-02-02)
|
||||
|
||||
### Uruchamianie testów lokalnie
|
||||
## Infrastruktura testowa
|
||||
|
||||
```bash
|
||||
# Wszystkie testy
|
||||
pytest tests/ -v
|
||||
|
||||
# Tylko unit testy
|
||||
pytest tests/unit/ -v
|
||||
|
||||
# Tylko integration testy
|
||||
pytest tests/integration/ -v
|
||||
|
||||
# Testy z coverage
|
||||
pytest tests/ --cov=. --cov-report=html
|
||||
pytest tests/ -v # Wszystkie testy
|
||||
pytest tests/unit/ -v # Unit testy (bez bazy)
|
||||
pytest tests/integration/ -v # Integration testy (z bazą)
|
||||
pytest tests/ --cov=. --cov-report=html # Testy z coverage
|
||||
```
|
||||
|
||||
### Struktura testów
|
||||
CI/CD: `.github/workflows/test.yml` (unit → e2e na staging → smoke na prod)
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Fixtures (app, client, auth_client, admin_client)
|
||||
├── pytest.ini # Konfiguracja pytest
|
||||
├── unit/ # Unit testy (bez bazy)
|
||||
├── integration/ # Integration testy (z bazą)
|
||||
├── security/ # OWASP security tests
|
||||
├── smoke/ # Smoke testy produkcyjne
|
||||
└── e2e/ # Playwright E2E (na staging)
|
||||
```
|
||||
|
||||
### CI/CD (GitHub Actions)
|
||||
|
||||
Workflow: `.github/workflows/test.yml`
|
||||
|
||||
| Job | Trigger | Środowisko |
|
||||
|-----|---------|------------|
|
||||
| unit-tests | push/PR | GitHub runner |
|
||||
| e2e-tests | po unit | staging.nordabiznes.pl |
|
||||
| smoke-tests | master only | nordabiznes.pl |
|
||||
|
||||
### GitHub Secrets
|
||||
|
||||
| Secret | Użycie |
|
||||
|--------|--------|
|
||||
| STAGING_URL | E2E tests base URL |
|
||||
| TEST_USER_EMAIL | Logowanie w testach |
|
||||
| TEST_USER_PASSWORD | Logowanie w testach |
|
||||
| TEST_DATABASE_URL | Integration tests |
|
||||
|
||||
### Testowanie na produkcji
|
||||
|
||||
**Konta testowe (PROD):**
|
||||
|
||||
| Konto | Email | Hasło | Rola |
|
||||
|-------|-------|-------|------|
|
||||
| Test User | `test@nordabiznes.pl` | `&Rc2LdbSw&jiGR0ek@Bz` | Użytkownik |
|
||||
| Test Admin | `testadmin@nordabiznes.pl` | `cSfQbbwegwv1v3Q2Dm0Q` | Admin |
|
||||
**Konta testowe (PROD):** Brak (usunięte 2026-02-04)
|
||||
|
||||
## API Endpoints (podstawowe)
|
||||
|
||||
@ -351,291 +194,27 @@ Workflow: `.github/workflows/test.yml`
|
||||
|
||||
Pełna lista: `docs/architecture/10-api-endpoints.md`
|
||||
|
||||
## SearchService
|
||||
## Moduły (szczegóły w docs/)
|
||||
|
||||
Unified search dla chatbota i `/search`. Szczegóły: `docs/DEVELOPMENT.md#searchservice`
|
||||
- **SearchService:** Unified search dla chatbota i `/search` — `docs/DEVELOPMENT.md#searchservice`
|
||||
- **Chatbot AI:** Limit 8 firm, 10 wiadomości historii — `docs/DEVELOPMENT.md#chatbot-ai`
|
||||
- **News Monitoring:** Panel `/admin/news`, statusy: pending/approved/rejected
|
||||
- **ZOPK News:** Panel `/admin/zopk/news`, auto-approve score >= 3 — szczegóły w `docs/CLAUDE-REFERENCE.md`
|
||||
- **Social Media:** Panel `/admin/social-media` (FB, IG, LinkedIn, YT, TikTok, Twitter)
|
||||
- **Audyt SEO:** Panel `/admin/seo`, API Google PageSpeed Insights
|
||||
- **Website Updater:** Cron miesięczny, `scripts/website_content_updater.py` — szczegóły w `docs/CLAUDE-REFERENCE.md`
|
||||
|
||||
## Chatbot AI
|
||||
## NordaGPT - Konfiguracja AI
|
||||
|
||||
- **Limit firm:** 8
|
||||
- **Historia:** 10 wiadomości
|
||||
|
||||
Szczegóły: `docs/DEVELOPMENT.md#chatbot-ai`
|
||||
- **Model:** `gemini-3-flash-preview` (alias `3-flash`) — thinking mode, 7x lepszy reasoning
|
||||
- **SDK:** `google-genai>=1.0.0`
|
||||
- **Inicjalizacja:** `app.py:286` — `gemini_service.init_gemini_service(model='3-flash')`
|
||||
- **Zmiana modelu:** Edytuj alias w `app.py:286`. Katalog modeli: `docs/CLAUDE-REFERENCE.md`
|
||||
|
||||
## Powiązane zasoby
|
||||
|
||||
- **Źródło danych:** https://norda-biznes.info/czlonkowie
|
||||
- **Backup:** Proxmox Backup Server (VM snapshots)
|
||||
- **Backup:** Proxmox Backup Server — szczegóły DR: `docs/DR-PLAYBOOK.md` i `docs/CLAUDE-REFERENCE.md`
|
||||
- **DNS wewnętrzny:** nordabiznes.inpi.local
|
||||
|
||||
## Disaster Recovery
|
||||
|
||||
**Pełna dokumentacja:** `docs/DR-PLAYBOOK.md`
|
||||
|
||||
### Metryki SLA
|
||||
|
||||
| Metryka | Wartość |
|
||||
|---------|---------|
|
||||
| **RTO** | 30-60 min |
|
||||
| **RPO** | 1 godzina |
|
||||
|
||||
### Lokalizacje backupów
|
||||
|
||||
| Lokalizacja | Ścieżka | Retencja |
|
||||
|-------------|---------|----------|
|
||||
| Hourly (lokalnie) | `/var/backups/nordabiz/hourly/` | 24h |
|
||||
| Daily (lokalnie) | `/var/backups/nordabiz/daily/` | 30 dni |
|
||||
| Offsite (PBS) | `10.22.68.127:/backup/nordabiz/` | 30 dni |
|
||||
| VM Snapshots | Proxmox | 3 snapshoty |
|
||||
|
||||
### Szybkie przywracanie
|
||||
|
||||
```bash
|
||||
# Lista dostępnych backupów
|
||||
ssh maciejpi@10.22.68.249 "ls -lt /var/backups/nordabiz/hourly/ | head -5"
|
||||
|
||||
# Restore z backupu
|
||||
ssh maciejpi@10.22.68.249 "sudo /var/www/nordabiznes/scripts/dr-restore.sh /var/backups/nordabiz/hourly/nordabiz_YYYYMMDD_HH.dump"
|
||||
|
||||
# Weryfikacja
|
||||
curl -I https://nordabiznes.pl/health
|
||||
```
|
||||
|
||||
### Cron backupy (automatyczne)
|
||||
|
||||
```bash
|
||||
# Backup co godzinę
|
||||
0 * * * * postgres pg_dump -Fc nordabiz > /var/backups/nordabiz/hourly/nordabiz_$(date +\%Y\%m\%d_\%H).dump
|
||||
|
||||
# Backup dzienny
|
||||
0 2 * * * postgres pg_dump -Fc nordabiz > /var/backups/nordabiz/daily/nordabiz_$(date +\%Y\%m\%d).dump
|
||||
|
||||
# Sync do PBS
|
||||
0 4 * * * root rsync -avz /var/backups/nordabiz/daily/ maciejpi@10.22.68.127:/backup/nordabiz/daily/
|
||||
```
|
||||
|
||||
Data wdrożenia: 2026-02-02
|
||||
|
||||
## Szablon profilu firmy
|
||||
|
||||
### Docelowa struktura (po optymalizacji)
|
||||
|
||||
```
|
||||
1. Header (nazwa, kategoria, badge, krótki opis)
|
||||
2. Pasek kontaktowy (www, email, telefon, lokalizacja)
|
||||
3. O firmie (połączone opisy)
|
||||
4. Usługi i kompetencje (połączone tagi)
|
||||
5. Wyróżniki (połączone z wartościami)
|
||||
6. Dane kontaktowe (pełne karty)
|
||||
7. Informacje prawne i biznesowe
|
||||
8. Social Media (wszystkie 6 platform)
|
||||
9. Strona WWW (analiza techniczna)
|
||||
```
|
||||
|
||||
**Szablon:** `templates/company_detail.html`
|
||||
|
||||
## News Monitoring
|
||||
|
||||
System monitoringu wzmianek o firmach w mediach. Panel: `/admin/news`
|
||||
|
||||
**Statusy:** `pending`, `approved`, `rejected`
|
||||
|
||||
Szczegóły: `docs/DEVELOPMENT.md#news-monitoring`
|
||||
|
||||
## ZOP Kaszubia News (ZOPK)
|
||||
|
||||
System monitoringu newsów projektu **Zielony Okręg Przemysłowy Kaszubia**.
|
||||
Panel: `/admin/zopk/news`
|
||||
|
||||
### Tematy istotne
|
||||
- Zielony Okręg Przemysłowy Kaszubia
|
||||
- Elektrownia jądrowa (Lubiatowo-Kopalino)
|
||||
- Offshore wind Bałtyk (Baltic Power, Baltica)
|
||||
- Via Pomerania, Droga Czerwona
|
||||
- Kongsberg (inwestycje w Rumi)
|
||||
- Izba Przedsiębiorców NORDA
|
||||
|
||||
### Tematy do odrzucenia
|
||||
- Turystyka na Kaszubach
|
||||
- Polityka ogólnopolska
|
||||
- Inne regiony Polski
|
||||
- Wypadki, clickbait
|
||||
|
||||
### Reguły auto-approve (WAŻNE!)
|
||||
|
||||
**Próg: score >= 3**
|
||||
|
||||
| Score | Status |
|
||||
|-------|--------|
|
||||
| 1-2 | `pending` |
|
||||
| 3-5 | `auto_approved` |
|
||||
|
||||
**Plik:** `zopk_news_service.py` (linie 890, 1124, 1145)
|
||||
|
||||
## Social Media
|
||||
|
||||
Panel audytu: `/admin/social-media`
|
||||
Platformy: Facebook, Instagram, LinkedIn, YouTube, TikTok, Twitter
|
||||
|
||||
Szczegóły: `docs/DEVELOPMENT.md#social-media`
|
||||
|
||||
## Audyt SEO
|
||||
|
||||
Panel: `/admin/seo`
|
||||
API: Google PageSpeed Insights
|
||||
|
||||
Szczegóły: `docs/DEVELOPMENT.md#audyt-seo`
|
||||
|
||||
## Dodatkowe rejestry (TODO - przyszłe integracje)
|
||||
|
||||
| Rejestr | Opis | API |
|
||||
|---------|------|-----|
|
||||
| **CRBR** | Centralny Rejestr Beneficjentów Rzeczywistych | https://crbr.podatki.gov.pl |
|
||||
| **SUDOP** | System Udostępniania Danych o Pomocy Publicznej | https://sudop.uokik.gov.pl |
|
||||
|
||||
Rejestry te mogą dostarczyć dodatkowe dane o firmach (beneficjenci rzeczywiści, otrzymana pomoc publiczna).
|
||||
|
||||
## Plan rozwoju
|
||||
|
||||
Roadmap, priorytety i strategia monetyzacji: `docs/ROADMAP.md`
|
||||
|
||||
## Cykliczna aktualizacja danych ze stron www firm
|
||||
|
||||
### Mechanizm
|
||||
|
||||
Skrypt `scripts/website_content_updater.py` automatycznie:
|
||||
1. Pobiera treść strony www każdej firmy członkowskiej
|
||||
2. Używa **Gemini 3 Flash** (darmowy plan) do ekstrakcji:
|
||||
- `services_extracted` - lista usług oferowanych przez firmę
|
||||
- `main_keywords` - główne słowa kluczowe
|
||||
- `content_summary` - krótkie podsumowanie działalności
|
||||
3. Zapisuje do tabeli `company_website_analysis`
|
||||
|
||||
### Uruchamianie ręczne
|
||||
|
||||
```bash
|
||||
# Wszystkie firmy
|
||||
cd /var/www/nordabiznes
|
||||
/var/www/nordabiznes/venv/bin/python3 scripts/website_content_updater.py
|
||||
|
||||
# Konkretna firma
|
||||
/var/www/nordabiznes/venv/bin/python3 scripts/website_content_updater.py --company-id 26
|
||||
|
||||
# Tylko firmy nieaktualizowane 30+ dni
|
||||
/var/www/nordabiznes/venv/bin/python3 scripts/website_content_updater.py --stale-days 30
|
||||
|
||||
# Podgląd bez zapisywania (dry-run)
|
||||
/var/www/nordabiznes/venv/bin/python3 scripts/website_content_updater.py --dry-run
|
||||
```
|
||||
|
||||
### Cron (automatyczne uruchamianie)
|
||||
|
||||
Uruchamiany **1-ego każdego miesiąca o 3:00** dla firm nieaktualizowanych 30+ dni:
|
||||
|
||||
```bash
|
||||
# Dodaj do crontab na serwerze produkcyjnym:
|
||||
sudo crontab -e
|
||||
|
||||
# Wpis:
|
||||
0 3 1 * * cd /var/www/nordabiznes && /var/www/nordabiznes/venv/bin/python3 scripts/website_content_updater.py --stale-days 30 >> /var/log/nordabiznes/website_updater.log 2>&1
|
||||
```
|
||||
|
||||
### Logi
|
||||
|
||||
Logi zapisywane do: `/var/log/nordabiznes/website_updater.log`
|
||||
|
||||
### Rate limiting
|
||||
|
||||
- **2 sekundy** przerwy między firmami (respektowanie limitów free tier Gemini)
|
||||
- ~150 firm = ~5 minut na pełną aktualizację
|
||||
|
||||
Data wdrożenia: 2026-02-01
|
||||
|
||||
## NordaGPT - Konfiguracja AI
|
||||
|
||||
### Aktualny model (stan: 2026-01-29)
|
||||
- **Model:** `gemini-3-flash-preview` (Gemini 3 Flash Preview)
|
||||
- **SDK:** `google-genai>=1.0.0` (nowy SDK z thinking mode)
|
||||
- **Inicjalizacja:** `app.py:286` - `gemini_service.init_gemini_service(model='3-flash')`
|
||||
- **Silnik chatu:** `nordabiz_chat.py` używa globalnego `gemini_service`
|
||||
|
||||
### Thinking Mode (NOWE!)
|
||||
Użytkownicy mogą wybierać poziom rozumowania AI w UI chatu (dropdown obok badge "Gemini 3").
|
||||
|
||||
| Poziom | Opis | Zastosowanie |
|
||||
|--------|------|--------------|
|
||||
| **Błyskawiczny** (minimal) | Najszybsze odpowiedzi | Proste pytania: "kto?", "gdzie?" |
|
||||
| **Szybki** (low) | Zrównoważony | Większość pytań o firmy i usługi |
|
||||
| **Głęboki** (high) | Maksymalna analiza | Złożone pytania, rekomendacje, strategia |
|
||||
|
||||
**Zmiana poziomu:**
|
||||
- UI: Dropdown w headerze chatu
|
||||
- API: `POST /api/chat/settings` z `{"thinking_level": "high"}`
|
||||
- Zapisywane w sesji użytkownika
|
||||
|
||||
### Dostępne modele w `gemini_service.py`
|
||||
| Alias | Model ID | Opis |
|
||||
|-------|----------|------|
|
||||
| `flash` | `gemini-2.5-flash` | Ogólnego przeznaczenia |
|
||||
| `flash-lite` | `gemini-2.5-flash-lite` | Ultra tani ($0.10/$0.40 per 1M) |
|
||||
| `pro` | `gemini-2.5-pro` | Najlepszy reasoning |
|
||||
| `flash-2.0` | `gemini-2.0-flash` | Poprzednia generacja (wycofywany 31.03.2026) |
|
||||
| `3-flash` | `gemini-3-flash-preview` | **AKTUALNY** - 7x lepszy reasoning, thinking mode |
|
||||
| `3-pro` | `gemini-3-pro-preview` | Premium - 2M context |
|
||||
|
||||
### Zmiana modelu
|
||||
```python
|
||||
# W app.py linia ~286:
|
||||
gemini_service.init_gemini_service(model='3-flash') # Zmień alias tutaj
|
||||
```
|
||||
|
||||
### UI Badge
|
||||
W `templates/chat.html` badge w headerze: `<span class="chat-header-badge">Gemini 3</span>`
|
||||
|
||||
## Prezentacja dla członków Izby (AKTYWNY PROJEKT)
|
||||
|
||||
### Cel projektu
|
||||
Stworzenie materiałów wideo prezentujących portal NordaBiz dla członków Izby NORDA.
|
||||
|
||||
### Produkty końcowe
|
||||
1. **Podcast NotebookLM** (2-3 min) - rozmowa AI o portalu
|
||||
2. **Zajawka Remotion** (30s) - scenariusz "Problem → Rozwiązanie"
|
||||
3. **Tutorial wideo** (2-3 min) - nagrania portalu + dialogi Zofia/Marek
|
||||
4. **Integracja z portalem** - Akademia + widget na dashboardzie
|
||||
|
||||
### Dokument źródłowy dla NotebookLM
|
||||
`docs/notebooklm-source.md` - markdown do wgrania do notebooklm.google.com
|
||||
|
||||
### Scenariusz zajawki 30s (Remotion)
|
||||
```
|
||||
[0-8s] "Szukasz partnera do projektu?"
|
||||
[8-16s] "Nie wiesz, kto w Izbie ma potrzebne kompetencje?"
|
||||
[16-24s] "NordaGPT zna 150 firm i pomoże Ci znaleźć"
|
||||
[24-30s] "nordabiznes.pl - Twoja sieć kontaktów"
|
||||
```
|
||||
|
||||
### Głosy edge-tts
|
||||
- Marek: `pl-PL-MarekNeural` (męski)
|
||||
- Zofia: `pl-PL-ZofiaNeural` (żeński)
|
||||
|
||||
### GIFy do nagrania (Chrome MCP)
|
||||
1. Strona główna zalogowanego
|
||||
2. Katalog firm + filtrowanie
|
||||
3. Profil firmy
|
||||
4. Chat NordaGPT - pytanie
|
||||
5. Chat NordaGPT - odpowiedź
|
||||
6. Forum
|
||||
7. Kalendarz
|
||||
8. Tablica B2B
|
||||
|
||||
### Pliki Remotion
|
||||
- Lokalizacja: `/Users/maciejpi/claude/projects/active/remotion/my-video/`
|
||||
- Komponenty: `NordaBizZajawka.tsx`, `NordaBizTutorial.tsx`
|
||||
- Audio: `public/audio/`, `public/voice/tutorial/`
|
||||
- Nagrania: `public/recordings/*.gif`
|
||||
|
||||
### Konto testowe (PROD)
|
||||
- Email: `test@nordabiznes.pl`
|
||||
- Hasło: `&Rc2LdbSw&jiGR0ek@Bz`
|
||||
|
||||
Data projektu: 2026-01-29
|
||||
- **Prezentacja Izby:** Remotion wideo — szczegóły w `docs/CLAUDE-REFERENCE.md`
|
||||
- **Plan rozwoju:** `docs/ROADMAP.md`
|
||||
|
||||
@ -323,6 +323,97 @@ def admin_company_delete(company_id):
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/companies/<int:company_id>/hard-delete', methods=['POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.ADMIN)
|
||||
def admin_company_hard_delete(company_id):
|
||||
"""Permanently delete an archived company and all related data."""
|
||||
from sqlalchemy import text
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||
|
||||
if company.status != 'archived':
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Tylko zarchiwizowane firmy mogą być trwale usunięte. Najpierw zarchiwizuj firmę.'
|
||||
}), 400
|
||||
|
||||
company_name = company.name
|
||||
db.expunge(company)
|
||||
|
||||
# 1) Nullable FKs → SET NULL
|
||||
nullable_fk_updates = [
|
||||
"UPDATE users SET company_id = NULL WHERE company_id = :cid",
|
||||
"UPDATE companies SET parent_company_id = NULL WHERE parent_company_id = :cid",
|
||||
"UPDATE companies SET it_provider_company_id = NULL WHERE it_provider_company_id = :cid",
|
||||
"UPDATE norda_events SET speaker_company_id = NULL WHERE speaker_company_id = :cid",
|
||||
"UPDATE classifieds SET company_id = NULL WHERE company_id = :cid",
|
||||
"UPDATE membership_applications SET company_id = NULL WHERE company_id = :cid",
|
||||
"UPDATE zopk_knowledge_entities SET company_id = NULL WHERE company_id = :cid",
|
||||
"UPDATE membership_fee_config SET company_id = NULL WHERE company_id = :cid",
|
||||
"UPDATE ai_usage_logs SET company_id = NULL WHERE company_id = :cid",
|
||||
]
|
||||
|
||||
# 2) NOT NULL FKs without CASCADE → DELETE records
|
||||
not_null_fk_deletes = [
|
||||
"DELETE FROM company_services WHERE company_id = :cid",
|
||||
"DELETE FROM company_competencies WHERE company_id = :cid",
|
||||
"DELETE FROM certifications WHERE company_id = :cid",
|
||||
"DELETE FROM awards WHERE company_id = :cid",
|
||||
"DELETE FROM company_events WHERE company_id = :cid",
|
||||
"DELETE FROM company_digital_maturity WHERE company_id = :cid",
|
||||
"DELETE FROM company_website_analysis WHERE company_id = :cid",
|
||||
"DELETE FROM company_quality_tracking WHERE company_id = :cid",
|
||||
"DELETE FROM company_website_content WHERE company_id = :cid",
|
||||
"DELETE FROM company_ai_insights WHERE company_id = :cid",
|
||||
"DELETE FROM ai_enrichment_proposals WHERE company_id = :cid",
|
||||
"DELETE FROM maturity_assessments WHERE company_id = :cid",
|
||||
]
|
||||
|
||||
# 3) CASCADE tables (auto-handled by DB, but explicit for safety)
|
||||
cascade_fk_deletes = [
|
||||
"DELETE FROM user_company_permissions WHERE company_id = :cid",
|
||||
"DELETE FROM company_contacts WHERE company_id = :cid",
|
||||
"DELETE FROM company_social_media WHERE company_id = :cid",
|
||||
"DELETE FROM company_recommendations WHERE company_id = :cid",
|
||||
"DELETE FROM gbp_audits WHERE company_id = :cid",
|
||||
"DELETE FROM it_audits WHERE company_id = :cid",
|
||||
"DELETE FROM it_collaboration_matches WHERE company_a_id = :cid OR company_b_id = :cid",
|
||||
"DELETE FROM membership_fees WHERE company_id = :cid",
|
||||
"DELETE FROM zopk_company_links WHERE company_id = :cid",
|
||||
"DELETE FROM company_people WHERE company_id = :cid",
|
||||
"DELETE FROM krs_audits WHERE company_id = :cid",
|
||||
"DELETE FROM company_pkd WHERE company_id = :cid",
|
||||
"DELETE FROM company_financial_reports WHERE company_id = :cid",
|
||||
]
|
||||
|
||||
for sql in nullable_fk_updates + not_null_fk_deletes + cascade_fk_deletes:
|
||||
try:
|
||||
db.execute(text(sql), {"cid": company_id})
|
||||
except Exception:
|
||||
pass # Table may not exist yet
|
||||
|
||||
db.execute(text("DELETE FROM companies WHERE id = :cid"), {"cid": company_id})
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Admin {current_user.email} permanently deleted company {company_name} (ID: {company_id})")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Firma "{company_name}" została trwale usunięta'
|
||||
})
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error permanently deleting company {company_id}: {e}")
|
||||
return jsonify({'success': False, 'error': f'Błąd: {str(e)}'}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/companies/<int:company_id>/assign-user', methods=['POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
|
||||
@ -621,6 +621,9 @@ def release_notes():
|
||||
'<strong>Środowisko staging</strong> - wizualne wskaźniki testowe (oznaczenie staging w UI)',
|
||||
'Pinning wersji zależności w requirements.txt',
|
||||
'Aktualizacja beautifulsoup4, cryptography, SQLAlchemy, Werkzeug',
|
||||
'<strong>Strefa RADA</strong> - uproszczona do systemu posiedzeń (usunięto sekcję dokumentów i konwersję LibreOffice)',
|
||||
'<strong>Moduł Korzyści</strong> - kolumna prowizji widoczna tylko dla właściciela',
|
||||
'<strong>Trwałe usuwanie firm</strong> - workflow: aktywna → archiwum → trwałe usunięcie (tylko ADMIN)',
|
||||
],
|
||||
'fix': [
|
||||
'<strong>CSRF tokeny w formularzach publikacji</strong> - naprawiono brak tokenów w formularzach programu/protokołu',
|
||||
@ -628,6 +631,7 @@ def release_notes():
|
||||
'Przycisk RSVP dla wydarzeń z ograniczonym dostępem',
|
||||
'Poprawna nazwa endpointu membership.apply',
|
||||
'Obsługa brakujących bibliotek systemowych weasyprint (OSError)',
|
||||
'<strong>Usuwanie użytkowników</strong> - naprawiono błąd FK cascade przy relacjach zależnych',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -555,11 +555,19 @@
|
||||
<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
{% if company.status == 'archived' and current_user.is_admin %}
|
||||
<button class="btn-icon danger" onclick="hardDeleteCompany({{ company.id }}, '{{ company.name|e }}')" title="Trwale usuń">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
{% elif company.status != 'archived' %}
|
||||
<button class="btn-icon danger" onclick="deleteCompany({{ company.id }}, '{{ company.name|e }}')" title="Archiwizuj">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -945,8 +953,11 @@
|
||||
|
||||
// Delete (Archive) Company
|
||||
function deleteCompany(companyId, companyName) {
|
||||
document.getElementById('confirmIcon').className = 'modal-icon warning';
|
||||
document.getElementById('confirmTitle').textContent = 'Archiwizuj firmę';
|
||||
document.getElementById('confirmDescription').textContent = `Czy na pewno chcesz zarchiwizować firmę "${companyName}"? Firma nie będzie widoczna w katalogu.`;
|
||||
document.getElementById('confirmAction').textContent = 'Potwierdź';
|
||||
document.getElementById('confirmAction').className = 'btn btn-danger';
|
||||
confirmCallback = async () => {
|
||||
try {
|
||||
const response = await fetch(`/admin/companies/${companyId}/delete`, {
|
||||
@ -971,6 +982,41 @@
|
||||
document.getElementById('confirmModal').classList.add('active');
|
||||
}
|
||||
|
||||
// Hard Delete (Permanent) - only for archived companies
|
||||
function hardDeleteCompany(companyId, companyName) {
|
||||
document.getElementById('confirmIcon').className = 'modal-icon danger';
|
||||
document.getElementById('confirmTitle').textContent = 'Trwałe usunięcie firmy';
|
||||
document.getElementById('confirmDescription').innerHTML =
|
||||
`Czy na pewno chcesz <strong>trwale usunąć</strong> firmę "${companyName}"?<br><br>` +
|
||||
'<strong style="color: var(--error);">Ta operacja jest NIEODWRACALNA!</strong><br>' +
|
||||
'Wszystkie dane firmy zostaną permanentnie usunięte.';
|
||||
document.getElementById('confirmAction').textContent = 'Trwale usuń';
|
||||
document.getElementById('confirmAction').className = 'btn btn-danger';
|
||||
confirmCallback = async () => {
|
||||
try {
|
||||
const response = await fetch(`/admin/companies/${companyId}/hard-delete`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
const row = document.querySelector(`tr[data-company-id="${companyId}"]`);
|
||||
if (row) row.remove();
|
||||
showToast(data.message, 'success');
|
||||
} else {
|
||||
showToast(data.error || 'Wystąpił błąd', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('Błąd połączenia', 'error');
|
||||
}
|
||||
};
|
||||
document.getElementById('confirmModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeConfirmModal() {
|
||||
document.getElementById('confirmModal').classList.remove('active');
|
||||
confirmCallback = null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user