feat: multi-usuário com autenticação JWT
- Tabela `profiles` + coluna `profile_id` em todas as entidades (categories, transactions, recurring_expenses, accounts, player_profile, xp_events, player_quests, player_achievements, player_cosmetics) - Dados existentes migrados para profile_id = 1 (Manoel) - CLI `./api create-user --name <n> --password <p>` cria perfil com seed de categorias e player_profile; faz upsert de senha se já existir - Auth substituída: cookie+APP_PASSWORD → JWT Bearer 24h (HS256) - Middleware RequireAuth injeta profile_id no context de todas as rotas - Todos os repositórios filtram por profile_id do context - Endpoints: POST /api/auth/login, GET /api/auth/me, POST /api/auth/change-password, POST /api/logout - Frontend: auth store usa localStorage (fc_token/fc_profile), api.ts envia Authorization header, LoginView usa campo name - SettingsView reescrita com troca de senha e logout - docker-compose.yml: remove APP_USERNAME/APP_PASSWORD, adiciona JWT_SECRET Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"_note": "Each icon is a flat grid of single-char keys. '.' = transparent. Colors are filled at render time from CSS tokens or props.",
|
||||
"heart": {
|
||||
"rows": 8,
|
||||
"cols": 8,
|
||||
"grid": [
|
||||
"........",
|
||||
".XX..XX.",
|
||||
"XXXXXXXX",
|
||||
"XXXXXXXX",
|
||||
".XXXXXX.",
|
||||
"..XXXX..",
|
||||
"...XX...",
|
||||
"........"
|
||||
],
|
||||
"defaultColor": "var(--fc-red, #ff3b6b)"
|
||||
},
|
||||
"coin": {
|
||||
"rows": 8,
|
||||
"cols": 8,
|
||||
"grid": [
|
||||
"..XXXX..",
|
||||
".XYYYYX.",
|
||||
"XYYSYYYX",
|
||||
"XYYSYYYX",
|
||||
"XYYSYYYX",
|
||||
"XYYSYYYX",
|
||||
".XYYYYX.",
|
||||
"..XXXX.."
|
||||
],
|
||||
"colors": {
|
||||
"X": "var(--fc-gold, #ffd84d)",
|
||||
"Y": "var(--fc-gold, #ffd84d)",
|
||||
"S": "#a16207"
|
||||
}
|
||||
},
|
||||
"star": {
|
||||
"rows": 8,
|
||||
"cols": 8,
|
||||
"grid": [
|
||||
"...XX...",
|
||||
"...XX...",
|
||||
"XX.XX.XX",
|
||||
".XXXXXX.",
|
||||
"..XXXX..",
|
||||
".XXXXXX.",
|
||||
"XX.XX.XX",
|
||||
"...XX..."
|
||||
],
|
||||
"defaultColor": "var(--fc-gold, #ffd84d)"
|
||||
},
|
||||
"anchor": {
|
||||
"rows": 8,
|
||||
"cols": 8,
|
||||
"grid": [
|
||||
"...XX...",
|
||||
"..XXXX..",
|
||||
"...XX...",
|
||||
"XXXXXXXX",
|
||||
"...XX...",
|
||||
"X..XX..X",
|
||||
"XXXXXXXX",
|
||||
"..XXXX.."
|
||||
],
|
||||
"defaultColor": "var(--fc-accent-2, #00f0ff)"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 884 B |
|
After Width: | Height: | Size: 472 B |
|
After Width: | Height: | Size: 874 B |
|
After Width: | Height: | Size: 465 B |
|
After Width: | Height: | Size: 883 B |
|
After Width: | Height: | Size: 472 B |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 476 B |
|
After Width: | Height: | Size: 884 B |
|
After Width: | Height: | Size: 472 B |
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 464 B |
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 455 B |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"name": "manoel",
|
||||
"rows": 24,
|
||||
"cols": 17,
|
||||
"_colorSlots": {
|
||||
"L": "outline (immutable)",
|
||||
"s": "skin highlight (immutable)",
|
||||
"S": "skin shade (immutable)",
|
||||
"e": "eye (immutable)",
|
||||
"m": "mouth (immutable)",
|
||||
"h": "hat main",
|
||||
"w": "hat band",
|
||||
"c": "shirt main",
|
||||
"C": "shirt shade",
|
||||
"p": "pants main",
|
||||
"P": "pants shade",
|
||||
"b": "boots"
|
||||
},
|
||||
"_legend": {
|
||||
".": "transparent",
|
||||
"L": "#10131c",
|
||||
"s": "#f3c79b",
|
||||
"S": "#c98863",
|
||||
"e": "#10131c",
|
||||
"m": "#80484b",
|
||||
"h": "#ffd84d",
|
||||
"w": "#fde68a",
|
||||
"c": "#00f0ff",
|
||||
"C": "#0369a1",
|
||||
"p": "#3a1f6e",
|
||||
"P": "#2d1857",
|
||||
"b": "#0f172a"
|
||||
},
|
||||
"grid": [
|
||||
"....LLLLLLLLL....",
|
||||
"..LLhhhhhhhhhLL..",
|
||||
".LhhhhhhhhhhhhhL.",
|
||||
"LLhhhhhhhhhhhhhLL",
|
||||
"LwwwwwwwwwwwwwwwL",
|
||||
"LLLLLLLLLLLLLLLLL",
|
||||
"....LssssssssL...",
|
||||
"...LssssssssssL..",
|
||||
"..LsseSssssSesL..",
|
||||
"..LssssssssssL...",
|
||||
"..LSsLmmmmmLSsL..",
|
||||
"...LSssssssSSL...",
|
||||
"....LLsssssLL....",
|
||||
"...LccccccccL....",
|
||||
"..LcccCwwCccccL..",
|
||||
".LccccCwwCccccL..",
|
||||
".LccccCwwCccccL..",
|
||||
".LCCCCCCCCCCCCL..",
|
||||
"..LpppLLLLpppL...",
|
||||
"..LppppppppppL...",
|
||||
"..LppppppppppL...",
|
||||
"..LPPPPPPPPPPL...",
|
||||
"..LLbbLL.LLbbLL..",
|
||||
"...LLLL...LLLL..."
|
||||
],
|
||||
"cosmetics": [
|
||||
{
|
||||
"id": "default",
|
||||
"name": "Look Equipado (atual)",
|
||||
"unlockLv": 7,
|
||||
"preview": "manoel-default",
|
||||
"theme": {}
|
||||
},
|
||||
{
|
||||
"id": "lv01-tripulante",
|
||||
"name": "Tripulante Iniciante",
|
||||
"unlockLv": 1,
|
||||
"preview": "manoel-lv01",
|
||||
"_notes": "sem chapéu — h/w viram cabelo castanho",
|
||||
"theme": {
|
||||
"h": "#5a3a1d",
|
||||
"w": "#5a3a1d",
|
||||
"c": "#0ea5e9",
|
||||
"C": "#075985",
|
||||
"p": "#a16207",
|
||||
"P": "#7c4a08",
|
||||
"b": "#1e293b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lv03-bone-verolme",
|
||||
"name": "Boné Verolme",
|
||||
"unlockLv": 3,
|
||||
"preview": "manoel-lv03-bone-verolme",
|
||||
"theme": {
|
||||
"h": "#1e40af",
|
||||
"w": "#fde68a",
|
||||
"c": "#0ea5e9",
|
||||
"C": "#075985",
|
||||
"p": "#a16207",
|
||||
"P": "#7c4a08",
|
||||
"b": "#1e293b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lv05-wingspan",
|
||||
"name": "Camiseta Wingspan",
|
||||
"unlockLv": 5,
|
||||
"preview": "manoel-lv05-wingspan",
|
||||
"theme": {
|
||||
"h": "#ffd84d",
|
||||
"w": "#fde68a",
|
||||
"c": "#10b981",
|
||||
"C": "#065f46",
|
||||
"p": "#3a1f6e",
|
||||
"P": "#2d1857",
|
||||
"b": "#0f172a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lv07-chapeu-sol",
|
||||
"name": "Chapéu de Sol",
|
||||
"unlockLv": 7,
|
||||
"preview": "manoel-lv07-chapeu-sol",
|
||||
"theme": {
|
||||
"h": "#ffd84d",
|
||||
"w": "#fde68a",
|
||||
"c": "#00f0ff",
|
||||
"C": "#0369a1",
|
||||
"p": "#3a1f6e",
|
||||
"P": "#2d1857",
|
||||
"b": "#0f172a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lv10-anti-vento",
|
||||
"name": "Casaco Anti-Vento",
|
||||
"unlockLv": 10,
|
||||
"preview": "manoel-lv10-anti-vento",
|
||||
"theme": {
|
||||
"h": "#1e40af",
|
||||
"w": "#fde68a",
|
||||
"c": "#a855f7",
|
||||
"C": "#581c87",
|
||||
"p": "#1e293b",
|
||||
"P": "#0f172a",
|
||||
"b": "#0f172a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lv15-capitao",
|
||||
"name": "Trajado de Capitão",
|
||||
"unlockLv": 15,
|
||||
"preview": "manoel-lv15-capitao",
|
||||
"theme": {
|
||||
"h": "#ffd84d",
|
||||
"w": "#dc2626",
|
||||
"c": "#dc2626",
|
||||
"C": "#7a0e0e",
|
||||
"p": "#1c1917",
|
||||
"P": "#0c0a09",
|
||||
"b": "#0c0a09"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||