# DAL License Server — CLAUDE.md ## Pregled Univerzalni licencni server za **sve DAL proizvode**: ESIR, ARV, Light-Ticket, i buduće aplikacije. Zamenjuje stari `esir-license-server` (koji niko ne koristi u produkciji). Arhitektura prema ARV licencnoj dokumentaciji (`/root/projects/arv/TASKS/Licenciranje-Starter.md`), ali univerzalna — podržava N proizvoda. **Princip:** License Server je jedini koji ima RSA private key. Klijentske aplikacije imaju samo public key. Server potpisuje licence, klijenti verifikuju potpis. Niko osim servera ne može da kreira validnu licencu. **KRITIČNO — Testiranje:** Ova aplikacija je osnova budućeg poslovanja DAL-a. Mora biti **rigorozno testirana** svim poznatim tipovima testova: - **Unit testovi** — svaki servis, model, helper, middleware - **Integration testovi** — kompletni API flow-ovi (activate → validate → deactivate → re-activate) - **Security testovi** — SQL injection, brute force, API key bypass, tampered signatures, invalid inputs - **Edge case testovi** — expired licence, grace period granice, perpetual licence, race conditions - **Regression testovi** — svaki bug fix mora imati test koji potvrdjuje ispravku - **Load testovi** — rate limiting pod pritiskom, konkurentni zahtevi - Nikad ne smanjivati pokrivenost testovima. Svaka nova funkcionalnost MORA imati test pre merge-a. --- ## Kako radi ``` ┌─────────────────────────────────────────────────────┐ │ DAL LICENSE SERVER │ │ port: 8090 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ Admin API │ │ Klijent │ │ Admin Dashboard │ │ │ │ (CRUD) │ │ API │ │ (htmx) │ │ │ └──────────┘ └──────────┘ └──────────────────┘ │ │ │ │ │ │ │ └──────────────┼───────────────┘ │ │ │ │ │ ┌────────┴────────┐ │ │ │ MySQL baza │ │ │ │ license_db │ │ │ └─────────────────┘ │ │ │ │ RSA-2048 Private Key (samo ovde, nikad ne izlazi) │ └─────────────────────────────────────────────────────┘ ▲ ▲ ▲ │ HTTPS │ HTTPS │ HTTPS │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────────┐ │ ESIR │ │ ARV │ │ Light-Ticket │ │ klijent │ │ klijent │ │ klijent │ └─────────┘ └─────────┘ └──────────────┘ Svaki ima RSA public key ugrađen u binary Svaki čuva license.enc lokalno ``` --- ## Tok — od kupovine do rada ### 1. Admin kreira licencu ``` Admin → Dashboard → Nova licenca → Bira proizvod (ESIR / ARV / LIGHT_TICKET) → Unosi: firma, email, tip (MONTHLY/ANNUAL/PERPETUAL), limiti → Server generiše ključ: {PREFIX}-XXXX-XXXX-XXXX-XXXX → Ključ se šalje klijentu (email ili ručno) ``` ### 2. Klijent aktivira ``` Klijent instalira aplikaciju → Unese licencni ključ u Settings → Aplikacija šalje serveru: ključ + machine_fingerprint + app_version + OS → Server proverava: ključ validan? nije aktiviran drugde? nije istekao? → Server potpisuje licencne podatke RSA private key-em → Vraća: licencni JSON + RSA potpis → Klijent kreira license.enc (AES-256-GCM + RSA potpis) → Aplikacija radi OFFLINE dok licenca važi ``` ### 3. Svakodnevni rad (offline) ``` Aplikacija se pokrene → Čita license.enc sa diska → Dekriptuje (AES sa machine fingerprint) → Proverava RSA potpis (public key ugrađen u binary) → Proverava fingerprint (isti računar?) → Proverava rok (nije istekao?) → SVE OK → normalan rad → Internet NIJE potreban za svakodnevni rad ``` ### 4. Opciona online provera ``` Jednom dnevno (ako ima internet): → Aplikacija šalje: ključ + fingerprint → Server proverava: nije revocirana? → Ako je revocirana na serveru → klijent invalidira lokalnu licencu → Ako nema interneta → preskače, radi sa lokalnim fajlom ``` --- ## Tech stack | Komponenta | Tehnologija | |------------|-------------| | Backend | Go + net/http (Go 1.22+ routing) | | Admin UI | htmx + Go html/template | | Baza | MySQL 8.0 | | Kripto | RSA-2048 (potpis), AES-256-GCM (enkripcija na klijentima) | | Auth | API key za admin API, session za dashboard | --- ## Go struktura projekta ``` dal-license-server/ ├── cmd/ │ └── server/ │ └── main.go ├── internal/ │ ├── config/ │ │ └── config.go # .env konfiguracija │ ├── model/ │ │ ├── license.go # License, Product, LicenseType │ │ ├── activation.go # Activation model │ │ ├── audit.go # Audit log model │ │ └── request.go # Request/Response structs │ ├── repository/ │ │ ├── license_repo.go # License CRUD │ │ ├── activation_repo.go # Activation CRUD │ │ └── audit_repo.go # Audit log │ ├── service/ │ │ ├── license_service.go # Poslovna logika │ │ ├── activation_service.go # Aktivacija/deaktivacija │ │ ├── crypto_service.go # RSA potpisivanje │ │ └── keygen.go # Generisanje ključeva │ ├── handler/ │ │ ├── client_handler.go # API za klijentske app-e (activate, deactivate, validate) │ │ ├── admin_handler.go # Admin CRUD API │ │ ├── dashboard_handler.go # Admin dashboard (htmx) │ │ └── helpers.go # JSON/error helpers │ ├── middleware/ │ │ ├── auth.go # API key + session auth │ │ └── ratelimit.go # Rate limiting │ └── router/ │ └── router.go ├── templates/ │ ├── layout/ │ │ └── base.html │ ├── pages/ │ │ ├── login.html │ │ ├── dashboard.html # Statistike po proizvodu │ │ ├── licenses.html # Lista licenci + CRUD │ │ ├── license-detail.html # Detalji licence + aktivacije │ │ └── audit.html # Audit log pregled │ └── partials/ │ ├── license-row.html │ └── stats.html ├── crypto/ │ ├── private.pem # RSA private key (NIKAD u git-u!) │ └── public.pem # RSA public key (deli se sa klijentima) ├── migrations/ │ ├── 001_create_tables.sql │ └── 002_seed_products.sql ├── .env.example ├── .gitignore ├── go.mod ├── README.md ├── TESTING.md └── CLAUDE.md ``` --- ## Baza podataka (MySQL) ### Tabele #### `products` | Kolona | Tip | Opis | |--------|-----|------| | id | BIGINT PK AUTO_INCREMENT | | | code | VARCHAR(20) UNIQUE | ESIR, ARV, LIGHT_TICKET | | name | VARCHAR(100) | Puno ime proizvoda | | key_prefix | VARCHAR(10) | ESIR-, ARV-, LT- | | default_limits | JSON | Default limiti za taj proizvod | | available_features | JSON | Sve moguće features za taj proizvod | | active | BOOLEAN DEFAULT TRUE | | | created_at | TIMESTAMP | | **Seed podaci:** ```sql INSERT INTO products (code, name, key_prefix, default_limits, available_features) VALUES ('ESIR', 'ESIR Fiskalizacija', 'ESIR-', '{"max_installations": 1}', '["FISCALIZATION", "REPORTS"]'), ('ARV', 'ARV Evidencija RV', 'ARV-', '{"max_employees": 50, "max_readers": 4}', '["TIME_ATTENDANCE", "BASIC_REPORTS", "EMPLOYEE_MANAGEMENT", "SHIFTS", "HR_MODULE", "ACCESS_CONTROL"]'), ('LIGHT_TICKET', 'Light-Ticket', 'LT-', '{"max_operators": 3}', '["TICKET_VALIDATION", "REPORTS", "EXCEL_EXPORT", "LIVE_FEED"]'); ``` **Light-Ticket paketi licenci (odlučeno 03.03.2026):** - Admin ručno upisuje `max_operators` prilikom kreiranja licence - Nema eksplicitnog `edition` polja — paketi su samo konvencija: | Paket | max_operators | Tip licence | Features | |-------|--------------|-------------|----------| | Starter | 3 | MONTHLY ili PERPETUAL | Sve | | Pro | 10 | MONTHLY ili PERPETUAL | Sve | | Enterprise | 0 (neograničeno) | MONTHLY ili PERPETUAL | Sve | - Default u seed-u je `max_operators: 3` (Starter) - Sve features su iste za sve pakete — razlikuje se SAMO `max_operators` - Tipovi licence za LT: MONTHLY (mesečna) i PERPETUAL (trajna). TRIAL i ANNUAL nisu predviđeni za V1. #### `licenses` | Kolona | Tip | Opis | |--------|-----|------| | id | BIGINT PK AUTO_INCREMENT | | | product_id | BIGINT FK(products.id) | Koji proizvod | | license_key | VARCHAR(25) UNIQUE | {PREFIX}-XXXX-XXXX-XXXX-XXXX | | license_type | VARCHAR(20) NOT NULL | TRIAL / MONTHLY / ANNUAL / PERPETUAL | | customer_name | VARCHAR(255) NOT NULL | Naziv firme | | customer_pib | VARCHAR(20) | PIB (opciono) | | customer_email | VARCHAR(255) | Email | | limits | JSON NOT NULL | {"max_employees": 50, "max_readers": 4} | | features | JSON NOT NULL | ["TIME_ATTENDANCE", "BASIC_REPORTS"] | | issued_at | TIMESTAMP DEFAULT NOW() | | | expires_at | TIMESTAMP NULL | NULL = neograničena (PERPETUAL) | | grace_days | INT DEFAULT 30 | Koliko dana grace period | | active | BOOLEAN DEFAULT TRUE | | | revoked | BOOLEAN DEFAULT FALSE | | | revoked_at | TIMESTAMP NULL | | | revoked_reason | TEXT | | | notes | TEXT | Interne beleške | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | **Indeksi:** - `idx_licenses_key` na (license_key) — UNIQUE - `idx_licenses_product` na (product_id) - `idx_licenses_customer` na (customer_name) - `idx_licenses_expires` na (expires_at) #### `activations` | Kolona | Tip | Opis | |--------|-----|------| | id | BIGINT PK AUTO_INCREMENT | | | license_id | BIGINT FK(licenses.id) | | | machine_fingerprint | VARCHAR(100) NOT NULL | sha256:... | | hostname | VARCHAR(100) | Ime računara | | os_info | VARCHAR(50) | windows / linux | | app_version | VARCHAR(20) | Verzija aplikacije | | ip_address | VARCHAR(45) | IP pri aktivaciji | | activated_at | TIMESTAMP DEFAULT NOW() | | | deactivated_at | TIMESTAMP NULL | NULL = aktivna | | is_active | BOOLEAN DEFAULT TRUE | | | last_seen_at | TIMESTAMP | Poslednja online provera | **Indeksi:** - `idx_activations_license` na (license_id) - `idx_activations_fingerprint` na (machine_fingerprint) - `idx_activations_active` na (license_id, is_active) #### `audit_log` | Kolona | Tip | Opis | |--------|-----|------| | id | BIGINT PK AUTO_INCREMENT | | | license_id | BIGINT FK(licenses.id) NULL | | | action | VARCHAR(30) NOT NULL | ACTIVATE, DEACTIVATE, VALIDATE, REVOKE, FORCE_RELEASE, CREATE, UPDATE | | ip_address | VARCHAR(45) | | | details | JSON | Dodatni podaci (fingerprint, hostname, error, itd.) | | created_at | TIMESTAMP DEFAULT NOW() | | **Indeksi:** - `idx_audit_license` na (license_id) - `idx_audit_action` na (action) - `idx_audit_created` na (created_at) --- ## API endpointi ### Klijentski API (za ESIR, ARV, Light-Ticket aplikacije) | Metoda | Putanja | Auth | Opis | |--------|---------|------|------| | POST | `/api/v1/activate` | - | Aktivacija licence | | POST | `/api/v1/deactivate` | - | Deaktivacija (transfer) | | POST | `/api/v1/validate` | - | Opciona online provera | | GET | `/api/v1/check-update` | - | Provera za update aplikacije | #### POST `/api/v1/activate` ```json // Request (od klijentske aplikacije) { "license_key": "LT-K7M2-9P4N-R3W8-J6T1", "machine_fingerprint": "sha256:a1b2c3d4e5f6...", "app_version": "1.0.0", "os": "windows", "hostname": "FIRMA-PC" } // Response 200 { "license": { "license_key": "LT-K7M2-9P4N-R3W8-J6T1", "product": "LIGHT_TICKET", "license_type": "MONTHLY", "issued_at": "2026-03-01T00:00:00Z", "expires_at": "2026-04-01T00:00:00Z", "activated_at": "2026-03-03T10:00:00Z", "machine_fingerprint": "sha256:a1b2c3d4e5f6...", "grace_days": 30, "limits": { "max_operators": 3 }, "features": ["TICKET_VALIDATION", "REPORTS", "EXCEL_EXPORT", "LIVE_FEED"], "customer": { "name": "Firma DOO", "email": "admin@firma.rs" } }, "signature": "RSA-SHA256:base64encodedSignature..." } // Response 400 { "error": { "code": "ALREADY_ACTIVATED", "message": "Licenca je već aktivirana na drugom računaru", "details": { "activated_on": "DRUGA-PC", "activated_at": "2026-02-15T08:00:00Z" } } } ``` **Error kodovi:** INVALID_KEY, ALREADY_ACTIVATED, KEY_EXPIRED, KEY_REVOKED, PRODUCT_MISMATCH #### POST `/api/v1/deactivate` ```json // Request { "license_key": "LT-K7M2-9P4N-R3W8-J6T1", "machine_fingerprint": "sha256:a1b2c3d4e5f6..." } // Response 200 { "message": "Licenca uspešno deaktivirana", "can_reactivate": true } ``` #### POST `/api/v1/validate` ```json // Request { "license_key": "LT-K7M2-9P4N-R3W8-J6T1", "machine_fingerprint": "sha256:a1b2c3d4e5f6..." } // Response 200 { "valid": true, "expires_at": "2026-04-01T00:00:00Z", "revoked": false } ``` ### Admin API | Metoda | Putanja | Auth | Opis | |--------|---------|------|------| | GET | `/api/v1/admin/products` | API key | Lista proizvoda | | GET | `/api/v1/admin/licenses` | API key | Lista licenci (filter po proizvodu) | | POST | `/api/v1/admin/licenses` | API key | Kreiraj licencu | | GET | `/api/v1/admin/licenses/{id}` | API key | Detalji licence | | PUT | `/api/v1/admin/licenses/{id}` | API key | Izmeni licencu | | POST | `/api/v1/admin/licenses/{id}/revoke` | API key | Opozovi licencu | | POST | `/api/v1/admin/licenses/{id}/release` | API key | Force release (računar crkao) | | GET | `/api/v1/admin/licenses/{id}/activations` | API key | Aktivacije za licencu | | GET | `/api/v1/admin/audit` | API key | Audit log | | GET | `/api/v1/admin/stats` | API key | Statistike | ### Admin Dashboard (htmx) | Metoda | Putanja | Auth | Opis | |--------|---------|------|------| | GET | `/login` | - | Login | | POST | `/login` | - | Login submit | | GET | `/dashboard` | Session | Početna — statistike po proizvodu | | GET | `/licenses` | Session | Tabela licenci + filteri | | GET | `/licenses/new` | Session | Forma za novu licencu | | POST | `/licenses` | Session | Kreiraj licencu | | GET | `/licenses/{id}` | Session | Detalji + aktivacije | | POST | `/licenses/{id}/revoke` | Session | Opozovi | | POST | `/licenses/{id}/release` | Session | Force release | | GET | `/audit` | Session | Audit log | --- ## RSA ključevi ### Generisanje (jednom, čuva se zauvek) ```bash # Generiši private key (ČUVAJ TAJNO) openssl genrsa -out crypto/private.pem 2048 # Izvuci public key (deli se sa klijentima) openssl rsa -in crypto/private.pem -pubout -out crypto/public.pem ``` ### Korišćenje - **License Server:** koristi `private.pem` za potpisivanje licenci pri aktivaciji - **Klijenti (ARV, ESIR, LT):** imaju `public.pem` ugrađen u Go binary (`embed`) za verifikaciju potpisa - **Isti par ključeva** za sve proizvode — jedan server, jedan ključ ### Šta se potpisuje ``` Server prima activate request → Kreira licencni JSON (bez signature polja) → Potpisuje JSON sa RSA-SHA256 (private key) → Dodaje potpis u response → Klijent prima JSON + potpis → Klijent verifikuje potpis (public key) → Klijent enkriptuje JSON sa AES-256-GCM (ključ = machine_fingerprint + app_secret) → Klijent sačuva kao license.enc ``` --- ## Licencni ključ — format ``` {PREFIX}-XXXX-XXXX-XXXX-XXXX Primeri: ESIR-K7M2-9P4N-R3W8-J6T1 ARV-A3B5-C8D2-E7F4-G9H6 LT-M4N8-P2Q6-R5S3-T7U9 ``` - Prefix po proizvodu: čita se iz `products.key_prefix` - 4 grupe po 4 alfanumerička karaktera - Karakteri: A-H, J-N, P-Y, 2-9 (bez O/0/I/1 konfuzije) - Generisanje: crypto/rand --- ## Tipovi licenci | Tip | Trajanje | Obnova | Primer | |-----|----------|--------|--------| | TRIAL | 30 dana | Nema | Besplatno testiranje | | MONTHLY | 30 dana | Automatski ili ručno | Light-Ticket | | ANNUAL | 365 dana | Ručno | ARV, ESIR | | PERPETUAL | Zauvek | Nema (expires_at = NULL) | Kupljena zauvek | ### Grace period - Default: 30 dana posle isteka - Konfigurisano per licenca (`grace_days` kolona) - Tokom grace perioda: pun rad + upozorenje na klijentskoj app - Posle grace-a: read-only režim (GET dozvoljen, POST/PUT/DELETE blokiran) --- ## Dashboard — šta prikazuje ### Početna strana - **Po proizvodu:** broj aktivnih / isteklih / u grace-u / trial - **Ukupno:** sve licence, aktivne aktivacije - **Alarm:** licence koje ističu u narednih 7 dana - **Poslednja aktivnost:** zadnjih 10 akcija iz audit loga ### Lista licenci - **Filter:** proizvod, status (active/expired/revoked/trial), pretraga po firmi - **Kolone:** ključ, firma, proizvod, tip, ističe, aktivacija, status - **Sortiranje:** po datumu isteka (najhitnije prvo) ### Detalji licence - Sve informacije o licenci - Lista aktivacija (hostname, OS, verzija, IP, poslednji put viđen) - Audit log za tu licencu - Akcije: produži, opozovi, force release --- ## Migracija sa ESIR License Server-a Pošto stari server niko ne koristi u produkciji, migracija je jednostavna: 1. Kreiraj novi `dal-license-server` projekat 2. Kopiraj korisnu logiku iz `esir-license-server` (keygen, verify flow, helpers) 3. Proširi model (product, features, limits, RSA) 4. Zameni port 8090 (isti port, drop-in replacement) 5. Stari `esir-license-server` → arhiviraj --- ## Implementacioni taskovi ### Faza 0: Priprema - **T0-01:** Go projekat (go mod init, struktura foldera) - **T0-02:** .env.example, .gitignore, README.md - **T0-03:** Git repo na Gitea - **T0-04:** Generisanje RSA ključeva (private.pem, public.pem) ### Faza 1: Baza - **T1-01:** Config modul (.env) - **T1-02:** MySQL konekcija - **T1-03:** Migracija 001_create_tables.sql (products, licenses, activations, audit_log) - **T1-04:** Migracija 002_seed_products.sql (ESIR, ARV, LIGHT_TICKET) - **T1-05:** Repository sloj (license_repo, activation_repo, audit_repo) ### Faza 2: Core servis - **T2-01:** Keygen — generisanje ključa sa prefix-om po proizvodu - **T2-02:** Crypto service — RSA potpisivanje licencnog JSON-a - **T2-03:** License service — CRUD, validacija, revoke - **T2-04:** Activation service — activate, deactivate, force release, validate - **T2-05:** Audit logging — svaka akcija se loguje ### Faza 3: Klijentski API - **T3-01:** POST /api/v1/activate - **T3-02:** POST /api/v1/deactivate - **T3-03:** POST /api/v1/validate - **T3-04:** Rate limiting na klijentske endpointe ### Faza 4: Admin API - **T4-01:** Auth middleware (API key) - **T4-02:** CRUD endpointi za licence - **T4-03:** Revoke + Force release endpointi - **T4-04:** Aktivacije i audit endpointi - **T4-05:** Statistike endpoint ### Faza 5: Admin Dashboard (htmx) - **T5-01:** Login stranica + session auth - **T5-02:** Dashboard — statistike po proizvodu - **T5-03:** Lista licenci — tabela, filteri, pretraga - **T5-04:** Nova licenca — forma sa izborom proizvoda - **T5-05:** Detalji licence — info, aktivacije, audit, akcije - **T5-06:** Audit log stranica ### Faza 6: Testovi - **T6-01:** Unit — keygen (format, prefix, uniqueness) - **T6-02:** Unit — crypto (RSA sign/verify, tampered data) - **T6-03:** Unit — license service (create, expire, grace, revoke) - **T6-04:** Unit — activation service (activate, deactivate, already_activated, force_release) - **T6-05:** Integration — activate flow (full roundtrip) - **T6-06:** Integration — deactivate flow - **T6-07:** Integration — expired license - **T6-08:** Integration — revoked license - **T6-09:** Security — SQL injection na svim inputima - **T6-10:** Security — rate limiting (brute force key) - **T6-11:** Security — API key validation - **T6-12:** TESTING.md ### Faza 7: DevOps - **T7-01:** Gitea CI workflow - **T7-02:** Systemd service fajl - **T7-03:** Backup skripta za bazu --- ## Konfiguracija (.env) ```env # Server APP_PORT=8090 APP_ENV=development # MySQL DB_HOST=localhost DB_PORT=3306 DB_NAME=dal_license_db DB_USER=license DB_PASS=OBAVEZNO-PROMENITI # Auth ADMIN_API_KEY=OBAVEZNO-GENERISATI SESSION_SECRET=OBAVEZNO-PROMENITI # RSA RSA_PRIVATE_KEY_PATH=./crypto/private.pem # Rate limiting RATE_LIMIT_ACTIVATE=10/min RATE_LIMIT_VALIDATE=60/min # Logging LOG_LEVEL=info LOG_FILE=./log/server.log ``` --- ## Konvencije - Go net/http (bez framework-a, isto kao ESIR) - Go 1.22+ method routing (`POST /api/v1/activate`) - database/sql + raw SQL (bez ORM-a) - RSA-2048 za potpis, SHA-256 za fingerprint - API key u `X-API-Key` header-u za admin - Sve akcije se loguju u audit_log - Error wrapping: `fmt.Errorf("activate: %w", err)` - Licencni ključ se NIKAD ne loguje ceo — samo prefix + poslednja 4 karaktera --- ## Bezbednost - `private.pem` NIKAD u git-u (u .gitignore) - `private.pem` permisije: 600 (samo owner čita) - Admin API key min 32 karaktera - Rate limiting na activate/validate (zaštita od brute force) - Audit log za svaku akciju - HTTPS u produkciji (TLS termination na reverse proxy) - Licencni ključ u logovima maskiran: `LT-K7M2-****-****-J6T1` --- ## Status implementacije (mart 2026) **Kompletno implementiran** — server je funkcionalan, testiran (22/22 testova prošlo). ### Implementirano - ✅ Go projekat inicijalizovan (go.mod, go.sum) - ✅ MySQL baza `dal_license_db` kreirana, user `license` - ✅ Migracije (001_create_tables.sql, 002_seed_products.sql) - ✅ RSA-2048 ključevi generisani (crypto/private.pem, crypto/public.pem) - ✅ Seed podaci: 3 proizvoda (ESIR, ARV, LIGHT_TICKET) - ✅ Client API: activate, deactivate, validate - ✅ Admin API: CRUD licence, stats, audit log - ✅ Dashboard: login, pregled licenci, kreiranje, detalji, audit log - ✅ Rate limiting (in-memory sliding window) - ✅ API key autentifikacija (X-API-Key header) - ✅ RSA-SHA256 potpisivanje licencnih podataka - ✅ Audit log za sve akcije - ✅ Specifikacija kompletna (ovaj CLAUDE.md) ### Struktura fajlova ``` cmd/server/main.go — Entry point, MySQL konekcija, migracije, wire-up internal/config/config.go — .env loading, DSN builder (multiStatements=true) internal/model/ license.go — Product, License, LicenseWithActivation modeli activation.go — Activation model audit.go — AuditEntry model request.go — Request/Response strukture (API) internal/repository/ license_repo.go — License CRUD, product queries, stats activation_repo.go — Activation CRUD, deactivate, force release audit_repo.go — Audit log insert/list internal/service/ license_service.go — License CRUD biznis logika, expiry kalkulacija activation_service.go — Activate, Deactivate, Validate, ForceRelease crypto_service.go — RSA-2048 potpisivanje (SHA-256, PKCS1v15) keygen.go — Generisanje ključeva: {PREFIX}-XXXX-XXXX-XXXX-XXXX internal/handler/ client_handler.go — POST /api/v1/activate, deactivate, validate admin_handler.go — Admin CRUD API endpointi dashboard_handler.go — Dashboard stranice, in-memory sesije helpers.go — writeJSON, writeError, clientIP internal/middleware/ auth.go — API key auth (X-API-Key header) ratelimit.go — In-memory sliding window rate limiter internal/router/router.go — Sve rute (client, admin, dashboard) migrations/ 001_create_tables.sql — products, licenses, activations, audit_log 002_seed_products.sql — ESIR, ARV, LIGHT_TICKET templates/ layout/base.html — Glavni layout sa navbar-om pages/login.html — Login stranica pages/dashboard.html — Dashboard sa statistikama pages/licenses.html — Lista licenci sa filterima pages/license-new.html — Forma za novu licencu pages/license-detail.html — Detalji licence, aktivacije, audit pages/audit.html — Globalni audit log static/css/style.css — Kompletni CSS static/js/htmx.min.js — htmx biblioteka crypto/private.pem — RSA-2048 privatni ključ (chmod 600) crypto/public.pem — RSA javni ključ ``` ### Konfiguracija (.env) ``` APP_PORT=8090 DB_HOST=localhost / DB_PORT=3306 DB_NAME=dal_license_db DB_USER=license / DB_PASS=license_pass_2026 ADMIN_API_KEY=dal-admin-key-2026-supersecret-change-me ADMIN_PASSWORD=DalAdmin2026! RSA_PRIVATE_KEY_PATH=./crypto/private.pem ``` ### DSN napomena DSN sadrži `multiStatements=true` — neophodno za izvršavanje migracija sa više SQL naredbi u jednom `db.Exec()` pozivu. ### Testirano (22/22) 1. Health endpoint 2. Products list 3. Create license (sva 3 proizvoda) 4. List licenses 5. License detail 6. Activate license 7. Already activated (drugi hardver) 8. Deactivate license 9. Re-activate (novi hardver) 10. Validate license 11. Revoke license 12. Validate revoked (odbijeno) 13. Invalid license key 14. API key auth (401 bez ključa) 15. Stats endpoint 16. Audit log 17. Dashboard login 18. Dashboard index 19. Licenses page 20. New license form 21. License detail page 22. Audit page ### Sledeći koraci - Faza 8 (Light-Ticket): Integracija sa license serverom - ARV: Integracija sa license serverom - ESIR: Integracija sa license serverom --- *Kreirano: mart 2026* *Ažurirano: 04.03.2026 — Kompletna implementacija, 22/22 testova* *Autor: Nenad Đukić / DAL d.o.o.*