create-fluxstack 1.0.13 → 1.0.15
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.
- package/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +196 -33
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { PromptConfig } from "./types.js"
|
|
2
|
+
|
|
3
|
+
export class PromptSystem {
|
|
4
|
+
async prompt(config: PromptConfig): Promise<any> {
|
|
5
|
+
// Simple implementation using process.stdin
|
|
6
|
+
// In a real implementation, you'd use a library like inquirer or prompts
|
|
7
|
+
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
process.stdout.write(`${config.message} `)
|
|
10
|
+
|
|
11
|
+
if (config.default !== undefined) {
|
|
12
|
+
process.stdout.write(`(${config.default}) `)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (config.choices) {
|
|
16
|
+
process.stdout.write(`\nChoices: ${config.choices.map(c => c.name || c.value).join(', ')}\n`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
process.stdout.write(': ')
|
|
20
|
+
|
|
21
|
+
process.stdin.once('data', (data) => {
|
|
22
|
+
const input = data.toString().trim()
|
|
23
|
+
|
|
24
|
+
if (!input && config.default !== undefined) {
|
|
25
|
+
resolve(config.default)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.validate) {
|
|
30
|
+
const validation = config.validate(input)
|
|
31
|
+
if (validation !== true) {
|
|
32
|
+
console.log(`Error: ${validation}`)
|
|
33
|
+
// In a real implementation, you'd re-prompt
|
|
34
|
+
resolve(input)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (config.choices) {
|
|
40
|
+
const choice = config.choices.find(c =>
|
|
41
|
+
c.name === input || c.value === input
|
|
42
|
+
)
|
|
43
|
+
if (choice) {
|
|
44
|
+
resolve(choice.value)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
resolve(input)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async confirm(message: string, defaultValue: boolean = false): Promise<boolean> {
|
|
55
|
+
return this.prompt({
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
message: `${message} (y/n)`,
|
|
58
|
+
default: defaultValue
|
|
59
|
+
}).then(result => {
|
|
60
|
+
const value = String(result).toLowerCase()
|
|
61
|
+
return value === 'y' || value === 'yes' || value === 'true'
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async select(message: string, choices: Array<{ name: string; value: any; description?: string }>): Promise<any> {
|
|
66
|
+
return this.prompt({
|
|
67
|
+
type: 'select',
|
|
68
|
+
message,
|
|
69
|
+
choices
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async input(message: string, defaultValue?: string, validate?: (value: string) => boolean | string): Promise<string> {
|
|
74
|
+
return this.prompt({
|
|
75
|
+
type: 'input',
|
|
76
|
+
message,
|
|
77
|
+
default: defaultValue,
|
|
78
|
+
validate
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const promptSystem = new PromptSystem()
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import type { Generator } from "./index.js"
|
|
2
|
+
import type { GeneratorContext, GeneratorOptions, Template } from "./types.js"
|
|
3
|
+
import { templateEngine } from "./template-engine.js"
|
|
4
|
+
|
|
5
|
+
export class RouteGenerator implements Generator {
|
|
6
|
+
name = 'route'
|
|
7
|
+
description = 'Generate API routes with controllers'
|
|
8
|
+
|
|
9
|
+
async generate(context: GeneratorContext, options: GeneratorOptions): Promise<void> {
|
|
10
|
+
const template = this.getTemplate(options.template)
|
|
11
|
+
|
|
12
|
+
if (template.hooks?.beforeGenerate) {
|
|
13
|
+
await template.hooks.beforeGenerate(context, options)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const files = await templateEngine.processTemplate(template, context, options)
|
|
17
|
+
|
|
18
|
+
if (options.dryRun) {
|
|
19
|
+
console.log(`\n📋 Would generate route '${options.name}':\n`)
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
console.log(`${file.action === 'create' ? '📄' : '✏️'} ${file.path}`)
|
|
22
|
+
}
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await templateEngine.generateFiles(files, options.dryRun)
|
|
27
|
+
|
|
28
|
+
if (template.hooks?.afterGenerate) {
|
|
29
|
+
const filePaths = files.map(f => f.path)
|
|
30
|
+
await template.hooks.afterGenerate(context, options, filePaths)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`\n✅ Generated route '${options.name}' with ${files.length} files`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private getTemplate(templateName?: string): Template {
|
|
37
|
+
switch (templateName) {
|
|
38
|
+
case 'minimal':
|
|
39
|
+
return this.getMinimalTemplate()
|
|
40
|
+
case 'auth':
|
|
41
|
+
return this.getAuthTemplate()
|
|
42
|
+
case 'crud':
|
|
43
|
+
default:
|
|
44
|
+
return this.getCrudTemplate()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private getCrudTemplate(): Template {
|
|
49
|
+
return {
|
|
50
|
+
name: 'crud-route',
|
|
51
|
+
description: 'Full CRUD API routes with validation',
|
|
52
|
+
files: [
|
|
53
|
+
{
|
|
54
|
+
path: 'app/server/routes/{{kebabName}}.routes.ts',
|
|
55
|
+
content: `import { Elysia, t } from 'elysia'
|
|
56
|
+
import { {{pascalName}}Controller } from '../controllers/{{kebabName}}.controller'
|
|
57
|
+
import { errorHandler } from '../../../core/utils/errors/middleware'
|
|
58
|
+
import { logger } from '../../../core/utils/logger'
|
|
59
|
+
|
|
60
|
+
const controller = new {{pascalName}}Controller()
|
|
61
|
+
|
|
62
|
+
export const {{camelName}}Routes = new Elysia({ prefix: '/api/{{kebabName}}s' })
|
|
63
|
+
.use(errorHandler)
|
|
64
|
+
.onBeforeHandle(({ request }) => {
|
|
65
|
+
logger.request(request.method, new URL(request.url).pathname)
|
|
66
|
+
})
|
|
67
|
+
.get('/', async () => {
|
|
68
|
+
return await controller.getAll()
|
|
69
|
+
}, {
|
|
70
|
+
detail: {
|
|
71
|
+
tags: ['{{pascalName}}'],
|
|
72
|
+
summary: 'Get all {{camelName}}s',
|
|
73
|
+
description: 'Retrieve a list of all {{camelName}}s'
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.get('/:id', async ({ params }) => {
|
|
77
|
+
return await controller.getById({ params })
|
|
78
|
+
}, {
|
|
79
|
+
params: t.Object({
|
|
80
|
+
id: t.String({
|
|
81
|
+
description: '{{pascalName}} ID',
|
|
82
|
+
example: '{{kebabName}}_123'
|
|
83
|
+
})
|
|
84
|
+
}),
|
|
85
|
+
detail: {
|
|
86
|
+
tags: ['{{pascalName}}'],
|
|
87
|
+
summary: 'Get {{camelName}} by ID',
|
|
88
|
+
description: 'Retrieve a specific {{camelName}} by its ID'
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
.post('/', async ({ body }) => {
|
|
92
|
+
return await controller.create({ body })
|
|
93
|
+
}, {
|
|
94
|
+
body: t.Object({
|
|
95
|
+
name: t.String({
|
|
96
|
+
minLength: 1,
|
|
97
|
+
maxLength: 100,
|
|
98
|
+
description: '{{pascalName}} name',
|
|
99
|
+
example: 'Sample {{pascalName}}'
|
|
100
|
+
}),
|
|
101
|
+
description: t.Optional(t.String({
|
|
102
|
+
maxLength: 500,
|
|
103
|
+
description: '{{pascalName}} description',
|
|
104
|
+
example: 'This is a sample {{camelName}} description'
|
|
105
|
+
}))
|
|
106
|
+
}),
|
|
107
|
+
detail: {
|
|
108
|
+
tags: ['{{pascalName}}'],
|
|
109
|
+
summary: 'Create new {{camelName}}',
|
|
110
|
+
description: 'Create a new {{camelName}} with the provided data'
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.put('/:id', async ({ params, body }) => {
|
|
114
|
+
return await controller.update({ params, body })
|
|
115
|
+
}, {
|
|
116
|
+
params: t.Object({
|
|
117
|
+
id: t.String({
|
|
118
|
+
description: '{{pascalName}} ID',
|
|
119
|
+
example: '{{kebabName}}_123'
|
|
120
|
+
})
|
|
121
|
+
}),
|
|
122
|
+
body: t.Partial(t.Object({
|
|
123
|
+
name: t.String({
|
|
124
|
+
minLength: 1,
|
|
125
|
+
maxLength: 100,
|
|
126
|
+
description: '{{pascalName}} name',
|
|
127
|
+
example: 'Updated {{pascalName}}'
|
|
128
|
+
}),
|
|
129
|
+
description: t.String({
|
|
130
|
+
maxLength: 500,
|
|
131
|
+
description: '{{pascalName}} description',
|
|
132
|
+
example: 'Updated description'
|
|
133
|
+
})
|
|
134
|
+
})),
|
|
135
|
+
detail: {
|
|
136
|
+
tags: ['{{pascalName}}'],
|
|
137
|
+
summary: 'Update {{camelName}}',
|
|
138
|
+
description: 'Update an existing {{camelName}} with new data'
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.delete('/:id', async ({ params }) => {
|
|
142
|
+
return await controller.delete({ params })
|
|
143
|
+
}, {
|
|
144
|
+
params: t.Object({
|
|
145
|
+
id: t.String({
|
|
146
|
+
description: '{{pascalName}} ID',
|
|
147
|
+
example: '{{kebabName}}_123'
|
|
148
|
+
})
|
|
149
|
+
}),
|
|
150
|
+
detail: {
|
|
151
|
+
tags: ['{{pascalName}}'],
|
|
152
|
+
summary: 'Delete {{camelName}}',
|
|
153
|
+
description: 'Delete a {{camelName}} by its ID'
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
.get('/search', async ({ query }) => {
|
|
157
|
+
// Assuming controller has a search method
|
|
158
|
+
return await (controller as any).search({ query })
|
|
159
|
+
}, {
|
|
160
|
+
query: t.Object({
|
|
161
|
+
q: t.String({
|
|
162
|
+
minLength: 1,
|
|
163
|
+
description: 'Search query',
|
|
164
|
+
example: 'search term'
|
|
165
|
+
}),
|
|
166
|
+
limit: t.Optional(t.Number({
|
|
167
|
+
minimum: 1,
|
|
168
|
+
maximum: 100,
|
|
169
|
+
default: 10,
|
|
170
|
+
description: 'Maximum number of results'
|
|
171
|
+
})),
|
|
172
|
+
offset: t.Optional(t.Number({
|
|
173
|
+
minimum: 0,
|
|
174
|
+
default: 0,
|
|
175
|
+
description: 'Number of results to skip'
|
|
176
|
+
}))
|
|
177
|
+
}),
|
|
178
|
+
detail: {
|
|
179
|
+
tags: ['{{pascalName}}'],
|
|
180
|
+
summary: 'Search {{camelName}}s',
|
|
181
|
+
description: 'Search for {{camelName}}s using a query string'
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
`
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
path: 'app/server/routes/index.ts',
|
|
188
|
+
content: `import { Elysia } from 'elysia'
|
|
189
|
+
import { {{camelName}}Routes } from './{{kebabName}}.routes'
|
|
190
|
+
|
|
191
|
+
// Import other route modules here
|
|
192
|
+
// import { userRoutes } from './user.routes'
|
|
193
|
+
// import { authRoutes } from './auth.routes'
|
|
194
|
+
|
|
195
|
+
export const apiRoutes = new Elysia({ prefix: '/api' })
|
|
196
|
+
.use({{camelName}}Routes)
|
|
197
|
+
// Add other routes here
|
|
198
|
+
// .use(userRoutes)
|
|
199
|
+
// .use(authRoutes)
|
|
200
|
+
.get('/health', () => ({
|
|
201
|
+
status: 'ok',
|
|
202
|
+
timestamp: new Date().toISOString(),
|
|
203
|
+
service: '{{projectName}}'
|
|
204
|
+
}), {
|
|
205
|
+
detail: {
|
|
206
|
+
tags: ['Health'],
|
|
207
|
+
summary: 'Health check',
|
|
208
|
+
description: 'Check if the API is running'
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
export default apiRoutes
|
|
213
|
+
`,
|
|
214
|
+
condition: (variables) => !variables.skipIndex
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
hooks: {
|
|
218
|
+
afterGenerate: async (context, options, files) => {
|
|
219
|
+
context.logger.info(`Generated route files:`)
|
|
220
|
+
files.forEach(file => {
|
|
221
|
+
context.logger.info(` - ${file}`)
|
|
222
|
+
})
|
|
223
|
+
context.logger.info(`\nNext steps:`)
|
|
224
|
+
context.logger.info(`1. Generate the corresponding controller: flux generate controller ${options.name}`)
|
|
225
|
+
context.logger.info(`2. Import and use the routes in your main server file`)
|
|
226
|
+
context.logger.info(`3. Test the API endpoints using the Swagger documentation`)
|
|
227
|
+
context.logger.info(`\nAPI endpoints created:`)
|
|
228
|
+
context.logger.info(` GET /api/${options.name}s`)
|
|
229
|
+
context.logger.info(` GET /api/${options.name}s/:id`)
|
|
230
|
+
context.logger.info(` POST /api/${options.name}s`)
|
|
231
|
+
context.logger.info(` PUT /api/${options.name}s/:id`)
|
|
232
|
+
context.logger.info(` DELETE /api/${options.name}s/:id`)
|
|
233
|
+
context.logger.info(` GET /api/${options.name}s/search`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private getMinimalTemplate(): Template {
|
|
240
|
+
return {
|
|
241
|
+
name: 'minimal-route',
|
|
242
|
+
description: 'Minimal API route with basic endpoints',
|
|
243
|
+
files: [
|
|
244
|
+
{
|
|
245
|
+
path: 'app/server/routes/{{kebabName}}.routes.ts',
|
|
246
|
+
content: `import { Elysia } from 'elysia'
|
|
247
|
+
|
|
248
|
+
export const {{camelName}}Routes = new Elysia({ prefix: '/api/{{kebabName}}s' })
|
|
249
|
+
.get('/', () => {
|
|
250
|
+
// TODO: Implement list logic
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
data: [],
|
|
254
|
+
message: 'List {{camelName}}s'
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.get('/:id', ({ params }) => {
|
|
258
|
+
// TODO: Implement get by id logic
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
data: { id: params.id },
|
|
262
|
+
message: 'Get {{camelName}}'
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
.post('/', ({ body }) => {
|
|
266
|
+
// TODO: Implement create logic
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
data: body,
|
|
270
|
+
message: '{{pascalName}} created'
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
.put('/:id', ({ params, body }) => {
|
|
274
|
+
// TODO: Implement update logic
|
|
275
|
+
return {
|
|
276
|
+
success: true,
|
|
277
|
+
data: { id: params.id, ...body },
|
|
278
|
+
message: '{{pascalName}} updated'
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
.delete('/:id', ({ params }) => {
|
|
282
|
+
// TODO: Implement delete logic
|
|
283
|
+
return {
|
|
284
|
+
success: true,
|
|
285
|
+
message: '{{pascalName}} deleted'
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
`
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private getAuthTemplate(): Template {
|
|
295
|
+
return {
|
|
296
|
+
name: 'auth-route',
|
|
297
|
+
description: 'Authentication routes with JWT',
|
|
298
|
+
files: [
|
|
299
|
+
{
|
|
300
|
+
path: 'app/server/routes/auth.routes.ts',
|
|
301
|
+
content: `import { Elysia, t } from 'elysia'
|
|
302
|
+
import { AuthController } from '../controllers/auth.controller'
|
|
303
|
+
import { errorHandler } from '../../../core/utils/errors/middleware'
|
|
304
|
+
import { authMiddleware } from '../middleware/auth.middleware'
|
|
305
|
+
import { logger } from '../../../core/utils/logger'
|
|
306
|
+
|
|
307
|
+
const controller = new AuthController()
|
|
308
|
+
|
|
309
|
+
export const authRoutes = new Elysia({ prefix: '/api/auth' })
|
|
310
|
+
.use(errorHandler)
|
|
311
|
+
.onBeforeHandle(({ request }) => {
|
|
312
|
+
logger.request(request.method, new URL(request.url).pathname)
|
|
313
|
+
})
|
|
314
|
+
.post('/register', async ({ body }) => {
|
|
315
|
+
return await controller.register({ body })
|
|
316
|
+
}, {
|
|
317
|
+
body: t.Object({
|
|
318
|
+
email: t.String({
|
|
319
|
+
format: 'email',
|
|
320
|
+
description: 'User email address',
|
|
321
|
+
example: 'user@example.com'
|
|
322
|
+
}),
|
|
323
|
+
password: t.String({
|
|
324
|
+
minLength: 8,
|
|
325
|
+
description: 'User password (minimum 8 characters)',
|
|
326
|
+
example: 'securePassword123'
|
|
327
|
+
}),
|
|
328
|
+
name: t.String({
|
|
329
|
+
minLength: 1,
|
|
330
|
+
maxLength: 100,
|
|
331
|
+
description: 'User full name',
|
|
332
|
+
example: 'John Doe'
|
|
333
|
+
})
|
|
334
|
+
}),
|
|
335
|
+
detail: {
|
|
336
|
+
tags: ['Authentication'],
|
|
337
|
+
summary: 'Register new user',
|
|
338
|
+
description: 'Create a new user account'
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
.post('/login', async ({ body }) => {
|
|
342
|
+
return await controller.login({ body })
|
|
343
|
+
}, {
|
|
344
|
+
body: t.Object({
|
|
345
|
+
email: t.String({
|
|
346
|
+
format: 'email',
|
|
347
|
+
description: 'User email address',
|
|
348
|
+
example: 'user@example.com'
|
|
349
|
+
}),
|
|
350
|
+
password: t.String({
|
|
351
|
+
minLength: 1,
|
|
352
|
+
description: 'User password',
|
|
353
|
+
example: 'securePassword123'
|
|
354
|
+
})
|
|
355
|
+
}),
|
|
356
|
+
detail: {
|
|
357
|
+
tags: ['Authentication'],
|
|
358
|
+
summary: 'User login',
|
|
359
|
+
description: 'Authenticate user and return JWT token'
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
.post('/logout', async ({ headers }) => {
|
|
363
|
+
return await controller.logout({ headers })
|
|
364
|
+
}, {
|
|
365
|
+
beforeHandle: authMiddleware,
|
|
366
|
+
detail: {
|
|
367
|
+
tags: ['Authentication'],
|
|
368
|
+
summary: 'User logout',
|
|
369
|
+
description: 'Logout user and invalidate token'
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
.get('/me', async ({ headers }) => {
|
|
373
|
+
return await controller.getProfile({ headers })
|
|
374
|
+
}, {
|
|
375
|
+
beforeHandle: authMiddleware,
|
|
376
|
+
detail: {
|
|
377
|
+
tags: ['Authentication'],
|
|
378
|
+
summary: 'Get user profile',
|
|
379
|
+
description: 'Get current user profile information'
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
.put('/me', async ({ headers, body }) => {
|
|
383
|
+
return await controller.updateProfile({ headers, body })
|
|
384
|
+
}, {
|
|
385
|
+
beforeHandle: authMiddleware,
|
|
386
|
+
body: t.Partial(t.Object({
|
|
387
|
+
name: t.String({
|
|
388
|
+
minLength: 1,
|
|
389
|
+
maxLength: 100,
|
|
390
|
+
description: 'User full name'
|
|
391
|
+
}),
|
|
392
|
+
email: t.String({
|
|
393
|
+
format: 'email',
|
|
394
|
+
description: 'User email address'
|
|
395
|
+
})
|
|
396
|
+
})),
|
|
397
|
+
detail: {
|
|
398
|
+
tags: ['Authentication'],
|
|
399
|
+
summary: 'Update user profile',
|
|
400
|
+
description: 'Update current user profile information'
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
.post('/refresh', async ({ body }) => {
|
|
404
|
+
return await controller.refreshToken({ body })
|
|
405
|
+
}, {
|
|
406
|
+
body: t.Object({
|
|
407
|
+
refreshToken: t.String({
|
|
408
|
+
description: 'Refresh token',
|
|
409
|
+
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
|
410
|
+
})
|
|
411
|
+
}),
|
|
412
|
+
detail: {
|
|
413
|
+
tags: ['Authentication'],
|
|
414
|
+
summary: 'Refresh access token',
|
|
415
|
+
description: 'Get new access token using refresh token'
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
.post('/forgot-password', async ({ body }) => {
|
|
419
|
+
return await controller.forgotPassword({ body })
|
|
420
|
+
}, {
|
|
421
|
+
body: t.Object({
|
|
422
|
+
email: t.String({
|
|
423
|
+
format: 'email',
|
|
424
|
+
description: 'User email address',
|
|
425
|
+
example: 'user@example.com'
|
|
426
|
+
})
|
|
427
|
+
}),
|
|
428
|
+
detail: {
|
|
429
|
+
tags: ['Authentication'],
|
|
430
|
+
summary: 'Forgot password',
|
|
431
|
+
description: 'Send password reset email'
|
|
432
|
+
}
|
|
433
|
+
})
|
|
434
|
+
.post('/reset-password', async ({ body }) => {
|
|
435
|
+
return await controller.resetPassword({ body })
|
|
436
|
+
}, {
|
|
437
|
+
body: t.Object({
|
|
438
|
+
token: t.String({
|
|
439
|
+
description: 'Password reset token',
|
|
440
|
+
example: 'reset-token-123'
|
|
441
|
+
}),
|
|
442
|
+
password: t.String({
|
|
443
|
+
minLength: 8,
|
|
444
|
+
description: 'New password (minimum 8 characters)',
|
|
445
|
+
example: 'newSecurePassword123'
|
|
446
|
+
})
|
|
447
|
+
}),
|
|
448
|
+
detail: {
|
|
449
|
+
tags: ['Authentication'],
|
|
450
|
+
summary: 'Reset password',
|
|
451
|
+
description: 'Reset user password using reset token'
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
`
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
path: 'app/server/middleware/auth.middleware.ts',
|
|
458
|
+
content: `import { Context } from 'elysia'
|
|
459
|
+
import { UnauthorizedError } from '../../../core/utils/errors'
|
|
460
|
+
import { logger } from '../../../core/utils/logger'
|
|
461
|
+
|
|
462
|
+
// JWT verification function (implement based on your JWT library)
|
|
463
|
+
async function verifyJWT(token: string): Promise<any> {
|
|
464
|
+
// TODO: Implement JWT verification
|
|
465
|
+
// This is a placeholder - replace with actual JWT verification
|
|
466
|
+
if (!token || token === 'invalid') {
|
|
467
|
+
throw new Error('Invalid token')
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
userId: 'user_123',
|
|
472
|
+
email: 'user@example.com',
|
|
473
|
+
name: 'John Doe'
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function authMiddleware(context: Context) {
|
|
478
|
+
try {
|
|
479
|
+
const authorization = context.headers.authorization
|
|
480
|
+
|
|
481
|
+
if (!authorization) {
|
|
482
|
+
throw new UnauthorizedError('Authorization header is required')
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const token = authorization.replace('Bearer ', '')
|
|
486
|
+
|
|
487
|
+
if (!token) {
|
|
488
|
+
throw new UnauthorizedError('Bearer token is required')
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const user = await verifyJWT(token)
|
|
492
|
+
|
|
493
|
+
// Add user to context for use in route handlers
|
|
494
|
+
;(context as any).user = user
|
|
495
|
+
|
|
496
|
+
logger.debug('User authenticated', { userId: user.userId })
|
|
497
|
+
|
|
498
|
+
} catch (error) {
|
|
499
|
+
logger.warn('Authentication failed', { error: error instanceof Error ? error.message : 'Unknown error' })
|
|
500
|
+
|
|
501
|
+
if (error instanceof UnauthorizedError) {
|
|
502
|
+
throw error
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
throw new UnauthorizedError('Invalid or expired token')
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
`
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|