create-fluxstack 1.12.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/LLMD/INDEX.md +8 -1
  2. package/LLMD/agent.md +867 -0
  3. package/LLMD/config/environment-vars.md +30 -0
  4. package/LLMD/patterns/anti-patterns.md +100 -0
  5. package/LLMD/reference/routing.md +39 -39
  6. package/LLMD/resources/live-auth.md +465 -0
  7. package/LLMD/resources/live-components.md +168 -26
  8. package/LLMD/resources/live-logging.md +220 -0
  9. package/LLMD/resources/live-upload.md +59 -8
  10. package/LLMD/resources/rest-auth.md +290 -0
  11. package/README.md +520 -340
  12. package/app/client/index.html +2 -2
  13. package/app/client/public/favicon.svg +46 -0
  14. package/app/client/src/App.tsx +13 -1
  15. package/app/client/src/assets/fluxstack-static.svg +46 -0
  16. package/app/client/src/assets/fluxstack.svg +183 -0
  17. package/app/client/src/components/AppLayout.tsx +139 -9
  18. package/app/client/src/components/BackButton.tsx +13 -13
  19. package/app/client/src/components/DemoPage.tsx +4 -4
  20. package/app/client/src/live/AuthDemo.tsx +334 -0
  21. package/app/client/src/live/ChatDemo.tsx +2 -2
  22. package/app/client/src/live/CounterDemo.tsx +12 -12
  23. package/app/client/src/live/FormDemo.tsx +2 -2
  24. package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
  25. package/app/client/src/live/RoomChatDemo.tsx +24 -16
  26. package/app/client/src/main.tsx +13 -13
  27. package/app/client/src/pages/ApiTestPage.tsx +6 -6
  28. package/app/client/src/pages/HomePage.tsx +80 -52
  29. package/app/server/auth/AuthManager.ts +213 -0
  30. package/app/server/auth/DevAuthProvider.ts +66 -0
  31. package/app/server/auth/HashManager.ts +123 -0
  32. package/app/server/auth/JWTAuthProvider.example.ts +101 -0
  33. package/app/server/auth/RateLimiter.ts +106 -0
  34. package/app/server/auth/contracts.ts +192 -0
  35. package/app/server/auth/guards/SessionGuard.ts +167 -0
  36. package/app/server/auth/guards/TokenGuard.ts +202 -0
  37. package/app/server/auth/index.ts +174 -0
  38. package/app/server/auth/middleware.ts +163 -0
  39. package/app/server/auth/providers/InMemoryProvider.ts +162 -0
  40. package/app/server/auth/sessions/SessionManager.ts +164 -0
  41. package/app/server/cache/CacheManager.ts +81 -0
  42. package/app/server/cache/MemoryDriver.ts +112 -0
  43. package/app/server/cache/contracts.ts +49 -0
  44. package/app/server/cache/index.ts +42 -0
  45. package/app/server/index.ts +14 -0
  46. package/app/server/live/LiveAdminPanel.ts +174 -0
  47. package/app/server/live/LiveChat.ts +78 -77
  48. package/app/server/live/LiveCounter.ts +1 -0
  49. package/app/server/live/LiveForm.ts +1 -0
  50. package/app/server/live/LiveLocalCounter.ts +38 -32
  51. package/app/server/live/LiveProtectedChat.ts +151 -0
  52. package/app/server/live/LiveRoomChat.ts +1 -0
  53. package/app/server/live/LiveUpload.ts +1 -0
  54. package/app/server/live/register-components.ts +19 -19
  55. package/app/server/routes/auth.routes.ts +278 -0
  56. package/app/server/routes/index.ts +2 -0
  57. package/config/index.ts +8 -0
  58. package/config/system/auth.config.ts +49 -0
  59. package/config/system/runtime.config.ts +4 -0
  60. package/config/system/session.config.ts +33 -0
  61. package/core/build/optimizer.ts +235 -235
  62. package/core/client/LiveComponentsProvider.tsx +76 -5
  63. package/core/client/components/Live.tsx +17 -10
  64. package/core/client/components/LiveDebugger.tsx +1324 -0
  65. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  66. package/core/client/hooks/useLiveComponent.ts +58 -5
  67. package/core/client/hooks/useLiveDebugger.ts +392 -0
  68. package/core/client/index.ts +16 -1
  69. package/core/framework/server.ts +36 -4
  70. package/core/plugins/built-in/index.ts +134 -134
  71. package/core/plugins/built-in/live-components/commands/create-live-component.ts +19 -8
  72. package/core/plugins/built-in/monitoring/index.ts +10 -3
  73. package/core/plugins/built-in/vite/index.ts +151 -20
  74. package/core/plugins/config.ts +5 -4
  75. package/core/plugins/discovery.ts +11 -2
  76. package/core/plugins/manager.ts +11 -5
  77. package/core/plugins/module-resolver.ts +1 -1
  78. package/core/plugins/registry.ts +53 -25
  79. package/core/server/index.ts +15 -15
  80. package/core/server/live/ComponentRegistry.ts +134 -50
  81. package/core/server/live/FileUploadManager.ts +188 -24
  82. package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
  83. package/core/server/live/LiveDebugger.ts +462 -0
  84. package/core/server/live/LiveLogger.ts +144 -0
  85. package/core/server/live/LiveRoomManager.ts +22 -5
  86. package/core/server/live/StateSignature.ts +704 -643
  87. package/core/server/live/WebSocketConnectionManager.ts +11 -10
  88. package/core/server/live/auth/LiveAuthContext.ts +71 -0
  89. package/core/server/live/auth/LiveAuthManager.ts +304 -0
  90. package/core/server/live/auth/index.ts +19 -0
  91. package/core/server/live/auth/types.ts +179 -0
  92. package/core/server/live/auto-generated-components.ts +8 -2
  93. package/core/server/live/index.ts +16 -0
  94. package/core/server/live/websocket-plugin.ts +323 -22
  95. package/core/server/plugins/static-files-plugin.ts +179 -69
  96. package/core/templates/create-project.ts +0 -3
  97. package/core/types/build.ts +219 -219
  98. package/core/types/plugin.ts +107 -107
  99. package/core/types/types.ts +278 -22
  100. package/core/utils/index.ts +17 -17
  101. package/core/utils/logger/index.ts +5 -2
  102. package/core/utils/logger/startup-banner.ts +82 -82
  103. package/core/utils/version.ts +6 -6
  104. package/package.json +1 -8
  105. package/plugins/crypto-auth/index.ts +6 -0
  106. package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
  107. package/plugins/crypto-auth/server/index.ts +24 -21
  108. package/rest-tests/README.md +57 -0
  109. package/rest-tests/auth-token.http +113 -0
  110. package/rest-tests/auth.http +112 -0
  111. package/rest-tests/rooms-token.http +69 -0
  112. package/rest-tests/users-token.http +62 -0
  113. package/.dockerignore +0 -81
  114. package/Dockerfile +0 -70
  115. package/LIVE_COMPONENTS_REVIEW.md +0 -781
  116. package/app/client/src/assets/react.svg +0 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * FluxStack Auth - Session Manager
3
+ *
4
+ * Gerencia sessões de usuários usando o cache system modular.
5
+ * Cada sessão é um registro no cache com TTL automático.
6
+ *
7
+ * ```ts
8
+ * const sessionId = await sessionManager.create({ userId: 1 })
9
+ * const data = await sessionManager.read(sessionId)
10
+ * await sessionManager.destroy(sessionId)
11
+ * ```
12
+ */
13
+
14
+ import type { CacheDriver } from '@server/cache/contracts'
15
+ import { cacheManager } from '@server/cache'
16
+
17
+ export interface SessionData {
18
+ [key: string]: unknown
19
+ }
20
+
21
+ export interface SessionConfig {
22
+ /** Tempo de vida da sessão em segundos (default: 7200 = 2h) */
23
+ lifetime: number
24
+ /** Nome do cookie (default: 'fluxstack_session') */
25
+ cookieName: string
26
+ /** Cookie httpOnly (default: true) */
27
+ httpOnly: boolean
28
+ /** Cookie secure (default: false em dev, true em prod) */
29
+ secure: boolean
30
+ /** Cookie sameSite (default: 'lax') */
31
+ sameSite: 'strict' | 'lax' | 'none'
32
+ /** Cookie path (default: '/') */
33
+ path: string
34
+ /** Cookie domain (default: undefined = current domain) */
35
+ domain?: string
36
+ }
37
+
38
+ const DEFAULT_CONFIG: SessionConfig = {
39
+ lifetime: 7200,
40
+ cookieName: 'fluxstack_session',
41
+ httpOnly: true,
42
+ secure: false,
43
+ sameSite: 'lax',
44
+ path: '/',
45
+ }
46
+
47
+ export class SessionManager {
48
+ private cache: CacheDriver
49
+ private config: SessionConfig
50
+
51
+ constructor(cache?: CacheDriver, config?: Partial<SessionConfig>) {
52
+ this.cache = cache ?? cacheManager.driver()
53
+ this.config = { ...DEFAULT_CONFIG, ...config }
54
+ }
55
+
56
+ /**
57
+ * Cria uma nova sessão e retorna o ID.
58
+ */
59
+ async create(data: SessionData = {}): Promise<string> {
60
+ const sessionId = this.generateId()
61
+ await this.cache.set(
62
+ this.cacheKey(sessionId),
63
+ { ...data, _createdAt: Date.now() },
64
+ this.config.lifetime
65
+ )
66
+ return sessionId
67
+ }
68
+
69
+ /**
70
+ * Lê os dados de uma sessão.
71
+ */
72
+ async read(sessionId: string): Promise<SessionData | null> {
73
+ return this.cache.get<SessionData>(this.cacheKey(sessionId))
74
+ }
75
+
76
+ /**
77
+ * Atualiza os dados de uma sessão (merge com dados existentes).
78
+ */
79
+ async update(sessionId: string, data: SessionData): Promise<void> {
80
+ const existing = await this.read(sessionId)
81
+ if (!existing) return
82
+
83
+ await this.cache.set(
84
+ this.cacheKey(sessionId),
85
+ { ...existing, ...data },
86
+ this.config.lifetime
87
+ )
88
+ }
89
+
90
+ /**
91
+ * Define um valor específico na sessão.
92
+ */
93
+ async put(sessionId: string, key: string, value: unknown): Promise<void> {
94
+ const existing = await this.read(sessionId)
95
+ if (!existing) return
96
+
97
+ existing[key] = value
98
+ await this.cache.set(
99
+ this.cacheKey(sessionId),
100
+ existing,
101
+ this.config.lifetime
102
+ )
103
+ }
104
+
105
+ /**
106
+ * Remove um valor específico da sessão.
107
+ */
108
+ async forget(sessionId: string, key: string): Promise<void> {
109
+ const existing = await this.read(sessionId)
110
+ if (!existing) return
111
+
112
+ delete existing[key]
113
+ await this.cache.set(
114
+ this.cacheKey(sessionId),
115
+ existing,
116
+ this.config.lifetime
117
+ )
118
+ }
119
+
120
+ /**
121
+ * Destroi uma sessão.
122
+ */
123
+ async destroy(sessionId: string): Promise<void> {
124
+ await this.cache.delete(this.cacheKey(sessionId))
125
+ }
126
+
127
+ /**
128
+ * Regenera o ID da sessão (mantém os dados).
129
+ * Importante após login para prevenir session fixation.
130
+ */
131
+ async regenerate(oldSessionId: string): Promise<string> {
132
+ const data = await this.read(oldSessionId)
133
+ await this.destroy(oldSessionId)
134
+
135
+ const newSessionId = this.generateId()
136
+ if (data) {
137
+ await this.cache.set(
138
+ this.cacheKey(newSessionId),
139
+ data,
140
+ this.config.lifetime
141
+ )
142
+ }
143
+ return newSessionId
144
+ }
145
+
146
+ /** Retorna a configuração de sessão */
147
+ getConfig(): SessionConfig {
148
+ return this.config
149
+ }
150
+
151
+ /** Gera um session ID criptograficamente seguro */
152
+ private generateId(): string {
153
+ const bytes = new Uint8Array(32)
154
+ crypto.getRandomValues(bytes)
155
+ return Array.from(bytes)
156
+ .map(b => b.toString(16).padStart(2, '0'))
157
+ .join('')
158
+ }
159
+
160
+ /** Chave no cache para a sessão */
161
+ private cacheKey(sessionId: string): string {
162
+ return `session:${sessionId}`
163
+ }
164
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * FluxStack Cache - Cache Manager
3
+ *
4
+ * Factory para drivers de cache. Extensível com extend().
5
+ *
6
+ * Uso:
7
+ * ```ts
8
+ * const cache = cacheManager.driver() // driver padrão (memory)
9
+ * const redis = cacheManager.driver('redis') // driver específico
10
+ *
11
+ * await cache.set('key', 'value', 60)
12
+ * const val = await cache.get('key')
13
+ * ```
14
+ */
15
+
16
+ import type { CacheDriver } from './contracts'
17
+ import { MemoryCacheDriver } from './MemoryDriver'
18
+
19
+ type DriverFactory = () => CacheDriver
20
+
21
+ export class CacheManager {
22
+ private drivers = new Map<string, CacheDriver>()
23
+ private factories = new Map<string, DriverFactory>()
24
+ private defaultDriverName: string
25
+
26
+ constructor(defaultDriver: string = 'memory') {
27
+ this.defaultDriverName = defaultDriver
28
+
29
+ // Registrar driver padrão
30
+ this.factories.set('memory', () => new MemoryCacheDriver())
31
+ }
32
+
33
+ /**
34
+ * Retorna uma instância do driver de cache.
35
+ * Drivers são criados uma vez e reutilizados (singleton por nome).
36
+ */
37
+ driver(name?: string): CacheDriver {
38
+ const driverName = name ?? this.defaultDriverName
39
+
40
+ // Cache de instâncias
41
+ if (this.drivers.has(driverName)) {
42
+ return this.drivers.get(driverName)!
43
+ }
44
+
45
+ const factory = this.factories.get(driverName)
46
+ if (!factory) {
47
+ throw new Error(
48
+ `Cache driver '${driverName}' not registered. ` +
49
+ `Available: ${Array.from(this.factories.keys()).join(', ')}`
50
+ )
51
+ }
52
+
53
+ const driver = factory()
54
+ this.drivers.set(driverName, driver)
55
+ return driver
56
+ }
57
+
58
+ /**
59
+ * Registra um driver customizado.
60
+ *
61
+ * ```ts
62
+ * cacheManager.extend('redis', () => new RedisCacheDriver({ url: '...' }))
63
+ * ```
64
+ */
65
+ extend(name: string, factory: DriverFactory): void {
66
+ this.factories.set(name, factory)
67
+ }
68
+
69
+ /** Retorna o nome do driver padrão */
70
+ getDefaultDriver(): string {
71
+ return this.defaultDriverName
72
+ }
73
+
74
+ /** Altera o driver padrão */
75
+ setDefaultDriver(name: string): void {
76
+ this.defaultDriverName = name
77
+ }
78
+ }
79
+
80
+ /** Instância global do cache manager */
81
+ export const cacheManager = new CacheManager()
@@ -0,0 +1,112 @@
1
+ /**
2
+ * FluxStack Cache - Memory Driver
3
+ *
4
+ * Armazena cache na memória do processo.
5
+ * Ideal para desenvolvimento e testes.
6
+ *
7
+ * Para produção, troque por Redis, Memcached, etc.
8
+ */
9
+
10
+ import type { CacheDriver } from './contracts'
11
+
12
+ interface CacheEntry<T = unknown> {
13
+ value: T
14
+ expiresAt: number | null // null = sem expiração
15
+ }
16
+
17
+ export class MemoryCacheDriver implements CacheDriver {
18
+ private store = new Map<string, CacheEntry>()
19
+ private gcInterval: ReturnType<typeof setInterval> | null = null
20
+
21
+ constructor(gcIntervalMs: number = 60_000) {
22
+ // GC periódico para limpar entradas expiradas
23
+ this.gcInterval = setInterval(() => this.gc(), gcIntervalMs)
24
+ if (this.gcInterval.unref) {
25
+ this.gcInterval.unref()
26
+ }
27
+ }
28
+
29
+ async get<T = unknown>(key: string): Promise<T | null> {
30
+ const entry = this.store.get(key)
31
+ if (!entry) return null
32
+
33
+ // Verificar expiração
34
+ if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
35
+ this.store.delete(key)
36
+ return null
37
+ }
38
+
39
+ return entry.value as T
40
+ }
41
+
42
+ async set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void> {
43
+ const expiresAt = ttlSeconds && ttlSeconds > 0
44
+ ? Date.now() + (ttlSeconds * 1000)
45
+ : null
46
+
47
+ this.store.set(key, { value, expiresAt })
48
+ }
49
+
50
+ async has(key: string): Promise<boolean> {
51
+ const value = await this.get(key)
52
+ return value !== null
53
+ }
54
+
55
+ async delete(key: string): Promise<boolean> {
56
+ return this.store.delete(key)
57
+ }
58
+
59
+ async flush(): Promise<void> {
60
+ this.store.clear()
61
+ }
62
+
63
+ async increment(key: string, amount: number = 1): Promise<number> {
64
+ const current = await this.get<number>(key)
65
+ const newValue = (current ?? 0) + amount
66
+
67
+ // Preservar TTL original se existir
68
+ const entry = this.store.get(key)
69
+ const remainingTtl = entry?.expiresAt
70
+ ? Math.max(0, Math.ceil((entry.expiresAt - Date.now()) / 1000))
71
+ : undefined
72
+
73
+ await this.set(key, newValue, remainingTtl)
74
+ return newValue
75
+ }
76
+
77
+ async decrement(key: string, amount: number = 1): Promise<number> {
78
+ return this.increment(key, -amount)
79
+ }
80
+
81
+ async remember<T = unknown>(key: string, ttlSeconds: number, callback: () => Promise<T>): Promise<T> {
82
+ const cached = await this.get<T>(key)
83
+ if (cached !== null) return cached
84
+
85
+ const value = await callback()
86
+ await this.set(key, value, ttlSeconds)
87
+ return value
88
+ }
89
+
90
+ async gc(): Promise<void> {
91
+ const now = Date.now()
92
+ for (const [key, entry] of this.store) {
93
+ if (entry.expiresAt !== null && now > entry.expiresAt) {
94
+ this.store.delete(key)
95
+ }
96
+ }
97
+ }
98
+
99
+ /** Para testes: retorna quantidade de itens no cache */
100
+ get size(): number {
101
+ return this.store.size
102
+ }
103
+
104
+ /** Cleanup ao destruir */
105
+ destroy(): void {
106
+ if (this.gcInterval) {
107
+ clearInterval(this.gcInterval)
108
+ this.gcInterval = null
109
+ }
110
+ this.store.clear()
111
+ }
112
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * FluxStack Cache System - Contracts
3
+ *
4
+ * Interface modular para cache. Hoje usa memória do processo,
5
+ * amanhã pode trocar para Redis, Memcached, SQLite, etc.
6
+ *
7
+ * Inspirado no Cache do Laravel: driver swappable via config.
8
+ */
9
+
10
+ /**
11
+ * Interface que todo driver de cache deve implementar.
12
+ *
13
+ * Para criar um driver customizado (ex: Redis):
14
+ * ```ts
15
+ * class RedisCacheDriver implements CacheDriver {
16
+ * async get(key) { return redis.get(key) }
17
+ * async set(key, value, ttl) { redis.setex(key, ttl, value) }
18
+ * // ...
19
+ * }
20
+ * ```
21
+ */
22
+ export interface CacheDriver {
23
+ /** Busca valor do cache. Retorna null se não existe ou expirou. */
24
+ get<T = unknown>(key: string): Promise<T | null>
25
+
26
+ /** Armazena valor no cache. TTL em segundos (0 = sem expiração). */
27
+ set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void>
28
+
29
+ /** Verifica se a chave existe no cache. */
30
+ has(key: string): Promise<boolean>
31
+
32
+ /** Remove uma chave do cache. */
33
+ delete(key: string): Promise<boolean>
34
+
35
+ /** Remove todas as chaves do cache. */
36
+ flush(): Promise<void>
37
+
38
+ /** Incrementa um valor numérico. Retorna o novo valor. */
39
+ increment(key: string, amount?: number): Promise<number>
40
+
41
+ /** Decrementa um valor numérico. Retorna o novo valor. */
42
+ decrement(key: string, amount?: number): Promise<number>
43
+
44
+ /** Busca ou armazena: se não existe, executa callback e salva. */
45
+ remember<T = unknown>(key: string, ttlSeconds: number, callback: () => Promise<T>): Promise<T>
46
+
47
+ /** Remove entradas expiradas (garbage collection). */
48
+ gc(): Promise<void>
49
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * FluxStack Cache System
3
+ *
4
+ * Sistema modular de cache. Hoje usa memória, amanhã Redis.
5
+ *
6
+ * ```ts
7
+ * import { cache, cacheManager } from '@server/cache'
8
+ *
9
+ * // Usar cache padrão
10
+ * await cache.set('user:1', userData, 3600)
11
+ * const user = await cache.get('user:1')
12
+ *
13
+ * // Trocar driver
14
+ * cacheManager.extend('redis', () => new MyRedisDriver())
15
+ * const redis = cacheManager.driver('redis')
16
+ * ```
17
+ */
18
+
19
+ export type { CacheDriver } from './contracts'
20
+ export { MemoryCacheDriver } from './MemoryDriver'
21
+ export { CacheManager, cacheManager } from './CacheManager'
22
+
23
+ /** Atalho: driver de cache padrão (lazy-initialized) */
24
+ import type { CacheDriver as CacheDriverType } from './contracts'
25
+ import { cacheManager as _cm } from './CacheManager'
26
+
27
+ let _cacheInstance: CacheDriverType | null = null
28
+
29
+ function getCacheInstance(): CacheDriverType {
30
+ if (!_cacheInstance) {
31
+ _cacheInstance = _cm.driver()
32
+ }
33
+ return _cacheInstance
34
+ }
35
+
36
+ export const cache: CacheDriverType = new Proxy({} as CacheDriverType, {
37
+ get(_, prop: string) {
38
+ const instance = getCacheInstance()
39
+ const value = (instance as any)[prop]
40
+ return typeof value === 'function' ? value.bind(instance) : value
41
+ }
42
+ })
@@ -17,6 +17,20 @@ import { liveComponentsPlugin } from "@core/server/live/websocket-plugin"
17
17
  import { appInstance } from "@server/app"
18
18
  import { appConfig } from "@config"
19
19
 
20
+ // 🔒 Auth provider para Live Components
21
+ import { liveAuthManager } from "@core/server/live/auth"
22
+ import { DevAuthProvider } from "./auth/DevAuthProvider"
23
+
24
+ // 🔐 Auth system (Guard + Provider, Laravel-inspired)
25
+ import { initAuth } from "@server/auth"
26
+
27
+ // Registrar provider de desenvolvimento (tokens simples para testes)
28
+ liveAuthManager.register(new DevAuthProvider())
29
+ console.log('🔓 DevAuthProvider registered')
30
+
31
+ // Inicializar sistema de autenticação
32
+ initAuth()
33
+
20
34
  const framework = new FluxStackFramework()
21
35
  .use(swaggerPlugin)
22
36
  .use(liveComponentsPlugin)
@@ -0,0 +1,174 @@
1
+ // 🔒 LiveAdminPanel - Exemplo completo de Live Component com autenticação
2
+ //
3
+ // Demonstra todos os cenários de auth:
4
+ // 1. Componente público (sem auth) → LiveCounter
5
+ // 2. Componente protegido (auth required) → este componente
6
+ // 3. Componente com roles → este componente (role: admin)
7
+ // 4. Actions com permissões granulares → deleteUser requer 'users.delete'
8
+ // 5. Acesso ao $auth dentro de actions → getAuthInfo, audit trail
9
+ //
10
+ // Client: import { LiveAdminPanel } from '@server/live/LiveAdminPanel'
11
+ // Client link: import type { AdminPanelDemo as _Client } from '@client/src/live/AdminPanelDemo'
12
+
13
+ import { LiveComponent } from '@core/types/types'
14
+ import type { LiveComponentAuth, LiveActionAuthMap } from '@core/server/live/auth/types'
15
+
16
+ // ===== State =====
17
+
18
+ interface User {
19
+ id: string
20
+ name: string
21
+ role: string
22
+ createdAt: number
23
+ }
24
+
25
+ interface AuditEntry {
26
+ action: string
27
+ performedBy: string
28
+ target?: string
29
+ timestamp: number
30
+ }
31
+
32
+ interface AdminPanelState {
33
+ users: User[]
34
+ audit: AuditEntry[]
35
+ currentUser: string | null
36
+ currentRoles: string[]
37
+ isAdmin: boolean
38
+ }
39
+
40
+ // ===== Component =====
41
+
42
+ export class LiveAdminPanel extends LiveComponent<AdminPanelState> {
43
+ static componentName = 'LiveAdminPanel'
44
+ static publicActions = ['getAuthInfo', 'init', 'listUsers', 'addUser', 'deleteUser', 'clearAudit'] as const
45
+
46
+ static defaultState: AdminPanelState = {
47
+ users: [
48
+ { id: '1', name: 'Alice', role: 'admin', createdAt: Date.now() },
49
+ { id: '2', name: 'Bob', role: 'user', createdAt: Date.now() },
50
+ { id: '3', name: 'Carol', role: 'moderator', createdAt: Date.now() },
51
+ ],
52
+ audit: [],
53
+ currentUser: null,
54
+ currentRoles: [],
55
+ isAdmin: false,
56
+ }
57
+
58
+ // ─────────────────────────────────────────
59
+ // 🔒 Auth: requer autenticação + role admin
60
+ // ─────────────────────────────────────────
61
+ static auth: LiveComponentAuth = {
62
+ required: true,
63
+ roles: ['admin'],
64
+ }
65
+
66
+ // ─────────────────────────────────────────
67
+ // 🔒 Auth por action: permissões granulares
68
+ // ─────────────────────────────────────────
69
+ static actionAuth: LiveActionAuthMap = {
70
+ deleteUser: { permissions: ['users.delete'] },
71
+ clearAudit: { roles: ['admin'] },
72
+ }
73
+
74
+ // ===== Actions =====
75
+
76
+ /**
77
+ * Retorna info do usuário autenticado.
78
+ * Qualquer admin pode chamar (protegido pelo static auth do componente).
79
+ */
80
+ async getAuthInfo() {
81
+ return {
82
+ authenticated: this.$auth.authenticated,
83
+ userId: this.$auth.user?.id,
84
+ roles: this.$auth.user?.roles || [],
85
+ permissions: this.$auth.user?.permissions || [],
86
+ isAdmin: this.$auth.hasRole('admin'),
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Popula o state com info do usuário autenticado.
92
+ * Chamado pelo client após mount para exibir quem está logado.
93
+ */
94
+ async init() {
95
+ this.setState({
96
+ currentUser: this.$auth.user?.id || null,
97
+ currentRoles: this.$auth.user?.roles || [],
98
+ isAdmin: this.$auth.hasRole('admin'),
99
+ })
100
+
101
+ this.addAudit('LOGIN', this.$auth.user?.id || 'unknown')
102
+
103
+ return { success: true }
104
+ }
105
+
106
+ /**
107
+ * Lista usuários - qualquer admin pode.
108
+ */
109
+ async listUsers() {
110
+ return { users: this.state.users }
111
+ }
112
+
113
+ /**
114
+ * Adiciona um usuário - qualquer admin pode.
115
+ */
116
+ async addUser(payload: { name: string; role: string }) {
117
+ const user: User = {
118
+ id: String(Date.now()),
119
+ name: payload.name,
120
+ role: payload.role,
121
+ createdAt: Date.now(),
122
+ }
123
+
124
+ this.setState({
125
+ users: [...this.state.users, user],
126
+ })
127
+
128
+ this.addAudit('ADD_USER', this.$auth.user?.id || 'unknown', user.name)
129
+
130
+ return { success: true, user }
131
+ }
132
+
133
+ /**
134
+ * 🔒 Deleta um usuário.
135
+ * Requer permissão 'users.delete' (via static actionAuth).
136
+ * Se o usuário não tiver essa permissão, o framework bloqueia ANTES
137
+ * de executar este método.
138
+ */
139
+ async deleteUser(payload: { userId: string }) {
140
+ const user = this.state.users.find(u => u.id === payload.userId)
141
+ if (!user) throw new Error('User not found')
142
+
143
+ this.setState({
144
+ users: this.state.users.filter(u => u.id !== payload.userId),
145
+ })
146
+
147
+ this.addAudit('DELETE_USER', this.$auth.user?.id || 'unknown', user.name)
148
+
149
+ return { success: true }
150
+ }
151
+
152
+ /**
153
+ * 🔒 Limpa o audit log.
154
+ * Requer role 'admin' (via static actionAuth).
155
+ */
156
+ async clearAudit() {
157
+ this.setState({ audit: [] })
158
+ return { success: true }
159
+ }
160
+
161
+ // ===== Helpers (privados, não expostos como actions) =====
162
+
163
+ private addAudit(action: string, performedBy: string, target?: string) {
164
+ const entry: AuditEntry = {
165
+ action,
166
+ performedBy,
167
+ target,
168
+ timestamp: Date.now(),
169
+ }
170
+ this.setState({
171
+ audit: [...this.state.audit, entry].slice(-20),
172
+ })
173
+ }
174
+ }