streamby-core 0.27.4 → 0.28.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/dist/index.d.ts +1 -0
- package/dist/middleware/createRouter.js +4 -0
- package/dist/middleware/routes/member.js +82 -0
- package/dist/middleware/routes/project.js +29 -1
- package/dist/middleware/routes/subscription.d.ts +4 -0
- package/dist/middleware/routes/subscription.js +34 -0
- package/dist/models/Model.js +4 -2
- package/dist/types/index.d.ts +9 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ const connection_1 = require("./routes/connection");
|
|
|
23
23
|
const dbConnection_1 = require("./routes/dbConnection");
|
|
24
24
|
const storageConnection_1 = require("./routes/storageConnection");
|
|
25
25
|
const notification_1 = require("./routes/notification");
|
|
26
|
+
const subscription_1 = require("./routes/subscription");
|
|
26
27
|
function createStreamByRouter(config) {
|
|
27
28
|
const router = express_1.default.Router();
|
|
28
29
|
if (config.encrypt)
|
|
@@ -42,6 +43,7 @@ function createStreamByRouter(config) {
|
|
|
42
43
|
const mainDb = config.databases.find(db => db.main);
|
|
43
44
|
if (mainDb) {
|
|
44
45
|
(0, manager_1.registerModel)('users', [mainDb.id], 'users', 'accounts');
|
|
46
|
+
(0, manager_1.registerModel)('user_subscriptions', [mainDb.id], 'user_subscriptions', 'streamby');
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
if (config.websocket?.server) {
|
|
@@ -50,6 +52,7 @@ function createStreamByRouter(config) {
|
|
|
50
52
|
router.use((0, user_1.userRouter)(config));
|
|
51
53
|
router.use((0, auth_2.authRouter)(config));
|
|
52
54
|
router.use((0, auth_1.authenticate)(config));
|
|
55
|
+
router.use(subscription_1.ensureSubscription);
|
|
53
56
|
router.use((0, export_1.exportRouter)(config));
|
|
54
57
|
router.use((0, member_1.memberRouter)(config));
|
|
55
58
|
router.use((0, storage_2.storageRouter)(config));
|
|
@@ -60,5 +63,6 @@ function createStreamByRouter(config) {
|
|
|
60
63
|
router.use((0, dbConnection_1.dbConnectionRouter)(config));
|
|
61
64
|
router.use((0, storageConnection_1.storageConnectionRouter)(config));
|
|
62
65
|
router.use((0, notification_1.notificationRouter)(config));
|
|
66
|
+
router.use((0, subscription_1.subscriptionRouter)(config));
|
|
63
67
|
return router;
|
|
64
68
|
}
|
|
@@ -197,6 +197,88 @@ function memberRouter(config) {
|
|
|
197
197
|
res.status(500).json({ message: 'Failed to reject invitation', details: err.message });
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
|
+
router.post('/projects/:id/request-join', async (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const auth = req.auth;
|
|
203
|
+
const projectId = req.params.id;
|
|
204
|
+
const project = await Project.findOne({ _id: projectId });
|
|
205
|
+
if (!project) {
|
|
206
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
207
|
+
}
|
|
208
|
+
if (project.public === false) {
|
|
209
|
+
return res.status(403).json({ message: 'This project is not open for join requests' });
|
|
210
|
+
}
|
|
211
|
+
const alreadyMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
|
|
212
|
+
if (alreadyMember) {
|
|
213
|
+
return res.status(400).json({ message: 'You are already a member or have a pending request' });
|
|
214
|
+
}
|
|
215
|
+
const User = (0, manager_1.getModel)('users');
|
|
216
|
+
const user = await User.findOne({ _id: auth.userId });
|
|
217
|
+
const username = user?.username || auth.username;
|
|
218
|
+
if (project.dbType === 'sql') {
|
|
219
|
+
const connection = getSqlConnection();
|
|
220
|
+
if (!connection) {
|
|
221
|
+
return res.status(503).json({ message: 'SQL connection not available' });
|
|
222
|
+
}
|
|
223
|
+
await sql_1.sqlAdapter.create(connection, 'project_members', {
|
|
224
|
+
projectId,
|
|
225
|
+
userId: auth.userId,
|
|
226
|
+
role: 'viewer',
|
|
227
|
+
archived: false,
|
|
228
|
+
status: 'pending',
|
|
229
|
+
invitedBy: auth.userId,
|
|
230
|
+
}, 'streamby');
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
await Project.update({ _id: projectId }, {
|
|
234
|
+
$push: { members: { userId: auth.userId, username, role: 'viewer', archived: false, status: 'pending', invitedBy: auth.userId } },
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const admins = (project.members || []).filter((m) => m.role === 'admin' && m.status === 'active');
|
|
238
|
+
await Promise.all(admins.map((admin) => (0, notification_1.createNotification)(admin.userId, 'join_request', `${username} has requested to join "${project.name}"`, { projectId, userId: auth.userId })));
|
|
239
|
+
res.status(201).json({ message: 'Join request sent successfully' });
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
res.status(500).json({ message: 'Failed to send join request', details: err.message });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
router.patch('/projects/:id/members/:userId/approve', async (req, res) => {
|
|
246
|
+
try {
|
|
247
|
+
const auth = req.auth;
|
|
248
|
+
const { id: projectId, userId: targetUserId } = req.params;
|
|
249
|
+
const project = await Project.findOne({ _id: projectId });
|
|
250
|
+
if (!project) {
|
|
251
|
+
return res.status(404).json({ message: 'Project not found' });
|
|
252
|
+
}
|
|
253
|
+
const requestingMember = project.members?.find((m) => m.userId?.toString() === auth.userId?.toString());
|
|
254
|
+
if (!requestingMember || requestingMember.status !== 'active' || requestingMember.role !== 'admin') {
|
|
255
|
+
return res.status(403).json({ message: 'Only project admins can approve requests' });
|
|
256
|
+
}
|
|
257
|
+
const targetMember = project.members?.find((m) => m.userId?.toString() === targetUserId?.toString());
|
|
258
|
+
if (!targetMember) {
|
|
259
|
+
return res.status(404).json({ message: 'Member not found in project' });
|
|
260
|
+
}
|
|
261
|
+
if (targetMember.status !== 'pending') {
|
|
262
|
+
return res.status(400).json({ message: 'No pending request for this user' });
|
|
263
|
+
}
|
|
264
|
+
if (project.dbType === 'sql') {
|
|
265
|
+
const connection = getSqlConnection();
|
|
266
|
+
if (!connection) {
|
|
267
|
+
return res.status(503).json({ message: 'SQL connection not available' });
|
|
268
|
+
}
|
|
269
|
+
await sql_1.sqlAdapter.update(connection, 'project_members', { projectId, userId: targetUserId }, { status: 'active' }, 'streamby');
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
const updatedMembers = project.members.map((m) => m.userId?.toString() === targetUserId ? { ...m, status: 'active' } : m);
|
|
273
|
+
await Project.update({ _id: projectId }, { members: updatedMembers });
|
|
274
|
+
}
|
|
275
|
+
await (0, notification_1.createNotification)(targetUserId, 'join_approved', `Your request to join "${project.name}" has been approved`, { projectId });
|
|
276
|
+
res.status(200).json({ message: 'Join request approved' });
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
res.status(500).json({ message: 'Failed to approve join request', details: err.message });
|
|
280
|
+
}
|
|
281
|
+
});
|
|
200
282
|
router.delete('/projects/:id/members/:userId', async (req, res) => {
|
|
201
283
|
try {
|
|
202
284
|
const auth = req.auth;
|
|
@@ -44,13 +44,40 @@ function projectRouter(config) {
|
|
|
44
44
|
res.status(500).json({ message: 'Failed to list projects', details: err });
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
|
+
router.get('/projects/explore', async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const auth = req.auth;
|
|
50
|
+
const allProjects = await Project.find({});
|
|
51
|
+
const result = allProjects
|
|
52
|
+
.filter((p) => p.public !== false)
|
|
53
|
+
.map((p) => ({
|
|
54
|
+
id: p._id || p.id,
|
|
55
|
+
name: p.name,
|
|
56
|
+
description: p.description || '',
|
|
57
|
+
image: p.image || '',
|
|
58
|
+
memberCount: (p.members || []).filter((m) => m.status === 'active').length,
|
|
59
|
+
isMember: (p.members || []).some((m) => m.userId?.toString() === auth.userId && m.status === 'active'),
|
|
60
|
+
hasPendingRequest: (p.members || []).some((m) => m.userId?.toString() === auth.userId && m.status === 'pending'),
|
|
61
|
+
}));
|
|
62
|
+
res.json({ projects: result });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
res.status(500).json({ message: 'Failed to explore projects', details: err.message });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
47
68
|
router.post('/projects/create', async (req, res) => {
|
|
48
69
|
try {
|
|
49
70
|
const auth = req.auth;
|
|
71
|
+
if (req.subscription === 'freemium') {
|
|
72
|
+
return res.status(403).json({ error: 'Freemium users cannot create projects' });
|
|
73
|
+
}
|
|
50
74
|
if (auth.role !== 'admin' && auth.role !== 'editor') {
|
|
51
75
|
return res.status(403).json({ message: 'Permission denied' });
|
|
52
76
|
}
|
|
53
|
-
const { name, description, image, allowedOrigin } = req.body;
|
|
77
|
+
const { name, description, image, allowedOrigin, public: isPublic = true } = req.body;
|
|
78
|
+
if (isPublic === false && req.subscription === 'freemium') {
|
|
79
|
+
return res.status(403).json({ error: 'Private projects require a subscriber plan' });
|
|
80
|
+
}
|
|
54
81
|
const mainDb = config.databases?.find(db => db.main) ?? config.databases?.[0];
|
|
55
82
|
if (!mainDb) {
|
|
56
83
|
return res.status(500).json({ message: 'No database configured' });
|
|
@@ -67,6 +94,7 @@ function projectRouter(config) {
|
|
|
67
94
|
description: description || '',
|
|
68
95
|
image: image || '',
|
|
69
96
|
allowedOrigin: allowedOrigin || [],
|
|
97
|
+
public: isPublic,
|
|
70
98
|
members: [{ userId: auth.userId, username: user.username, role: "admin", status: "active", archived: false }],
|
|
71
99
|
});
|
|
72
100
|
const allProjects = await Project.find({});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { StreamByConfig } from '../../types';
|
|
3
|
+
export declare const ensureSubscription: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
4
|
+
export declare function subscriptionRouter(config: StreamByConfig): Router;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureSubscription = void 0;
|
|
4
|
+
exports.subscriptionRouter = subscriptionRouter;
|
|
5
|
+
const express_1 = require("express");
|
|
6
|
+
const manager_1 = require("../../models/manager");
|
|
7
|
+
const resolveSubscription = async (auth) => {
|
|
8
|
+
const Subscription = (0, manager_1.getModel)('user_subscriptions');
|
|
9
|
+
const existing = await Subscription.findOne({ user_id: auth.userId });
|
|
10
|
+
if (existing)
|
|
11
|
+
return existing.plan;
|
|
12
|
+
const plan = auth.role === 'admin' ? 'admin' : 'freemium';
|
|
13
|
+
await Subscription.create({ user_id: auth.userId, plan, dbType: 'sql' });
|
|
14
|
+
return plan;
|
|
15
|
+
};
|
|
16
|
+
const ensureSubscription = async (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
const auth = req.auth;
|
|
19
|
+
const plan = await resolveSubscription(auth);
|
|
20
|
+
req.subscription = plan;
|
|
21
|
+
next();
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
res.status(403).json({ error: 'Unauthorized', details: err.message });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.ensureSubscription = ensureSubscription;
|
|
28
|
+
function subscriptionRouter(config) {
|
|
29
|
+
const router = (0, express_1.Router)();
|
|
30
|
+
router.get('/user/subscription', async (req, res) => {
|
|
31
|
+
res.json({ subscription: req.subscription });
|
|
32
|
+
});
|
|
33
|
+
return router;
|
|
34
|
+
}
|
package/dist/models/Model.js
CHANGED
|
@@ -180,8 +180,10 @@ class Model {
|
|
|
180
180
|
if (this.tableName === 'projects' && key === 'members') {
|
|
181
181
|
membersToInsert = data[key];
|
|
182
182
|
}
|
|
183
|
-
else if (
|
|
184
|
-
|
|
183
|
+
else if (key.toLowerCase() === 'dbtype') {
|
|
184
|
+
if (this.tableName === 'projects')
|
|
185
|
+
dataToInsert["dbType"] = data[key];
|
|
186
|
+
// else: routing hint only — never a real column
|
|
185
187
|
}
|
|
186
188
|
else {
|
|
187
189
|
dataToInsert[key] = data[key];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface ProjectInfo {
|
|
|
54
54
|
dbType?: DatabaseType;
|
|
55
55
|
name: string;
|
|
56
56
|
image?: string;
|
|
57
|
+
public?: boolean;
|
|
57
58
|
members?: {
|
|
58
59
|
role: 'viewer' | 'editor' | 'admin';
|
|
59
60
|
userId: string;
|
|
@@ -135,6 +136,14 @@ export interface Auth {
|
|
|
135
136
|
role: 'viewer' | 'editor' | 'admin';
|
|
136
137
|
}
|
|
137
138
|
export type AuthProvider = (req: Request) => Promise<Auth>;
|
|
139
|
+
export type UserPlan = 'freemium' | 'subscriber' | 'admin';
|
|
140
|
+
export interface UserSubscription {
|
|
141
|
+
id: string;
|
|
142
|
+
user_id: string;
|
|
143
|
+
plan: UserPlan;
|
|
144
|
+
created_at: Date;
|
|
145
|
+
updated_at: Date;
|
|
146
|
+
}
|
|
138
147
|
export type ExternalDbType = 'postgresql' | 'mongodb';
|
|
139
148
|
export interface DbConnection {
|
|
140
149
|
id: string;
|