streamby-core 0.21.6 β†’ 0.21.8

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/README.md CHANGED
@@ -1,206 +1,206 @@
1
- # @streamby/core
2
-
3
- Middleware framework for building storage-agnostic media APIs.
4
- Part of the [StreamBy](https://streamby.nhexa.cl) ecosystem by TerminalCore Labs.
5
-
6
- ---
7
-
8
- ## πŸš€ What is it?
9
-
10
- `@streamby/core` is a plug-and-play middleware for Express (or compatible frameworks) that enables file uploads, listings and **multi-project access** over services like:
11
-
12
- - βœ… AWS S3
13
- - βœ… Google Cloud Storage (soon)
14
- - βœ… Cloudflare R2 (soon)
15
- - βœ… Local/Personal servers (soon)
16
-
17
- It's designed to be installed inside your existing API as a library β€” **no need to run a separate backend**.
18
-
19
- ---
20
-
21
- ## πŸ“¦ Installation
22
-
23
- ```bash
24
- npm install @streamby/core
25
- ```
26
-
27
- ---
28
-
29
- ## 🧱 Basic usage
30
-
31
- ```ts
32
- import express from 'express';
33
- import { createStreamByRouter } from '@streamby/core';
34
-
35
- const app = express();
36
-
37
- app.use('/streamby', createStreamByRouter({
38
- storageProvider: {
39
- type: 's3',
40
- config: {
41
- bucket: 'your-bucket-name',
42
- region: 'your-region',
43
- accessKeyId: 'YOUR_AWS_KEY',
44
- secretAccessKey: 'YOUR_AWS_SECRET'
45
- }
46
- },
47
- authProvider: async (req) => {
48
- // extract token/cookie and validate
49
- return {
50
- userId: 'demo',
51
- projects: ['demo-project'],
52
- role: 'admin',
53
- };
54
- },
55
- projectProvider: async (projectId) => {
56
- // fetch project from your DB
57
- return {
58
- id: projectId,
59
- name: 'Demo Project',
60
- description: 'Demo for StreamBy integration',
61
- rootFolders: [],
62
- settings: { allowUpload: true }
63
- };
64
- }
65
- }));
66
-
67
- app.listen(3000);
68
- ```
69
-
70
- ---
71
-
72
- ## πŸ§ͺ Testing
73
-
74
- ```bash
75
- npm run test
76
- ```
77
-
78
- Unit tests use [Vitest](https://vitest.dev) and [Supertest](https://www.npmjs.com/package/supertest).
79
-
80
- ---
81
-
82
- ## πŸ“ Features
83
-
84
- - πŸ“‚ Multi-project access control
85
- - πŸ” Project-aware file uploads & listings
86
- - 🧹 Modular adapters per storage provider
87
- - 🧰 Built-in testability with mock auth & storage
88
- - ✨ Simple to extend for custom business logic
89
-
90
- ---
91
-
92
- ## πŸ—ΊοΈ API Endpoints
93
-
94
- Here's a summary of the key API endpoints provided by `@streamby/core`:
95
-
96
- ### `GET /streamby/projects`
97
-
98
- Lists all projects accessible by the authenticated user.
99
-
100
- **Query Parameters:**
101
- - `archived`: (Optional) Filter projects by their archived status.
102
- - `true`: Returns only archived projects.
103
- - `false`: Returns only unarchived projects.
104
- - (Omitted): Returns all projects (both archived and unarchived).
105
-
106
- **Example Response:**
107
- ```json
108
- {
109
- "projects": [
110
- {
111
- "id": "project1_id",
112
- "dbType": "nosql",
113
- "name": "My NoSQL Project",
114
- "image": "url_to_image",
115
- "archived": false
116
- },
117
- {
118
- "id": "project2_id",
119
- "dbType": "sql",
120
- "name": "My SQL Project",
121
- "image": "url_to_image",
122
- "archived": true
123
- }
124
- ]
125
- }
126
- ```
127
-
128
- ### `PATCH /streamby/projects/:id/archive`
129
-
130
- Archives a specific project. After archiving, it returns the complete list of projects for the user, including the updated project.
131
-
132
- **Example Response:**
133
- ```json
134
- {
135
- "success": true,
136
- "projects": [
137
- {
138
- "id": "project1_id",
139
- "dbType": "nosql",
140
- "name": "My NoSQL Project",
141
- "image": "url_to_image",
142
- "archived": true
143
- },
144
- {
145
- "id": "project2_id",
146
- "dbType": "sql",
147
- "name": "My SQL Project",
148
- "image": "url_to_image",
149
- "archived": false
150
- }
151
- ]
152
- }
153
- ```
154
-
155
- ### `PATCH /streamby/projects/:id/unarchive`
156
-
157
- Unarchives a specific project. After unarchiving, it returns the complete list of projects for the user, including the updated project.
158
-
159
- **Example Response:**
160
- ```json
161
- {
162
- "success": true,
163
- "projects": [
164
- {
165
- "id": "project1_id",
166
- "dbType": "nosql",
167
- "name": "My NoSQL Project",
168
- "image": "url_to_image",
169
- "archived": false
170
- },
171
- {
172
- "id": "project2_id",
173
- "dbType": "sql",
174
- "name": "My SQL Project",
175
- "image": "url_to_image",
176
- "archived": false
177
- }
178
- ]
179
- }
180
- ```
181
-
182
- ---
183
-
184
- ## 🚣 Roadmap
185
-
186
- - [ ] Support for Google Cloud Storage
187
- - [ ] CLI tool for uploading files
188
- - [ ] Role-based access control middleware
189
- - [ ] Plugin system for extended routes
190
- - [ ] Streaming support (HLS, audio, etc.)
191
-
192
- ---
193
-
194
- ## πŸ§‘β€πŸ’» Maintained by
195
-
196
- **TerminalCore Labs** – part of [Nhexa Entertainment](https://nhexa.cl)
197
- Developed as open-source infrastructure for creative and media-oriented apps.
198
-
199
- Visit us [here](https://terminalcore.cl)
200
-
201
- ---
202
-
203
- ## πŸ“Ÿ License
204
-
205
- [MIT](./LICENSE)
206
-
1
+ # @streamby/core
2
+
3
+ Middleware framework for building storage-agnostic media APIs.
4
+ Part of the [StreamBy](https://streamby.nhexa.cl) ecosystem by TerminalCore Labs.
5
+
6
+ ---
7
+
8
+ ## πŸš€ What is it?
9
+
10
+ `@streamby/core` is a plug-and-play middleware for Express (or compatible frameworks) that enables file uploads, listings and **multi-project access** over services like:
11
+
12
+ - βœ… AWS S3
13
+ - βœ… Google Cloud Storage (soon)
14
+ - βœ… Cloudflare R2 (soon)
15
+ - βœ… Local/Personal servers (soon)
16
+
17
+ It's designed to be installed inside your existing API as a library β€” **no need to run a separate backend**.
18
+
19
+ ---
20
+
21
+ ## πŸ“¦ Installation
22
+
23
+ ```bash
24
+ npm install @streamby/core
25
+ ```
26
+
27
+ ---
28
+
29
+ ## 🧱 Basic usage
30
+
31
+ ```ts
32
+ import express from 'express';
33
+ import { createStreamByRouter } from '@streamby/core';
34
+
35
+ const app = express();
36
+
37
+ app.use('/streamby', createStreamByRouter({
38
+ storageProvider: {
39
+ type: 's3',
40
+ config: {
41
+ bucket: 'your-bucket-name',
42
+ region: 'your-region',
43
+ accessKeyId: 'YOUR_AWS_KEY',
44
+ secretAccessKey: 'YOUR_AWS_SECRET'
45
+ }
46
+ },
47
+ authProvider: async (req) => {
48
+ // extract token/cookie and validate
49
+ return {
50
+ userId: 'demo',
51
+ projects: ['demo-project'],
52
+ role: 'admin',
53
+ };
54
+ },
55
+ projectProvider: async (projectId) => {
56
+ // fetch project from your DB
57
+ return {
58
+ id: projectId,
59
+ name: 'Demo Project',
60
+ description: 'Demo for StreamBy integration',
61
+ rootFolders: [],
62
+ settings: { allowUpload: true }
63
+ };
64
+ }
65
+ }));
66
+
67
+ app.listen(3000);
68
+ ```
69
+
70
+ ---
71
+
72
+ ## πŸ§ͺ Testing
73
+
74
+ ```bash
75
+ npm run test
76
+ ```
77
+
78
+ Unit tests use [Vitest](https://vitest.dev) and [Supertest](https://www.npmjs.com/package/supertest).
79
+
80
+ ---
81
+
82
+ ## πŸ“ Features
83
+
84
+ - πŸ“‚ Multi-project access control
85
+ - πŸ” Project-aware file uploads & listings
86
+ - 🧹 Modular adapters per storage provider
87
+ - 🧰 Built-in testability with mock auth & storage
88
+ - ✨ Simple to extend for custom business logic
89
+
90
+ ---
91
+
92
+ ## πŸ—ΊοΈ API Endpoints
93
+
94
+ Here's a summary of the key API endpoints provided by `@streamby/core`:
95
+
96
+ ### `GET /streamby/projects`
97
+
98
+ Lists all projects accessible by the authenticated user.
99
+
100
+ **Query Parameters:**
101
+ - `archived`: (Optional) Filter projects by their archived status.
102
+ - `true`: Returns only archived projects.
103
+ - `false`: Returns only unarchived projects.
104
+ - (Omitted): Returns all projects (both archived and unarchived).
105
+
106
+ **Example Response:**
107
+ ```json
108
+ {
109
+ "projects": [
110
+ {
111
+ "id": "project1_id",
112
+ "dbType": "nosql",
113
+ "name": "My NoSQL Project",
114
+ "image": "url_to_image",
115
+ "archived": false
116
+ },
117
+ {
118
+ "id": "project2_id",
119
+ "dbType": "sql",
120
+ "name": "My SQL Project",
121
+ "image": "url_to_image",
122
+ "archived": true
123
+ }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ ### `PATCH /streamby/projects/:id/archive`
129
+
130
+ Archives a specific project. After archiving, it returns the complete list of projects for the user, including the updated project.
131
+
132
+ **Example Response:**
133
+ ```json
134
+ {
135
+ "success": true,
136
+ "projects": [
137
+ {
138
+ "id": "project1_id",
139
+ "dbType": "nosql",
140
+ "name": "My NoSQL Project",
141
+ "image": "url_to_image",
142
+ "archived": true
143
+ },
144
+ {
145
+ "id": "project2_id",
146
+ "dbType": "sql",
147
+ "name": "My SQL Project",
148
+ "image": "url_to_image",
149
+ "archived": false
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ ### `PATCH /streamby/projects/:id/unarchive`
156
+
157
+ Unarchives a specific project. After unarchiving, it returns the complete list of projects for the user, including the updated project.
158
+
159
+ **Example Response:**
160
+ ```json
161
+ {
162
+ "success": true,
163
+ "projects": [
164
+ {
165
+ "id": "project1_id",
166
+ "dbType": "nosql",
167
+ "name": "My NoSQL Project",
168
+ "image": "url_to_image",
169
+ "archived": false
170
+ },
171
+ {
172
+ "id": "project2_id",
173
+ "dbType": "sql",
174
+ "name": "My SQL Project",
175
+ "image": "url_to_image",
176
+ "archived": false
177
+ }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 🚣 Roadmap
185
+
186
+ - [ ] Support for Google Cloud Storage
187
+ - [ ] CLI tool for uploading files
188
+ - [ ] Role-based access control middleware
189
+ - [ ] Plugin system for extended routes
190
+ - [ ] Streaming support (HLS, audio, etc.)
191
+
192
+ ---
193
+
194
+ ## πŸ§‘β€πŸ’» Maintained by
195
+
196
+ **TerminalCore Labs** – part of [Nhexa Entertainment](https://nhexa.cl)
197
+ Developed as open-source infrastructure for creative and media-oriented apps.
198
+
199
+ Visit us [here](https://terminalcore.cl)
200
+
201
+ ---
202
+
203
+ ## πŸ“Ÿ License
204
+
205
+ [MIT](./LICENSE)
206
+
@@ -12,10 +12,10 @@ function authRouter(config) {
12
12
  !auth.role) {
13
13
  throw new Error('Invalid or missing authentication context');
14
14
  }
15
- return res.status(200).json({ logged: true, ...auth });
15
+ return res.status(200).json({ logged: true, message: 'Authentication successful', ...auth });
16
16
  }
17
17
  catch (err) {
18
- return res.status(401).json({ logged: false });
18
+ return res.status(401).json({ logged: false, message: err.message });
19
19
  }
20
20
  });
21
21
  return router;
@@ -11,16 +11,16 @@ function databaseRouter(config) {
11
11
  if (!auth ||
12
12
  !auth.userId ||
13
13
  !auth.role) {
14
- return res.status(401).json({ error: 'Unauthorized' });
14
+ return res.status(401).json({ message: 'Unauthorized' });
15
15
  }
16
16
  const connectedDbIds = (0, connectionManager_1.getConnectedIds)();
17
17
  const databases = (config.databases || [])
18
18
  .filter(db => connectedDbIds.includes(db.id))
19
19
  .map(db => ({ name: db.id, value: db.type }));
20
- res.status(200).json({ databases });
20
+ res.status(200).json({ databases, message: 'Databases retrieved successfully' });
21
21
  }
22
22
  catch (err) {
23
- res.status(500).json({ error: 'Failed to get databases' });
23
+ res.status(500).json({ message: err.message });
24
24
  }
25
25
  });
26
26
  return router;
@@ -14,22 +14,22 @@ function exportRouter(config) {
14
14
  if (!auth ||
15
15
  !auth.userId ||
16
16
  !auth.role) {
17
- return res.status(401).json({ error: 'Unauthorized' });
17
+ return res.status(401).json({ message: 'Unauthorized' });
18
18
  }
19
19
  const projectId = req.params.id;
20
20
  const exportId = req.params.export_id;
21
21
  const project = await Project.findOne({ _id: projectId });
22
22
  if (!project) {
23
- return res.status(404).json({ error: 'Project not found' });
23
+ return res.status(404).json({ message: 'Project not found' });
24
24
  }
25
25
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
26
- return res.status(403).json({ error: 'Unauthorized access' });
26
+ return res.status(403).json({ message: 'Unauthorized access' });
27
27
  }
28
28
  const data = await Export.findOne({ _id: exportId });
29
- res.json({ data: { ...data, id: data._id || data.id, _id: undefined } });
29
+ res.json({ data: { ...data, id: data._id || data.id, _id: undefined }, message: 'Export data fetched successfully' });
30
30
  }
31
31
  catch (err) {
32
- res.status(500).json({ error: 'Failed to fetch export data', details: err.message });
32
+ res.status(500).json({ message: 'Failed to fetch export data', details: err.message });
33
33
  }
34
34
  });
35
35
  router.post('/projects/:id/exports', async (req, res) => {
@@ -38,16 +38,16 @@ function exportRouter(config) {
38
38
  if (!auth ||
39
39
  !auth.userId ||
40
40
  !auth.role) {
41
- return res.status(401).json({ error: 'Unauthorized' });
41
+ return res.status(401).json({ message: 'Unauthorized' });
42
42
  }
43
43
  const projectId = req.params.id;
44
44
  const { name, description, collectionName } = req.body;
45
45
  if (!name || !collectionName) {
46
- return res.status(400).json({ error: 'Missing export name or collectionName' });
46
+ return res.status(400).json({ message: 'Missing export name or collectionName' });
47
47
  }
48
48
  const project = await Project.findOne({ _id: projectId });
49
49
  if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
50
- return res.status(403).json({ error: 'Unauthorized project access' });
50
+ return res.status(403).json({ message: 'Unauthorized project access' });
51
51
  }
52
52
  const newExport = await Export.create({
53
53
  name,
@@ -55,10 +55,10 @@ function exportRouter(config) {
55
55
  collectionName,
56
56
  projectId
57
57
  });
58
- res.status(201).json({ data: { ...newExport, id: newExport._id || newExport.id, _id: undefined } });
58
+ res.status(201).json({ data: { ...newExport, id: newExport._id || newExport.id, _id: undefined }, message: 'Export created successfully' });
59
59
  }
60
60
  catch (err) {
61
- res.status(500).json({ error: 'Failed to create export', details: err.message });
61
+ res.status(500).json({ message: 'Failed to create export', details: err.message });
62
62
  }
63
63
  });
64
64
  return router;
@@ -16,20 +16,20 @@ function fileRouter(config) {
16
16
  if (!auth ||
17
17
  !auth.userId ||
18
18
  !auth.role) {
19
- return res.status(401).json({ error: 'Unauthorized' });
19
+ return res.status(401).json({ message: 'Unauthorized' });
20
20
  }
21
21
  if (auth.role !== 'admin' && auth.role !== 'editor') {
22
- return res.status(403).json({ error: 'Permission denied' });
22
+ return res.status(403).json({ message: 'Permission denied' });
23
23
  }
24
24
  const projectId = req.params.id;
25
25
  if (!projectId) {
26
- return res.status(400).json({ error: 'Missing projectId' });
26
+ return res.status(400).json({ message: 'Missing projectId' });
27
27
  }
28
28
  const response = await (0, presign_1.getPresignedProjectImageUrl)(adapter, projectId);
29
- res.status(200).json(response);
29
+ res.status(200).json({ ...response, message: 'Presigned URL generated successfully' });
30
30
  }
31
31
  catch (err) {
32
- res.status(500).json({ error: 'Failed to generate presigned URL', details: err.message });
32
+ res.status(500).json({ message: 'Failed to generate presigned URL', details: err.message });
33
33
  }
34
34
  });
35
35
  router.delete('/delete-project-image-url/:id', async (req, res) => {
@@ -38,21 +38,21 @@ function fileRouter(config) {
38
38
  if (!auth ||
39
39
  !auth.userId ||
40
40
  !auth.role) {
41
- return res.status(401).json({ error: 'Unauthorized' });
41
+ return res.status(401).json({ message: 'Unauthorized' });
42
42
  }
43
43
  if (auth.role !== 'admin' && auth.role !== 'editor') {
44
- return res.status(403).json({ error: 'Permission denied' });
44
+ return res.status(403).json({ message: 'Permission denied' });
45
45
  }
46
46
  const projectId = req.params.id;
47
47
  if (!projectId) {
48
- return res.status(400).json({ error: 'Missing projectId' });
48
+ return res.status(400).json({ message: 'Missing projectId' });
49
49
  }
50
50
  await (0, file_1.deleteProjectImage)(adapter, projectId);
51
51
  const updated = await Project.update({ _id: projectId }, { image: '' });
52
- res.status(201).json(updated);
52
+ res.status(201).json({ ...updated, message: 'Image deleted successfully' });
53
53
  }
54
54
  catch (err) {
55
- res.status(500).json({ error: 'Failed to delete image', details: err.message });
55
+ res.status(500).json({ message: 'Failed to delete image', details: err.message });
56
56
  }
57
57
  });
58
58
  router.get('/files', async (req, res) => {
@@ -61,17 +61,17 @@ function fileRouter(config) {
61
61
  if (!auth ||
62
62
  !auth.userId ||
63
63
  !auth.role) {
64
- return res.status(401).json({ error: 'Unauthorized' });
64
+ return res.status(401).json({ message: 'Unauthorized' });
65
65
  }
66
66
  const projectId = (req.query.projectId || req.headers['x-project-id']);
67
67
  if (!projectId) {
68
- return res.status(403).json({ error: 'Unauthorized or missing projectId' });
68
+ return res.status(403).json({ message: 'Unauthorized or missing projectId' });
69
69
  }
70
70
  const files = await (0, file_1.listFilesService)(adapter, req, projectId);
71
- res.json(files);
71
+ res.json({ files, message: 'Files listed successfully' });
72
72
  }
73
73
  catch (err) {
74
- res.status(500).json({ error: 'Failed to list files', details: err });
74
+ res.status(500).json({ message: 'Failed to list files', details: err });
75
75
  }
76
76
  });
77
77
  return router;
@@ -6,8 +6,10 @@ 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");
9
10
  function projectRouter(config) {
10
11
  const router = (0, express_1.Router)();
12
+ router.use((0, auth_2.authenticate)(config));
11
13
  const Project = (0, manager_1.getModel)('projects');
12
14
  const mapProjectToResponseFormat = (project, userId) => {
13
15
  const currentUserMember = project.members?.find((member) => member.userId === userId);
@@ -21,12 +23,7 @@ function projectRouter(config) {
21
23
  };
22
24
  router.get('/projects', async (req, res) => {
23
25
  try {
24
- const auth = await config.authProvider(req);
25
- if (!auth ||
26
- !auth.userId ||
27
- !auth.role) {
28
- return res.status(401).json({ error: 'Unauthorized' });
29
- }
26
+ const auth = req.auth;
30
27
  const archivedQuery = req.query.archived;
31
28
  const filterArchived = archivedQuery !== undefined ? String(archivedQuery).toLowerCase() === 'true' : undefined;
32
29
  const allProjects = await Project.find({}); // Fetch all projects
@@ -42,34 +39,29 @@ function projectRouter(config) {
42
39
  return true;
43
40
  })
44
41
  .map(project => mapProjectToResponseFormat(project, auth.userId));
45
- res.json({ projects });
42
+ res.json({ projects, message: 'Projects listed successfully' });
46
43
  }
47
44
  catch (err) {
48
45
  console.error('Error in /projects endpoint:', err);
49
- res.status(500).json({ error: 'Failed to list projects', details: err });
46
+ res.status(500).json({ message: 'Failed to list projects', details: err });
50
47
  }
51
48
  });
52
49
  router.post('/projects/create', async (req, res) => {
53
50
  try {
54
- const auth = await config.authProvider(req);
55
- if (!auth ||
56
- !auth.userId ||
57
- !auth.role) {
58
- return res.status(401).json({ error: 'Unauthorized' });
59
- }
51
+ const auth = req.auth;
60
52
  if (auth.role !== 'admin' && auth.role !== 'editor') {
61
- return res.status(403).json({ error: 'Permission denied' });
53
+ return res.status(403).json({ message: 'Permission denied' });
62
54
  }
63
55
  const { name, description, dbType, image } = req.body;
64
56
  const mainDb = config.databases?.find(db => db.main);
65
57
  if (!mainDb) {
66
- return res.status(500).json({ error: 'Main database not configured' });
58
+ return res.status(500).json({ message: 'Main database not configured' });
67
59
  }
68
60
  const userDbType = mainDb.type;
69
61
  const User = (0, manager_1.getModel)('users', userDbType);
70
62
  const user = await User.findOne({ _id: auth.userId });
71
63
  if (!user) {
72
- return res.status(404).json({ error: 'User not found' });
64
+ return res.status(404).json({ message: 'User not found' });
73
65
  }
74
66
  const newProject = await Project.create({
75
67
  dbType: dbType || 'nosql',
@@ -78,102 +70,103 @@ function projectRouter(config) {
78
70
  image: image || '',
79
71
  members: [{ userId: auth.userId, username: user.username, role: "admin", archived: false }]
80
72
  });
81
- res.status(201).json({ project: { ...newProject, id: newProject._id || newProject.id, _id: undefined } });
73
+ const allProjects = await Project.find({});
74
+ const projects = allProjects
75
+ .filter(project => {
76
+ const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
77
+ return isMember; // Only include projects where the user is a member
78
+ })
79
+ .map(project => mapProjectToResponseFormat(project, auth.userId));
80
+ res.status(200).json({ success: true, projects: projects, projectId: newProject._id || newProject.id, message: 'Project created successfully' });
82
81
  }
83
82
  catch (err) {
84
- res.status(500).json({ error: 'Failed to create project', details: err.message });
83
+ res.status(500).json({ message: 'Failed to create project', details: err.message });
85
84
  }
86
85
  });
87
86
  router.get('/projects/:id', async (req, res) => {
88
87
  try {
89
- const auth = await config.authProvider(req);
90
- if (!auth ||
91
- !auth.userId ||
92
- !auth.role) {
93
- return res.status(401).json({ error: 'Unauthorized' });
94
- }
88
+ const auth = req.auth;
95
89
  const projectId = req.params.id;
96
90
  const project = await Project.findOne({ _id: projectId });
97
91
  if (!project) {
98
- return res.status(404).json({ error: 'Project not found' });
92
+ return res.status(404).json({ message: 'Project not found' });
99
93
  }
100
94
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
101
- return res.status(403).json({ error: 'Unauthorized project access' });
95
+ return res.status(403).json({ message: 'Unauthorized project access' });
102
96
  }
103
- res.json({ project: { ...project, id: project._id || project.id, _id: undefined } });
97
+ res.json({ project: { ...project, id: project._id || project.id, _id: undefined }, message: 'Project fetched successfully' });
104
98
  }
105
99
  catch (err) {
106
- res.status(500).json({ error: 'Failed to fetch project', details: err.message });
100
+ res.status(500).json({ message: 'Failed to fetch project', details: err.message });
107
101
  }
108
102
  });
109
103
  router.patch('/projects/:id', async (req, res) => {
110
104
  try {
111
- const auth = await config.authProvider(req);
112
- if (!auth ||
113
- !auth.userId ||
114
- !auth.role) {
115
- return res.status(401).json({ error: 'Unauthorized' });
116
- }
105
+ const auth = req.auth;
117
106
  if (auth.role !== 'admin' && auth.role !== 'editor') {
118
- return res.status(403).json({ error: 'Permission denied' });
107
+ return res.status(403).json({ message: 'Permission denied' });
119
108
  }
120
109
  const projectId = req.params.id;
121
110
  const updates = req.body;
122
111
  const project = await Project.findOne({ _id: projectId });
123
112
  if (!project) {
124
- return res.status(404).json({ error: 'Project not found' });
113
+ return res.status(404).json({ message: 'Project not found' });
125
114
  }
126
115
  if (!updates || typeof updates !== 'object') {
127
- return res.status(400).json({ error: 'Missing updates payload' });
116
+ return res.status(400).json({ message: 'Missing updates payload' });
128
117
  }
129
118
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
130
- return res.status(403).json({ error: 'Unauthorized project access' });
119
+ return res.status(403).json({ message: 'Unauthorized project access' });
131
120
  }
132
121
  const updated = await Project.update({ _id: projectId }, updates);
133
122
  if (!updated) {
134
- return res.status(404).json({ error: 'Project not found or not updated' });
123
+ return res.status(404).json({ message: 'Project not found or not updated' });
135
124
  }
136
- res.status(200).json({ success: true, project: { ...updated, id: updated._id || updated.id, _id: undefined } });
125
+ const allProjects = await Project.find({});
126
+ const projects = allProjects
127
+ .filter(project => {
128
+ const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
129
+ return isMember; // Only include projects where the user is a member
130
+ })
131
+ .map(project => mapProjectToResponseFormat(project, auth.userId));
132
+ res.status(200).json({ success: true, projects: projects, projectId: updated._id || updated.id, message: 'Project updated successfully' });
137
133
  }
138
134
  catch (err) {
139
- res.status(500).json({ error: 'Failed to update project', details: err.message });
135
+ res.status(500).json({ message: 'Failed to update project', details: err.message });
140
136
  }
141
137
  });
142
138
  router.delete('/projects/:id', async (req, res) => {
143
139
  try {
144
- const auth = await config.authProvider(req);
145
- if (!auth ||
146
- !auth.userId ||
147
- !auth.role) {
148
- return res.status(401).json({ error: 'Unauthorized' });
149
- }
140
+ const auth = req.auth;
150
141
  const projectId = req.params.id;
151
142
  const project = await Project.findOne({ _id: projectId });
152
143
  if (!project) {
153
- return res.status(404).json({ error: 'Project not found' });
144
+ return res.status(404).json({ message: 'Project not found' });
154
145
  }
155
146
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
156
- return res.status(403).json({ error: 'Unauthorized project access' });
147
+ return res.status(403).json({ message: 'Unauthorized project access' });
157
148
  }
158
149
  await Project.delete({ _id: projectId });
159
- res.status(200).json({ success: true });
150
+ const allProjects = await Project.find({});
151
+ const projects = allProjects
152
+ .filter(project => {
153
+ const isMember = project.members?.some((m) => m.userId?.toString() === auth.userId?.toString());
154
+ return isMember; // Only include projects where the user is a member
155
+ })
156
+ .map(project => mapProjectToResponseFormat(project, auth.userId));
157
+ res.status(200).json({ success: true, projects: projects, message: 'Project deleted successfully' });
160
158
  }
161
159
  catch (err) {
162
- res.status(500).json({ error: 'Failed to delete project', details: err.message });
160
+ res.status(500).json({ message: 'Failed to delete project', details: err.message });
163
161
  }
164
162
  });
165
163
  router.patch('/projects/:id/archive', async (req, res) => {
166
164
  try {
167
- const auth = await config.authProvider(req);
168
- if (!auth ||
169
- !auth.userId ||
170
- !auth.role) {
171
- return res.status(401).json({ error: 'Unauthorized' });
172
- }
165
+ const auth = req.auth;
173
166
  const projectId = req.params.id;
174
167
  const project = await Project.findOne({ _id: projectId });
175
168
  if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
176
- return res.status(403).json({ error: 'Unauthorized project access' });
169
+ return res.status(403).json({ message: 'Unauthorized project access' });
177
170
  }
178
171
  if (project.dbType === 'sql') {
179
172
  const connection = (0, connectionManager_1.getConnection)(config.databases?.find(db => db.main)?.id || '').client;
@@ -182,7 +175,7 @@ function projectRouter(config) {
182
175
  else {
183
176
  const memberIndex = project.members.findIndex((member) => member.userId === auth.userId);
184
177
  if (memberIndex === -1) {
185
- return res.status(403).json({ error: 'Unauthorized project access' });
178
+ return res.status(403).json({ message: 'Unauthorized project access' });
186
179
  }
187
180
  project.members[memberIndex].archived = true;
188
181
  project.members[memberIndex].archivedBy = auth.userId;
@@ -197,24 +190,19 @@ function projectRouter(config) {
197
190
  return isMember; // Only include projects where the user is a member
198
191
  })
199
192
  .map(project => mapProjectToResponseFormat(project, auth.userId));
200
- res.status(200).json({ success: true, projects: projects });
193
+ res.status(200).json({ success: true, projects: projects, message: 'Project archived successfully' });
201
194
  }
202
195
  catch (err) {
203
- res.status(500).json({ error: 'Failed to archive project', details: err.message });
196
+ res.status(500).json({ message: 'Failed to archive project', details: err.message });
204
197
  }
205
198
  });
206
199
  router.patch('/projects/:id/unarchive', async (req, res) => {
207
200
  try {
208
- const auth = await config.authProvider(req);
209
- if (!auth ||
210
- !auth.userId ||
211
- !auth.role) {
212
- return res.status(401).json({ error: 'Unauthorized' });
213
- }
201
+ const auth = req.auth;
214
202
  const projectId = req.params.id;
215
203
  const project = await Project.findOne({ _id: projectId });
216
204
  if (!project || !(0, auth_1.isProjectMember)(project, auth.userId)) {
217
- return res.status(403).json({ error: 'Unauthorized project access' });
205
+ return res.status(403).json({ message: 'Unauthorized project access' });
218
206
  }
219
207
  if (project.dbType === 'sql') {
220
208
  const connection = (0, connectionManager_1.getConnection)(config.databases?.find(db => db.main)?.id || '').client;
@@ -223,7 +211,7 @@ function projectRouter(config) {
223
211
  else {
224
212
  const memberIndex = project.members.findIndex((member) => member.userId === auth.userId);
225
213
  if (memberIndex === -1) {
226
- return res.status(403).json({ error: 'Unauthorized project access' });
214
+ return res.status(403).json({ message: 'Unauthorized project access' });
227
215
  }
228
216
  project.members[memberIndex].archived = false;
229
217
  project.members[memberIndex].archivedBy = null;
@@ -238,31 +226,26 @@ function projectRouter(config) {
238
226
  return isMember; // Only include projects where the user is a member
239
227
  })
240
228
  .map(project => mapProjectToResponseFormat(project, auth.userId));
241
- res.status(200).json({ success: true, projects: projects });
229
+ res.status(200).json({ success: true, projects: projects, message: 'Project unarchived successfully' });
242
230
  }
243
231
  catch (err) {
244
- res.status(500).json({ error: 'Failed to unarchive project', details: err.message });
232
+ res.status(500).json({ message: 'Failed to unarchive project', details: err.message });
245
233
  }
246
234
  });
247
235
  router.get('/projects/:id/members', async (req, res) => {
248
236
  try {
249
- const auth = await config.authProvider(req);
250
- if (!auth ||
251
- !auth.userId ||
252
- !auth.role) {
253
- return res.status(401).json({ error: 'Unauthorized' });
254
- }
237
+ const auth = req.auth;
255
238
  const projectId = req.params.id;
256
239
  const project = await Project.findOne({ _id: projectId });
257
240
  if (!project) {
258
- return res.status(404).json({ error: 'Project not found' });
241
+ return res.status(404).json({ message: 'Project not found' });
259
242
  }
260
243
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
261
- return res.status(403).json({ error: 'Unauthorized project access' });
244
+ return res.status(403).json({ message: 'Unauthorized project access' });
262
245
  }
263
246
  const mainDb = config.databases?.find(db => db.main);
264
247
  if (!mainDb) {
265
- return res.status(500).json({ error: 'Main database not configured' });
248
+ return res.status(500).json({ message: 'Main database not configured' });
266
249
  }
267
250
  const userDbType = mainDb.type;
268
251
  const User = (0, manager_1.getModel)('users', userDbType);
@@ -275,43 +258,38 @@ function projectRouter(config) {
275
258
  profilePic: user ? user.profilePic : ''
276
259
  };
277
260
  }));
278
- res.json({ members: membersWithUsernames });
261
+ res.json({ members: membersWithUsernames, message: 'Project members fetched successfully' });
279
262
  }
280
263
  catch (err) {
281
- res.status(500).json({ error: 'Failed to fetch project members', details: err.message });
264
+ res.status(500).json({ message: 'Failed to fetch project members', details: err.message });
282
265
  }
283
266
  });
284
267
  router.post('/projects/:id/image', async (req, res) => {
285
268
  try {
286
- const auth = await config.authProvider(req);
287
- if (!auth ||
288
- !auth.userId ||
289
- !auth.role) {
290
- return res.status(401).json({ error: 'Unauthorized' });
291
- }
269
+ const auth = req.auth;
292
270
  if (auth.role !== 'admin' && auth.role !== 'editor') {
293
- return res.status(403).json({ error: 'Permission denied' });
271
+ return res.status(403).json({ message: 'Permission denied' });
294
272
  }
295
273
  const projectId = req.params.id;
296
274
  const { imageUrl } = req.body;
297
275
  if (!projectId || !imageUrl) {
298
- return res.status(400).json({ error: 'Missing projectId or imageUrl' });
276
+ return res.status(400).json({ message: 'Missing projectId or imageUrl' });
299
277
  }
300
278
  const project = await Project.findOne({ _id: projectId });
301
279
  if (!project) {
302
- return res.status(404).json({ error: 'Project not found' });
280
+ return res.status(404).json({ message: 'Project not found' });
303
281
  }
304
282
  if (!(0, auth_1.isProjectMember)(project, auth.userId)) {
305
- return res.status(403).json({ error: 'Unauthorized project access' });
283
+ return res.status(403).json({ message: 'Unauthorized project access' });
306
284
  }
307
285
  const updatedProject = await Project.update({ _id: projectId }, { image: imageUrl });
308
286
  if (!updatedProject) {
309
- return res.status(404).json({ error: 'Project not found or not updated' });
287
+ return res.status(404).json({ message: 'Project not found or not updated' });
310
288
  }
311
- res.status(200).json({ success: true, project: { ...updatedProject, id: updatedProject._id || updatedProject.id, _id: undefined } });
289
+ res.status(200).json({ success: true, project: { ...updatedProject, id: updatedProject._id || updatedProject.id, _id: undefined }, message: 'Project image updated successfully' });
312
290
  }
313
291
  catch (err) {
314
- res.status(500).json({ error: 'Failed to update project image', details: err.message });
292
+ res.status(500).json({ message: 'Failed to update project image', details: err.message });
315
293
  }
316
294
  });
317
295
  return router;
@@ -1,2 +1,5 @@
1
- import { AuthContext } from '../types';
2
- export declare function checkRole(auth: AuthContext, required: 'viewer' | 'editor' | 'admin'): void;
1
+ import { Auth } from '../types';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { StreamByConfig } from '../types';
4
+ export declare const authenticate: (config: StreamByConfig) => (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>> | undefined>;
5
+ export declare function checkRole(auth: Auth, required: 'viewer' | 'editor' | 'admin'): void;
@@ -1,6 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authenticate = void 0;
3
4
  exports.checkRole = checkRole;
5
+ const authenticate = (config) => async (req, res, next) => {
6
+ try {
7
+ const auth = await config.authProvider(req);
8
+ if (!auth || !auth.userId || !auth.role) {
9
+ return res.status(401).json({ error: 'Unauthorized' });
10
+ }
11
+ req.auth = auth;
12
+ next();
13
+ }
14
+ catch (error) {
15
+ console.error('Authentication failed:', error);
16
+ return res.status(401).json({ error: 'Unauthorized' });
17
+ }
18
+ };
19
+ exports.authenticate = authenticate;
4
20
  function checkRole(auth, required) {
5
21
  const roles = ['viewer', 'editor', 'admin'];
6
22
  const userIdx = roles.indexOf(auth.role);
@@ -61,12 +61,12 @@ export interface StreamByConfig {
61
61
  databases?: DatabaseCredential[];
62
62
  adapter?: StorageAdapter;
63
63
  }
64
- export interface AuthContext {
64
+ export interface Auth {
65
65
  userId: string;
66
66
  username: string;
67
67
  role: 'viewer' | 'editor' | 'admin';
68
68
  }
69
- export type AuthProvider = (req: Request) => Promise<AuthContext>;
69
+ export type AuthProvider = (req: Request) => Promise<Auth>;
70
70
  export interface Export {
71
71
  _id?: string;
72
72
  id: string;
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "streamby-core",
3
- "version": "0.21.6",
4
- "description": "StreamBy middleware framework for media storage management",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist"
9
- ],
10
- "scripts": {
11
- "build": "tsc",
12
- "test": "vitest",
13
- "dev": "ts-node-dev --respawn --clear dev.ts",
14
- "prepublishOnly": "npm run build"
15
- },
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/GwerhDev/StreamBy-Package.git"
19
- },
20
- "keywords": [
21
- "streamby",
22
- "middleware",
23
- "upload",
24
- "storage",
25
- "s3",
26
- "express",
27
- "typescript"
28
- ],
29
- "author": "TerminalCore Labs",
30
- "license": "MIT",
31
- "dependencies": {
32
- "@aws-sdk/client-s3": "^3.528.0",
33
- "@aws-sdk/s3-request-presigner": "^3.787.0",
34
- "@prisma/client": "^6.12.0",
35
- "bcrypt": "^5.1.1",
36
- "dotenv": "^16.5.0",
37
- "express": "^4.0.0",
38
- "mongodb": "^6.17.0",
39
- "mongoose": "^8.13.2",
40
- "pg": "^8.16.3",
41
- "prisma": "^6.12.0"
42
- },
43
- "devDependencies": {
44
- "@types/bcrypt": "^5.0.2",
45
- "@types/cors": "^2.8.18",
46
- "@types/express": "^4.17.17",
47
- "@types/mongodb": "^4.0.7",
48
- "@types/node": "^22.15.21",
49
- "@types/pg": "^8.15.4",
50
- "@types/supertest": "^6.0.3",
51
- "cors": "^2.8.5",
52
- "supertest": "^7.1.4",
53
- "ts-node-dev": "^2.0.0",
54
- "typescript": "^5.8.3",
55
- "vitest": "^3.1.1"
56
- },
57
- "peerDependencies": {
58
- "express": "^4.0.0"
59
- }
60
- }
1
+ {
2
+ "name": "streamby-core",
3
+ "version": "0.21.8",
4
+ "description": "StreamBy middleware framework for media storage management",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "vitest",
13
+ "dev": "ts-node-dev --respawn --clear dev.ts",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/GwerhDev/StreamBy-Package.git"
19
+ },
20
+ "keywords": [
21
+ "streamby",
22
+ "middleware",
23
+ "upload",
24
+ "storage",
25
+ "s3",
26
+ "express",
27
+ "typescript"
28
+ ],
29
+ "author": "TerminalCore Labs",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@aws-sdk/client-s3": "^3.528.0",
33
+ "@aws-sdk/s3-request-presigner": "^3.787.0",
34
+ "@prisma/client": "^6.12.0",
35
+ "bcrypt": "^5.1.1",
36
+ "dotenv": "^16.5.0",
37
+ "express": "^4.0.0",
38
+ "mongodb": "^6.17.0",
39
+ "mongoose": "^8.13.2",
40
+ "pg": "^8.16.3",
41
+ "prisma": "^6.12.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/bcrypt": "^5.0.2",
45
+ "@types/cors": "^2.8.18",
46
+ "@types/express": "^4.17.17",
47
+ "@types/mongodb": "^4.0.7",
48
+ "@types/node": "^22.15.21",
49
+ "@types/pg": "^8.15.4",
50
+ "@types/supertest": "^6.0.3",
51
+ "cors": "^2.8.5",
52
+ "supertest": "^7.1.4",
53
+ "ts-node-dev": "^2.0.0",
54
+ "typescript": "^5.8.3",
55
+ "vitest": "^3.1.1"
56
+ },
57
+ "peerDependencies": {
58
+ "express": "^4.0.0"
59
+ }
60
+ }