streamby-core 0.26.4 → 0.26.6

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.
@@ -8,19 +8,19 @@ const express_1 = __importDefault(require("express"));
8
8
  const manager_1 = require("../models/manager");
9
9
  const storage_1 = require("../providers/storage");
10
10
  const connectionManager_1 = require("../adapters/database/connectionManager");
11
- const auth_1 = require("./routes/auth");
12
- const database_1 = require("./routes/database");
11
+ const wsHub_1 = require("../services/wsHub");
12
+ const auth_1 = require("../services/auth");
13
+ const encryption_1 = require("../utils/encryption");
14
+ const auth_2 = require("./routes/auth");
15
+ const user_1 = require("./routes/user");
16
+ const export_1 = require("./routes/export");
17
+ const member_1 = require("./routes/member");
13
18
  const storage_2 = require("./routes/storage");
14
19
  const project_1 = require("./routes/project");
15
- const export_1 = require("./routes/export");
20
+ const database_1 = require("./routes/database");
16
21
  const credential_1 = require("./routes/credential");
17
22
  const connection_1 = require("./routes/connection");
18
- const member_1 = require("./routes/member");
19
- const user_1 = require("./routes/user");
20
23
  const notification_1 = require("./routes/notification");
21
- const auth_2 = require("../services/auth");
22
- const encryption_1 = require("../utils/encryption");
23
- const wsHub_1 = require("../services/wsHub");
24
24
  function createStreamByRouter(config) {
25
25
  const router = express_1.default.Router();
26
26
  if (config.encrypt)
@@ -29,13 +29,13 @@ function createStreamByRouter(config) {
29
29
  const adapter = config.adapter || (0, storage_1.createStorageProvider)(config.storageProviders);
30
30
  if (config.databases) {
31
31
  const allDbIds = config.databases.map(db => db.id);
32
- const sqlDbs = config.databases.filter(db => db.type === 'sql');
33
- const streambySchema = sqlDbs.length > 0 ? 'streamby' : undefined;
34
- (0, manager_1.registerModel)('projects', allDbIds, 'projects', streambySchema);
32
+ const streambySchema = 'streamby';
35
33
  (0, manager_1.registerModel)('exports', allDbIds, 'exports', streambySchema);
34
+ (0, manager_1.registerModel)('projects', allDbIds, 'projects', streambySchema);
36
35
  const nosqlDbIds = config.databases.filter(db => db.type === 'nosql').map(db => db.id);
37
36
  if (nosqlDbIds.length > 0) {
38
- (0, manager_1.registerModel)('notifications', nosqlDbIds, 'notifications');
37
+ (0, manager_1.registerModel)('notifications', nosqlDbIds, 'notifications', streambySchema);
38
+ (0, manager_1.registerModel)('storage_files', nosqlDbIds, 'storage_files');
39
39
  }
40
40
  const mainDb = config.databases.find(db => db.main);
41
41
  if (mainDb) {
@@ -45,16 +45,16 @@ function createStreamByRouter(config) {
45
45
  if (config.websocket?.server) {
46
46
  (0, wsHub_1.initWsHub)(config.websocket.server, config, config.websocket.path);
47
47
  }
48
- router.use((0, auth_2.authenticate)(config));
49
- router.use((0, auth_1.authRouter)(config));
50
- router.use((0, database_1.databaseRouter)(config));
48
+ router.use((0, user_1.userRouter)(config));
49
+ router.use((0, auth_2.authRouter)(config));
50
+ router.use((0, auth_1.authenticate)(config));
51
+ router.use((0, export_1.exportRouter)(config));
52
+ router.use((0, member_1.memberRouter)(config));
51
53
  router.use((0, storage_2.storageRouter)(config));
52
54
  router.use((0, project_1.projectRouter)(config));
53
- router.use((0, export_1.exportRouter)(config));
55
+ router.use((0, database_1.databaseRouter)(config));
54
56
  router.use((0, credential_1.credentialRouter)(config));
55
- router.use((0, connection_1.apiConnectionRouter)(config));
56
- router.use((0, member_1.memberRouter)(config));
57
- router.use((0, user_1.userRouter)(config));
57
+ router.use((0, connection_1.connectionRouter)(config));
58
58
  router.use((0, notification_1.notificationRouter)(config));
59
59
  return router;
60
60
  }
@@ -1,3 +1,3 @@
1
1
  import { Router } from 'express';
2
2
  import { StreamByConfig } from '../../types';
3
- export declare function apiConnectionRouter(config: StreamByConfig): Router;
3
+ export declare function connectionRouter(config: StreamByConfig): Router;
@@ -33,13 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.apiConnectionRouter = apiConnectionRouter;
36
+ exports.connectionRouter = connectionRouter;
37
37
  const express_1 = require("express");
38
38
  const manager_1 = require("../../models/manager");
39
39
  const auth_1 = require("../../utils/auth");
40
40
  const apiConnection_1 = require("../../services/apiConnection");
41
41
  const VALID_METHODS = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'];
42
- function apiConnectionRouter(config) {
42
+ function connectionRouter(config) {
43
43
  const router = (0, express_1.Router)();
44
44
  const Project = (0, manager_1.getModel)('projects');
45
45
  // List API connections for a project
@@ -9,6 +9,37 @@ const pipeline_1 = require("../../services/pipeline");
9
9
  const connectionManager_1 = require("../../adapters/database/connectionManager");
10
10
  const mongodb_1 = require("mongodb");
11
11
  const encryption_1 = require("../../utils/encryption");
12
+ const DEFAULT_NODE_SCHEMA = {
13
+ nodes: [
14
+ {
15
+ id: 'client',
16
+ type: 'clientNode',
17
+ position: { x: 0, y: 100 },
18
+ data: { label: 'Client', subtitle: 'GET' },
19
+ width: 148,
20
+ height: 100,
21
+ },
22
+ {
23
+ id: 'streamby',
24
+ type: 'streambyNode',
25
+ position: { x: 240, y: 100 },
26
+ data: { label: 'StreamBy', subtitle: 'Middleware' },
27
+ width: 158,
28
+ height: 100,
29
+ },
30
+ ],
31
+ edges: [
32
+ {
33
+ id: 'e-client-streamby',
34
+ source: 'client',
35
+ sourceHandle: 'out-right',
36
+ target: 'streamby',
37
+ targetHandle: 'in-left',
38
+ animated: true,
39
+ style: { stroke: '#38B6FF', strokeWidth: 2 },
40
+ },
41
+ ],
42
+ };
12
43
  function exportRouter(config) {
13
44
  const router = (0, express_1.Router)();
14
45
  const Project = (0, manager_1.getModel)('projects');
@@ -162,15 +193,13 @@ function exportRouter(config) {
162
193
  if (!name) {
163
194
  return res.status(400).json({ message: 'Missing export name' });
164
195
  }
165
- if (!nodeSchema) {
166
- return res.status(400).json({ message: 'Missing nodeSchema' });
167
- }
196
+ const resolvedNodeSchema = nodeSchema ?? DEFAULT_NODE_SCHEMA;
168
197
  const exportType = useConnections ? 'externalApi' : 'json';
169
198
  const project = await Project.findOne({ _id: projectId });
170
199
  if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
171
200
  return res.status(403).json({ message: 'Unauthorized project access' });
172
201
  }
173
- const result = await (0, export_1.createExport)(config, projectId, description, name, project.dbType, exportType, isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials);
202
+ const result = await (0, export_1.createExport)(config, projectId, description, name, project.dbType, exportType, isPrivate, allowedOrigin, resolvedNodeSchema, useConnections, useCredentials);
174
203
  res.status(201).json({ data: result, message: result.message });
175
204
  }
176
205
  catch (err) {
@@ -185,13 +214,6 @@ function exportRouter(config) {
185
214
  }
186
215
  const { id: projectId, export_id: exportId } = req.params;
187
216
  const { name, description, isPrivate, allowedOrigin, useConnections, useCredentials, nodeSchema } = req.body;
188
- if (!name) {
189
- return res.status(400).json({ message: 'Missing name' });
190
- }
191
- if (!nodeSchema) {
192
- return res.status(400).json({ message: 'Missing nodeSchema' });
193
- }
194
- const exportType = useConnections ? 'externalApi' : 'json';
195
217
  const project = await Project.findOne({ _id: projectId });
196
218
  if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
197
219
  return res.status(403).json({ message: 'Unauthorized project access' });
@@ -200,7 +222,11 @@ function exportRouter(config) {
200
222
  if (!exportMetadata) {
201
223
  return res.status(404).json({ message: 'Export not found' });
202
224
  }
203
- const result = await (0, export_1.updateExport)(config, projectId, exportId, description, name, project.dbType, exportType, isPrivate, allowedOrigin, nodeSchema, useConnections, useCredentials);
225
+ const resolvedName = name ?? exportMetadata.name;
226
+ const resolvedNodeSchema = nodeSchema ?? exportMetadata.nodeSchema;
227
+ const resolvedUseConnections = useConnections ?? exportMetadata.useConnections;
228
+ const exportType = resolvedUseConnections ? 'externalApi' : 'json';
229
+ const result = await (0, export_1.updateExport)(config, projectId, exportId, description ?? exportMetadata.description, resolvedName, project.dbType, exportType, isPrivate ?? exportMetadata.private, allowedOrigin ?? exportMetadata.allowedOrigin, resolvedNodeSchema, resolvedUseConnections, useCredentials ?? exportMetadata.useCredentials);
204
230
  res.status(200).json({ data: result, message: result.message });
205
231
  }
206
232
  catch (err) {
@@ -32,14 +32,15 @@ function memberRouter(config) {
32
32
  if (!mainDb) {
33
33
  return res.status(500).json({ message: 'Main database not configured' });
34
34
  }
35
- const User = (0, manager_1.getModel)('users');
35
+ const User = (0, manager_1.getModel)('users', mainDb.type);
36
36
  const membersWithUsernames = await Promise.all(project.members.map(async (member) => {
37
37
  const user = await User.findOne({ _id: member.userId });
38
38
  return {
39
39
  userId: member.userId,
40
40
  username: user ? user.username : 'Unknown',
41
41
  role: member.role,
42
- profilePic: user ? user.profilePic : '',
42
+ status: member.status,
43
+ profilePic: user ? user.profilePic : user.googlePic,
43
44
  };
44
45
  }));
45
46
  res.json({ members: membersWithUsernames, message: 'Project members fetched successfully' });
@@ -96,7 +97,7 @@ function memberRouter(config) {
96
97
  else {
97
98
  await Project.update({ _id: projectId }, { $push: { members: newMember } });
98
99
  }
99
- await (0, notification_1.createNotification)(userId, 'member_invited', `Te invitaron al proyecto "${project.name}"`, { projectId, role, invitedBy: auth.userId });
100
+ await (0, notification_1.createNotification)(userId, 'member_invited', `You has been invited to join the project "${project.name}"`, { projectId, role, invitedBy: auth.userId });
100
101
  res.status(201).json({ member: newMember, message: 'Member invited successfully' });
101
102
  }
102
103
  catch (err) {
@@ -3,14 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.notificationRouter = notificationRouter;
4
4
  const express_1 = require("express");
5
5
  const manager_1 = require("../../models/manager");
6
+ const connectionManager_1 = require("../../adapters/database/connectionManager");
7
+ const mongodb_1 = require("mongodb");
8
+ const MAX_NOTIFICATIONS = 20;
9
+ function getRawNotifCollection() {
10
+ const model = (0, manager_1.getModel)('notifications', 'nosql');
11
+ const connectionIds = model.getConnectionIds();
12
+ const activeId = connectionIds.find((id) => (0, connectionManager_1.getConnectedIds)().includes(id) && (0, connectionManager_1.getConnection)(id).type === 'nosql');
13
+ if (!activeId)
14
+ return null;
15
+ return (0, connectionManager_1.getConnection)(activeId).client.db().collection('notifications');
16
+ }
6
17
  function notificationRouter(config) {
7
18
  const router = (0, express_1.Router)();
8
- const Notification = (0, manager_1.getModel)('notifications');
9
19
  router.get('/notifications', async (req, res) => {
10
20
  try {
11
21
  const auth = req.auth;
12
- const notifications = await Notification.find({ userId: auth.userId, read: false });
13
- res.status(200).json({ data: notifications });
22
+ const limit = Math.min(parseInt(req.query.limit) || MAX_NOTIFICATIONS, MAX_NOTIFICATIONS);
23
+ const page = Math.max(parseInt(req.query.page) || 1, 1);
24
+ const skip = (page - 1) * limit;
25
+ const appIdQuery = req.query.appId;
26
+ const collection = getRawNotifCollection();
27
+ if (!collection)
28
+ return res.status(500).json({ message: 'Database not available' });
29
+ const filter = { userId: auth.userId.toString() };
30
+ if (appIdQuery !== undefined) {
31
+ filter.appId = appIdQuery === '0' ? null : appIdQuery;
32
+ }
33
+ const total = await collection.countDocuments(filter);
34
+ const notifications = await collection
35
+ .find(filter)
36
+ .sort({ createdAt: -1 })
37
+ .skip(skip)
38
+ .limit(limit)
39
+ .toArray();
40
+ res.status(200).json({
41
+ data: notifications,
42
+ pagination: { page, limit, total, pages: Math.ceil(total / limit) },
43
+ });
44
+ }
45
+ catch (err) {
46
+ res.status(500).json({ message: 'Failed to fetch notifications', details: err.message });
47
+ }
48
+ });
49
+ router.get('/notifications/:id', async (req, res) => {
50
+ try {
51
+ const auth = req.auth;
52
+ const { id } = req.params;
53
+ const notifications = (0, manager_1.getModel)('notifications', 'nosql');
54
+ const notification = await notifications.findOne({ _id: new mongodb_1.ObjectId(id), userId: auth.userId.toString() });
55
+ res.status(200).json({
56
+ data: notification,
57
+ });
14
58
  }
15
59
  catch (err) {
16
60
  res.status(500).json({ message: 'Failed to fetch notifications', details: err.message });
@@ -19,10 +63,15 @@ function notificationRouter(config) {
19
63
  router.patch('/notifications/read-all', async (req, res) => {
20
64
  try {
21
65
  const auth = req.auth;
22
- const model = Notification;
23
- if (model.updateMany) {
24
- await model.updateMany({ userId: auth.userId, read: false }, { $set: { read: true, readAt: new Date() } });
66
+ const appIdQuery = req.query.appId;
67
+ const collection = getRawNotifCollection();
68
+ if (!collection)
69
+ return res.status(500).json({ message: 'Database not available' });
70
+ const filter = { userId: auth.userId.toString(), read: false };
71
+ if (appIdQuery !== undefined) {
72
+ filter.appId = appIdQuery === '0' ? null : appIdQuery;
25
73
  }
74
+ await collection.updateMany(filter, { $set: { read: true, readAt: new Date() } });
26
75
  res.status(200).json({ message: 'All notifications marked as read' });
27
76
  }
28
77
  catch (err) {
@@ -33,7 +82,10 @@ function notificationRouter(config) {
33
82
  try {
34
83
  const auth = req.auth;
35
84
  const { id } = req.params;
36
- await Notification.update({ _id: id, userId: auth.userId }, { read: true, readAt: new Date() });
85
+ const collection = getRawNotifCollection();
86
+ if (!collection)
87
+ return res.status(500).json({ message: 'Database not available' });
88
+ await collection.updateOne({ _id: new mongodb_1.ObjectId(id), userId: auth.userId.toString() }, { $set: { read: true, readAt: new Date() } });
37
89
  res.status(200).json({ message: 'Notification marked as read' });
38
90
  }
39
91
  catch (err) {
@@ -6,10 +6,8 @@ const manager_1 = require("../../models/manager");
6
6
  const auth_1 = require("../../utils/auth");
7
7
  const connectionManager_1 = require("../../adapters/database/connectionManager");
8
8
  const sql_1 = require("../../adapters/database/sql");
9
- const auth_2 = require("../../services/auth");
10
9
  function projectRouter(config) {
11
10
  const router = (0, express_1.Router)();
12
- router.use((0, auth_2.authenticate)(config));
13
11
  const Project = (0, manager_1.getModel)('projects');
14
12
  const mapProjectToResponseFormat = (project, userId) => {
15
13
  const currentUserMember = project.members?.find((member) => member.userId === userId);
@@ -29,12 +27,11 @@ function projectRouter(config) {
29
27
  const allProjects = await Project.find({}); // Fetch all projects
30
28
  const projects = allProjects
31
29
  .filter(project => {
32
- const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
33
- if (!isMember)
30
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
31
+ if (!currentUserMember || currentUserMember.status !== 'active')
34
32
  return false;
35
33
  if (filterArchived !== undefined) {
36
- const currentUserMember = project.members?.find((member) => member.userId === auth.userId);
37
- return currentUserMember ? (currentUserMember.archived || false) === filterArchived : false;
34
+ return (currentUserMember.archived || false) === filterArchived;
38
35
  }
39
36
  return true;
40
37
  })
@@ -69,13 +66,15 @@ function projectRouter(config) {
69
66
  description: description || '',
70
67
  image: image || '',
71
68
  allowedOrigin: allowedOrigin || [],
72
- members: [{ userId: auth.userId, username: user.username, role: "admin", archived: false }],
69
+ members: [{ userId: auth.userId, username: user.username, role: "admin", status: "active", archived: false }],
73
70
  });
74
71
  const allProjects = await Project.find({});
75
72
  const projects = allProjects
76
73
  .filter(project => {
77
- const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
78
- return isMember; // Only include projects where the user is a member
74
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
75
+ if (!currentUserMember || currentUserMember.status !== 'active')
76
+ return false;
77
+ return true;
79
78
  })
80
79
  .map(project => mapProjectToResponseFormat(project, auth.userId));
81
80
  res.status(200).json({ success: true, projects: projects, projectId: newProject._id || newProject.id, message: 'Project created successfully' });
@@ -85,6 +84,38 @@ function projectRouter(config) {
85
84
  res.status(500).json({ message: 'Failed to create project', details: err.message });
86
85
  }
87
86
  });
87
+ router.get('/projects/:id/preview', async (req, res) => {
88
+ try {
89
+ const auth = req.auth;
90
+ const projectId = req.params.id;
91
+ const project = await Project.findOne({ _id: projectId });
92
+ if (!project) {
93
+ return res.status(404).json({ message: 'Project not found' });
94
+ }
95
+ const currentMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
96
+ const membership = currentMember
97
+ ? { isMember: true, status: currentMember.status || 'active' }
98
+ : { isMember: false, status: null };
99
+ res.json({
100
+ membership,
101
+ project: {
102
+ id: project._id || project.id,
103
+ name: project.name,
104
+ description: project.description || '',
105
+ image: project.image || '',
106
+ dbType: project.dbType,
107
+ members: (project.members || [])
108
+ .filter((m) => !m.archived)
109
+ .map((m) => ({ role: m.role })),
110
+ exports: (project.exports || []).map((e) => ({ method: e.method })),
111
+ },
112
+ message: 'Project preview fetched successfully',
113
+ });
114
+ }
115
+ catch (err) {
116
+ res.status(500).json({ message: 'Failed to fetch project preview', details: err.message });
117
+ }
118
+ });
88
119
  router.get('/projects/:id', async (req, res) => {
89
120
  try {
90
121
  const auth = req.auth;
@@ -136,8 +167,10 @@ function projectRouter(config) {
136
167
  const allProjects = await Project.find({});
137
168
  const projects = allProjects
138
169
  .filter(project => {
139
- const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
140
- return isMember; // Only include projects where the user is a member
170
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
171
+ if (!currentUserMember || currentUserMember.status !== 'active')
172
+ return false;
173
+ return true;
141
174
  })
142
175
  .map(project => mapProjectToResponseFormat(project, auth.userId));
143
176
  res.status(200).json({ success: true, projects: projects, projectId: updated._id || updated.id, message: 'Project updated successfully' });
@@ -160,11 +193,13 @@ function projectRouter(config) {
160
193
  await Project.useDbType(project.dbType).delete({ _id: projectId });
161
194
  const allProjects = await Project.find({});
162
195
  const projects = allProjects
163
- .filter(p => {
164
- const isMember = p.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
165
- return isMember; // Only include projects where the user is a member
196
+ .filter(project => {
197
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
198
+ if (!currentUserMember || currentUserMember.status !== 'active')
199
+ return false;
200
+ return true;
166
201
  })
167
- .map(p => mapProjectToResponseFormat(p, auth.userId));
202
+ .map(project => mapProjectToResponseFormat(project, auth.userId));
168
203
  res.status(200).json({ success: true, projects: projects, message: 'Project deleted successfully' });
169
204
  }
170
205
  catch (err) {
@@ -197,8 +232,10 @@ function projectRouter(config) {
197
232
  const allProjects = await Project.find({});
198
233
  const projects = allProjects
199
234
  .filter(project => {
200
- const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
201
- return isMember; // Only include projects where the user is a member
235
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
236
+ if (!currentUserMember || currentUserMember.status !== 'active')
237
+ return false;
238
+ return true;
202
239
  })
203
240
  .map(project => mapProjectToResponseFormat(project, auth.userId));
204
241
  res.status(200).json({ success: true, projects: projects, message: 'Project archived successfully' });
@@ -233,8 +270,10 @@ function projectRouter(config) {
233
270
  const allProjects = await Project.find({});
234
271
  const projects = allProjects
235
272
  .filter(project => {
236
- const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
237
- return isMember; // Only include projects where the user is a member
273
+ const currentUserMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
274
+ if (!currentUserMember || currentUserMember.status !== 'active')
275
+ return false;
276
+ return true;
238
277
  })
239
278
  .map(project => mapProjectToResponseFormat(project, auth.userId));
240
279
  res.status(200).json({ success: true, projects: projects, message: 'Project unarchived successfully' });
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.storageRouter = storageRouter;
4
7
  const express_1 = require("express");
@@ -6,7 +9,10 @@ const file_1 = require("../../services/file");
6
9
  const presign_1 = require("../../services/presign");
7
10
  const storage_1 = require("../../providers/storage");
8
11
  const manager_1 = require("../../models/manager");
12
+ const connectionManager_1 = require("../../adapters/database/connectionManager");
9
13
  const auth_1 = require("../../utils/auth");
14
+ const mongodb_1 = require("mongodb");
15
+ const crypto_1 = __importDefault(require("crypto"));
10
16
  const VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
11
17
  const VALID_3D_EXTENSIONS = new Set(['.glb', '.gltf', '.obj', '.fbx', '.stl', '.ply']);
12
18
  function validateContentType(contentType, filename, category) {
@@ -22,11 +28,13 @@ function validateContentType(contentType, filename, category) {
22
28
  }
23
29
  return false;
24
30
  }
25
- function sanitizeFilename(filename) {
26
- const dotIndex = filename.lastIndexOf('.');
27
- const ext = dotIndex !== -1 ? filename.slice(dotIndex) : '';
28
- const base = (dotIndex !== -1 ? filename.slice(0, dotIndex) : filename).replace(/[^a-zA-Z0-9._-]/g, '_');
29
- return `${base}${ext}`;
31
+ function getRawFilesCollection() {
32
+ const model = (0, manager_1.getModel)('storage_files', 'nosql');
33
+ const connectionIds = model.getConnectionIds();
34
+ const activeId = connectionIds.find((id) => (0, connectionManager_1.getConnectedIds)().includes(id) && (0, connectionManager_1.getConnection)(id).type === 'nosql');
35
+ if (!activeId)
36
+ return null;
37
+ return (0, connectionManager_1.getConnection)(activeId).client.db().collection('storage_files');
30
38
  }
31
39
  function storageRouter(config) {
32
40
  const router = (0, express_1.Router)();
@@ -34,19 +42,12 @@ function storageRouter(config) {
34
42
  const Project = (0, manager_1.getModel)('projects');
35
43
  router.get('/upload-project-image-url/:id', async (req, res) => {
36
44
  try {
37
- const auth = await config.authProvider(req);
38
- if (!auth ||
39
- !auth.userId ||
40
- !auth.role) {
41
- return res.status(401).json({ message: 'Unauthorized' });
42
- }
43
- if (auth.role !== 'admin' && auth.role !== 'editor') {
45
+ const auth = req.auth;
46
+ if (auth.role !== 'admin' && auth.role !== 'editor')
44
47
  return res.status(403).json({ message: 'Permission denied' });
45
- }
46
48
  const projectId = req.params.id;
47
- if (!projectId) {
49
+ if (!projectId)
48
50
  return res.status(400).json({ message: 'Missing projectId' });
49
- }
50
51
  const response = await (0, presign_1.getPresignedProjectImageUrl)(adapter, projectId);
51
52
  res.status(200).json({ ...response, message: 'Presigned URL generated successfully' });
52
53
  }
@@ -56,19 +57,12 @@ function storageRouter(config) {
56
57
  });
57
58
  router.delete('/delete-project-image-url/:id', async (req, res) => {
58
59
  try {
59
- const auth = await config.authProvider(req);
60
- if (!auth ||
61
- !auth.userId ||
62
- !auth.role) {
63
- return res.status(401).json({ message: 'Unauthorized' });
64
- }
65
- if (auth.role !== 'admin' && auth.role !== 'editor') {
60
+ const auth = req.auth;
61
+ if (auth.role !== 'admin' && auth.role !== 'editor')
66
62
  return res.status(403).json({ message: 'Permission denied' });
67
- }
68
63
  const projectId = req.params.id;
69
- if (!projectId) {
64
+ if (!projectId)
70
65
  return res.status(400).json({ message: 'Missing projectId' });
71
- }
72
66
  await (0, file_1.deleteProjectImage)(adapter, projectId);
73
67
  const updated = await Project.update({ _id: projectId }, { image: '' });
74
68
  res.status(201).json({ ...updated, message: 'Image deleted successfully' });
@@ -79,10 +73,7 @@ function storageRouter(config) {
79
73
  });
80
74
  router.get('/projects/:projectId/storage/:category/upload-url', async (req, res) => {
81
75
  try {
82
- const auth = await config.authProvider(req);
83
- if (!auth || !auth.userId || !auth.role) {
84
- return res.status(401).json({ message: 'Unauthorized' });
85
- }
76
+ const auth = req.auth;
86
77
  const { projectId, category } = req.params;
87
78
  const { fileName, contentType } = req.query;
88
79
  if (!VALID_CATEGORIES.includes(category)) {
@@ -98,150 +89,131 @@ function storageRouter(config) {
98
89
  return res.status(400).json({ message: `Files in the ${category} category must match ${hint}` });
99
90
  }
100
91
  const project = await Project.findOne({ _id: projectId });
101
- if (!project) {
92
+ if (!project)
102
93
  return res.status(404).json({ message: 'Project not found' });
103
- }
104
- if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
94
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
105
95
  return res.status(403).json({ message: 'Unauthorized access' });
106
- }
107
96
  if (!adapter.getPresignedUploadUrl) {
108
97
  return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
109
98
  }
110
- const key = `${projectId}/${category}/${sanitizeFilename(fileName)}`;
111
- const url = await adapter.getPresignedUploadUrl(key, contentType);
112
- res.json({ url });
99
+ const ext = fileName.includes('.') ? `.${fileName.split('.').pop().toLowerCase()}` : '';
100
+ const fileId = crypto_1.default.randomUUID();
101
+ const storageKey = `${projectId}/${category}/${fileId}${ext}`;
102
+ const collection = getRawFilesCollection();
103
+ if (collection) {
104
+ await collection.insertOne({
105
+ _id: new mongodb_1.ObjectId(),
106
+ fileId,
107
+ projectId,
108
+ category,
109
+ storageKey,
110
+ displayName: fileName,
111
+ contentType,
112
+ uploadedBy: auth.userId,
113
+ createdAt: new Date(),
114
+ });
115
+ }
116
+ const url = await adapter.getPresignedUploadUrl(storageKey, contentType);
117
+ res.json({ url, fileId, storageKey, displayName: fileName });
113
118
  }
114
119
  catch (err) {
115
120
  res.status(500).json({ message: 'Failed to generate upload URL', details: err.message });
116
121
  }
117
122
  });
118
- router.get('/projects/:projectId/storage/lasts', async (req, res) => {
123
+ router.patch('/projects/:projectId/storage/files/:fileId', async (req, res) => {
119
124
  try {
120
- const auth = await config.authProvider(req);
121
- if (!auth || !auth.userId || !auth.role) {
122
- return res.status(401).json({ message: 'Unauthorized' });
125
+ const auth = req.auth;
126
+ const { projectId, fileId } = req.params;
127
+ const { displayName } = req.body;
128
+ if (!displayName || typeof displayName !== 'string') {
129
+ return res.status(400).json({ message: 'displayName is required' });
123
130
  }
124
- const { projectId } = req.params;
125
131
  const project = await Project.findOne({ _id: projectId });
126
- if (!project) {
132
+ if (!project)
127
133
  return res.status(404).json({ message: 'Project not found' });
128
- }
129
- if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
134
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
130
135
  return res.status(403).json({ message: 'Unauthorized access' });
131
- }
132
- if (!adapter.listFilesByCategory) {
133
- return res.status(501).json({ message: 'Storage adapter does not support listing files by category' });
134
- }
135
- const results = await Promise.allSettled(VALID_CATEGORIES.map(category => adapter.listFilesByCategory(projectId, category)));
136
- const allFiles = results.flatMap(result => result.status === 'fulfilled' ? result.value : []);
137
- const latest = allFiles
138
- .sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime())
139
- .slice(0, 12);
140
- res.json({ data: latest });
136
+ const collection = getRawFilesCollection();
137
+ if (!collection)
138
+ return res.status(500).json({ message: 'Database not available' });
139
+ const result = await collection.findOneAndUpdate({ fileId, projectId }, { $set: { displayName } }, { returnDocument: 'after' });
140
+ if (!result)
141
+ return res.status(404).json({ message: 'File not found' });
142
+ res.json({ message: 'File renamed successfully', file: result });
141
143
  }
142
144
  catch (err) {
143
- res.status(500).json({ message: 'Failed to fetch latest files', details: err.message });
145
+ res.status(500).json({ message: 'Failed to rename file', details: err.message });
144
146
  }
145
147
  });
146
- router.get('/projects/:projectId/storage/:category', async (req, res) => {
148
+ router.delete('/projects/:projectId/storage/files/:fileId', async (req, res) => {
147
149
  try {
148
- const auth = await config.authProvider(req);
149
- if (!auth || !auth.userId || !auth.role) {
150
- return res.status(401).json({ message: 'Unauthorized' });
151
- }
152
- const { projectId, category } = req.params;
153
- if (!VALID_CATEGORIES.includes(category)) {
154
- return res.status(400).json({ message: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}` });
155
- }
150
+ const auth = req.auth;
151
+ const { projectId, fileId } = req.params;
156
152
  const project = await Project.findOne({ _id: projectId });
157
- if (!project) {
153
+ if (!project)
158
154
  return res.status(404).json({ message: 'Project not found' });
159
- }
160
- if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
155
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
161
156
  return res.status(403).json({ message: 'Unauthorized access' });
162
- }
163
- if (!adapter.listFilesByCategory) {
164
- return res.status(501).json({ message: 'Storage adapter does not support listing files by category' });
165
- }
166
- const files = await adapter.listFilesByCategory(projectId, category);
167
- res.json({ data: files });
157
+ const collection = getRawFilesCollection();
158
+ if (!collection)
159
+ return res.status(500).json({ message: 'Database not available' });
160
+ const fileDoc = await collection.findOne({ fileId, projectId });
161
+ if (!fileDoc)
162
+ return res.status(404).json({ message: 'File not found' });
163
+ if (adapter.deleteFile)
164
+ await adapter.deleteFile(fileDoc.storageKey);
165
+ await collection.deleteOne({ fileId, projectId });
166
+ res.json({ message: 'File deleted successfully' });
168
167
  }
169
168
  catch (err) {
170
- res.status(500).json({ message: 'Failed to list files', details: err.message });
169
+ res.status(500).json({ message: 'Failed to delete file', details: err.message });
171
170
  }
172
171
  });
173
- router.patch('/projects/:projectId/storage/:category/:key', async (req, res) => {
172
+ router.get('/projects/:projectId/storage/lasts', async (req, res) => {
174
173
  try {
175
- const auth = await config.authProvider(req);
176
- if (!auth || !auth.userId || !auth.role) {
177
- return res.status(401).json({ message: 'Unauthorized' });
178
- }
179
- const { projectId, category } = req.params;
180
- const key = decodeURIComponent(req.params.key);
181
- const { contentType } = req.body;
182
- if (!VALID_CATEGORIES.includes(category)) {
183
- return res.status(400).json({ message: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}` });
184
- }
185
- if (!contentType) {
186
- return res.status(400).json({ message: 'contentType is required' });
187
- }
188
- if (!validateContentType(contentType, key, category)) {
189
- const hint = category === '3d-models'
190
- ? '.glb, .gltf, .obj, .fbx, .stl, or .ply'
191
- : `${category.slice(0, -1)}/* content type`;
192
- return res.status(400).json({ message: `Files in the ${category} category must match ${hint}` });
193
- }
174
+ const auth = req.auth;
175
+ const { projectId } = req.params;
194
176
  const project = await Project.findOne({ _id: projectId });
195
- if (!project) {
177
+ if (!project)
196
178
  return res.status(404).json({ message: 'Project not found' });
197
- }
198
- if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
179
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
199
180
  return res.status(403).json({ message: 'Unauthorized access' });
200
- }
201
- if (!adapter.getPresignedUploadUrl) {
202
- return res.status(501).json({ message: 'Storage adapter does not support presigned uploads' });
203
- }
204
- const url = await adapter.getPresignedUploadUrl(key, contentType);
205
- res.json({ url });
181
+ const collection = getRawFilesCollection();
182
+ if (!collection)
183
+ return res.status(500).json({ message: 'Database not available' });
184
+ const files = await collection.find({ projectId }).sort({ createdAt: -1 }).limit(12).toArray();
185
+ const data = await Promise.all(files.map(f => resolveFileUrl(f, adapter)));
186
+ res.json({ data });
206
187
  }
207
188
  catch (err) {
208
- res.status(500).json({ message: 'Failed to generate overwrite URL', details: err.message });
189
+ res.status(500).json({ message: 'Failed to fetch latest files', details: err.message });
209
190
  }
210
191
  });
211
- router.delete('/projects/:projectId/storage/:category/:key', async (req, res) => {
192
+ router.get('/projects/:projectId/storage/:category', async (req, res) => {
212
193
  try {
213
- const auth = await config.authProvider(req);
214
- if (!auth || !auth.userId || !auth.role) {
215
- return res.status(401).json({ message: 'Unauthorized' });
216
- }
194
+ const auth = req.auth;
217
195
  const { projectId, category } = req.params;
218
- const key = decodeURIComponent(req.params.key);
219
196
  if (!VALID_CATEGORIES.includes(category)) {
220
197
  return res.status(400).json({ message: `Invalid category. Must be one of: ${VALID_CATEGORIES.join(', ')}` });
221
198
  }
222
199
  const project = await Project.findOne({ _id: projectId });
223
- if (!project) {
200
+ if (!project)
224
201
  return res.status(404).json({ message: 'Project not found' });
225
- }
226
- if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
202
+ if (!(0, auth_1.isProjectMember)(project, auth.userId))
227
203
  return res.status(403).json({ message: 'Unauthorized access' });
228
- }
229
- if (!adapter.deleteFile) {
230
- return res.status(501).json({ message: 'Storage adapter does not support file deletion' });
231
- }
232
- await adapter.deleteFile(key);
233
- res.json({ message: 'File deleted successfully.' });
204
+ const collection = getRawFilesCollection();
205
+ if (!collection)
206
+ return res.status(500).json({ message: 'Database not available' });
207
+ const files = await collection.find({ projectId, category }).sort({ createdAt: -1 }).toArray();
208
+ const data = await Promise.all(files.map(f => resolveFileUrl(f, adapter)));
209
+ res.json({ data });
234
210
  }
235
211
  catch (err) {
236
- res.status(500).json({ message: 'Failed to delete file', details: err.message });
212
+ res.status(500).json({ message: 'Failed to list files', details: err.message });
237
213
  }
238
214
  });
239
- router.get('/storages', async (req, res) => {
215
+ router.get('/storages', async (_req, res) => {
240
216
  try {
241
- const auth = await config.authProvider(req);
242
- if (!auth || !auth.userId || !auth.role) {
243
- return res.status(401).json({ message: 'Unauthorized' });
244
- }
245
217
  const STORAGE_DISPLAY = {
246
218
  s3: { value: 'aws-s3', name: 'AWS S3' },
247
219
  gcs: { value: 'google-cloud-storage', name: 'Google Cloud Storage' },
@@ -260,3 +232,22 @@ function storageRouter(config) {
260
232
  });
261
233
  return router;
262
234
  }
235
+ async function resolveFileUrl(file, adapter) {
236
+ let url = null;
237
+ if (adapter.getPresignedGetUrl) {
238
+ try {
239
+ url = await adapter.getPresignedGetUrl(file.storageKey);
240
+ }
241
+ catch { /* leave null */ }
242
+ }
243
+ return {
244
+ id: file.fileId,
245
+ displayName: file.displayName,
246
+ storageKey: file.storageKey,
247
+ category: file.category,
248
+ contentType: file.contentType,
249
+ uploadedBy: file.uploadedBy,
250
+ createdAt: file.createdAt,
251
+ url,
252
+ };
253
+ }
@@ -182,6 +182,7 @@ class Model {
182
182
  projectId: created.id,
183
183
  userId: member.userId,
184
184
  role: member.role,
185
+ status: member.status,
185
186
  archived: member.archived || false,
186
187
  }, this.schema);
187
188
  }
@@ -2,9 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createNotification = createNotification;
4
4
  const manager_1 = require("../models/manager");
5
+ const connectionManager_1 = require("../adapters/database/connectionManager");
5
6
  const wsHub_1 = require("./wsHub");
7
+ const MAX_NOTIFICATIONS = 20;
8
+ function getRawNotifCollection() {
9
+ const model = (0, manager_1.getModel)('notifications', 'nosql');
10
+ const connectionIds = model.getConnectionIds();
11
+ const activeId = connectionIds.find((id) => (0, connectionManager_1.getConnectedIds)().includes(id) && (0, connectionManager_1.getConnection)(id).type === 'nosql');
12
+ if (!activeId)
13
+ return null;
14
+ return (0, connectionManager_1.getConnection)(activeId).client.db().collection('notifications');
15
+ }
6
16
  async function createNotification(userId, type, message, data, appId, callback) {
7
- const Notification = (0, manager_1.getModel)('notifications');
17
+ const Notification = (0, manager_1.getModel)('notifications', 'nosql');
8
18
  const notification = await Notification.create({
9
19
  userId,
10
20
  appId: appId ?? null,
@@ -16,6 +26,15 @@ async function createNotification(userId, type, message, data, appId, callback)
16
26
  readAt: null,
17
27
  createdAt: new Date(),
18
28
  });
29
+ const collection = getRawNotifCollection();
30
+ if (collection) {
31
+ const count = await collection.countDocuments({ userId });
32
+ if (count > MAX_NOTIFICATIONS) {
33
+ const oldest = await collection.find({ userId }).sort({ createdAt: 1 }).limit(1).toArray();
34
+ if (oldest.length > 0)
35
+ await collection.deleteOne({ _id: oldest[0]._id });
36
+ }
37
+ }
19
38
  (0, wsHub_1.emitToUser)(userId, { type: 'notification', data: notification });
20
39
  return notification;
21
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.26.4",
3
+ "version": "0.26.6",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",