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,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Middleware
|
|
3
|
+
* Provides request validation using schemas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from 'elysia'
|
|
7
|
+
|
|
8
|
+
export interface ValidationSchema {
|
|
9
|
+
body?: Record<string, any>
|
|
10
|
+
query?: Record<string, any>
|
|
11
|
+
params?: Record<string, any>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ValidationError {
|
|
15
|
+
field: string
|
|
16
|
+
message: string
|
|
17
|
+
value?: any
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create validation middleware for a specific schema
|
|
22
|
+
*/
|
|
23
|
+
export const validationMiddleware = (schema: ValidationSchema) => ({
|
|
24
|
+
name: 'validation',
|
|
25
|
+
|
|
26
|
+
beforeHandle: async (context: Context) => {
|
|
27
|
+
const errors: ValidationError[] = []
|
|
28
|
+
|
|
29
|
+
// Validate body
|
|
30
|
+
if (schema.body && context.body) {
|
|
31
|
+
const bodyErrors = validateObject(context.body, schema.body, 'body')
|
|
32
|
+
errors.push(...bodyErrors)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validate query parameters
|
|
36
|
+
if (schema.query && context.query) {
|
|
37
|
+
const queryErrors = validateObject(context.query, schema.query, 'query')
|
|
38
|
+
errors.push(...queryErrors)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Validate path parameters
|
|
42
|
+
if (schema.params && context.params) {
|
|
43
|
+
const paramErrors = validateObject(context.params, schema.params, 'params')
|
|
44
|
+
errors.push(...paramErrors)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
return new Response(
|
|
49
|
+
JSON.stringify({
|
|
50
|
+
error: 'Validation failed',
|
|
51
|
+
details: errors
|
|
52
|
+
}),
|
|
53
|
+
{
|
|
54
|
+
status: 400,
|
|
55
|
+
headers: { 'Content-Type': 'application/json' }
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate an object against a schema
|
|
64
|
+
*/
|
|
65
|
+
function validateObject(
|
|
66
|
+
obj: any,
|
|
67
|
+
schema: Record<string, any>,
|
|
68
|
+
prefix: string
|
|
69
|
+
): ValidationError[] {
|
|
70
|
+
const errors: ValidationError[] = []
|
|
71
|
+
|
|
72
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
73
|
+
const value = obj[field]
|
|
74
|
+
const fieldPath = `${prefix}.${field}`
|
|
75
|
+
|
|
76
|
+
// Check required fields
|
|
77
|
+
if (rules.required && (value === undefined || value === null || value === '')) {
|
|
78
|
+
errors.push({
|
|
79
|
+
field: fieldPath,
|
|
80
|
+
message: `${field} is required`,
|
|
81
|
+
value
|
|
82
|
+
})
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Skip validation if field is not required and not present
|
|
87
|
+
if (!rules.required && (value === undefined || value === null)) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Type validation
|
|
92
|
+
if (rules.type) {
|
|
93
|
+
const typeError = validateType(value, rules.type, fieldPath)
|
|
94
|
+
if (typeError) {
|
|
95
|
+
errors.push(typeError)
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// String validations
|
|
101
|
+
if (rules.type === 'string' && typeof value === 'string') {
|
|
102
|
+
if (rules.minLength && value.length < rules.minLength) {
|
|
103
|
+
errors.push({
|
|
104
|
+
field: fieldPath,
|
|
105
|
+
message: `${field} must be at least ${rules.minLength} characters`,
|
|
106
|
+
value
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (rules.maxLength && value.length > rules.maxLength) {
|
|
111
|
+
errors.push({
|
|
112
|
+
field: fieldPath,
|
|
113
|
+
message: `${field} must be no more than ${rules.maxLength} characters`,
|
|
114
|
+
value
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (rules.pattern && !new RegExp(rules.pattern).test(value)) {
|
|
119
|
+
errors.push({
|
|
120
|
+
field: fieldPath,
|
|
121
|
+
message: `${field} format is invalid`,
|
|
122
|
+
value
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (rules.email && !isValidEmail(value)) {
|
|
127
|
+
errors.push({
|
|
128
|
+
field: fieldPath,
|
|
129
|
+
message: `${field} must be a valid email address`,
|
|
130
|
+
value
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Number validations
|
|
136
|
+
if (rules.type === 'number' && typeof value === 'number') {
|
|
137
|
+
if (rules.min !== undefined && value < rules.min) {
|
|
138
|
+
errors.push({
|
|
139
|
+
field: fieldPath,
|
|
140
|
+
message: `${field} must be at least ${rules.min}`,
|
|
141
|
+
value
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (rules.max !== undefined && value > rules.max) {
|
|
146
|
+
errors.push({
|
|
147
|
+
field: fieldPath,
|
|
148
|
+
message: `${field} must be no more than ${rules.max}`,
|
|
149
|
+
value
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Array validations
|
|
155
|
+
if (rules.type === 'array' && Array.isArray(value)) {
|
|
156
|
+
if (rules.minItems && value.length < rules.minItems) {
|
|
157
|
+
errors.push({
|
|
158
|
+
field: fieldPath,
|
|
159
|
+
message: `${field} must have at least ${rules.minItems} items`,
|
|
160
|
+
value
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (rules.maxItems && value.length > rules.maxItems) {
|
|
165
|
+
errors.push({
|
|
166
|
+
field: fieldPath,
|
|
167
|
+
message: `${field} must have no more than ${rules.maxItems} items`,
|
|
168
|
+
value
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Enum validation
|
|
174
|
+
if (rules.enum && !rules.enum.includes(value)) {
|
|
175
|
+
errors.push({
|
|
176
|
+
field: fieldPath,
|
|
177
|
+
message: `${field} must be one of: ${rules.enum.join(', ')}`,
|
|
178
|
+
value
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return errors
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate value type
|
|
188
|
+
*/
|
|
189
|
+
function validateType(value: any, expectedType: string, fieldPath: string): ValidationError | null {
|
|
190
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value
|
|
191
|
+
|
|
192
|
+
if (actualType !== expectedType) {
|
|
193
|
+
return {
|
|
194
|
+
field: fieldPath,
|
|
195
|
+
message: `Expected ${expectedType}, got ${actualType}`,
|
|
196
|
+
value
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Validate email format
|
|
205
|
+
*/
|
|
206
|
+
function isValidEmail(email: string): boolean {
|
|
207
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
208
|
+
return emailRegex.test(email)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Common validation schemas
|
|
213
|
+
*/
|
|
214
|
+
export const commonSchemas = {
|
|
215
|
+
createUser: {
|
|
216
|
+
body: {
|
|
217
|
+
name: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
required: true,
|
|
220
|
+
minLength: 2,
|
|
221
|
+
maxLength: 100
|
|
222
|
+
},
|
|
223
|
+
email: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
required: true,
|
|
226
|
+
email: true,
|
|
227
|
+
maxLength: 255
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
updateUser: {
|
|
233
|
+
params: {
|
|
234
|
+
id: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
required: true,
|
|
237
|
+
pattern: '^\\d+$'
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
body: {
|
|
241
|
+
name: {
|
|
242
|
+
type: 'string',
|
|
243
|
+
required: false,
|
|
244
|
+
minLength: 2,
|
|
245
|
+
maxLength: 100
|
|
246
|
+
},
|
|
247
|
+
email: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
required: false,
|
|
250
|
+
email: true,
|
|
251
|
+
maxLength: 255
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
pagination: {
|
|
257
|
+
query: {
|
|
258
|
+
page: {
|
|
259
|
+
type: 'string',
|
|
260
|
+
required: false,
|
|
261
|
+
pattern: '^\\d+$'
|
|
262
|
+
},
|
|
263
|
+
limit: {
|
|
264
|
+
type: 'string',
|
|
265
|
+
required: false,
|
|
266
|
+
pattern: '^\\d+$'
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { Elysia } from "elysia"
|
|
1
|
+
import { Elysia, t } from "elysia"
|
|
2
2
|
import { usersRoutes } from "./users.routes"
|
|
3
|
+
import { uploadRoutes } from "./upload"
|
|
3
4
|
|
|
4
5
|
export const apiRoutes = new Elysia({ prefix: "/api" })
|
|
5
6
|
.get("/", () => ({ message: "🔥 Hot Reload funcionando! FluxStack API v1.4.0 ⚡" }), {
|
|
7
|
+
response: t.Object({
|
|
8
|
+
message: t.String()
|
|
9
|
+
}),
|
|
6
10
|
detail: {
|
|
7
11
|
tags: ['Health'],
|
|
8
12
|
summary: 'API Root',
|
|
@@ -16,10 +20,18 @@ export const apiRoutes = new Elysia({ prefix: "/api" })
|
|
|
16
20
|
version: "1.4.0",
|
|
17
21
|
environment: "development"
|
|
18
22
|
}), {
|
|
23
|
+
response: t.Object({
|
|
24
|
+
status: t.String(),
|
|
25
|
+
timestamp: t.String(),
|
|
26
|
+
uptime: t.String(),
|
|
27
|
+
version: t.String(),
|
|
28
|
+
environment: t.String()
|
|
29
|
+
}),
|
|
19
30
|
detail: {
|
|
20
31
|
tags: ['Health'],
|
|
21
32
|
summary: 'Health Check',
|
|
22
33
|
description: 'Returns the current health status of the API server'
|
|
23
34
|
}
|
|
24
35
|
})
|
|
25
|
-
.use(usersRoutes)
|
|
36
|
+
.use(usersRoutes)
|
|
37
|
+
.use(uploadRoutes)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Elysia, t } from 'elysia'
|
|
2
|
+
import { writeFile, mkdir } from 'fs/promises'
|
|
3
|
+
import { existsSync } from 'fs'
|
|
4
|
+
import { join, extname } from 'path'
|
|
5
|
+
|
|
6
|
+
export const uploadRoutes = new Elysia({ prefix: '/upload' })
|
|
7
|
+
.post('/avatar', async ({ body }: { body: { file: File } }) => {
|
|
8
|
+
try {
|
|
9
|
+
const { file } = body
|
|
10
|
+
|
|
11
|
+
if (!file) {
|
|
12
|
+
throw new Error('No file provided')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Validate file type
|
|
16
|
+
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']
|
|
17
|
+
if (!allowedTypes.includes(file.type)) {
|
|
18
|
+
throw new Error('Invalid file type. Only JPEG, PNG, WebP and GIF are allowed.')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Validate file size (5MB max)
|
|
22
|
+
const maxSize = 5 * 1024 * 1024 // 5MB
|
|
23
|
+
if (file.size > maxSize) {
|
|
24
|
+
throw new Error('File too large. Maximum size is 5MB.')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create uploads directory if it doesn't exist
|
|
28
|
+
const uploadsDir = join(process.cwd(), 'uploads', 'avatars')
|
|
29
|
+
if (!existsSync(uploadsDir)) {
|
|
30
|
+
await mkdir(uploadsDir, { recursive: true })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Generate unique filename
|
|
34
|
+
const timestamp = Date.now()
|
|
35
|
+
const randomId = Math.random().toString(36).substring(2, 8)
|
|
36
|
+
const extension = extname(file.name) || '.jpg'
|
|
37
|
+
const filename = `avatar-${timestamp}-${randomId}${extension}`
|
|
38
|
+
const filepath = join(uploadsDir, filename)
|
|
39
|
+
|
|
40
|
+
// Convert file to buffer and save
|
|
41
|
+
const buffer = await file.arrayBuffer()
|
|
42
|
+
await writeFile(filepath, new Uint8Array(buffer))
|
|
43
|
+
|
|
44
|
+
// Return the URL path for the uploaded file
|
|
45
|
+
const imageUrl = `/uploads/avatars/${filename}`
|
|
46
|
+
|
|
47
|
+
console.log('📸 Avatar uploaded successfully:', {
|
|
48
|
+
filename,
|
|
49
|
+
size: file.size,
|
|
50
|
+
type: file.type,
|
|
51
|
+
url: imageUrl
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
message: 'Avatar uploaded successfully',
|
|
57
|
+
imageUrl,
|
|
58
|
+
filename,
|
|
59
|
+
size: file.size,
|
|
60
|
+
type: file.type
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
} catch (error: any) {
|
|
64
|
+
console.error('❌ Avatar upload failed:', error.message)
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: error.message || 'Upload failed',
|
|
69
|
+
imageUrl: null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, {
|
|
73
|
+
body: t.Object({
|
|
74
|
+
file: t.File({
|
|
75
|
+
type: ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'],
|
|
76
|
+
maxSize: 5 * 1024 * 1024 // 5MB
|
|
77
|
+
})
|
|
78
|
+
}),
|
|
79
|
+
response: {
|
|
80
|
+
200: t.Object({
|
|
81
|
+
success: t.Boolean(),
|
|
82
|
+
message: t.Optional(t.String()),
|
|
83
|
+
error: t.Optional(t.String()),
|
|
84
|
+
imageUrl: t.Union([t.String(), t.Null()]),
|
|
85
|
+
filename: t.Optional(t.String()),
|
|
86
|
+
size: t.Optional(t.Number()),
|
|
87
|
+
type: t.Optional(t.String())
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Note: File serving is now handled by the static-files plugin at /uploads/*
|
|
@@ -18,11 +18,12 @@ export const usersRoutes = new Elysia({ prefix: "/users" })
|
|
|
18
18
|
}
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
.get("/:id", async ({ params: { id } }) => {
|
|
21
|
+
.get("/:id", async ({ params: { id }, set }) => {
|
|
22
22
|
const userId = parseInt(id)
|
|
23
23
|
const result = await UsersController.getUserById(userId)
|
|
24
24
|
|
|
25
25
|
if (!result) {
|
|
26
|
+
set.status = 404
|
|
26
27
|
return { error: "Usuário não encontrado" }
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -31,14 +32,6 @@ export const usersRoutes = new Elysia({ prefix: "/users" })
|
|
|
31
32
|
params: t.Object({
|
|
32
33
|
id: t.String()
|
|
33
34
|
}),
|
|
34
|
-
response: t.Object({
|
|
35
|
-
user: t.Object({
|
|
36
|
-
id: t.Number(),
|
|
37
|
-
name: t.String(),
|
|
38
|
-
email: t.String(),
|
|
39
|
-
createdAt: t.Date()
|
|
40
|
-
})
|
|
41
|
-
}),
|
|
42
35
|
detail: {
|
|
43
36
|
tags: ['Users'],
|
|
44
37
|
summary: 'Get User by ID',
|