create-fluxstack 1.10.1 → 1.12.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.
- package/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +107 -150
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -60
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +242 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +285 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +17 -68
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -69
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/package.json +12 -13
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- package/fluxstack.config.ts +0 -354
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// LiveRoomChat - Chat usando o sistema de salas $room
|
|
2
|
+
|
|
3
|
+
import { LiveComponent, type FluxStackWebSocket } from '@core/types/types'
|
|
4
|
+
|
|
5
|
+
// Componente Cliente (Ctrl+Click para navegar)
|
|
6
|
+
import type { RoomChatDemo as _Client } from '@client/src/live/RoomChatDemo'
|
|
7
|
+
|
|
8
|
+
// Tipos
|
|
9
|
+
export interface ChatMessage {
|
|
10
|
+
id: string
|
|
11
|
+
user: string
|
|
12
|
+
text: string
|
|
13
|
+
timestamp: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RoomInfo {
|
|
17
|
+
id: string
|
|
18
|
+
name: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class LiveRoomChat extends LiveComponent<typeof LiveRoomChat.defaultState> {
|
|
22
|
+
static componentName = 'LiveRoomChat'
|
|
23
|
+
static defaultState = {
|
|
24
|
+
username: '',
|
|
25
|
+
activeRoom: null as string | null,
|
|
26
|
+
rooms: [] as RoomInfo[],
|
|
27
|
+
messages: {} as Record<string, ChatMessage[]>,
|
|
28
|
+
typingUsers: {} as Record<string, string[]>
|
|
29
|
+
}
|
|
30
|
+
protected roomType = 'room-chat'
|
|
31
|
+
|
|
32
|
+
private typingTimers = new Map<string, NodeJS.Timeout>()
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
initialState: Partial<typeof LiveRoomChat.defaultState> = {},
|
|
36
|
+
ws: FluxStackWebSocket,
|
|
37
|
+
options?: { room?: string; userId?: string }
|
|
38
|
+
) {
|
|
39
|
+
super(initialState, ws, options)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ===== Gerenciamento de Salas =====
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Entrar em uma sala de chat
|
|
46
|
+
*/
|
|
47
|
+
async joinRoom(payload: { roomId: string; roomName?: string }) {
|
|
48
|
+
const { roomId, roomName } = payload
|
|
49
|
+
|
|
50
|
+
// Entrar na sala
|
|
51
|
+
this.$room(roomId).join()
|
|
52
|
+
|
|
53
|
+
// Escutar mensagens novas de outros usuários
|
|
54
|
+
this.$room(roomId).on('message:new', (msg: ChatMessage) => {
|
|
55
|
+
this.addMessageToState(roomId, msg)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Escutar usuários digitando
|
|
59
|
+
this.$room(roomId).on('user:typing', (data: { user: string; typing: boolean }) => {
|
|
60
|
+
this.updateTypingUsers(roomId, data.user, data.typing)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Atualizar lista de salas e inicializar mensagens
|
|
64
|
+
const rooms = [
|
|
65
|
+
...this.state.rooms.filter(r => r.id !== roomId),
|
|
66
|
+
{ id: roomId, name: roomName || roomId }
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
this.setState({
|
|
70
|
+
rooms,
|
|
71
|
+
activeRoom: roomId,
|
|
72
|
+
messages: {
|
|
73
|
+
...this.state.messages,
|
|
74
|
+
[roomId]: this.state.messages[roomId] || []
|
|
75
|
+
},
|
|
76
|
+
typingUsers: {
|
|
77
|
+
...this.state.typingUsers,
|
|
78
|
+
[roomId]: []
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
roomId,
|
|
85
|
+
rooms: this.$rooms
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private addMessageToState(roomId: string, msg: ChatMessage) {
|
|
90
|
+
const currentMessages = this.state.messages[roomId] || []
|
|
91
|
+
const newMessages = [...currentMessages, msg].slice(-100)
|
|
92
|
+
|
|
93
|
+
this.setState({
|
|
94
|
+
messages: {
|
|
95
|
+
...this.state.messages,
|
|
96
|
+
[roomId]: newMessages
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private updateTypingUsers(roomId: string, user: string, typing: boolean) {
|
|
102
|
+
const current = this.state.typingUsers[roomId] || []
|
|
103
|
+
let updated: string[]
|
|
104
|
+
|
|
105
|
+
if (typing && !current.includes(user)) {
|
|
106
|
+
updated = [...current, user]
|
|
107
|
+
} else if (!typing) {
|
|
108
|
+
updated = current.filter(u => u !== user)
|
|
109
|
+
} else {
|
|
110
|
+
return // Sem mudança
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.setState({
|
|
114
|
+
typingUsers: {
|
|
115
|
+
...this.state.typingUsers,
|
|
116
|
+
[roomId]: updated
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sair de uma sala
|
|
123
|
+
*/
|
|
124
|
+
async leaveRoom(payload: { roomId: string }) {
|
|
125
|
+
const { roomId } = payload
|
|
126
|
+
|
|
127
|
+
// Sair da sala
|
|
128
|
+
this.$room(roomId).leave()
|
|
129
|
+
|
|
130
|
+
// Atualizar lista
|
|
131
|
+
const rooms = this.state.rooms.filter(r => r.id !== roomId)
|
|
132
|
+
const activeRoom = this.state.activeRoom === roomId
|
|
133
|
+
? (rooms[0]?.id || null)
|
|
134
|
+
: this.state.activeRoom
|
|
135
|
+
|
|
136
|
+
// Remover mensagens e typing da sala
|
|
137
|
+
const { [roomId]: _msgs, ...restMessages } = this.state.messages
|
|
138
|
+
const { [roomId]: _typing, ...restTyping } = this.state.typingUsers
|
|
139
|
+
|
|
140
|
+
this.setState({
|
|
141
|
+
rooms,
|
|
142
|
+
activeRoom,
|
|
143
|
+
messages: restMessages,
|
|
144
|
+
typingUsers: restTyping
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
rooms: this.$rooms
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Trocar sala ativa (para UI)
|
|
155
|
+
*/
|
|
156
|
+
async switchRoom(payload: { roomId: string }) {
|
|
157
|
+
if (!this.$rooms.includes(payload.roomId)) {
|
|
158
|
+
throw new Error('Not in this room')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.setState({ activeRoom: payload.roomId })
|
|
162
|
+
return { success: true }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ===== Mensagens =====
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Enviar mensagem para sala ativa
|
|
169
|
+
*/
|
|
170
|
+
async sendMessage(payload: { text: string; roomId?: string }) {
|
|
171
|
+
const roomId = payload.roomId || this.state.activeRoom
|
|
172
|
+
if (!roomId) throw new Error('No active room')
|
|
173
|
+
|
|
174
|
+
const text = payload.text?.trim()
|
|
175
|
+
if (!text) throw new Error('Message cannot be empty')
|
|
176
|
+
if (text.length > 1000) throw new Error('Message too long')
|
|
177
|
+
|
|
178
|
+
const message: ChatMessage = {
|
|
179
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
180
|
+
user: this.state.username || 'Anônimo',
|
|
181
|
+
text,
|
|
182
|
+
timestamp: Date.now()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Adicionar ao meu estado local
|
|
186
|
+
this.addMessageToState(roomId, message)
|
|
187
|
+
|
|
188
|
+
// Emitir para outros na sala (eles vão receber via $room.on)
|
|
189
|
+
this.$room(roomId).emit('message:new', message)
|
|
190
|
+
|
|
191
|
+
// Parar de digitar
|
|
192
|
+
this.stopTyping({ roomId })
|
|
193
|
+
|
|
194
|
+
return { success: true, message }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Indicar que está digitando
|
|
199
|
+
*/
|
|
200
|
+
async startTyping(payload: { roomId?: string }) {
|
|
201
|
+
const roomId = payload.roomId || this.state.activeRoom
|
|
202
|
+
if (!roomId) return { success: false }
|
|
203
|
+
|
|
204
|
+
// Emitir evento
|
|
205
|
+
this.$room(roomId).emit('user:typing', {
|
|
206
|
+
user: this.state.username,
|
|
207
|
+
typing: true
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Limpar timer anterior
|
|
211
|
+
const existingTimer = this.typingTimers.get(roomId)
|
|
212
|
+
if (existingTimer) clearTimeout(existingTimer)
|
|
213
|
+
|
|
214
|
+
// Auto-parar após 3 segundos
|
|
215
|
+
const timer = setTimeout(() => {
|
|
216
|
+
this.stopTyping({ roomId })
|
|
217
|
+
}, 3000)
|
|
218
|
+
this.typingTimers.set(roomId, timer)
|
|
219
|
+
|
|
220
|
+
return { success: true }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Parar de digitar
|
|
225
|
+
*/
|
|
226
|
+
async stopTyping(payload: { roomId?: string }) {
|
|
227
|
+
const roomId = payload.roomId || this.state.activeRoom
|
|
228
|
+
if (!roomId) return { success: false }
|
|
229
|
+
|
|
230
|
+
// Limpar timer
|
|
231
|
+
const timer = this.typingTimers.get(roomId)
|
|
232
|
+
if (timer) {
|
|
233
|
+
clearTimeout(timer)
|
|
234
|
+
this.typingTimers.delete(roomId)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Emitir evento
|
|
238
|
+
this.$room(roomId).emit('user:typing', {
|
|
239
|
+
user: this.state.username,
|
|
240
|
+
typing: false
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return { success: true }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ===== Configuração =====
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Definir nome do usuário
|
|
250
|
+
*/
|
|
251
|
+
async setUsername(payload: { username: string }) {
|
|
252
|
+
const username = payload.username?.trim()
|
|
253
|
+
if (!username) throw new Error('Username required')
|
|
254
|
+
if (username.length > 30) throw new Error('Username too long')
|
|
255
|
+
|
|
256
|
+
this.setState({ username })
|
|
257
|
+
return { success: true, username }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Obter estado de uma sala
|
|
262
|
+
*/
|
|
263
|
+
async getRoomState(payload: { roomId: string }) {
|
|
264
|
+
const state = this.$room(payload.roomId).state
|
|
265
|
+
return {
|
|
266
|
+
success: true,
|
|
267
|
+
state: {
|
|
268
|
+
messages: state.messages || [],
|
|
269
|
+
typingUsers: state.typingUsers || []
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===== Cleanup =====
|
|
275
|
+
|
|
276
|
+
public destroy() {
|
|
277
|
+
// Limpar timers
|
|
278
|
+
for (const timer of this.typingTimers.values()) {
|
|
279
|
+
clearTimeout(timer)
|
|
280
|
+
}
|
|
281
|
+
this.typingTimers.clear()
|
|
282
|
+
|
|
283
|
+
super.destroy()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// LiveUpload - Estado de upload chunked + sincronização UI
|
|
2
|
+
|
|
3
|
+
import { LiveComponent } from '@core/types/types'
|
|
4
|
+
|
|
5
|
+
// Componente Cliente (Ctrl+Click para navegar)
|
|
6
|
+
import type { UploadDemo as _Client } from '@client/src/live/UploadDemo'
|
|
7
|
+
|
|
8
|
+
export class LiveUpload extends LiveComponent<typeof LiveUpload.defaultState> {
|
|
9
|
+
static componentName = 'LiveUpload'
|
|
10
|
+
static defaultState = {
|
|
11
|
+
status: 'idle' as 'idle' | 'uploading' | 'complete' | 'error',
|
|
12
|
+
progress: 0,
|
|
13
|
+
fileName: '',
|
|
14
|
+
fileSize: 0,
|
|
15
|
+
fileType: '',
|
|
16
|
+
fileUrl: '',
|
|
17
|
+
bytesUploaded: 0,
|
|
18
|
+
totalBytes: 0,
|
|
19
|
+
error: null as string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async startUpload(payload: { fileName: string; fileSize: number; fileType: string }) {
|
|
23
|
+
const normalized = payload.fileName.toLowerCase()
|
|
24
|
+
if (normalized.includes('..') || normalized.includes('/') || normalized.includes('\\')) {
|
|
25
|
+
throw new Error('Invalid file name')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// All file types allowed - no extension blocking
|
|
29
|
+
// Security note: Configure allowed extensions per your application needs
|
|
30
|
+
|
|
31
|
+
this.setState({
|
|
32
|
+
status: 'uploading',
|
|
33
|
+
progress: 0,
|
|
34
|
+
fileName: payload.fileName,
|
|
35
|
+
fileSize: payload.fileSize,
|
|
36
|
+
fileType: payload.fileType,
|
|
37
|
+
fileUrl: '',
|
|
38
|
+
bytesUploaded: 0,
|
|
39
|
+
totalBytes: payload.fileSize,
|
|
40
|
+
error: null
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return { success: true }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async updateProgress(payload: { progress: number; bytesUploaded: number; totalBytes: number }) {
|
|
47
|
+
const progress = Math.max(0, Math.min(100, payload.progress))
|
|
48
|
+
this.setState({
|
|
49
|
+
progress,
|
|
50
|
+
bytesUploaded: payload.bytesUploaded,
|
|
51
|
+
totalBytes: payload.totalBytes
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return { success: true, progress }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async completeUpload(payload: { fileUrl: string }) {
|
|
58
|
+
this.setState({
|
|
59
|
+
status: 'complete',
|
|
60
|
+
progress: 100,
|
|
61
|
+
fileUrl: payload.fileUrl,
|
|
62
|
+
error: null
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return { success: true }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async failUpload(payload: { error: string }) {
|
|
69
|
+
this.setState({
|
|
70
|
+
status: 'error',
|
|
71
|
+
error: payload.error || 'Upload failed'
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return { success: true }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async reset() {
|
|
78
|
+
this.setState({ ...LiveUpload.defaultState })
|
|
79
|
+
return { success: true }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Elysia, t } from "elysia"
|
|
2
2
|
import { usersRoutes } from "./users.routes"
|
|
3
|
+
import { roomRoutes } from "./room.routes"
|
|
3
4
|
|
|
4
5
|
export const apiRoutes = new Elysia({ prefix: "/api" })
|
|
5
6
|
.get("/", () => ({ message: "🔥 Hot Reload funcionando! FluxStack API v1.4.0 ⚡" }), {
|
|
@@ -32,5 +33,6 @@ export const apiRoutes = new Elysia({ prefix: "/api" })
|
|
|
32
33
|
description: 'Returns the current health status of the API server'
|
|
33
34
|
}
|
|
34
35
|
})
|
|
35
|
-
// Register
|
|
36
|
+
// Register routes
|
|
36
37
|
.use(usersRoutes)
|
|
38
|
+
.use(roomRoutes)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// 🔥 Room API Routes - Enviar mensagens para salas via HTTP
|
|
2
|
+
//
|
|
3
|
+
// Permite que sistemas externos (webhooks, outros serviços, etc)
|
|
4
|
+
// enviem mensagens para salas de chat via API REST
|
|
5
|
+
|
|
6
|
+
import { Elysia, t } from 'elysia'
|
|
7
|
+
import { liveRoomManager } from '@core/server/live/LiveRoomManager'
|
|
8
|
+
import { roomEvents } from '@core/server/live/RoomEventBus'
|
|
9
|
+
|
|
10
|
+
export const roomRoutes = new Elysia({ prefix: '/rooms' })
|
|
11
|
+
|
|
12
|
+
// Enviar mensagem para uma sala
|
|
13
|
+
.post('/:roomId/messages', ({ params, body }) => {
|
|
14
|
+
const { roomId } = params
|
|
15
|
+
const { user, text } = body
|
|
16
|
+
|
|
17
|
+
const message = {
|
|
18
|
+
id: `api-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
19
|
+
user: user || 'API Bot',
|
|
20
|
+
text,
|
|
21
|
+
timestamp: Date.now()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Emitir evento para a sala
|
|
25
|
+
// Isso vai:
|
|
26
|
+
// 1. Notificar handlers server-side via roomEvents
|
|
27
|
+
// 2. Broadcast via WebSocket para frontends
|
|
28
|
+
const notified = liveRoomManager.emitToRoom(roomId, 'message:new', message)
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
message,
|
|
33
|
+
notified,
|
|
34
|
+
roomId
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
params: t.Object({
|
|
38
|
+
roomId: t.String({ description: 'ID da sala (ex: geral, tech, random)' })
|
|
39
|
+
}),
|
|
40
|
+
body: t.Object({
|
|
41
|
+
user: t.Optional(t.String({ description: 'Nome do usuário (opcional, default: API Bot)' })),
|
|
42
|
+
text: t.String({ description: 'Texto da mensagem', minLength: 1, maxLength: 1000 })
|
|
43
|
+
}),
|
|
44
|
+
response: t.Object({
|
|
45
|
+
success: t.Boolean(),
|
|
46
|
+
message: t.Object({
|
|
47
|
+
id: t.String(),
|
|
48
|
+
user: t.String(),
|
|
49
|
+
text: t.String(),
|
|
50
|
+
timestamp: t.Number()
|
|
51
|
+
}),
|
|
52
|
+
notified: t.Number(),
|
|
53
|
+
roomId: t.String()
|
|
54
|
+
}),
|
|
55
|
+
detail: {
|
|
56
|
+
tags: ['Rooms'],
|
|
57
|
+
summary: 'Enviar mensagem para sala',
|
|
58
|
+
description: 'Envia uma mensagem para todos os usuários conectados em uma sala específica'
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Emitir evento customizado para uma sala
|
|
63
|
+
.post('/:roomId/emit', ({ params, body }) => {
|
|
64
|
+
const { roomId } = params
|
|
65
|
+
const { event, data } = body
|
|
66
|
+
|
|
67
|
+
const notified = liveRoomManager.emitToRoom(roomId, event, data)
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
success: true,
|
|
71
|
+
event,
|
|
72
|
+
notified,
|
|
73
|
+
roomId
|
|
74
|
+
}
|
|
75
|
+
}, {
|
|
76
|
+
params: t.Object({
|
|
77
|
+
roomId: t.String({ description: 'ID da sala' })
|
|
78
|
+
}),
|
|
79
|
+
body: t.Object({
|
|
80
|
+
event: t.String({ description: 'Nome do evento (ex: user:typing, notification)' }),
|
|
81
|
+
data: t.Any({ description: 'Dados do evento' })
|
|
82
|
+
}),
|
|
83
|
+
response: t.Object({
|
|
84
|
+
success: t.Boolean(),
|
|
85
|
+
event: t.String(),
|
|
86
|
+
notified: t.Number(),
|
|
87
|
+
roomId: t.String()
|
|
88
|
+
}),
|
|
89
|
+
detail: {
|
|
90
|
+
tags: ['Rooms'],
|
|
91
|
+
summary: 'Emitir evento customizado',
|
|
92
|
+
description: 'Emite um evento customizado para todos os componentes em uma sala'
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Obter estatísticas das salas
|
|
97
|
+
.get('/stats', () => {
|
|
98
|
+
const roomStats = liveRoomManager.getStats()
|
|
99
|
+
const eventStats = roomEvents.getStats()
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
rooms: roomStats,
|
|
104
|
+
events: eventStats
|
|
105
|
+
}
|
|
106
|
+
}, {
|
|
107
|
+
response: t.Object({
|
|
108
|
+
success: t.Boolean(),
|
|
109
|
+
rooms: t.Any(),
|
|
110
|
+
events: t.Any()
|
|
111
|
+
}),
|
|
112
|
+
detail: {
|
|
113
|
+
tags: ['Rooms'],
|
|
114
|
+
summary: 'Estatísticas das salas',
|
|
115
|
+
description: 'Retorna estatísticas sobre salas ativas e eventos registrados'
|
|
116
|
+
}
|
|
117
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Elysia, t } from 'elysia'
|
|
2
|
-
import { UsersController } from '
|
|
2
|
+
import { UsersController } from '@app/server/controllers/users.controller'
|
|
3
|
+
import type { CreateUserRequest } from '@app/shared/types'
|
|
3
4
|
|
|
4
5
|
// ===== Request/Response Schemas =====
|
|
5
6
|
|
|
@@ -77,9 +78,7 @@ const ErrorResponseSchema = t.Object({
|
|
|
77
78
|
*/
|
|
78
79
|
export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
79
80
|
// GET /users - Get all users
|
|
80
|
-
.get('/', async () => {
|
|
81
|
-
return await UsersController.getUsers()
|
|
82
|
-
}, {
|
|
81
|
+
.get('/', async () => UsersController.getUsers(), {
|
|
83
82
|
detail: {
|
|
84
83
|
summary: 'Get All Users',
|
|
85
84
|
description: 'Retrieves a list of all registered users',
|
|
@@ -90,19 +89,18 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
|
90
89
|
|
|
91
90
|
// GET /users/:id - Get user by ID
|
|
92
91
|
.get('/:id', async ({ params, set }) => {
|
|
93
|
-
const id =
|
|
92
|
+
const id = Number(params.id)
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
if (isNaN(id)) {
|
|
94
|
+
if (!Number.isFinite(id)) {
|
|
97
95
|
set.status = 400
|
|
98
|
-
return { error: 'ID
|
|
96
|
+
return { success: false, error: 'ID invalido' }
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
const result = await UsersController.getUserById(id)
|
|
102
100
|
|
|
103
|
-
if (!result) {
|
|
101
|
+
if (!result.success) {
|
|
104
102
|
set.status = 404
|
|
105
|
-
return
|
|
103
|
+
return result
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
return result
|
|
@@ -124,17 +122,17 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
|
124
122
|
|
|
125
123
|
// POST /users - Create new user
|
|
126
124
|
.post('/', async ({ body, set }) => {
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
const payload = body as CreateUserRequest
|
|
126
|
+
|
|
127
|
+
if (!payload.name || !payload.email) {
|
|
129
128
|
set.status = 400
|
|
130
129
|
return {
|
|
131
130
|
success: false,
|
|
132
|
-
error: 'Nome e email
|
|
131
|
+
error: 'Nome e email sao obrigatorios'
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
if (body.name.length < 2) {
|
|
135
|
+
if (payload.name.trim().length < 2) {
|
|
138
136
|
set.status = 400
|
|
139
137
|
return {
|
|
140
138
|
success: false,
|
|
@@ -142,23 +140,28 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
|
142
140
|
}
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!emailRegex.test(body.email)) {
|
|
143
|
+
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/
|
|
144
|
+
if (!emailRegex.test(payload.email)) {
|
|
148
145
|
set.status = 400
|
|
149
146
|
return {
|
|
150
147
|
success: false,
|
|
151
|
-
error: 'Email
|
|
148
|
+
error: 'Email invalido'
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
151
|
|
|
155
|
-
const
|
|
152
|
+
const sanitizedPayload: CreateUserRequest = {
|
|
153
|
+
name: payload.name.trim(),
|
|
154
|
+
email: payload.email.trim()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = await UsersController.createUser(sanitizedPayload)
|
|
156
158
|
|
|
157
|
-
// If email is duplicate, still return 200 but with success: false
|
|
158
159
|
if (!result.success) {
|
|
160
|
+
set.status = 409
|
|
159
161
|
return result
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
set.status = 201
|
|
162
165
|
return result
|
|
163
166
|
}, {
|
|
164
167
|
detail: {
|
|
@@ -168,30 +171,34 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
|
168
171
|
},
|
|
169
172
|
body: CreateUserRequestSchema,
|
|
170
173
|
response: {
|
|
171
|
-
|
|
174
|
+
201: CreateUserResponseSchema,
|
|
172
175
|
400: t.Object({
|
|
173
176
|
success: t.Literal(false),
|
|
174
177
|
error: t.String()
|
|
178
|
+
}),
|
|
179
|
+
409: t.Object({
|
|
180
|
+
success: t.Literal(false),
|
|
181
|
+
error: t.String()
|
|
175
182
|
})
|
|
176
183
|
}
|
|
177
184
|
})
|
|
178
185
|
|
|
179
186
|
// DELETE /users/:id - Delete user
|
|
180
187
|
.delete('/:id', async ({ params, set }) => {
|
|
181
|
-
const id =
|
|
188
|
+
const id = Number(params.id)
|
|
182
189
|
|
|
183
|
-
if (
|
|
190
|
+
if (!Number.isFinite(id)) {
|
|
184
191
|
set.status = 400
|
|
185
192
|
return {
|
|
186
193
|
success: false,
|
|
187
|
-
message: 'ID
|
|
194
|
+
message: 'ID invalido'
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
const result = await UsersController.deleteUser(id)
|
|
192
199
|
|
|
193
200
|
if (!result.success) {
|
|
194
|
-
|
|
201
|
+
set.status = 404
|
|
195
202
|
return result
|
|
196
203
|
}
|
|
197
204
|
|
|
@@ -210,6 +217,7 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
|
|
|
210
217
|
400: t.Object({
|
|
211
218
|
success: t.Literal(false),
|
|
212
219
|
message: t.String()
|
|
213
|
-
})
|
|
220
|
+
}),
|
|
221
|
+
404: DeleteUserResponseSchema
|
|
214
222
|
}
|
|
215
223
|
})
|
|
@@ -11,8 +11,20 @@ export interface CreateUserRequest {
|
|
|
11
11
|
email: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export interface
|
|
14
|
+
export interface UserListResponse {
|
|
15
|
+
success: true
|
|
16
|
+
users: User[]
|
|
17
|
+
count: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UserDetailResponse {
|
|
15
21
|
success: boolean
|
|
16
22
|
user?: User
|
|
23
|
+
error?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MutationResponse {
|
|
27
|
+
success: boolean
|
|
17
28
|
message?: string
|
|
18
|
-
|
|
29
|
+
error?: string
|
|
30
|
+
}
|