create-fluxstack 1.7.5 → 1.8.3
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/.dockerignore +82 -0
- package/.env.example +19 -0
- package/Dockerfile +70 -0
- package/README.md +6 -3
- package/app/client/SIMPLIFICATION.md +140 -0
- package/app/client/frontend-only.ts +1 -1
- package/app/client/src/App.tsx +148 -283
- package/app/client/src/index.css +5 -20
- package/app/client/src/lib/eden-api.ts +53 -220
- package/app/client/src/main.tsx +2 -3
- package/app/server/app.ts +20 -5
- package/app/server/backend-only.ts +15 -12
- package/app/server/controllers/users.controller.ts +57 -31
- package/app/server/index.ts +86 -96
- package/app/server/live/register-components.ts +18 -7
- package/app/server/routes/env-test.ts +110 -0
- package/app/server/routes/index.ts +1 -8
- package/app/server/routes/users.routes.ts +192 -91
- package/config/app.config.ts +2 -54
- package/config/client.config.ts +95 -0
- package/config/fluxstack.config.ts +2 -2
- package/config/index.ts +57 -22
- package/config/monitoring.config.ts +114 -0
- package/config/plugins.config.ts +80 -0
- package/config/runtime.config.ts +0 -17
- package/config/server.config.ts +50 -30
- package/core/build/bundler.ts +17 -16
- package/core/build/flux-plugins-generator.ts +34 -23
- package/core/build/index.ts +32 -31
- package/core/build/live-components-generator.ts +44 -30
- package/core/build/optimizer.ts +37 -17
- package/core/cli/command-registry.ts +4 -14
- package/core/cli/commands/plugin-deps.ts +8 -8
- package/core/cli/generators/component.ts +3 -3
- package/core/cli/generators/controller.ts +4 -4
- package/core/cli/generators/index.ts +8 -8
- package/core/cli/generators/interactive.ts +4 -4
- package/core/cli/generators/plugin.ts +3 -3
- package/core/cli/generators/prompts.ts +1 -1
- package/core/cli/generators/route.ts +27 -11
- package/core/cli/generators/service.ts +5 -5
- package/core/cli/generators/template-engine.ts +1 -1
- package/core/cli/generators/types.ts +1 -1
- package/core/cli/index.ts +158 -189
- package/core/cli/plugin-discovery.ts +3 -3
- package/core/client/hooks/index.ts +2 -2
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +1 -1
- package/core/client/hooks/useChunkedUpload.ts +1 -1
- package/core/client/hooks/useHybridLiveComponent.ts +1 -1
- package/core/client/hooks/useWebSocket.ts +1 -1
- package/core/config/env.ts +5 -1
- package/core/config/runtime-config.ts +12 -10
- package/core/config/schema.ts +33 -2
- package/core/framework/server.ts +30 -14
- package/core/framework/types.ts +2 -2
- package/core/index.ts +31 -23
- package/core/live/ComponentRegistry.ts +1 -1
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1 -1
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +65 -161
- package/core/plugins/built-in/static/index.ts +75 -277
- package/core/plugins/built-in/swagger/index.ts +301 -231
- package/core/plugins/built-in/vite/index.ts +342 -377
- package/core/plugins/config.ts +2 -2
- package/core/plugins/dependency-manager.ts +2 -2
- package/core/plugins/discovery.ts +1 -1
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/manager.ts +19 -4
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +25 -21
- package/core/plugins/types.ts +147 -5
- package/core/server/backend-entry.ts +51 -0
- package/core/server/framework.ts +2 -2
- package/core/server/live/ComponentRegistry.ts +9 -26
- package/core/server/live/FileUploadManager.ts +1 -1
- package/core/server/live/auto-generated-components.ts +26 -0
- package/core/server/live/websocket-plugin.ts +211 -19
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +4 -4
- package/core/server/plugins/database.ts +1 -2
- package/core/server/plugins/static-files-plugin.ts +259 -231
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +4 -4
- package/core/server/standalone.ts +16 -1
- package/core/testing/index.ts +1 -1
- package/core/testing/setup.ts +1 -1
- package/core/types/plugin.ts +6 -0
- package/core/utils/build-logger.ts +324 -0
- package/core/utils/config-schema.ts +2 -6
- package/core/utils/helpers.ts +14 -9
- package/core/utils/logger/startup-banner.ts +7 -33
- package/core/utils/regenerate-files.ts +69 -0
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +68 -25
- package/fluxstack.config.ts +138 -252
- package/package.json +3 -18
- package/plugins/crypto-auth/index.ts +52 -47
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/helpers.ts +16 -1
- package/vitest.config.ts +17 -26
- package/app/client/src/App.css +0 -883
- package/app/client/src/components/ErrorBoundary.tsx +0 -107
- package/app/client/src/components/ErrorDisplay.css +0 -365
- package/app/client/src/components/ErrorDisplay.tsx +0 -258
- package/app/client/src/components/FluxStackConfig.tsx +0 -1321
- package/app/client/src/components/HybridLiveCounter.tsx +0 -140
- package/app/client/src/components/LiveClock.tsx +0 -286
- package/app/client/src/components/MainLayout.tsx +0 -388
- package/app/client/src/components/SidebarNavigation.tsx +0 -391
- package/app/client/src/components/StateDemo.tsx +0 -178
- package/app/client/src/components/SystemMonitor.tsx +0 -1044
- package/app/client/src/components/UserProfile.tsx +0 -809
- package/app/client/src/hooks/useAuth.ts +0 -39
- package/app/client/src/hooks/useNotifications.ts +0 -56
- package/app/client/src/lib/errors.ts +0 -340
- package/app/client/src/lib/hooks/useErrorHandler.ts +0 -258
- package/app/client/src/lib/index.ts +0 -45
- package/app/client/src/pages/ApiDocs.tsx +0 -182
- package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
- package/app/client/src/pages/Demo.tsx +0 -174
- package/app/client/src/pages/HybridLive.tsx +0 -263
- package/app/client/src/pages/Overview.tsx +0 -155
- package/app/client/src/store/README.md +0 -43
- package/app/client/src/store/index.ts +0 -16
- package/app/client/src/store/slices/uiSlice.ts +0 -151
- package/app/client/src/store/slices/userSlice.ts +0 -161
- package/app/client/src/test/README.md +0 -257
- package/app/client/src/test/setup.ts +0 -70
- package/app/client/src/test/types.ts +0 -12
- package/app/server/live/CounterComponent.ts +0 -191
- package/app/server/live/FluxStackConfig.ts +0 -534
- package/app/server/live/SidebarNavigation.ts +0 -157
- package/app/server/live/SystemMonitor.ts +0 -595
- package/app/server/live/SystemMonitorIntegration.ts +0 -151
- package/app/server/live/UserProfileComponent.ts +0 -141
- package/app/server/middleware/auth.ts +0 -136
- package/app/server/middleware/errorHandling.ts +0 -252
- package/app/server/middleware/index.ts +0 -10
- package/app/server/middleware/rateLimit.ts +0 -193
- package/app/server/middleware/requestLogging.ts +0 -215
- package/app/server/middleware/validation.ts +0 -270
- package/app/server/routes/config.ts +0 -145
- package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
- package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
- package/app/server/routes/exemplo-posts.routes.ts +0 -161
- package/app/server/routes/upload.ts +0 -92
- package/app/server/services/NotificationService.ts +0 -302
- package/app/server/services/UserService.ts +0 -222
- package/app/server/services/index.ts +0 -46
- package/app/server/types/index.ts +0 -1
- package/config/build.config.ts +0 -24
|
@@ -1,92 +0,0 @@
|
|
|
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/*
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notification Service
|
|
3
|
-
* Handles application notifications and messaging
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { BaseService } from '../../../core/server/services/index.js'
|
|
7
|
-
|
|
8
|
-
export interface Notification {
|
|
9
|
-
id: string
|
|
10
|
-
userId?: number
|
|
11
|
-
type: 'info' | 'success' | 'warning' | 'error'
|
|
12
|
-
title: string
|
|
13
|
-
message: string
|
|
14
|
-
data?: Record<string, any>
|
|
15
|
-
read: boolean
|
|
16
|
-
createdAt: string
|
|
17
|
-
expiresAt?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface CreateNotificationData {
|
|
21
|
-
userId?: number
|
|
22
|
-
type: 'info' | 'success' | 'warning' | 'error'
|
|
23
|
-
title: string
|
|
24
|
-
message: string
|
|
25
|
-
data?: Record<string, any>
|
|
26
|
-
expiresIn?: number // seconds
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class NotificationService extends BaseService {
|
|
30
|
-
private notifications: Notification[] = []
|
|
31
|
-
|
|
32
|
-
async initialize(): Promise<void> {
|
|
33
|
-
await super.initialize()
|
|
34
|
-
|
|
35
|
-
// Start cleanup interval for expired notifications
|
|
36
|
-
setInterval(() => {
|
|
37
|
-
this.cleanupExpiredNotifications()
|
|
38
|
-
}, 60000) // Check every minute
|
|
39
|
-
|
|
40
|
-
this.logger.info('NotificationService initialized')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Create a new notification
|
|
45
|
-
*/
|
|
46
|
-
async createNotification(data: CreateNotificationData): Promise<Notification> {
|
|
47
|
-
return this.executeWithLogging('createNotification', async () => {
|
|
48
|
-
this.validateRequired(data, ['type', 'title', 'message'])
|
|
49
|
-
|
|
50
|
-
const notification: Notification = {
|
|
51
|
-
id: this.generateId(),
|
|
52
|
-
userId: data.userId,
|
|
53
|
-
type: data.type,
|
|
54
|
-
title: data.title.trim(),
|
|
55
|
-
message: data.message.trim(),
|
|
56
|
-
data: data.data,
|
|
57
|
-
read: false,
|
|
58
|
-
createdAt: new Date().toISOString(),
|
|
59
|
-
expiresAt: data.expiresIn
|
|
60
|
-
? new Date(Date.now() + data.expiresIn * 1000).toISOString()
|
|
61
|
-
: undefined
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
this.notifications.push(notification)
|
|
65
|
-
|
|
66
|
-
this.logger.info('Notification created', {
|
|
67
|
-
notificationId: notification.id,
|
|
68
|
-
userId: notification.userId,
|
|
69
|
-
type: notification.type
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return notification
|
|
73
|
-
}, { userId: data.userId, type: data.type })
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Get notifications for a user
|
|
78
|
-
*/
|
|
79
|
-
async getUserNotifications(
|
|
80
|
-
userId: number,
|
|
81
|
-
options: {
|
|
82
|
-
unreadOnly?: boolean
|
|
83
|
-
limit?: number
|
|
84
|
-
offset?: number
|
|
85
|
-
} = {}
|
|
86
|
-
): Promise<{
|
|
87
|
-
notifications: Notification[]
|
|
88
|
-
total: number
|
|
89
|
-
unreadCount: number
|
|
90
|
-
}> {
|
|
91
|
-
return this.executeWithLogging('getUserNotifications', async () => {
|
|
92
|
-
let userNotifications = this.notifications.filter(n => n.userId === userId)
|
|
93
|
-
|
|
94
|
-
if (options.unreadOnly) {
|
|
95
|
-
userNotifications = userNotifications.filter(n => !n.read)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Sort by creation date (newest first)
|
|
99
|
-
userNotifications.sort((a, b) =>
|
|
100
|
-
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
const total = userNotifications.length
|
|
104
|
-
const unreadCount = this.notifications.filter(n =>
|
|
105
|
-
n.userId === userId && !n.read
|
|
106
|
-
).length
|
|
107
|
-
|
|
108
|
-
// Apply pagination
|
|
109
|
-
const offset = options.offset || 0
|
|
110
|
-
const limit = options.limit || 50
|
|
111
|
-
const paginatedNotifications = userNotifications.slice(offset, offset + limit)
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
notifications: paginatedNotifications,
|
|
115
|
-
total,
|
|
116
|
-
unreadCount
|
|
117
|
-
}
|
|
118
|
-
}, { userId, ...options })
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Get global notifications (not user-specific)
|
|
123
|
-
*/
|
|
124
|
-
async getGlobalNotifications(options: {
|
|
125
|
-
limit?: number
|
|
126
|
-
offset?: number
|
|
127
|
-
} = {}): Promise<{
|
|
128
|
-
notifications: Notification[]
|
|
129
|
-
total: number
|
|
130
|
-
}> {
|
|
131
|
-
return this.executeWithLogging('getGlobalNotifications', async () => {
|
|
132
|
-
let globalNotifications = this.notifications.filter(n => !n.userId)
|
|
133
|
-
|
|
134
|
-
// Sort by creation date (newest first)
|
|
135
|
-
globalNotifications.sort((a, b) =>
|
|
136
|
-
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
const total = globalNotifications.length
|
|
140
|
-
|
|
141
|
-
// Apply pagination
|
|
142
|
-
const offset = options.offset || 0
|
|
143
|
-
const limit = options.limit || 50
|
|
144
|
-
const paginatedNotifications = globalNotifications.slice(offset, offset + limit)
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
notifications: paginatedNotifications,
|
|
148
|
-
total
|
|
149
|
-
}
|
|
150
|
-
}, options)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Mark notification as read
|
|
155
|
-
*/
|
|
156
|
-
async markAsRead(notificationId: string, userId?: number): Promise<boolean> {
|
|
157
|
-
return this.executeWithLogging('markAsRead', async () => {
|
|
158
|
-
const notification = this.notifications.find(n => n.id === notificationId)
|
|
159
|
-
|
|
160
|
-
if (!notification) {
|
|
161
|
-
return false
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Check if user has permission to mark this notification as read
|
|
165
|
-
if (notification.userId && notification.userId !== userId) {
|
|
166
|
-
throw new Error('Permission denied')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
notification.read = true
|
|
170
|
-
|
|
171
|
-
this.logger.info('Notification marked as read', {
|
|
172
|
-
notificationId,
|
|
173
|
-
userId: notification.userId
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
return true
|
|
177
|
-
}, { notificationId, userId })
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Mark all notifications as read for a user
|
|
182
|
-
*/
|
|
183
|
-
async markAllAsRead(userId: number): Promise<number> {
|
|
184
|
-
return this.executeWithLogging('markAllAsRead', async () => {
|
|
185
|
-
const userNotifications = this.notifications.filter(n =>
|
|
186
|
-
n.userId === userId && !n.read
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
userNotifications.forEach(notification => {
|
|
190
|
-
notification.read = true
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
const count = userNotifications.length
|
|
194
|
-
|
|
195
|
-
this.logger.info('All notifications marked as read', { userId, count })
|
|
196
|
-
|
|
197
|
-
return count
|
|
198
|
-
}, { userId })
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Delete a notification
|
|
203
|
-
*/
|
|
204
|
-
async deleteNotification(notificationId: string, userId?: number): Promise<boolean> {
|
|
205
|
-
return this.executeWithLogging('deleteNotification', async () => {
|
|
206
|
-
const index = this.notifications.findIndex(n => n.id === notificationId)
|
|
207
|
-
|
|
208
|
-
if (index === -1) {
|
|
209
|
-
return false
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const notification = this.notifications[index]
|
|
213
|
-
|
|
214
|
-
// Check if user has permission to delete this notification
|
|
215
|
-
if (notification.userId && notification.userId !== userId) {
|
|
216
|
-
throw new Error('Permission denied')
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
this.notifications.splice(index, 1)
|
|
220
|
-
|
|
221
|
-
this.logger.info('Notification deleted', {
|
|
222
|
-
notificationId,
|
|
223
|
-
userId: notification.userId
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
return true
|
|
227
|
-
}, { notificationId, userId })
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Broadcast notification to all users
|
|
232
|
-
*/
|
|
233
|
-
async broadcastNotification(data: Omit<CreateNotificationData, 'userId'>): Promise<Notification> {
|
|
234
|
-
return this.executeWithLogging('broadcastNotification', async () => {
|
|
235
|
-
return this.createNotification(data)
|
|
236
|
-
}, { type: data.type })
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Get notification statistics
|
|
241
|
-
*/
|
|
242
|
-
async getNotificationStats(): Promise<{
|
|
243
|
-
total: number
|
|
244
|
-
unread: number
|
|
245
|
-
byType: Record<string, number>
|
|
246
|
-
recentCount: number
|
|
247
|
-
}> {
|
|
248
|
-
return this.executeWithLogging('getNotificationStats', async () => {
|
|
249
|
-
const total = this.notifications.length
|
|
250
|
-
const unread = this.notifications.filter(n => !n.read).length
|
|
251
|
-
|
|
252
|
-
// Count by type
|
|
253
|
-
const byType: Record<string, number> = {}
|
|
254
|
-
this.notifications.forEach(n => {
|
|
255
|
-
byType[n.type] = (byType[n.type] || 0) + 1
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
// Recent notifications (last 24 hours)
|
|
259
|
-
const dayAgo = new Date()
|
|
260
|
-
dayAgo.setDate(dayAgo.getDate() - 1)
|
|
261
|
-
const recentCount = this.notifications.filter(n =>
|
|
262
|
-
new Date(n.createdAt) > dayAgo
|
|
263
|
-
).length
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
total,
|
|
267
|
-
unread,
|
|
268
|
-
byType,
|
|
269
|
-
recentCount
|
|
270
|
-
}
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Cleanup expired notifications
|
|
276
|
-
*/
|
|
277
|
-
private async cleanupExpiredNotifications(): Promise<void> {
|
|
278
|
-
const now = new Date()
|
|
279
|
-
const initialCount = this.notifications.length
|
|
280
|
-
|
|
281
|
-
this.notifications = this.notifications.filter(notification => {
|
|
282
|
-
if (!notification.expiresAt) {
|
|
283
|
-
return true
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return new Date(notification.expiresAt) > now
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const removedCount = initialCount - this.notifications.length
|
|
290
|
-
|
|
291
|
-
if (removedCount > 0) {
|
|
292
|
-
this.logger.info(`Cleaned up ${removedCount} expired notifications`)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Generate unique notification ID
|
|
298
|
-
*/
|
|
299
|
-
private generateId(): string {
|
|
300
|
-
return `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
301
|
-
}
|
|
302
|
-
}
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Service
|
|
3
|
-
* Handles user-related business logic
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { BaseService } from '../../../core/server/services/index.js'
|
|
7
|
-
|
|
8
|
-
export interface User {
|
|
9
|
-
id: number
|
|
10
|
-
name: string
|
|
11
|
-
email: string
|
|
12
|
-
createdAt: string
|
|
13
|
-
updatedAt?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface CreateUserData {
|
|
17
|
-
name: string
|
|
18
|
-
email: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface UpdateUserData {
|
|
22
|
-
name?: string
|
|
23
|
-
email?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class UserService extends BaseService {
|
|
27
|
-
private users: User[] = []
|
|
28
|
-
private nextId = 1
|
|
29
|
-
|
|
30
|
-
async initialize(): Promise<void> {
|
|
31
|
-
await super.initialize()
|
|
32
|
-
|
|
33
|
-
// Initialize with some sample data
|
|
34
|
-
this.users = [
|
|
35
|
-
{
|
|
36
|
-
id: this.nextId++,
|
|
37
|
-
name: 'John Doe',
|
|
38
|
-
email: 'john@example.com',
|
|
39
|
-
createdAt: new Date().toISOString()
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: this.nextId++,
|
|
43
|
-
name: 'Jane Smith',
|
|
44
|
-
email: 'jane@example.com',
|
|
45
|
-
createdAt: new Date().toISOString()
|
|
46
|
-
}
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
this.logger.info(`UserService initialized with ${this.users.length} users`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get all users
|
|
54
|
-
*/
|
|
55
|
-
async getAllUsers(): Promise<User[]> {
|
|
56
|
-
return this.executeWithLogging('getAllUsers', async () => {
|
|
57
|
-
return [...this.users]
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get user by ID
|
|
63
|
-
*/
|
|
64
|
-
async getUserById(id: number): Promise<User | null> {
|
|
65
|
-
return this.executeWithLogging('getUserById', async () => {
|
|
66
|
-
const user = this.users.find(u => u.id === id)
|
|
67
|
-
return user || null
|
|
68
|
-
}, { userId: id })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Get user by email
|
|
73
|
-
*/
|
|
74
|
-
async getUserByEmail(email: string): Promise<User | null> {
|
|
75
|
-
return this.executeWithLogging('getUserByEmail', async () => {
|
|
76
|
-
const user = this.users.find(u => u.email === email)
|
|
77
|
-
return user || null
|
|
78
|
-
}, { email })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a new user
|
|
83
|
-
*/
|
|
84
|
-
async createUser(data: CreateUserData): Promise<User> {
|
|
85
|
-
return this.executeWithLogging('createUser', async () => {
|
|
86
|
-
this.validateRequired(data, ['name', 'email'])
|
|
87
|
-
|
|
88
|
-
// Check if email already exists
|
|
89
|
-
const existingUser = await this.getUserByEmail(data.email)
|
|
90
|
-
if (existingUser) {
|
|
91
|
-
throw new Error(`User with email '${data.email}' already exists`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const user: User = {
|
|
95
|
-
id: this.nextId++,
|
|
96
|
-
name: data.name.trim(),
|
|
97
|
-
email: data.email.trim().toLowerCase(),
|
|
98
|
-
createdAt: new Date().toISOString()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.users.push(user)
|
|
102
|
-
|
|
103
|
-
this.logger.info('User created', { userId: user.id, email: user.email })
|
|
104
|
-
|
|
105
|
-
return user
|
|
106
|
-
}, { email: data.email })
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Update an existing user
|
|
111
|
-
*/
|
|
112
|
-
async updateUser(id: number, data: UpdateUserData): Promise<User> {
|
|
113
|
-
return this.executeWithLogging('updateUser', async () => {
|
|
114
|
-
const userIndex = this.users.findIndex(u => u.id === id)
|
|
115
|
-
if (userIndex === -1) {
|
|
116
|
-
throw new Error(`User with ID ${id} not found`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check email uniqueness if email is being updated
|
|
120
|
-
if (data.email) {
|
|
121
|
-
const existingUser = await this.getUserByEmail(data.email)
|
|
122
|
-
if (existingUser && existingUser.id !== id) {
|
|
123
|
-
throw new Error(`User with email '${data.email}' already exists`)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const updatedUser: User = {
|
|
128
|
-
...this.users[userIndex],
|
|
129
|
-
...data,
|
|
130
|
-
updatedAt: new Date().toISOString()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (data.email) {
|
|
134
|
-
updatedUser.email = data.email.trim().toLowerCase()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (data.name) {
|
|
138
|
-
updatedUser.name = data.name.trim()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
this.users[userIndex] = updatedUser
|
|
142
|
-
|
|
143
|
-
this.logger.info('User updated', { userId: id })
|
|
144
|
-
|
|
145
|
-
return updatedUser
|
|
146
|
-
}, { userId: id })
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Delete a user
|
|
151
|
-
*/
|
|
152
|
-
async deleteUser(id: number): Promise<boolean> {
|
|
153
|
-
return this.executeWithLogging('deleteUser', async () => {
|
|
154
|
-
const userIndex = this.users.findIndex(u => u.id === id)
|
|
155
|
-
if (userIndex === -1) {
|
|
156
|
-
return false
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
this.users.splice(userIndex, 1)
|
|
160
|
-
|
|
161
|
-
this.logger.info('User deleted', { userId: id })
|
|
162
|
-
|
|
163
|
-
return true
|
|
164
|
-
}, { userId: id })
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Search users by name or email
|
|
169
|
-
*/
|
|
170
|
-
async searchUsers(query: string): Promise<User[]> {
|
|
171
|
-
return this.executeWithLogging('searchUsers', async () => {
|
|
172
|
-
if (!query || query.trim().length === 0) {
|
|
173
|
-
return []
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const searchTerm = query.trim().toLowerCase()
|
|
177
|
-
|
|
178
|
-
return this.users.filter(user =>
|
|
179
|
-
user.name.toLowerCase().includes(searchTerm) ||
|
|
180
|
-
user.email.toLowerCase().includes(searchTerm)
|
|
181
|
-
)
|
|
182
|
-
}, { query })
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Get user statistics
|
|
187
|
-
*/
|
|
188
|
-
async getUserStats(): Promise<{
|
|
189
|
-
totalUsers: number
|
|
190
|
-
recentUsers: number
|
|
191
|
-
topDomains: Array<{ domain: string; count: number }>
|
|
192
|
-
}> {
|
|
193
|
-
return this.executeWithLogging('getUserStats', async () => {
|
|
194
|
-
const totalUsers = this.users.length
|
|
195
|
-
|
|
196
|
-
// Users created in the last 7 days
|
|
197
|
-
const weekAgo = new Date()
|
|
198
|
-
weekAgo.setDate(weekAgo.getDate() - 7)
|
|
199
|
-
const recentUsers = this.users.filter(user =>
|
|
200
|
-
new Date(user.createdAt) > weekAgo
|
|
201
|
-
).length
|
|
202
|
-
|
|
203
|
-
// Top email domains
|
|
204
|
-
const domainCounts = new Map<string, number>()
|
|
205
|
-
this.users.forEach(user => {
|
|
206
|
-
const domain = user.email.split('@')[1]
|
|
207
|
-
domainCounts.set(domain, (domainCounts.get(domain) || 0) + 1)
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const topDomains = Array.from(domainCounts.entries())
|
|
211
|
-
.map(([domain, count]) => ({ domain, count }))
|
|
212
|
-
.sort((a, b) => b.count - a.count)
|
|
213
|
-
.slice(0, 5)
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
totalUsers,
|
|
217
|
-
recentUsers,
|
|
218
|
-
topDomains
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Service Registry
|
|
3
|
-
* Configures and exports all application services
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FluxStackConfig } from '../../../core/config'
|
|
7
|
-
import type { Logger } from '../../../core/utils/logger/index'
|
|
8
|
-
import { ServiceContainer } from '../../../core/server/services/index.js'
|
|
9
|
-
import { UserService } from './UserService'
|
|
10
|
-
import { NotificationService } from './NotificationService'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create and configure the service container
|
|
14
|
-
*/
|
|
15
|
-
export function createServiceContainer(config: FluxStackConfig, logger: Logger): ServiceContainer {
|
|
16
|
-
const container = new ServiceContainer(logger)
|
|
17
|
-
|
|
18
|
-
// Register all services
|
|
19
|
-
container.registerMany([
|
|
20
|
-
{
|
|
21
|
-
name: 'userService',
|
|
22
|
-
constructor: UserService,
|
|
23
|
-
singleton: true
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: 'notificationService',
|
|
27
|
-
constructor: NotificationService,
|
|
28
|
-
dependencies: [], // Could depend on userService if needed
|
|
29
|
-
singleton: true
|
|
30
|
-
}
|
|
31
|
-
])
|
|
32
|
-
|
|
33
|
-
return container
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Re-export service classes and types
|
|
37
|
-
export { BaseService, ServiceContainer } from '../../../core/server/services/index.js'
|
|
38
|
-
export { UserService } from './UserService'
|
|
39
|
-
export { NotificationService } from './NotificationService'
|
|
40
|
-
|
|
41
|
-
export type { ServiceContext, ServiceDefinition } from '../../../core/server/services/index.js'
|
|
42
|
-
export type { User, CreateUserData, UpdateUserData } from './UserService'
|
|
43
|
-
export type {
|
|
44
|
-
Notification,
|
|
45
|
-
CreateNotificationData
|
|
46
|
-
} from './NotificationService'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '../../shared/types'
|
package/config/build.config.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build & Client Configuration
|
|
3
|
-
* Declarative build and client config for FluxStack framework
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { defineConfig, config } from '@/core/utils/config-schema'
|
|
7
|
-
|
|
8
|
-
export const buildConfig = defineConfig({
|
|
9
|
-
// Client build settings
|
|
10
|
-
clientBuildDir: config.string('CLIENT_BUILD_DIR', 'dist/client'),
|
|
11
|
-
clientSourceMaps: config.boolean('CLIENT_SOURCEMAPS', false),
|
|
12
|
-
clientMinify: config.boolean('CLIENT_MINIFY', true),
|
|
13
|
-
clientTarget: config.string('CLIENT_TARGET', 'es2020'),
|
|
14
|
-
|
|
15
|
-
// API proxy settings
|
|
16
|
-
apiUrl: config.string('API_URL', 'http://localhost:3000'),
|
|
17
|
-
proxyChangeOrigin: config.boolean('PROXY_CHANGE_ORIGIN', true),
|
|
18
|
-
|
|
19
|
-
// Monitoring
|
|
20
|
-
monitoringEnabled: config.boolean('MONITORING_ENABLED', false)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
export type BuildConfig = typeof buildConfig
|
|
24
|
-
export default buildConfig
|