streamby-core 0.26.2 → 0.26.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/dist/index.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export { createStreamByRouter } from './middleware/createRouter';
2
+ export { emitToUser } from './services/wsHub';
3
+ export { createNotification } from './services/notification';
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createStreamByRouter = void 0;
3
+ exports.createNotification = exports.emitToUser = exports.createStreamByRouter = void 0;
4
4
  var createRouter_1 = require("./middleware/createRouter");
5
5
  Object.defineProperty(exports, "createStreamByRouter", { enumerable: true, get: function () { return createRouter_1.createStreamByRouter; } });
6
+ var wsHub_1 = require("./services/wsHub");
7
+ Object.defineProperty(exports, "emitToUser", { enumerable: true, get: function () { return wsHub_1.emitToUser; } });
8
+ var notification_1 = require("./services/notification");
9
+ Object.defineProperty(exports, "createNotification", { enumerable: true, get: function () { return notification_1.createNotification; } });
@@ -17,8 +17,10 @@ const credential_1 = require("./routes/credential");
17
17
  const connection_1 = require("./routes/connection");
18
18
  const member_1 = require("./routes/member");
19
19
  const user_1 = require("./routes/user");
20
+ const notification_1 = require("./routes/notification");
20
21
  const auth_2 = require("../services/auth");
21
22
  const encryption_1 = require("../utils/encryption");
23
+ const wsHub_1 = require("../services/wsHub");
22
24
  function createStreamByRouter(config) {
23
25
  const router = express_1.default.Router();
24
26
  if (config.encrypt)
@@ -31,11 +33,18 @@ function createStreamByRouter(config) {
31
33
  const streambySchema = sqlDbs.length > 0 ? 'streamby' : undefined;
32
34
  (0, manager_1.registerModel)('projects', allDbIds, 'projects', streambySchema);
33
35
  (0, manager_1.registerModel)('exports', allDbIds, 'exports', streambySchema);
36
+ const nosqlDbIds = config.databases.filter(db => db.type === 'nosql').map(db => db.id);
37
+ if (nosqlDbIds.length > 0) {
38
+ (0, manager_1.registerModel)('notifications', nosqlDbIds, 'notifications');
39
+ }
34
40
  const mainDb = config.databases.find(db => db.main);
35
41
  if (mainDb) {
36
42
  (0, manager_1.registerModel)('users', [mainDb.id], 'users', 'accounts');
37
43
  }
38
44
  }
45
+ if (config.websocket?.server) {
46
+ (0, wsHub_1.initWsHub)(config.websocket.server, config, config.websocket.path);
47
+ }
39
48
  router.use((0, auth_2.authenticate)(config));
40
49
  router.use((0, auth_1.authRouter)(config));
41
50
  router.use((0, database_1.databaseRouter)(config));
@@ -46,5 +55,6 @@ function createStreamByRouter(config) {
46
55
  router.use((0, connection_1.apiConnectionRouter)(config));
47
56
  router.use((0, member_1.memberRouter)(config));
48
57
  router.use((0, user_1.userRouter)(config));
58
+ router.use((0, notification_1.notificationRouter)(config));
49
59
  return router;
50
60
  }
@@ -6,6 +6,7 @@ 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 notification_1 = require("../../services/notification");
9
10
  const VALID_ROLES = ['viewer', 'editor', 'admin'];
10
11
  function memberRouter(config) {
11
12
  const router = (0, express_1.Router)();
@@ -77,7 +78,7 @@ function memberRouter(config) {
77
78
  if (!user) {
78
79
  return res.status(404).json({ message: 'User not found' });
79
80
  }
80
- const newMember = { userId, username: user.username, role, archived: false };
81
+ const newMember = { userId, username: user.username, role, archived: false, status: 'pending', invitedBy: auth.userId };
81
82
  if (project.dbType === 'sql') {
82
83
  const connection = getSqlConnection();
83
84
  if (!connection) {
@@ -88,11 +89,14 @@ function memberRouter(config) {
88
89
  userId,
89
90
  role,
90
91
  archived: false,
92
+ status: 'pending',
93
+ invitedBy: auth.userId,
91
94
  }, 'streamby');
92
95
  }
93
96
  else {
94
97
  await Project.update({ _id: projectId }, { $push: { members: newMember } });
95
98
  }
99
+ await (0, notification_1.createNotification)(userId, 'member_invited', `Te invitaron al proyecto "${project.name}"`, { projectId, role, invitedBy: auth.userId });
96
100
  res.status(201).json({ member: newMember, message: 'Member invited successfully' });
97
101
  }
98
102
  catch (err) {
@@ -139,6 +143,59 @@ function memberRouter(config) {
139
143
  res.status(500).json({ message: 'Failed to update member role', details: err.message });
140
144
  }
141
145
  });
146
+ router.patch('/projects/:id/members/:userId/accept', async (req, res) => {
147
+ try {
148
+ const auth = req.auth;
149
+ const { id: projectId, userId: targetUserId } = req.params;
150
+ if (auth.userId !== targetUserId) {
151
+ return res.status(403).json({ message: 'You can only accept your own invitations' });
152
+ }
153
+ const project = await Project.findOne({ _id: projectId });
154
+ if (!project) {
155
+ return res.status(404).json({ message: 'Project not found' });
156
+ }
157
+ const member = project.members?.find((m) => m.userId?.toString() === targetUserId);
158
+ if (!member) {
159
+ return res.status(404).json({ message: 'Invitation not found' });
160
+ }
161
+ if (member.status !== 'pending') {
162
+ return res.status(400).json({ message: 'Invitation is no longer pending' });
163
+ }
164
+ const updatedMembers = project.members.map((m) => m.userId?.toString() === targetUserId ? { ...m, status: 'active' } : m);
165
+ await Project.update({ _id: projectId }, { members: updatedMembers });
166
+ await (0, notification_1.createNotification)(member.invitedBy, 'member_accepted', `${member.username} aceptó la invitación al proyecto "${project.name}"`, { projectId, userId: targetUserId });
167
+ res.status(200).json({ message: 'Invitation accepted' });
168
+ }
169
+ catch (err) {
170
+ res.status(500).json({ message: 'Failed to accept invitation', details: err.message });
171
+ }
172
+ });
173
+ router.patch('/projects/:id/members/:userId/reject', async (req, res) => {
174
+ try {
175
+ const auth = req.auth;
176
+ const { id: projectId, userId: targetUserId } = req.params;
177
+ if (auth.userId !== targetUserId) {
178
+ return res.status(403).json({ message: 'You can only reject your own invitations' });
179
+ }
180
+ const project = await Project.findOne({ _id: projectId });
181
+ if (!project) {
182
+ return res.status(404).json({ message: 'Project not found' });
183
+ }
184
+ const member = project.members?.find((m) => m.userId?.toString() === targetUserId);
185
+ if (!member) {
186
+ return res.status(404).json({ message: 'Invitation not found' });
187
+ }
188
+ if (member.status !== 'pending') {
189
+ return res.status(400).json({ message: 'Invitation is no longer pending' });
190
+ }
191
+ await Project.update({ _id: projectId }, { $pull: { members: { userId: targetUserId } } });
192
+ await (0, notification_1.createNotification)(member.invitedBy, 'member_rejected', `${member.username} rechazó la invitación al proyecto "${project.name}"`, { projectId, userId: targetUserId });
193
+ res.status(200).json({ message: 'Invitation rejected' });
194
+ }
195
+ catch (err) {
196
+ res.status(500).json({ message: 'Failed to reject invitation', details: err.message });
197
+ }
198
+ });
142
199
  router.delete('/projects/:id/members/:userId', async (req, res) => {
143
200
  try {
144
201
  const auth = req.auth;
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import { StreamByConfig } from '../../types';
3
+ export declare function notificationRouter(config: StreamByConfig): Router;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.notificationRouter = notificationRouter;
4
+ const express_1 = require("express");
5
+ const manager_1 = require("../../models/manager");
6
+ function notificationRouter(config) {
7
+ const router = (0, express_1.Router)();
8
+ const Notification = (0, manager_1.getModel)('notifications');
9
+ router.get('/notifications', async (req, res) => {
10
+ try {
11
+ const auth = req.auth;
12
+ const notifications = await Notification.find({ userId: auth.userId, read: false });
13
+ res.status(200).json({ data: notifications });
14
+ }
15
+ catch (err) {
16
+ res.status(500).json({ message: 'Failed to fetch notifications', details: err.message });
17
+ }
18
+ });
19
+ router.patch('/notifications/read-all', async (req, res) => {
20
+ try {
21
+ 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() } });
25
+ }
26
+ res.status(200).json({ message: 'All notifications marked as read' });
27
+ }
28
+ catch (err) {
29
+ res.status(500).json({ message: 'Failed to update notifications', details: err.message });
30
+ }
31
+ });
32
+ router.patch('/notifications/:id/read', async (req, res) => {
33
+ try {
34
+ const auth = req.auth;
35
+ const { id } = req.params;
36
+ await Notification.update({ _id: id, userId: auth.userId }, { read: true, readAt: new Date() });
37
+ res.status(200).json({ message: 'Notification marked as read' });
38
+ }
39
+ catch (err) {
40
+ res.status(500).json({ message: 'Failed to update notification', details: err.message });
41
+ }
42
+ });
43
+ return router;
44
+ }
@@ -0,0 +1 @@
1
+ export declare function createNotification(userId: string, type: string, message: string, data?: any, appId?: string, callback?: string): Promise<any>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNotification = createNotification;
4
+ const manager_1 = require("../models/manager");
5
+ const wsHub_1 = require("./wsHub");
6
+ async function createNotification(userId, type, message, data, appId, callback) {
7
+ const Notification = (0, manager_1.getModel)('notifications');
8
+ const notification = await Notification.create({
9
+ userId,
10
+ appId: appId ?? null,
11
+ type,
12
+ message,
13
+ data: data ?? null,
14
+ callback: callback ?? null,
15
+ read: false,
16
+ readAt: null,
17
+ createdAt: new Date(),
18
+ });
19
+ (0, wsHub_1.emitToUser)(userId, { type: 'notification', data: notification });
20
+ return notification;
21
+ }
@@ -0,0 +1,4 @@
1
+ import { Server } from 'http';
2
+ import { StreamByConfig } from '../types';
3
+ export declare function initWsHub(server: Server, config: StreamByConfig, path?: string): void;
4
+ export declare function emitToUser(userId: string, event: object): void;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initWsHub = initWsHub;
4
+ exports.emitToUser = emitToUser;
5
+ const ws_1 = require("ws");
6
+ const connections = new Map();
7
+ function initWsHub(server, config, path = '/streamby/ws') {
8
+ const wss = new ws_1.WebSocketServer({ server, path });
9
+ wss.on('connection', async (ws, req) => {
10
+ let userId = null;
11
+ try {
12
+ const auth = await config.authProvider(req);
13
+ if (!auth?.userId) {
14
+ ws.close(1008, 'Unauthorized');
15
+ return;
16
+ }
17
+ userId = auth.userId;
18
+ }
19
+ catch {
20
+ ws.close(1008, 'Unauthorized');
21
+ return;
22
+ }
23
+ if (!connections.has(userId))
24
+ connections.set(userId, new Set());
25
+ connections.get(userId).add(ws);
26
+ ws.on('close', () => {
27
+ connections.get(userId)?.delete(ws);
28
+ if (connections.get(userId)?.size === 0)
29
+ connections.delete(userId);
30
+ });
31
+ ws.send(JSON.stringify({ type: 'connected', userId }));
32
+ });
33
+ }
34
+ function emitToUser(userId, event) {
35
+ const userConnections = connections.get(userId);
36
+ if (!userConnections)
37
+ return;
38
+ const payload = JSON.stringify(event);
39
+ for (const ws of userConnections) {
40
+ if (ws.readyState === ws_1.WebSocket.OPEN)
41
+ ws.send(payload);
42
+ }
43
+ }
@@ -99,6 +99,22 @@ export interface StreamByConfig {
99
99
  databases?: DatabaseCredential[];
100
100
  adapter?: StorageAdapter;
101
101
  encrypt?: string;
102
+ websocket?: {
103
+ server: any;
104
+ path?: string;
105
+ };
106
+ }
107
+ export interface Notification {
108
+ _id?: string;
109
+ userId: string;
110
+ appId?: string;
111
+ type: string;
112
+ message: string;
113
+ data?: any;
114
+ callback?: string;
115
+ read: boolean;
116
+ readAt?: Date;
117
+ createdAt: Date;
102
118
  }
103
119
  export interface Auth {
104
120
  userId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,9 +37,10 @@
37
37
  "express": "^4.0.0",
38
38
  "mongodb": "^6.17.0",
39
39
  "mongoose": "^8.13.2",
40
+ "node-fetch": "^3.0.0",
40
41
  "pg": "^8.16.3",
41
42
  "prisma": "^6.12.0",
42
- "node-fetch": "^3.0.0"
43
+ "ws": "^8.20.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@types/bcrypt": "^5.0.2",
@@ -49,6 +50,7 @@
49
50
  "@types/node": "^22.15.21",
50
51
  "@types/pg": "^8.15.4",
51
52
  "@types/supertest": "^6.0.3",
53
+ "@types/ws": "^8.18.1",
52
54
  "cors": "^2.8.5",
53
55
  "supertest": "^7.1.4",
54
56
  "ts-node-dev": "^2.0.0",