@bhooai/nexus-cli 2.0.2 → 2.0.4
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/package.json +1 -1
- package/src/commands/add.ts +1 -1
- package/src/commands/dev.ts +74 -125
- package/src/commands/init.ts +87 -136
- package/src/devPanel.ts +696 -0
- package/src/devServiceManager.ts +229 -0
- package/src/dispatcher.ts +22 -1
- package/src/examples.ts +90 -0
- package/src/features.ts +261 -0
- package/src/launcher.ts +164 -0
- package/src/layout.ts +101 -0
- package/src/templating/tree.ts +66 -0
- package/src/tui.ts +170 -0
- package/src/wizard.ts +691 -0
- package/templates/base/Dockerfile.ejs +1 -0
- package/templates/base/apps/admin/nginx.conf.ejs +30 -1
- package/templates/base/apps/admin/package.json.ejs +7 -2
- package/templates/base/apps/admin/postcss.config.js +5 -0
- package/templates/base/apps/admin/src/App.tsx +4127 -0
- package/templates/base/apps/admin/src/alertCenter.tsx +150 -0
- package/templates/base/apps/admin/src/api.ts +474 -0
- package/templates/base/apps/admin/src/assets/bhooai-nexus-logo.svg +25 -0
- package/templates/base/apps/admin/src/index.css +3481 -0
- package/templates/base/apps/admin/src/main.tsx.ejs +3 -3
- package/templates/base/apps/admin/src/vite-env.d.ts +19 -0
- package/templates/base/apps/admin/tailwind.config.js +9 -0
- package/templates/base/apps/admin/vite.config.ts.ejs +21 -2
- package/templates/base/apps/ai-server/main.py.ejs +94 -6
- package/templates/base/apps/backend/package.json.ejs +27 -0
- package/templates/base/apps/frontend/package.json.ejs +7 -0
- package/templates/base/apps/frontend/vite.config.ts.ejs +0 -1
- package/templates/base/docker-compose.yml.ejs +6 -1
- package/templates/base/nexus.config.ts.ejs +4 -4
- package/templates/features/auth/apps/backend/src/models/User.ts +21 -0
- package/templates/features/auth/apps/backend/src/routes/auth.ts +95 -0
- package/templates/features/email/apps/backend/src/mail/mailables/WelcomeMail.ts +25 -0
- package/templates/features/email/apps/backend/src/mail/templates/welcome.ejs.ejs +10 -0
- package/templates/features/graphql/apps/backend/src/graphql/post.graph.ts +61 -0
- package/templates/features/graphql/apps/backend/src/models/Post.ts +15 -0
- package/templates/features/payments/apps/backend/src/routes/payments.ts +45 -0
- package/templates/features/queue/apps/backend/src/events/JobQueued.ts +14 -0
- package/templates/features/queue/apps/backend/src/jobs/ExampleJob.ts +18 -0
- package/templates/features/queue/apps/backend/src/listeners/OnJobQueued.ts +12 -0
- package/templates/features/realtime/apps/backend/src/models/Message.ts +14 -0
- package/templates/features/realtime/apps/backend/src/ws/chat.room.ts +56 -0
- package/templates/features/storage/apps/backend/src/routes/uploads.ts +91 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realtime feature — WebSocket chat room. Auto-discovered from ws/*.room.ts
|
|
3
|
+
* and mounted on the /ws endpoint.
|
|
4
|
+
*
|
|
5
|
+
* Client protocol:
|
|
6
|
+
* → join { roomId, username }
|
|
7
|
+
* ← presence { type, username }
|
|
8
|
+
* → msg { roomId, text }
|
|
9
|
+
* ← msg { from, text, at }
|
|
10
|
+
* → typing { roomId }
|
|
11
|
+
* ← typing { username }
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
name: 'chat',
|
|
15
|
+
|
|
16
|
+
async onJoin(socket: any, payload: { roomId: string; username: string }) {
|
|
17
|
+
socket.data.username = payload.username;
|
|
18
|
+
socket.join(`chat:${payload.roomId}`);
|
|
19
|
+
socket.to(`chat:${payload.roomId}`).emit('presence', {
|
|
20
|
+
type: 'join',
|
|
21
|
+
username: payload.username,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async onLeave(socket: any, payload: { roomId: string }) {
|
|
26
|
+
socket.to(`chat:${payload.roomId}`).emit('presence', {
|
|
27
|
+
type: 'leave',
|
|
28
|
+
username: socket.data?.username,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
onMessage: {
|
|
33
|
+
'msg': async (socket: any, payload: { roomId: string; text: string }, ctx: any) => {
|
|
34
|
+
const msg = {
|
|
35
|
+
from: socket.data?.username ?? 'anon',
|
|
36
|
+
text: payload.text,
|
|
37
|
+
at: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
const { Message } = await import('../models/Message.js');
|
|
41
|
+
await Message.create({
|
|
42
|
+
roomId: payload.roomId,
|
|
43
|
+
userId: socket.data?.userId ?? 'anon',
|
|
44
|
+
username: msg.from,
|
|
45
|
+
text: msg.text,
|
|
46
|
+
});
|
|
47
|
+
} catch { /* non-fatal */ }
|
|
48
|
+
ctx.server.to(`chat:${payload.roomId}`).emit('msg', msg);
|
|
49
|
+
},
|
|
50
|
+
'typing': (socket: any, payload: { roomId: string }, ctx: any) => {
|
|
51
|
+
socket.to(`chat:${payload.roomId}`).emit('typing', {
|
|
52
|
+
username: socket.data?.username,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineRoutes, storage, uploadedFiles } from '@bhooai/nexus-core';
|
|
2
|
+
import { ValidationError, NotFoundError } from '@bhooai/nexus-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File-storage feature — public uploads + signed private URLs.
|
|
6
|
+
*
|
|
7
|
+
* Disks are configured in nexus.config.ts under `storage`:
|
|
8
|
+
* uploads → served at GET /uploads/* (public)
|
|
9
|
+
* private → signed-URL only (default TTL 5 min)
|
|
10
|
+
* S3 keys come from .env (NEXUS_STORAGE_S3_*).
|
|
11
|
+
*/
|
|
12
|
+
export interface FileEntry {
|
|
13
|
+
id: string;
|
|
14
|
+
disk: 'uploads' | 'private';
|
|
15
|
+
path: string;
|
|
16
|
+
url: string;
|
|
17
|
+
originalName: string;
|
|
18
|
+
mime: string;
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const files = new Map<string, FileEntry>();
|
|
23
|
+
|
|
24
|
+
export default defineRoutes([
|
|
25
|
+
{
|
|
26
|
+
method: 'GET',
|
|
27
|
+
path: '/api/files',
|
|
28
|
+
handler: async (ctx) => {
|
|
29
|
+
ctx.json({ files: [...files.values()] });
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
method: 'POST',
|
|
34
|
+
path: '/api/files',
|
|
35
|
+
handler: async (ctx) => {
|
|
36
|
+
const [file] = uploadedFiles(ctx);
|
|
37
|
+
if (!file) throw new ValidationError('a file is required');
|
|
38
|
+
const stored = await storage.disk('uploads').put('media', {
|
|
39
|
+
buffer: file.data,
|
|
40
|
+
originalName: file.filename,
|
|
41
|
+
mime: file.contentType,
|
|
42
|
+
});
|
|
43
|
+
const entry: FileEntry = {
|
|
44
|
+
id: stored.id, disk: 'uploads', path: stored.path, url: stored.url,
|
|
45
|
+
originalName: stored.originalName, mime: stored.mime, size: stored.size,
|
|
46
|
+
};
|
|
47
|
+
files.set(entry.id, entry);
|
|
48
|
+
ctx.json({ file: entry }, 201);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
method: 'POST',
|
|
53
|
+
path: '/api/files/private',
|
|
54
|
+
handler: async (ctx) => {
|
|
55
|
+
const [file] = uploadedFiles(ctx);
|
|
56
|
+
if (!file) throw new ValidationError('a file is required');
|
|
57
|
+
const stored = await storage.disk('private').put('secure', {
|
|
58
|
+
buffer: file.data,
|
|
59
|
+
originalName: file.filename,
|
|
60
|
+
mime: file.contentType,
|
|
61
|
+
});
|
|
62
|
+
const entry: FileEntry = {
|
|
63
|
+
id: stored.id, disk: 'private', path: stored.path, url: '',
|
|
64
|
+
originalName: stored.originalName, mime: stored.mime, size: stored.size,
|
|
65
|
+
};
|
|
66
|
+
files.set(entry.id, entry);
|
|
67
|
+
ctx.json({ file: entry }, 201);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
method: 'GET',
|
|
72
|
+
path: '/api/files/:id/signed',
|
|
73
|
+
handler: async (ctx) => {
|
|
74
|
+
const entry = files.get(ctx.params.id);
|
|
75
|
+
if (!entry) throw new NotFoundError('file not found');
|
|
76
|
+
const url = await storage.disk(entry.disk).signedUrl(entry.path, { ttl: 300 });
|
|
77
|
+
ctx.json({ url, expiresIn: 300 });
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
method: 'DELETE',
|
|
82
|
+
path: '/api/files/:id',
|
|
83
|
+
handler: async (ctx) => {
|
|
84
|
+
const entry = files.get(ctx.params.id);
|
|
85
|
+
if (!entry) throw new NotFoundError('file not found');
|
|
86
|
+
await storage.disk(entry.disk).delete(entry.path);
|
|
87
|
+
files.delete(entry.id);
|
|
88
|
+
ctx.json({ ok: true });
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
]);
|