create-fluxstack 1.4.1 → 1.5.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 (51) hide show
  1. package/.env.example +8 -1
  2. package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
  3. package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
  4. package/CRYPTO-AUTH-USAGE.md +491 -0
  5. package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
  6. package/QUICK-START-CRYPTO-AUTH.md +221 -0
  7. package/app/client/src/App.tsx +4 -1
  8. package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
  9. package/app/server/index.ts +4 -0
  10. package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
  11. package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
  12. package/app/server/routes/exemplo-posts.routes.ts +161 -0
  13. package/app/server/routes/index.ts +5 -1
  14. package/config/index.ts +9 -1
  15. package/core/cli/generators/plugin.ts +324 -34
  16. package/core/cli/generators/template-engine.ts +5 -0
  17. package/core/cli/plugin-discovery.ts +33 -12
  18. package/core/framework/server.ts +10 -0
  19. package/core/plugins/dependency-manager.ts +89 -22
  20. package/core/plugins/index.ts +4 -0
  21. package/core/plugins/manager.ts +3 -2
  22. package/core/plugins/module-resolver.ts +216 -0
  23. package/core/plugins/registry.ts +28 -1
  24. package/core/utils/logger/index.ts +4 -0
  25. package/fluxstack.config.ts +253 -114
  26. package/package.json +117 -117
  27. package/plugins/crypto-auth/README.md +722 -172
  28. package/plugins/crypto-auth/ai-context.md +1282 -0
  29. package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
  30. package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
  31. package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
  32. package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
  33. package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
  34. package/plugins/crypto-auth/client/components/index.ts +1 -4
  35. package/plugins/crypto-auth/client/index.ts +1 -1
  36. package/plugins/crypto-auth/config/index.ts +34 -0
  37. package/plugins/crypto-auth/index.ts +84 -152
  38. package/plugins/crypto-auth/package.json +65 -64
  39. package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
  40. package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
  41. package/plugins/crypto-auth/server/index.ts +15 -2
  42. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
  43. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
  44. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
  45. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
  46. package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
  47. package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
  48. package/plugins/crypto-auth/server/middlewares.ts +19 -0
  49. package/test-crypto-auth.ts +101 -0
  50. package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
  51. package/plugins/crypto-auth/plugin.json +0 -29
@@ -0,0 +1,383 @@
1
+ /**
2
+ * CLI Command: make:protected-route
3
+ * Gera rotas protegidas automaticamente
4
+ */
5
+
6
+ import type { CliCommand, CliContext } from '@/core/plugins/types'
7
+ import { writeFileSync, existsSync, mkdirSync } from 'fs'
8
+ import { join } from 'path'
9
+
10
+ const ROUTE_TEMPLATES = {
11
+ required: (name: string, pascalName: string) => `/**
12
+ * ${pascalName} Routes
13
+ * 🔒 Autenticação obrigatória
14
+ * Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth required
15
+ */
16
+
17
+ import { Elysia, t } from 'elysia'
18
+ import { cryptoAuthRequired, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
19
+
20
+ export const ${name}Routes = new Elysia({ prefix: '/${name}' })
21
+
22
+ // ========================================
23
+ // 🔒 ROTAS PROTEGIDAS (autenticação obrigatória)
24
+ // ========================================
25
+ .guard({}, (app) =>
26
+ app.use(cryptoAuthRequired())
27
+
28
+ // GET /api/${name}
29
+ .get('/', ({ request }) => {
30
+ const user = getCryptoAuthUser(request)!
31
+
32
+ return {
33
+ success: true,
34
+ message: 'Lista de ${name}',
35
+ user: {
36
+ publicKey: user.publicKey.substring(0, 16) + '...',
37
+ isAdmin: user.isAdmin
38
+ },
39
+ data: []
40
+ }
41
+ })
42
+
43
+ // GET /api/${name}/:id
44
+ .get('/:id', ({ request, params }) => {
45
+ const user = getCryptoAuthUser(request)!
46
+
47
+ return {
48
+ success: true,
49
+ message: 'Detalhes de ${name}',
50
+ id: params.id,
51
+ user: user.publicKey.substring(0, 8) + '...'
52
+ }
53
+ })
54
+
55
+ // POST /api/${name}
56
+ .post('/', ({ request, body }) => {
57
+ const user = getCryptoAuthUser(request)!
58
+ const data = body as any
59
+
60
+ return {
61
+ success: true,
62
+ message: '${pascalName} criado com sucesso',
63
+ createdBy: user.publicKey.substring(0, 8) + '...',
64
+ data
65
+ }
66
+ }, {
67
+ body: t.Object({
68
+ // Adicione seus campos aqui
69
+ name: t.String({ minLength: 3 })
70
+ })
71
+ })
72
+
73
+ // PUT /api/${name}/:id
74
+ .put('/:id', ({ request, params, body }) => {
75
+ const user = getCryptoAuthUser(request)!
76
+ const data = body as any
77
+
78
+ return {
79
+ success: true,
80
+ message: '${pascalName} atualizado',
81
+ id: params.id,
82
+ updatedBy: user.publicKey.substring(0, 8) + '...',
83
+ data
84
+ }
85
+ }, {
86
+ body: t.Object({
87
+ name: t.String({ minLength: 3 })
88
+ })
89
+ })
90
+
91
+ // DELETE /api/${name}/:id
92
+ .delete('/:id', ({ request, params }) => {
93
+ const user = getCryptoAuthUser(request)!
94
+
95
+ return {
96
+ success: true,
97
+ message: '${pascalName} deletado',
98
+ id: params.id,
99
+ deletedBy: user.publicKey.substring(0, 8) + '...'
100
+ }
101
+ })
102
+ )
103
+ `,
104
+
105
+ admin: (name: string, pascalName: string) => `/**
106
+ * ${pascalName} Routes
107
+ * 👑 Apenas administradores
108
+ * Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth admin
109
+ */
110
+
111
+ import { Elysia, t } from 'elysia'
112
+ import { cryptoAuthAdmin, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
113
+
114
+ export const ${name}Routes = new Elysia({ prefix: '/${name}' })
115
+
116
+ // ========================================
117
+ // 👑 ROTAS ADMIN (apenas administradores)
118
+ // ========================================
119
+ .guard({}, (app) =>
120
+ app.use(cryptoAuthAdmin())
121
+
122
+ // GET /api/${name}
123
+ .get('/', ({ request }) => {
124
+ const user = getCryptoAuthUser(request)!
125
+
126
+ return {
127
+ success: true,
128
+ message: 'Painel administrativo de ${name}',
129
+ admin: user.publicKey.substring(0, 8) + '...',
130
+ data: []
131
+ }
132
+ })
133
+
134
+ // POST /api/${name}
135
+ .post('/', ({ request, body }) => {
136
+ const user = getCryptoAuthUser(request)!
137
+ const data = body as any
138
+
139
+ return {
140
+ success: true,
141
+ message: '${pascalName} criado pelo admin',
142
+ admin: user.publicKey.substring(0, 8) + '...',
143
+ data
144
+ }
145
+ }, {
146
+ body: t.Object({
147
+ name: t.String({ minLength: 3 })
148
+ })
149
+ })
150
+
151
+ // DELETE /api/${name}/:id
152
+ .delete('/:id', ({ request, params }) => {
153
+ const user = getCryptoAuthUser(request)!
154
+
155
+ return {
156
+ success: true,
157
+ message: '${pascalName} deletado pelo admin',
158
+ id: params.id,
159
+ admin: user.publicKey.substring(0, 8) + '...'
160
+ }
161
+ })
162
+ )
163
+ `,
164
+
165
+ optional: (name: string, pascalName: string) => `/**
166
+ * ${pascalName} Routes
167
+ * 🌓 Autenticação opcional
168
+ * Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth optional
169
+ */
170
+
171
+ import { Elysia } from 'elysia'
172
+ import { cryptoAuthOptional, getCryptoAuthUser } from '@/plugins/crypto-auth/server'
173
+
174
+ export const ${name}Routes = new Elysia({ prefix: '/${name}' })
175
+
176
+ // ========================================
177
+ // 🌐 ROTA PÚBLICA
178
+ // ========================================
179
+ .get('/', () => ({
180
+ success: true,
181
+ message: 'Lista pública de ${name}',
182
+ data: []
183
+ }))
184
+
185
+ // ========================================
186
+ // 🌓 ROTAS COM AUTH OPCIONAL
187
+ // ========================================
188
+ .guard({}, (app) =>
189
+ app.use(cryptoAuthOptional())
190
+
191
+ // GET /api/${name}/:id
192
+ .get('/:id', ({ request, params }) => {
193
+ const user = getCryptoAuthUser(request)
194
+ const isAuthenticated = !!user
195
+
196
+ return {
197
+ success: true,
198
+ id: params.id,
199
+ message: isAuthenticated
200
+ ? \`${pascalName} personalizado para \${user.publicKey.substring(0, 8)}...\`
201
+ : 'Visualização pública de ${name}',
202
+ // Conteúdo extra apenas para autenticados
203
+ premiumContent: isAuthenticated ? 'Conteúdo exclusivo' : null,
204
+ viewer: isAuthenticated
205
+ ? user.publicKey.substring(0, 8) + '...'
206
+ : 'Visitante anônimo'
207
+ }
208
+ })
209
+ )
210
+ `,
211
+
212
+ public: (name: string, pascalName: string) => `/**
213
+ * ${pascalName} Routes
214
+ * 🌐 Totalmente público
215
+ * Auto-gerado pelo comando: flux crypto-auth:make:route ${name} --auth public
216
+ */
217
+
218
+ import { Elysia } from 'elysia'
219
+
220
+ export const ${name}Routes = new Elysia({ prefix: '/${name}' })
221
+
222
+ // ========================================
223
+ // 🌐 ROTAS PÚBLICAS
224
+ // ========================================
225
+
226
+ // GET /api/${name}
227
+ .get('/', () => ({
228
+ success: true,
229
+ message: 'Lista de ${name}',
230
+ data: []
231
+ }))
232
+
233
+ // GET /api/${name}/:id
234
+ .get('/:id', ({ params }) => ({
235
+ success: true,
236
+ id: params.id,
237
+ message: 'Detalhes de ${name}'
238
+ }))
239
+ `
240
+ }
241
+
242
+ function toPascalCase(str: string): string {
243
+ return str
244
+ .split(/[-_]/)
245
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
246
+ .join('')
247
+ }
248
+
249
+ export const makeProtectedRouteCommand: CliCommand = {
250
+ name: 'crypto-auth:make:route',
251
+ description: 'Gera um arquivo de rotas com proteção crypto-auth',
252
+ category: 'Crypto Auth',
253
+ aliases: ['crypto-auth:generate:route'],
254
+
255
+ arguments: [
256
+ {
257
+ name: 'name',
258
+ description: 'Nome da rota (ex: posts, users, admin)',
259
+ required: true,
260
+ type: 'string'
261
+ }
262
+ ],
263
+
264
+ options: [
265
+ {
266
+ name: 'auth',
267
+ short: 'a',
268
+ description: 'Tipo de autenticação (required, admin, optional, public)',
269
+ type: 'string',
270
+ default: 'required',
271
+ choices: ['required', 'admin', 'optional', 'public']
272
+ },
273
+ {
274
+ name: 'output',
275
+ short: 'o',
276
+ description: 'Diretório de saída (padrão: app/server/routes)',
277
+ type: 'string',
278
+ default: 'app/server/routes'
279
+ },
280
+ {
281
+ name: 'force',
282
+ short: 'f',
283
+ description: 'Sobrescrever arquivo existente',
284
+ type: 'boolean',
285
+ default: false
286
+ }
287
+ ],
288
+
289
+ examples: [
290
+ 'flux crypto-auth:make:route posts',
291
+ 'flux crypto-auth:make:route admin --auth admin',
292
+ 'flux crypto-auth:make:route feed --auth optional',
293
+ 'flux crypto-auth:make:route articles --auth required --force'
294
+ ],
295
+
296
+ handler: async (args, options, context) => {
297
+ const [name] = args as [string]
298
+ const { auth, output, force } = options as {
299
+ auth: 'required' | 'admin' | 'optional' | 'public'
300
+ output: string
301
+ force: boolean
302
+ }
303
+
304
+ // Validar nome
305
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
306
+ console.error('❌ Nome inválido. Use apenas letras minúsculas, números e hífens.')
307
+ console.error(' Exemplos válidos: posts, my-posts, user-settings')
308
+ return
309
+ }
310
+
311
+ const pascalName = toPascalCase(name)
312
+ const fileName = `${name}.routes.ts`
313
+ const outputDir = join(context.workingDir, output)
314
+ const filePath = join(outputDir, fileName)
315
+
316
+ // Verificar se arquivo existe
317
+ if (existsSync(filePath) && !force) {
318
+ console.error(`❌ Arquivo já existe: ${filePath}`)
319
+ console.error(' Use --force para sobrescrever')
320
+ return
321
+ }
322
+
323
+ // Criar diretório se não existir
324
+ if (!existsSync(outputDir)) {
325
+ mkdirSync(outputDir, { recursive: true })
326
+ }
327
+
328
+ // Gerar código
329
+ const template = ROUTE_TEMPLATES[auth]
330
+ const code = template(name, pascalName)
331
+
332
+ // Escrever arquivo
333
+ writeFileSync(filePath, code, 'utf-8')
334
+
335
+ console.log(`\n✅ Rota criada com sucesso!`)
336
+ console.log(`📁 Arquivo: ${filePath}`)
337
+ console.log(`🔐 Tipo de auth: ${auth}`)
338
+
339
+ // Instruções de uso
340
+ console.log(`\n📋 Próximos passos:`)
341
+ console.log(`\n1. Importar a rota em app/server/routes/index.ts:`)
342
+ console.log(` import { ${name}Routes } from './${name}.routes'`)
343
+ console.log(`\n2. Registrar no apiRoutes:`)
344
+ console.log(` export const apiRoutes = new Elysia({ prefix: '/api' })`)
345
+ console.log(` .use(${name}Routes)`)
346
+ console.log(`\n3. Rotas disponíveis:`)
347
+
348
+ const routes = {
349
+ required: [
350
+ `GET /api/${name}`,
351
+ `GET /api/${name}/:id`,
352
+ `POST /api/${name}`,
353
+ `PUT /api/${name}/:id`,
354
+ `DELETE /api/${name}/:id`
355
+ ],
356
+ admin: [
357
+ `GET /api/${name}`,
358
+ `POST /api/${name}`,
359
+ `DELETE /api/${name}/:id`
360
+ ],
361
+ optional: [
362
+ `GET /api/${name}`,
363
+ `GET /api/${name}/:id`
364
+ ],
365
+ public: [
366
+ `GET /api/${name}`,
367
+ `GET /api/${name}/:id`
368
+ ]
369
+ }
370
+
371
+ routes[auth].forEach(route => console.log(` ${route}`))
372
+
373
+ console.log(`\n4. Testar (sem auth):`)
374
+ console.log(` curl http://localhost:3000/api/${name}`)
375
+
376
+ if (auth !== 'public') {
377
+ const expectedStatus = auth === 'optional' ? '200 (sem conteúdo premium)' : '401'
378
+ console.log(` Esperado: ${expectedStatus}`)
379
+ }
380
+
381
+ console.log(`\n🚀 Pronto! Inicie o servidor com: bun run dev`)
382
+ }
383
+ }