feat(core): setup postgres, minio, and authentik next-auth

This commit is contained in:
AI Assistant
2026-02-27 10:29:54 +02:00
parent 3b1ba589f0
commit 0ad7e835bd
18 changed files with 1654 additions and 105 deletions
+2
View File
@@ -43,3 +43,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
/src/generated/prisma
+195 -41
View File
@@ -269,11 +269,165 @@ src/modules/hot-desk/
---
## PHASE 3 — Quality & Testing
## PHASE 3 — Replanificare Detaliată (Ideation & Requirements)
> Fază adăugată pentru rafinarea cerințelor, UX și logicii de business înainte de implementarea tehnică.
### 3.01 `[UI/UX]` Header & Navigare Globală
**Cerințe noi:**
- **Logo-uri:** Mărirea dimensiunii logo-urilor în header ("nu se vede nimic acum").
- **Mini-joc Logo:** La click pe logo-uri, acestea se schimbă (efect interactiv/fun, gimmick vizual).
- **Navigare:** Click pe titlul aplicației redirecționează către Dashboard.
### 3.02 `[BUSINESS]` Registratura — Integrare și UX
**Cerințe noi:**
- **Tipuri de documente (Bidirecțional):** Câmpul de tip document comunică cu Tag Manager. Dacă se introduce un tip nou, se salvează automat ca etichetă nouă sub o categorie fixă "Registratura" (pentru a acoperi și apeluri telefonice, video-uri, nu doar documente fizice).
- **Expeditor/Destinatar (Bidirecțional):** Autocomplete legat de Address Book. Dacă un contact nu există, se creează automat (minim Nume) și apare un popup rapid pentru a adăuga opțional Telefon/Email.
- **Status Simplificat:** Eliminarea statusului "Deschis". Înlocuire cu un simplu checkbox "Închis" (implicit totul e deschis).
- **Termen Limită Intern:** Clarificarea funcției (ex: termen pentru a răspunde la o intrare). Adăugare tooltip/info box explicativ pentru a maximiza eficiența echipei.
- **Pregătire Integrare ERP (Responsabil):** Adăugarea unui câmp "Responsabil" (Assignee) pentru a aloca sarcini interne, gândit arhitectural pentru a putea fi expus/sincronizat ușor printr-un API/Webhook viitor către un sistem ERP extern.
- **Legături între intrări/ieșiri (Thread-uri & Branches):** Posibilitatea de a lega o ieșire de o intrare specifică (ex: "Răspuns la adresa nr. X"), creând un "fir" (thread) vizual al conversației instituționale. Trebuie să suporte și "branching" (ex: o intrare generează mai multe ieșiri conexe către instituții diferite). UI-ul trebuie să rămână extrem de simplu și intuitiv (ex: vizualizare tip arbore simplificat sau listă indentată în detaliile documentului).
### 3.03 `[BUSINESS]` Registratura — Termene Legale (Flux Nou)
**Cerințe noi:**
- **Declanșare Termen:** Termenul legal pentru ieșiri curge DOAR de la data înregistrării la destinatar, nu de la data trimiterii interne.
- **Câmpuri Noi:** "Număr înregistrare destinatar" și "Data înregistrare destinatar".
- **Sistem de Alerte:** Notificări/Atenționări clare (în Dashboard și în modul) pentru ieșirile cu termen legal care NU au încă completate datele de la destinatar.
- **Categorii Termene:** Mutarea "Certificat de Urbanism" (CU) pe prima pagină/categorie principală (nu sub Avize). Adăugare subcategorie: "Cerere de prelungire CU".
- **Acord Tacit:** Modificare automată a statusului pentru documentele fără răspuns în termenul legal (acord tacit).
- **Generare Raport/Declarație (Integrare Word Templates):** Generarea unui mini-log/raport combinat cu o "Declarație pe proprie răspundere" a proiectantului. Se va folosi un șablon din modulul _Word Templates_, populat automat cu datele din Registratură (data trimiterii, lipsa răspunsului), permițând editarea ulterioară în Word.
- **Istoric Modificări Termene (Audit Log):** Un mini-log de audit vizibil pe fiecare termen legal (cine a modificat data, când a fost adăugat numărul de la destinatar). _Notă: Necesită implementarea autentificării (Phase 6 - Authentik) pentru a asocia acțiunile cu utilizatori reali din Active Directory._
- **Valabilitate Documente (Expirare CU/AC):** Adăugarea unui sistem de urmărire a valabilității pentru documente emise (ex: Certificat de Urbanism, Autorizație de Construire). Sistemul trebuie să permită setarea unei date de expirare și să genereze alerte/reamintiri cu X zile înainte de expirare (pentru a iniția procedurile de prelungire).
- **Pregătire Web Scraping (Wishlist):** Adăugarea unui câmp opțional "URL Verificare Status" (ex: link către portalul primăriei) și "ID Urmărire Extern". Arhitectura trebuie să permită pe viitor rularea unui job de fundal (ex: via N8N sau un worker intern) care să facă scraping/API call pe acel URL și să actualizeze automat statusul în ArchiTools.
### 3.04 `[ARCHITECTURE]` Autentificare & Identitate (Pregătire)
**Cerințe noi:**
- **Devansare Prioritate Authentik:** Deoarece funcționalități precum "Responsabil" și "Audit Log" depind de identitatea utilizatorului, integrarea cu Authentik (legat la Domain Controller/Active Directory) devine o prioritate arhitecturală.
- **Sincronizare Useri:** Sistemul trebuie să poată citi lista de utilizatori din Authentik/AD pentru a popula dropdown-urile de tip "Responsabil" chiar și pentru utilizatorii care nu s-au logat încă în ArchiTools.
### 3.05 `[BUSINESS]` Email Signature — Automatizare și Branding
**Cerințe noi:**
- **Sincronizare Active Directory:** Precompletarea automată a câmpurilor (Nume, Funcție, Telefon, Email) pe baza utilizatorului logat (via Authentik/AD), eliminând necesitatea introducerii manuale.
- **Bannere Promoționale Centrale:** Posibilitatea de a adăuga un banner imagine/link sub semnătură (ex: "Suntem închiși de sărbători", "Proiect nou"). Acestea trebuie să poată fi gestionate centralizat (ex: un admin setează bannerul activ pentru toată compania).
- **Slider Dimensiune Logo:** Adăugarea unui control (slider) în UI pentru a ajusta fin dimensiunea logo-ului companiei în semnătura generată.
- **Elemente Grafice Personalizate (Icoane Adresă/Telefon):** Înlocuirea elementelor grafice generice (liniile oblice de la Beletage) cu elemente vizuale specifice și distincte pentru Urban Switch și Studii de Teren, păstrând un aspect interesant și profesional.
### 3.06 `[BUSINESS]` Template Library (Fostul Word Templates)
**Cerințe noi:**
- **Redenumire și Extindere:** Modulul devine "Template Library" (Șabloane Documente) pentru a suporta nu doar Word, ci și Excel (.xlsx), Archicad (.tpl, .pln), DWG, PDF, etc.
- **Sistem de Versionare Automat:** Eliminarea introducerii manuale a versiunii. Adăugarea unui buton "Revizie Nouă" care preia datele curente, incrementează automat versiunea (ex: v1.0 -> v1.1) și păstrează istoricul versiunilor anterioare pentru a putea descărca o variantă mai veche la nevoie.
- **Clarificare UX (URL, Magic Wand, Placeholders):**
- _Problema curentă:_ UI-ul cu URL și "Magic Wand" este confuz pentru utilizatori.
- _Soluția:_ Simplificarea interfeței. "Magic Wand-ul" (care extrage automat variabile de tip `{{Nume}}` din text) va rula automat și invizibil în fundal _doar_ când se încarcă un fișier `.docx`. Pentru Excel sau Archicad, această secțiune de "Placeholders" va fi ascunsă sau marcată ca indisponibilă, evitând confuzia.
- _Încărcare Fișiere:_ Distincție clară în UI între "Încarcă fișier" (care se va salva în viitorul MinIO) și "Adaugă link extern" (pentru fișiere mari ținute pe SharePoint/Nextcloud).
### 3.07 `[BUSINESS]` Digital Signatures — Simplificare și Suport TIFF
**Cerințe noi:**
- **Simplificare Formular:** Eliminarea câmpurilor inutile pentru fluxul actual: "inițiale", "data expirare", "statut legal" și "note utilizare".
- **Suport Nativ TIFF:** Permiterea încărcării fișierelor `.tiff` (formatul principal folosit pentru semnături/ștampile). _Notă tehnică: Deoarece browserele nu randează nativ TIFF, se va implementa o conversie client-side (ex: via `tiff.js` sau un canvas) doar pentru preview-ul vizual, dar sistemul va stoca și va folosi fișierul TIFF original._
- **Organizare pe Subcategorii:** Adăugarea posibilității de a grupa semnăturile/ștampilele pe subcategorii clare (ex: "Colaboratori firma X", "Experți tehnici", "Verificatori de proiect").
- **Opțiuni de Descărcare Avansate:**
- Descărcare fișier original (`.tiff` / `.png`).
- Generare și descărcare document Word (`.docx`) gol care conține imaginea inserată automat.
- Generare și descărcare document PDF gol care conține imaginea inserată automat.
### 3.08 `[BUSINESS]` IT Inventory — Simplificare și Status Nou
**Cerințe noi:**
- **Eliminare Câmpuri Inutile:** Se vor șterge câmpurile "Atribuit către" (assignedTo), "Data achiziție", "Cost achiziție" și "Expirare garanție".
- **Tipuri Dinamice (Bidirecțional):** Câmpul "Tip echipament" (ex: Laptop, Monitor) trebuie să permită adăugarea interactivă de noi tipuri direct din formular (similar cu logica de la Registratură/Tag Manager).
- **Status Nou "Închiriat":** Adăugarea statusului "Închiriat" în lista de statusuri.
- **Atenționare Vizuală (Animație):** Când un echipament are statusul "Închiriat", badge-ul/rândul respectiv trebuie să aibă o animație subtilă (ex: un puls de culoare, un glow) pentru a "sări în ochi" în tabelul principal.
- **Vizualizare Rack (Server Rack 42U):**
- Adăugarea unei vizualizări grafice (schematice) a unui rack de servere standard de 42U.
- În formularul de echipament, dacă tipul este compatibil (ex: Server, Switch, UPS, Patch Panel), se vor adăuga două câmpuri noi: "Poziție Rack (U)" (ex: 12) și "Dimensiune (U)" (ex: 1U, 2U).
- Echipamentele care au aceste date completate vor fi randate automat în vizualizarea grafică a rack-ului.
- La hover pe un echipament din rack, va apărea un tooltip cu detaliile de bază (Nume, IP, Status).
### 3.09 `[BUSINESS]` Address Book — Tipuri Dinamice
**Cerințe noi:**
- **Tip Contact Dinamic:** Câmpul "Tip" (ex: Client, Furnizor, Instituție) trebuie să devină un câmp de tip "creatable select" (combinație între dropdown și text input). Utilizatorul trebuie să poată scrie un tip nou (ex: "Colaborator Extern"), iar acesta să se salveze automat și să apară în dropdown pentru viitoarele contacte.
### 3.10 `[UI/UX]` Hot Desk — Orientare Vizuală
**Cerințe noi:**
- **Punct de Reper (Fereastră):** În vizualizarea grafică a camerei (room layout), trebuie adăugată schematic o fereastră pe peretele din stânga. Acest element vizual va servi ca punct de reper pentru ca utilizatorii să se poată orienta ușor și să înțeleagă care birou este care (ex: "biroul de lângă geam", "biroul de la ușă").
### 3.11 `[BUSINESS]` Password Vault — Câmpuri Noi
**Cerințe noi:**
- **Câmp Email:** Adăugarea unui câmp distinct pentru "Email", separat de "Username" (deoarece multe conturi folosesc un email pentru login, dar au și un username separat, sau invers).
- **Link Clickabil:** Asigurarea că URL-ul salvat este afișat ca un link clickabil (deschidere în tab nou) direct din lista de parole, pentru acces rapid.
### 3.12 `[BUSINESS]` Mini Utilities — Extindere și Fix-uri
**Cerințe noi:**
- **Transformare Numere în Litere (Text):** Adăugarea unei funcții care transformă o sumă numerică în text (ex: `432.20` -> `patrusutetreizeicisidoi lei și douăzeci de bani` și varianta cu spații `patru sute treizeci și doi lei și douăzeci de bani`). Util pentru contracte și facturi.
- **Convertor Suprafețe (Bidirecțional):** Modificarea convertorului de suprafețe astfel încât utilizatorul să poată introduce o valoare în _orice_ câmp (mp, ha, ari etc.), iar restul câmpurilor să se calculeze automat pe baza acelei intrări.
- **Convertor U-R (Bidirecțional):** Modificarea convertorului U-value / R-value pentru a funcționa în ambele sensuri (introduci U și afli R, sau introduci R și afli U).
- **Fix MDLPA (Iframe Refused to Connect):** Rezolvarea problemei cu iframe-ul MDLPA care refuză conexiunea (probabil din cauza politicilor `X-Frame-Options` sau `Content-Security-Policy` de pe serverul lor). _Soluție propusă: Înlocuirea iframe-ului cu un link extern clar sau, dacă e posibil, un proxy/scraper intern._
- **Îmbunătățiri PDF Reducer:**
- Adăugare suport Drag & Drop pentru fișiere.
- Simplificare opțiuni compresie: eliminare "fără modificare" și "compresie minimă". Păstrare doar "Echilibrat" și "Maximă".
- Optimizare "Compresie Maximă": Ajustarea parametrilor (probabil via Stirling PDF API sau Ghostscript) pentru a egala performanța extremă de la iLovePDF (calitate acceptabilă la dimensiune minimă).
- Adăugare opțiune: "Remove Protection" (Deblocare PDF).
- Adăugare opțiune: "Convert to PDF/A" (pentru arhivare pe termen lung, cerut adesea de instituții).
- **Tool Nou: DWG to DXF:** Adăugarea unui convertor simplu din format DWG în DXF (necesită probabil un serviciu de backend sau integrare cu un API extern/CLI tool pe server).
- **Tool Nou (Propunere): Extractor Paletă Culori:** Un tool unde încarci o imagine (ex: un render sau un moodboard) și îți extrage automat paleta de culori principale în format HEX/RGB, util pentru prezentări.
### 3.13 `[BUSINESS]` Tag Manager — Sincronizare ManicTime
**Cerințe noi:**
- **Sincronizare Bidirecțională:** Modulul trebuie să citească și să scrie direct în fișierul text folosit de ManicTime (`\\time\tags\Tags.txt` aflat pe serverul Windows 10 Enterprise LTSC). Orice tag adăugat în ArchiTools trebuie să apară în ManicTime și invers.
- **Versionare Fișier (Backup):** La fiecare modificare a fișierului `Tags.txt` din ArchiTools, sistemul trebuie să creeze automat un backup al versiunii anterioare (ex: `Tags_backup_YYYYMMDD_HHMMSS.txt`) în același folder, pentru a preveni pierderea accidentală a structurii de tag-uri.
- **Validare Ierarhie:** Asigurarea că tag-urile adăugate respectă formatul ierarhic cerut de ManicTime (ex: `Proiect, Faza, Activitate`).
### 3.14 `[ARCHITECTURE]` Storage & Securitate
**Cerințe noi:**
- **Migrare Storage (Prioritate):** Devansarea migrării de la `localStorage` la o soluție robustă (MinIO pentru fișiere și o bază de date reală, ex: PostgreSQL/Prisma) pentru a asigura persistența și partajarea datelor între toți utilizatorii.
- **Criptare Parole:** În modulul Password Vault, parolele trebuie criptate real în baza de date (nu doar ascunse în UI), eliminând warning-ul actual de securitate.
- **Integrare Passbolt (Wishlist):** Studierea posibilității de a lega Password Vault-ul din ArchiTools direct de instanța voastră de Passbolt (via API), pentru a avea un singur "source of truth" securizat pentru parole.
### 3.15 `[BUSINESS]` AI Tools — Extindere și Integrare
**Cerințe noi:**
- **Integrare AI Chat cu Tag Manager:** Sesiunile de chat trebuie să fie legate direct de proiectele din Tag Manager. Lista de proiecte va fi menținută actualizată de AI, iar utilizatorul va putea selecta la ce proiect se referă discuția curentă pentru a oferi context automat.
- **Prompt Generator (Extindere):** Adăugarea unei bare de căutare (Search) și a unor șabloane specifice pentru generare de imagini (ex: Midjourney/Stable Diffusion prompts pentru randări, explainer images, infografice arhitecturale).
- **Activare AI Chat (Demo/Live):** Conectarea modulului de AI Chat la un API real (OpenAI/Anthropic/Ollama) pentru a putea fi testat de echipă.
- **Generatoare Media (Node-based Canvas):** Interfața pentru generarea de imagini/media să fie construită pe un sistem _node-based_ (infinite canvas), permițând legarea vizuală a prompturilor, imaginilor de referință și setărilor.
- **Nod de Interpretare 3D (Wishlist):** Un nod avansat care preia o imagine/screenshot simplu și o interpretează într-o mini-scenă 3D (un viewport simplificat). De exemplu, obiectul principal devine un volum pe care utilizatorul îl poate roti în 3D (sau roti camera în jurul lui) pentru a explica vizual AI-ului ce modificări spațiale dorește (inspirat din concepte tip arXiv:2601.23265, adaptat pentru use-case-ul de arhitectură).
---
## PHASE 4 — Quality & Testing
> Foundation work: tests, CI, docs, data safety.
### 3.01 `[STANDARD]` Install Testing Framework (Vitest)
### 4.01 `[STANDARD]` Install Testing Framework (Vitest)
**What:** Install and configure Vitest with React Testing Library.
@@ -286,7 +440,7 @@ npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom @vi
---
### 3.02 `[STANDARD]` Unit Tests — Critical Services
### 4.02 `[STANDARD]` Unit Tests — Critical Services
**What:** Write tests for the most critical business logic:
@@ -300,7 +454,7 @@ npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom @vi
---
### 3.03 `[STANDARD]` Data Export/Import for All Modules
### 4.03 `[STANDARD]` Data Export/Import for All Modules
**What:** Create a shared utility for backing up localStorage data:
@@ -313,7 +467,7 @@ npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom @vi
---
### 3.04 `[LIGHT]` Update Stale Documentation
### 4.04 `[LIGHT]` Update Stale Documentation
**What:** Update docs to reflect current state:
@@ -323,17 +477,17 @@ npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom @vi
---
### 3.05 `[LIGHT]` Wire External Tool URLs to Env Vars
### 4.05 `[LIGHT]` Wire External Tool URLs to Env Vars
**What:** `src/config/external-tools.ts` has hardcoded IPs. Wire to `process.env.NEXT_PUBLIC_*_URL` with fallback.
---
## PHASE 4 — AI Chat Integration
## PHASE 5 — AI Chat Integration
> Make Module 13 functional.
### 4.01 `[HEAVY]` AI Chat — Real API Integration
### 5.01 `[HEAVY]` AI Chat — Real API Integration
**What:** Replace demo mode with actual AI provider calls:
@@ -357,7 +511,7 @@ AI_DEFAULT_MODEL=claude-sonnet-4-6-20261001
---
### 4.02 `[STANDARD]` AI Chat — Domain-Specific System Prompts
### 5.02 `[STANDARD]` AI Chat — Domain-Specific System Prompts
**What:** Architecture office-focused conversation modes:
@@ -370,17 +524,17 @@ AI_DEFAULT_MODEL=claude-sonnet-4-6-20261001
---
### 4.03 `[LIGHT]` Enable AI Chat Feature Flag
### 5.03 `[LIGHT]` Enable AI Chat Feature Flag
**What:** Set `module.ai-chat` enabled in `flags.ts` + production `.env`.
---
## PHASE 5 — Authentication (Authentik SSO)
## PHASE 6 — Authentication (Authentik SSO)
> Real users, real permissions. Requires server admin access.
### 5.01 `[HEAVY]` Authentik OIDC Integration
### 6.01 `[HEAVY]` Authentik OIDC Integration
**What:** Replace stub user with real Authentik SSO.
@@ -399,53 +553,53 @@ AI_DEFAULT_MODEL=claude-sonnet-4-6-20261001
---
### 5.02 `[STANDARD]` Module-Level Access Control
### 6.02 `[STANDARD]` Module-Level Access Control
**What:** Implement `canAccessModule()` with role-based rules. FeatureGate checks flag + permission.
**Depends on:** 5.01
**Depends on:** 6.01
---
### 5.03 `[STANDARD]` Data Visibility Enforcement
### 6.03 `[STANDARD]` Data Visibility Enforcement
**What:** Filter storage results by `visibility` and `createdBy` fields (already stored on every entity, never enforced).
**Depends on:** 5.01
**Depends on:** 6.01
---
### 5.04 `[LIGHT]` Audit Logging
### 6.04 `[LIGHT]` Audit Logging
**What:** Log create/update/delete actions with user ID + timestamp. Console initially, later storage/N8N.
**Depends on:** 5.01
**Depends on:** 6.01
---
## PHASE 6 — Storage Migration (localStorage → Database)
## PHASE 7 — Storage Migration (localStorage → Database)
> Multi-user shared data. Requires PostgreSQL + infrastructure changes.
### 6.01 `[HEAVY]` PostgreSQL + Prisma Setup
### 7.01 `[HEAVY]` PostgreSQL + Prisma Setup
**What:** Add PostgreSQL container, create Prisma schema for all entities, run migrations.
**Infrastructure:** New `postgres` service in `docker-compose.yml`.
---
### 6.02 `[HEAVY]` API Storage Adapter
### 7.02 `[HEAVY]` API Storage Adapter
**What:** Create `ApiStorageAdapter` implementing `StorageService`. Use Next.js API routes + Prisma.
**Depends on:** 6.01
**Depends on:** 7.01
---
### 6.03 `[STANDARD]` Data Migration Tool
### 7.03 `[STANDARD]` Data Migration Tool
**What:** One-time export from localStorage → import to PostgreSQL. Preserve IDs and timestamps.
**Depends on:** 6.02
**Depends on:** 7.02
---
### 6.04 `[HEAVY]` MinIO File Storage
### 7.04 `[HEAVY]` MinIO File Storage
**What:** Create `MinioAdapter` for file uploads. Migrate base64 attachments to MinIO objects.
**MinIO already running** at http://10.10.10.166:9003.
@@ -453,83 +607,83 @@ AI_DEFAULT_MODEL=claude-sonnet-4-6-20261001
---
## PHASE 7 — Advanced Features
## PHASE 8 — Advanced Features
> Cross-cutting features that enhance the entire platform.
### 7.01 `[HEAVY]` Project Entity & Cross-Module Linking
### 8.01 `[HEAVY]` Project Entity & Cross-Module Linking
**What:** New module: Projects. Central entity linking Registratura entries, Tags, Contacts, Templates.
**Reference:** `docs/DATA-MODEL.md` lines 566-582.
---
### 7.02 `[STANDARD]` Global Search (Cmd+K)
### 8.02 `[STANDARD]` Global Search (Cmd+K)
**What:** Search across all modules. Each module registers a search provider. Header bar integration.
---
### 7.03 `[STANDARD]` Notification System
### 8.03 `[STANDARD]` Notification System
**What:** Bell icon in header. Deadline alerts, overdue warnings, tacit approval triggers.
---
### 7.04 `[STANDARD]` Registratura — Print/PDF Export
### 8.04 `[STANDARD]` Registratura — Print/PDF Export
**What:** Export registry as formatted PDF. Options: full registry, single entry, deadline summary.
---
### 7.05 `[STANDARD]` Word Templates — Clause Library + Document Generator
### 8.05 `[STANDARD]` Word Templates — Clause Library + Document Generator
**What:** In-app clause composition, template preview, simple Word generation from templates.
---
### 7.06 `[STANDARD]` N8N Webhook Integration
### 8.06 `[STANDARD]` N8N Webhook Integration
**What:** Fire webhooks on events (new entry, deadline approaching, status change). N8N at http://10.10.10.166:5678.
---
### 7.07 `[STANDARD]` Mobile Responsiveness Audit
### 8.07 `[STANDARD]` Mobile Responsiveness Audit
**What:** Test all modules on 375px/768px. Fix overflowing tables, forms, sidebar.
---
## PHASE 8 — Security & External Access
## PHASE 9 — Security & External Access
### 8.01 `[HEAVY]` Guest/External Access Role
### 9.01 `[HEAVY]` Guest/External Access Role
**What:** Read-only guest role, time-limited share links. Depends on Authentik (Phase 5).
**What:** Read-only guest role, time-limited share links. Depends on Authentik (Phase 6).
---
### 8.02 `[STANDARD]` CrowdSec Integration
### 9.02 `[STANDARD]` CrowdSec Integration
**What:** IP banning for brute force. CrowdSec at http://10.10.10.166:8088.
---
### 8.03 `[LIGHT]` SSL/TLS via Let's Encrypt
### 9.03 `[LIGHT]` SSL/TLS via Let's Encrypt
**What:** When public domain ready, configure in Nginx Proxy Manager.
---
## PHASE 9 — CI/CD
## PHASE 10 — CI/CD
### 9.01 `[STANDARD]` Gitea Actions CI Pipeline
### 10.01 `[STANDARD]` Gitea Actions CI Pipeline
**What:** `.gitea/workflows/ci.yml` — lint, typecheck, test, build on push.
**Check first:** Is Gitea Actions runner installed on server?
---
### 9.02 `[STANDARD]` E2E Tests (Playwright)
### 10.02 `[STANDARD]` E2E Tests (Playwright)
**What:** End-to-end tests for critical flows: navigation, Registratura CRUD, email signature, tag management.
+63
View File
@@ -4,6 +4,69 @@
---
## Session — 2026-02-27 (GitHub Copilot - Gemini 3.1 Pro)
### Completed
- **Etapa 2: Autentificare (Authentik / Active Directory)**
- Instalat `next-auth` pentru gestionarea sesiunilor în Next.js.
- Configurat provider-ul OIDC pentru Authentik (`src/app/api/auth/[...nextauth]/route.ts`).
- Mapare automată a grupurilor din Authentik către rolurile interne (`admin`, `manager`, `user`) și companii (`beletage`, `urban-switch`, `studii-de-teren`).
- Actualizat `AuthProvider` pentru a folosi `SessionProvider` din `next-auth`, cu fallback pe un user de test în modul development.
- Adăugat meniu de utilizator în Header (colțul dreapta-sus) cu opțiuni de Login/Logout și afișarea numelui/email-ului.
- Adăugat variabilele de mediu necesare în `.env` (`AUTHENTIK_CLIENT_ID`, `AUTHENTIK_CLIENT_SECRET`, `AUTHENTIK_ISSUER`).
- **Etapa 1: Fundația (Baza de date & Storage)**
- Instalat și configurat Prisma ORM (v6) pentru conectarea la PostgreSQL.
- Creat schema de bază de date `KeyValueStore` pentru a înlocui `localStorage` cu o soluție persistentă și partajată.
- Rulat prima migrare (`npx prisma db push`) pe serverul de producție (`10.10.10.166:5432`).
- Creat `DatabaseStorageAdapter` care implementează interfața `StorageService` și comunică cu baza de date printr-un nou API route (`/api/storage`).
- Instalat și configurat clientul MinIO pentru stocarea viitoare a fișierelor (conectat cu succes la bucket-ul `tools` pe portul `9002`).
- Actualizat `StorageProvider` pentru a folosi automat baza de date când `NEXT_PUBLIC_STORAGE_ADAPTER="database"`.
- Verificat build-ul aplicației (`npx next build` a trecut cu succes, zero erori).
### Notes
- Toate cele 14 module beneficiază acum instantaneu de persistență reală în baza de date PostgreSQL, fără a fi necesară rescrierea logicii lor interne.
- Autentificarea este pregătită. Urmează configurarea aplicației în interfața de admin Authentik.
---
## Session — 2026-02-26 (GitHub Copilot - Gemini 3.1 Pro)
### Completed
- **Phase 3: Replanificare Detaliată (Ideation & Requirements)**
- Added a new Phase 3 in `ROADMAP.md` to track detailed requirements before implementation.
- Shifted subsequent phases (Quality & Testing became Phase 4, etc.).
- Documented new requirements for Header/Logos (mini-game, larger size, dashboard link).
- Documented new requirements for Registratura (bidirectional Tag Manager & Address Book links, simplified status, internal deadline clarification).
- Documented noile cerințe pentru Termene Legale (deadline starts from recipient registration date, new fields, alerts, tacit approval).
- Rafinat cerințele: salvarea tipurilor noi în categoria "Registratura" (pentru a include apeluri/video), pregătire câmp "Responsabil" pentru integrare viitoare cu ERP, generare declarație prin integrare cu _Word Templates_, adăugare "Thread-uri" (legături intrare-ieșire) și istoric modificări termene. - Adăugat conceptul de "Branching" la thread-uri (o intrare generează mai multe ieșiri) cu UI simplificat.
- Adăugat secțiunea 3.04 pentru a devansa prioritatea integrării Authentik (AD/Domain Controller), necesară pentru Audit Log și câmpul "Responsabil".
- Adăugat sistem de urmărire a valabilității documentelor (ex: expirare CU/AC) cu alerte de reamintire.
- Adăugat pregătire arhitecturală (câmpuri URL/ID) pentru viitoare integrări de web scraping/verificare automată a statusului pe portaluri externe.
- Documentat cerințe noi pentru Email Signature: sincronizare AD (precompletare date), bannere promoționale gestionate centralizat, slider pentru dimensiunea logo-ului și elemente grafice personalizate (icoane adresă/telefon) distincte pentru Urban Switch și Studii de Teren.
- Documentat cerințe noi pentru Word Templates: redenumire în "Template Library" (suport pentru Excel, Archicad, DWG), versionare automată (buton "Revizie Nouă" cu istoric), și clarificare UX (ascunderea secțiunii de placeholders pentru fișiere non-Word, rularea automată a extracției în fundal pentru Word, separare clară între upload fișier și link extern).
- Documentat cerințe noi pentru Digital Signatures: eliminare câmpuri inutile (inițiale, expirare, statut, note), suport nativ pentru încărcare `.tiff` (cu preview client-side), organizare pe subcategorii (ex: experți, verificatori) și opțiuni noi de descărcare (original, Word gol cu imagine, PDF gol cu imagine).
- Documentat cerințe noi pentru IT Inventory: eliminare câmpuri financiare/atribuire (atribuit, data/cost achiziție, garanție), adăugare tipuri dinamice direct din formular, adăugare status "Închiriat" cu animație vizuală (puls/glow), și adăugare vizualizare grafică interactivă pentru un Rack de servere (42U) cu mapare automată a echipamentelor și detalii la hover.
- Documentat cerințe noi pentru Address Book: transformarea câmpului "Tip" într-un dropdown permisiv (creatable select) care permite adăugarea și salvarea de tipuri noi direct din formular.
- Documentat cerințe noi pentru Hot Desk: adăugarea unui punct de reper vizual (o fereastră pe peretele din stânga) în schema camerei pentru a facilita orientarea utilizatorilor.
- Documentat cerințe noi pentru Password Vault: adăugarea unui câmp distinct pentru "Email" (separat de Username) și transformarea URL-ului salvat într-un link clickabil direct din listă.
- Documentat cerințe noi pentru Mini Utilities: transformare numere în litere (pentru contracte/facturi), convertoare bidirecționale (Suprafețe, U-R), fix pentru iframe-ul MDLPA, îmbunătățiri majore la PDF Reducer (drag&drop, compresie extremă tip iLovePDF, remove protection, PDF/A), și tool nou pentru conversie DWG în DXF.
- Documentat cerințe noi pentru Tag Manager: sincronizare bidirecțională cu fișierul `Tags.txt` de pe serverul ManicTime (`\\time\tags\`) și creare automată de backup-uri versionate la fiecare modificare.
- Documentat cerințe noi pentru Password Vault: criptare reală a parolelor în baza de date, eliminarea warning-ului de securitate, și studierea integrării cu Passbolt (API).
- Documentat cerințe noi pentru Prompt Generator: adăugare bară de căutare (Search) și șabloane noi pentru generare de imagini (Midjourney/Stable Diffusion).
- Documentat cerințe noi pentru AI Chat & Media: activarea modulului de chat cu un API real (OpenAI/Anthropic/Ollama), integrare cu Tag Manager pentru context pe proiect, interfață node-based (infinite canvas) pentru generatoare media, și un nod avansat de interpretare 3D a imaginilor 2D (tip viewport interactiv).
- Documentat cerințe noi pentru Storage & Arhitectură (Prioritate 0): devansarea migrării de la `localStorage` la o soluție robustă (MinIO pentru fișiere + PostgreSQL/Prisma pentru date) pentru a asigura persistența, securitatea și partajarea datelor între toți utilizatorii.
### Notes
- No code changes made. Only documentation updated.
- Faza 3 (Ideation & Requirements) este complet documentată. Urmează planificarea concretă a execuției.
---
## Session — 2026-02-19 (GitHub Copilot - Claude Sonnet 4.6) [continued 2]
### Completed
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+884 -29
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -13,7 +13,9 @@
"clsx": "^2.1.1",
"jszip": "^3.10.1",
"lucide-react": "^0.564.0",
"minio": "^8.0.6",
"next": "16.1.6",
"next-auth": "^4.24.13",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "19.2.3",
@@ -23,6 +25,7 @@
"uuid": "^13.0.0"
},
"devDependencies": {
"@prisma/client": "^6.19.2",
"@tailwindcss/postcss": "^4",
"@types/jszip": "^3.4.0",
"@types/node": "^20",
@@ -31,6 +34,7 @@
"@types/uuid": "^10.0.0",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"prisma": "^6.19.2",
"shadcn": "^3.8.5",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model KeyValueStore {
id String @id @default(uuid())
namespace String
key String
value Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([namespace, key])
@@index([namespace])
}
+55
View File
@@ -0,0 +1,55 @@
import NextAuth, { NextAuthOptions } from "next-auth";
import AuthentikProvider from "next-auth/providers/authentik";
export const authOptions: NextAuthOptions = {
providers: [
AuthentikProvider({
clientId: process.env.AUTHENTIK_CLIENT_ID || "",
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || "",
issuer: process.env.AUTHENTIK_ISSUER || "",
}),
],
callbacks: {
async jwt({ token, user, profile }) {
if (user) {
token.id = user.id;
}
if (profile) {
// Map Authentik groups/roles to our internal roles
// This assumes Authentik sends groups in the profile
const groups = (profile as any).groups || [];
let role = "user";
if (groups.includes("architools-admin")) role = "admin";
else if (groups.includes("architools-manager")) role = "manager";
token.role = role;
// Map company based on groups or attributes
let company = "group";
if (groups.includes("company-beletage")) company = "beletage";
else if (groups.includes("company-urban-switch"))
company = "urban-switch";
else if (groups.includes("company-studii-de-teren"))
company = "studii-de-teren";
token.company = company;
}
return token;
},
async session({ session, token }) {
if (session.user) {
(session.user as any).id = token.id;
(session.user as any).role = token.role || "user";
(session.user as any).company = token.company || "group";
}
return session;
},
},
pages: {
// We can add custom sign-in pages later if needed
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
+130
View File
@@ -0,0 +1,130 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/core/storage/prisma";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const namespace = searchParams.get("namespace");
const key = searchParams.get("key");
if (!namespace) {
return NextResponse.json(
{ error: "Namespace is required" },
{ status: 400 },
);
}
try {
if (key) {
// Get single item
const item = await prisma.keyValueStore.findUnique({
where: {
namespace_key: {
namespace,
key,
},
},
});
return NextResponse.json({ value: item ? item.value : null });
} else {
// Get all items in namespace
const items = await prisma.keyValueStore.findMany({
where: { namespace },
});
// Return as a record { [key]: value }
const result: Record<string, any> = {};
for (const item of items) {
result[item.key] = item.value;
}
return NextResponse.json({ items: result });
}
} catch (error) {
console.error("Storage GET error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { namespace, key, value } = body;
if (!namespace || !key) {
return NextResponse.json(
{ error: "Namespace and key are required" },
{ status: 400 },
);
}
await prisma.keyValueStore.upsert({
where: {
namespace_key: {
namespace,
key,
},
},
update: {
value,
},
create: {
namespace,
key,
value,
},
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("Storage POST error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
export async function DELETE(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const namespace = searchParams.get("namespace");
const key = searchParams.get("key");
if (!namespace) {
return NextResponse.json(
{ error: "Namespace is required" },
{ status: 400 },
);
}
try {
if (key) {
// Delete single item
await prisma.keyValueStore
.delete({
where: {
namespace_key: {
namespace,
key,
},
},
})
.catch(() => {
// Ignore error if item doesn't exist
});
} else {
// Clear namespace
await prisma.keyValueStore.deleteMany({
where: { namespace },
});
}
return NextResponse.json({ success: true });
} catch (error) {
console.error("Storage DELETE error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
+42 -20
View File
@@ -1,7 +1,8 @@
'use client';
"use client";
import { createContext, useContext, useMemo, useCallback } from 'react';
import type { AuthContextValue, User, Role } from './types';
import { createContext, useContext, useMemo, useCallback } from "react";
import { SessionProvider, useSession } from "next-auth/react";
import type { AuthContextValue, User, Role, CompanyId } from "./types";
const ROLE_HIERARCHY: Record<Role, number> = {
admin: 4,
@@ -13,55 +14,76 @@ const ROLE_HIERARCHY: Record<Role, number> = {
const AuthContext = createContext<AuthContextValue | null>(null);
// Stub user for development (no auth required)
// Stub user for development fallback
const STUB_USER: User = {
id: 'dev-user',
name: 'Utilizator Intern',
email: 'dev@architools.local',
role: 'admin',
company: 'beletage',
id: "dev-user",
name: "Utilizator Intern",
email: "dev@architools.local",
role: "admin",
company: "beletage",
};
interface AuthProviderProps {
children: React.ReactNode;
}
export function AuthProvider({ children }: AuthProviderProps) {
// In the current phase, always return the stub user
// Future: replace with Authentik OIDC token resolution
const user = STUB_USER;
function AuthProviderInner({ children }: AuthProviderProps) {
const { data: session, status } = useSession();
// Use session user if available, otherwise fallback to stub in dev mode
// In production, we should probably force login if no session
const user: User | null = session?.user
? {
id: (session.user as any).id || "unknown",
name: session.user.name || "Unknown User",
email: session.user.email || "",
role: ((session.user as any).role as Role) || "user",
company: ((session.user as any).company as CompanyId) || "group",
}
: process.env.NODE_ENV === "development"
? STUB_USER
: null;
const hasRole = useCallback(
(requiredRole: Role) => {
if (!user) return false;
return ROLE_HIERARCHY[user.role] >= ROLE_HIERARCHY[requiredRole];
},
[user.role]
[user],
);
const canAccessModule = useCallback(
(_moduleId: string) => {
// Future: check module-level permissions
return true;
return !!user;
},
[]
[user],
);
const value: AuthContextValue = useMemo(
() => ({
user,
role: user.role,
isAuthenticated: true,
role: user?.role || "guest",
isAuthenticated: !!user,
hasRole,
canAccessModule,
}),
[user, hasRole, canAccessModule]
[user, hasRole, canAccessModule],
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function AuthProvider({ children }: AuthProviderProps) {
return (
<SessionProvider>
<AuthProviderInner>{children}</AuthProviderInner>
</SessionProvider>
);
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}
@@ -0,0 +1,112 @@
import type { StorageService } from "../types";
export class DatabaseStorageAdapter implements StorageService {
async get<T>(namespace: string, key: string): Promise<T | null> {
try {
const res = await fetch(
`/api/storage?namespace=${encodeURIComponent(namespace)}&key=${encodeURIComponent(key)}`,
);
if (!res.ok) return null;
const data = await res.json();
return data.value as T | null;
} catch (error) {
console.error("DatabaseStorageAdapter get error:", error);
return null;
}
}
async set<T>(namespace: string, key: string, value: T): Promise<void> {
try {
await fetch("/api/storage", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ namespace, key, value }),
});
} catch (error) {
console.error("DatabaseStorageAdapter set error:", error);
}
}
async delete(namespace: string, key: string): Promise<void> {
try {
await fetch(
`/api/storage?namespace=${encodeURIComponent(namespace)}&key=${encodeURIComponent(key)}`,
{
method: "DELETE",
},
);
} catch (error) {
console.error("DatabaseStorageAdapter delete error:", error);
}
}
async list(namespace: string): Promise<string[]> {
try {
const res = await fetch(
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
);
if (!res.ok) return [];
const data = await res.json();
return Object.keys(data.items || {});
} catch (error) {
console.error("DatabaseStorageAdapter list error:", error);
return [];
}
}
async query<T>(
namespace: string,
predicate: (item: T) => boolean,
): Promise<T[]> {
try {
const res = await fetch(
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
);
if (!res.ok) return [];
const data = await res.json();
const items = Object.values(data.items || {}) as T[];
return items.filter(predicate);
} catch (error) {
console.error("DatabaseStorageAdapter query error:", error);
return [];
}
}
async clear(namespace: string): Promise<void> {
try {
await fetch(`/api/storage?namespace=${encodeURIComponent(namespace)}`, {
method: "DELETE",
});
} catch (error) {
console.error("DatabaseStorageAdapter clear error:", error);
}
}
async export(namespace: string): Promise<Record<string, unknown>> {
try {
const res = await fetch(
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
);
if (!res.ok) return {};
const data = await res.json();
return data.items || {};
} catch (error) {
console.error("DatabaseStorageAdapter export error:", error);
return {};
}
}
async import(
namespace: string,
data: Record<string, unknown>,
): Promise<void> {
try {
// Import items one by one (or we could create a bulk endpoint, but this is fine for now)
for (const [key, value] of Object.entries(data)) {
await this.set(namespace, key, value);
}
} catch (error) {
console.error("DatabaseStorageAdapter import error:", error);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
import { Client } from "minio";
const globalForMinio = globalThis as unknown as {
minioClient: Client | undefined;
};
export const minioClient =
globalForMinio.minioClient ??
new Client({
endPoint: process.env.MINIO_ENDPOINT || "localhost",
port: parseInt(process.env.MINIO_PORT || "9000"),
useSSL: process.env.MINIO_USE_SSL === "true",
accessKey: process.env.MINIO_ACCESS_KEY || "",
secretKey: process.env.MINIO_SECRET_KEY || "",
});
if (process.env.NODE_ENV !== "production")
globalForMinio.minioClient = minioClient;
export const MINIO_BUCKET_NAME = process.env.MINIO_BUCKET_NAME || "tools";
// Helper to ensure bucket exists
export async function ensureBucketExists() {
try {
const exists = await minioClient.bucketExists(MINIO_BUCKET_NAME);
if (!exists) {
await minioClient.makeBucket(MINIO_BUCKET_NAME, "eu-west-1");
console.log(`Bucket '${MINIO_BUCKET_NAME}' created successfully.`);
}
} catch (error) {
console.error("Error checking/creating MinIO bucket:", error);
}
}
+16
View File
@@ -0,0 +1,16 @@
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
+13 -7
View File
@@ -1,14 +1,19 @@
'use client';
"use client";
import { createContext, useContext, useMemo } from 'react';
import type { StorageService } from './types';
import { LocalStorageAdapter } from './adapters/local-storage';
import { createContext, useContext, useMemo } from "react";
import type { StorageService } from "./types";
import { LocalStorageAdapter } from "./adapters/local-storage";
import { DatabaseStorageAdapter } from "./adapters/database-adapter";
const StorageContext = createContext<StorageService | null>(null);
function createAdapter(): StorageService {
// Future: select adapter based on environment variable
// const adapterType = process.env.NEXT_PUBLIC_STORAGE_ADAPTER;
const adapterType = process.env.NEXT_PUBLIC_STORAGE_ADAPTER;
if (adapterType === "database") {
return new DatabaseStorageAdapter();
}
return new LocalStorageAdapter();
}
@@ -28,6 +33,7 @@ export function StorageProvider({ children }: StorageProviderProps) {
export function useStorageService(): StorageService {
const ctx = useContext(StorageContext);
if (!ctx) throw new Error('useStorageService must be used within StorageProvider');
if (!ctx)
throw new Error("useStorageService must be used within StorageProvider");
return ctx;
}
+62 -8
View File
@@ -1,14 +1,25 @@
'use client';
"use client";
import { useTheme } from 'next-themes';
import { Moon, Sun, PanelLeft } from 'lucide-react';
import { Button } from '@/shared/components/ui/button';
import { useTheme } from "next-themes";
import {
Moon,
Sun,
PanelLeft,
User as UserIcon,
LogOut,
LogIn,
} from "lucide-react";
import { Button } from "@/shared/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/shared/components/ui/dropdown-menu';
DropdownMenuSeparator,
DropdownMenuLabel,
} from "@/shared/components/ui/dropdown-menu";
import { useAuth } from "@/core/auth";
import { signIn, signOut } from "next-auth/react";
interface HeaderProps {
onToggleSidebar?: () => void;
@@ -16,6 +27,7 @@ interface HeaderProps {
export function Header({ onToggleSidebar }: HeaderProps) {
const { setTheme } = useTheme();
const { user, isAuthenticated } = useAuth();
return (
<header className="flex h-14 items-center justify-between border-b bg-card px-4">
@@ -40,17 +52,59 @@ export function Header({ onToggleSidebar }: HeaderProps) {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
<DropdownMenuItem onClick={() => setTheme("light")}>
Luminos
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Întunecat
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
<DropdownMenuItem onClick={() => setTheme("system")}>
Sistem
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="rounded-full bg-muted"
>
<UserIcon className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
{isAuthenticated && user ? (
<>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{user.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
{user.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>
<LogOut className="mr-2 h-4 w-4" />
<span>Deconectare</span>
</DropdownMenuItem>
</>
) : (
<>
<DropdownMenuLabel>Neautentificat</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signIn("authentik")}>
<LogIn className="mr-2 h-4 w-4" />
<span>Autentificare (Authentik)</span>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
);
+23
View File
@@ -0,0 +1,23 @@
import { Client } from 'minio';
import * as dotenv from 'dotenv';
dotenv.config();
const minioClient = new Client({
endPoint: process.env.MINIO_ENDPOINT,
port: parseInt(process.env.MINIO_PORT),
useSSL: process.env.MINIO_USE_SSL === 'true',
accessKey: process.env.MINIO_ACCESS_KEY,
secretKey: process.env.MINIO_SECRET_KEY,
});
async function testMinio() {
try {
console.log('Testing MinIO connection on port', process.env.MINIO_PORT, '...');
const buckets = await minioClient.listBuckets();
console.log('Success! Buckets:', buckets.map(b => b.name));
} catch (err) {
console.error('MinIO Error:', err.message);
}
}
testMinio();