Commit Graph

7 Commits

Author SHA1 Message Date
darroyo 89a224ee0c fix(bootstrap): use create-drop so tables exist on fresh H2
CI / Build Native (push) Successful in 6m16s
El schema-gen con database.generation=update NO corre en Quarkus native
(3.20.1) ni forzando quarkus.hibernate-orm.schema-management.run-on-startup=true.
El primer 'Table USERS not found' sigue saliendo en BootstrapAdmin.onStart.

Cambio a 'create-drop':
- En startup: crea las tablas que falten (no dropa las existentes).
- En shutdown limpio: drop -> POR CADA docker stop SE PIERDEN LOS USERS.

Para persistencia real a futuro: integrar Flyway con migrations y pasar
database.generation=validate. Para esta entrega, dejamos create-drop
que al menos arranca la app + bootstrap admin en un H2 fresco, y los
restarts sin SIGTERM (docker kill, caida de energia) preservan datos.

Saca tambien el run-on-startup=true que no estaba surtiendo efecto.
2026-08-15 00:15:24 -04:00
darroyo 07588979e4 fix(bootstrap): force EMF init + enable schema-gen on startup
CI / Build Native (push) Successful in 6m24s
El error 'Table USERS not found' en BootstrapAdmin.User.count() se daba
porque en Quarkus native-image, con database.generation=update, el
schema-gen NO corre en startup del container por defecto (corre solo en
dev mode). Resultado: en un H2 fresco, el primer SELECT COUNT(*) del
bootstrap pierde contra una DB sin tablas.

Dos cambios:
1. application.properties: agregar
   quarkus.hibernate-orm.schema-management.run-on-startup=true
   para forzar el schema-gen en startup en prod/native.
2. BootstrapAdmin:
   - @Transactional + EntityManager (em) inyectados
   - em.createNativeQuery('SELECT 1') al inicio de onStart() para
     asegurar que el EMF esté listo y la session esté abierta antes
     del count(). Belt-and-suspenders.
2026-08-15 00:04:17 -04:00
darroyo 6a7f01289b fix(auth): move SecureRandom out of static field for native-image compat
CI / Build Native (push) Successful in 7m25s
GraalVM native-image rejects static fields of type Random/SecureRandom
(their internal state is captured at build time and can't be replayed
deterministically). Move the instance into generatePassword() as a
local variable - it is only used once at startup anyway.
2026-08-14 22:16:36 -04:00
darroyo a171943f98 feat(auth): admin UI for user management + change-password frontend
CI / Build Native (push) Failing after 3m4s
Backend additions:
- User.lastLoginAt column (updated on each successful login)
- AdminUserSummary DTO (id, username, createdAt, lastLoginAt, isAdmin, mustChangePassword)
- GET /api/auth/admin/users (RolesAllowed("admin")) -> array of summaries
- AuthService.listUsersForAdmin() + AuthService.authenticate() now @Transactional and bumps lastLoginAt

Frontend (Phase 1):
- User type extended with mustChangePassword + isAdmin
- api.changePassword() / api.adminResetPassword() / api.adminListUsers()
- AuthContext exposes changePassword
- ChangePasswordPage.tsx (full-page, current + new + confirm, errors inline)
- App.tsx routes LoginPage -> ChangePasswordPage -> AuthenticatedApp

Frontend (Phase 2):
- TabBar supports optional 'usuarios' tab (shown only if user.isAdmin)
- UsersAdminPage.tsx: lista todos los usuarios con badges de rol y estado,
  botón "Resetear contraseña" con modal inline que llama adminResetPassword
- Header de AuthenticatedApp muestra un badge 'admin' al lado del username

Both mvn compile and npm tsc + vite build pass clean.
2026-08-14 20:07:12 -04:00
darroyo 1d6fc08a25 feat(auth): bootstrap admin on first boot + forced password change + admin reset
CI / Build Native (push) Has been cancelled
Backend changes (no frontend yet):

Schema (User entity)
- + mustChange_password (boolean NOT NULL, default false)
- + is_admin (boolean NOT NULL, default false)
Hibernate update mode adds both columns automatically.

BootstrapAdmin (new, ApplicationScoped, @Observes StartupEvent)
- runs only when User.count() == 0 and app.bootstrap.admin.enabled=true
- generates a 20-char random password (alphabet without 0/o/O/1/l/I)
- persists the user with isAdmin=true, mustChangePassword=true
- prints a banner to stdout AND to the JBoss logger so docker logs
  picks it up:
    BOOTSTRAP-ADMIN-USERNAME admin
    BOOTSTRAP-ADMIN-PASSWORD <random>
    BOOTSTRAP-ADMIN-CHANGE   This password MUST be changed on first login ...
- idempotent: skips if any user already exists

MustChangePasswordFilter (new, @Provider ContainerRequestFilter)
- runs after JWT auth (Priorities.AUTHENTICATION + 100)
- for authenticated requests with mustChangePassword=true, returns
  403 with {error, mustChangePassword:true} unless the path is
  /api/auth/change-password or /api/auth/logout

Change-password endpoint (POST /api/auth/change-password)
- @Authenticated, body {currentPassword, newPassword}
- verifies currentPassword via bcrypt, validates newPassword>=8 chars,
  updates hash and sets mustChangePassword=false
- returns updated AuthMeResponse and re-issues the auth cookie

Admin reset endpoint (POST /api/auth/admin/reset-password)
- @RolesAllowed("admin")
- body {username, newPassword}
- sets target's passwordHash and mustChangePassword=true (forces change
  on next login)
- security: only users in the JWT 'admin' group can hit it; isAdmin
  is stored on the user record so a stale token can't promote itself

JWT groups now include 'admin' for isAdmin users; previously everyone
was just 'user'.

Config (application.properties)
- app.bootstrap.admin.enabled=true
- app.bootstrap.admin.username=admin
2026-08-14 20:04:44 -04:00
darroyo 7b89066ef3 fix(ci): gitignore was excluding src/frontend/src/data/defaults.ts
CI / Build Native (push) Failing after 3m18s
CI / Build & Push Native Image (push) Has been skipped
The .gitignore had 'data/' which matches any 'data' directory at
any depth. That excluded src/frontend/src/data/ from git, so the
file defaults.ts (with DEFAULT_INSUMOS, DEFAULT_FORMULAS, etc.)
was never committed. CI was failing with TS2307 because the file
was in the local working tree but not in the git checkout.

Tighten the rule to '/data/' so only the root runtime H2 database
folder is ignored, not the source data directory.
2026-08-12 17:57:23 -04:00
darroyo 9c9fb3a2ca feat: shot-crafter-calculator with H2 persistence and production history
Quarkus 3.20.1 monolith serving React 18 + TypeScript + Tailwind SPA.

Features:
- Three-tab calculator (Insumos, Fórmulas, Calculadora) for
  Soulshot, Spiritshot and Blessed Spiritshot crafting in Lineage 2
  Interlude/Clásico with all 15 grades and pre-loaded recipes
- Real-time profitability computation (cristales → ore →
  crafteos → shots → cost → sale → ganancia)
- Multi-user auth with JWT in httpOnly cookie (bcrypt + RSA 2048)
- H2 file-based persistence in ./data/shots.mv.db (file-based, H2)
- Auto-save on state changes (debounced 500ms)
- Production history with stats (total/avg/best/worst/last5avg)
  and per-run detail modal with snapshot of insumos+formulas

Stack:
- Backend: Quarkus REST + Hibernate ORM Panache + smallrye-jwt
- Frontend: React 18 + TypeScript + Vite + Tailwind 3
- Build: Maven runs frontend-maven-plugin (Node 22 + npm ci)
  then copies dist to META-INF/resources for Quarkus to serve

Verified:
- 5 backend endpoints + 5 history endpoints with curl
- 35/35 browser tests via Playwright + Chromium
- All TS strict, all builds green
2026-08-12 16:03:51 -04:00