@intranefr/superbackend 1.7.7 → 1.7.9
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/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
- package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
- package/.beads/config.yaml +4 -0
- package/.beads/issues.jsonl +8 -0
- package/.beads/metadata.json +4 -0
- package/.env.example +8 -0
- package/autochangelog/.env.example +36 -0
- package/autochangelog/README.md +412 -0
- package/autochangelog/config/database.js +27 -0
- package/autochangelog/package.json +47 -0
- package/autochangelog/public/landing.html +581 -0
- package/autochangelog/server.js +104 -0
- package/autochangelog/src/app.js +181 -0
- package/autochangelog/src/config/database.js +26 -0
- package/autochangelog/src/controllers/auth.js +488 -0
- package/autochangelog/src/controllers/changelog.js +682 -0
- package/autochangelog/src/controllers/project.js +580 -0
- package/autochangelog/src/controllers/repository.js +780 -0
- package/autochangelog/src/middleware/auth.js +386 -0
- package/autochangelog/src/models/Changelog.js +443 -0
- package/autochangelog/src/models/Project.js +226 -0
- package/autochangelog/src/models/Repository.js +366 -0
- package/autochangelog/src/models/User.js +223 -0
- package/autochangelog/src/routes/auth.routes.js +32 -0
- package/autochangelog/src/routes/changelog.routes.js +42 -0
- package/autochangelog/src/routes/github-auth.routes.js +102 -0
- package/autochangelog/src/routes/project.routes.js +50 -0
- package/autochangelog/src/routes/repository.routes.js +54 -0
- package/autochangelog/src/services/changelog.js +722 -0
- package/autochangelog/src/services/github.js +243 -0
- package/autochangelog/utils/logger.js +77 -0
- package/autochangelog/views/404.ejs +18 -0
- package/autochangelog/views/dashboard.ejs +596 -0
- package/autochangelog/views/index.ejs +231 -0
- package/autochangelog/views/layouts/main.ejs +44 -0
- package/autochangelog/views/login.ejs +104 -0
- package/autochangelog/views/partials/footer.ejs +20 -0
- package/autochangelog/views/partials/navbar.ejs +51 -0
- package/autochangelog/views/register.ejs +109 -0
- package/autochangelog-cli/README.md +266 -0
- package/autochangelog-cli/bin/autochangelog +120 -0
- package/autochangelog-cli/package.json +46 -0
- package/autochangelog-cli/src/cli/commands/auth.js +291 -0
- package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
- package/autochangelog-cli/src/cli/commands/project.js +427 -0
- package/autochangelog-cli/src/cli/commands/repo.js +557 -0
- package/autochangelog-cli/src/cli/commands/stats.js +706 -0
- package/autochangelog-cli/src/cli/utils/config.js +277 -0
- package/autochangelog-cli/src/cli/utils/errors.js +307 -0
- package/autochangelog-cli/src/cli/utils/logger.js +75 -0
- package/autochangelog-cli/src/cli/utils/output.js +357 -0
- package/package.json +8 -3
- package/plugins/supercli/README.md +108 -0
- package/plugins/supercli/plugin.json +123 -0
- package/server.js +1 -1
- package/src/cli/api.js +380 -0
- package/src/cli/direct/agent-utils.js +61 -0
- package/src/cli/direct/cli-utils.js +112 -0
- package/src/cli/direct/data-seeding.js +307 -0
- package/src/cli/direct/db-admin.js +84 -0
- package/src/cli/direct/db-advanced.js +372 -0
- package/src/cli/direct/db-utils.js +558 -0
- package/src/cli/direct/help.js +195 -0
- package/src/cli/direct/migration.js +107 -0
- package/src/cli/direct/rbac-advanced.js +132 -0
- package/src/cli/direct/resources-additional.js +400 -0
- package/src/cli/direct/resources-cms-advanced.js +173 -0
- package/src/cli/direct/resources-cms.js +247 -0
- package/src/cli/direct/resources-core.js +253 -0
- package/src/cli/direct/resources-execution.js +367 -0
- package/src/cli/direct/resources-health.js +152 -0
- package/src/cli/direct/resources-integrations.js +182 -0
- package/src/cli/direct/resources-logs.js +204 -0
- package/src/cli/direct/resources-org-rbac.js +187 -0
- package/src/cli/direct/resources-system.js +236 -0
- package/src/cli/direct.js +556 -0
- package/src/controllers/admin.controller.js +4 -0
- package/src/controllers/auth.controller.js +148 -1
- package/src/controllers/waitingList.controller.js +130 -1
- package/src/models/RbacRole.js +1 -1
- package/src/models/User.js +39 -5
- package/src/routes/auth.routes.js +6 -0
- package/src/routes/waitingList.routes.js +12 -2
- package/src/routes/waitingListAdmin.routes.js +3 -0
- package/src/services/email.service.js +1 -0
- package/src/services/github.service.js +255 -0
- package/src/services/rateLimiter.service.js +29 -1
- package/src/services/waitingListJson.service.js +32 -3
- package/views/admin-waiting-list.ejs +386 -3
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
const Project = require("../models/Project");
|
|
2
|
+
const Repository = require("../models/Repository");
|
|
3
|
+
const Changelog = require("../models/Changelog");
|
|
4
|
+
const User = require("../models/User");
|
|
5
|
+
const { checkSubscription, checkUsageLimits } = require("../middleware/auth");
|
|
6
|
+
const logger = require("../../utils/logger");
|
|
7
|
+
|
|
8
|
+
// Get user's overall statistics (for dashboard stats cards)
|
|
9
|
+
const getUserStatistics = async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
// Get all active projects for the user
|
|
12
|
+
const projects = await Project.findByUser(req.user._id, {
|
|
13
|
+
status: "active",
|
|
14
|
+
limit: 1000, // Get all projects
|
|
15
|
+
skip: 0,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Count total repositories and changelogs
|
|
19
|
+
const projectIds = projects.map((p) => p._id);
|
|
20
|
+
|
|
21
|
+
const [repositoriesCount, changelogsCount] = await Promise.all([
|
|
22
|
+
Repository.countDocuments({
|
|
23
|
+
projectId: { $in: projectIds },
|
|
24
|
+
status: "active",
|
|
25
|
+
}),
|
|
26
|
+
Changelog.countDocuments({
|
|
27
|
+
projectId: { $in: projectIds },
|
|
28
|
+
status: "completed",
|
|
29
|
+
createdAt: { $gte: new Date(new Date().setDate(1)) }, // Current month
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// Get user's plan (simplified - would integrate with SuperBackend billing in production)
|
|
34
|
+
const user = await User.findById(req.user._id);
|
|
35
|
+
const plan = user?.plan || "free";
|
|
36
|
+
|
|
37
|
+
res.json({
|
|
38
|
+
success: true,
|
|
39
|
+
data: {
|
|
40
|
+
projects: projects.length,
|
|
41
|
+
repositories: repositoriesCount,
|
|
42
|
+
changelogs: changelogsCount,
|
|
43
|
+
plan: plan.charAt(0).toUpperCase() + plan.slice(1),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
logger.error("Get user statistics error:", error);
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
success: false,
|
|
50
|
+
message: "Internal server error.",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Create a new project
|
|
56
|
+
const createProject = async (req, res) => {
|
|
57
|
+
try {
|
|
58
|
+
const { name, description, tags, settings } = req.body;
|
|
59
|
+
|
|
60
|
+
// Validation
|
|
61
|
+
if (!name || name.trim().length === 0) {
|
|
62
|
+
return res.status(400).json({
|
|
63
|
+
success: false,
|
|
64
|
+
message: "Project name is required.",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (name.length > 100) {
|
|
69
|
+
return res.status(400).json({
|
|
70
|
+
success: false,
|
|
71
|
+
message: "Project name cannot exceed 100 characters.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if user can create more projects
|
|
76
|
+
const canCreate = await Project.canCreateProject(req.user._id);
|
|
77
|
+
if (!canCreate) {
|
|
78
|
+
return res.status(403).json({
|
|
79
|
+
success: false,
|
|
80
|
+
message:
|
|
81
|
+
"You have reached your project limit. Upgrade your plan to create more projects.",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check subscription level for features
|
|
86
|
+
if (
|
|
87
|
+
settings &&
|
|
88
|
+
settings.githubIntegration &&
|
|
89
|
+
settings.githubIntegration.enabled
|
|
90
|
+
) {
|
|
91
|
+
const subscriptionCheck = checkSubscription("pro");
|
|
92
|
+
if (!subscriptionCheck) {
|
|
93
|
+
return res.status(403).json({
|
|
94
|
+
success: false,
|
|
95
|
+
message: "GitHub integration requires a Pro subscription.",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create project
|
|
101
|
+
const project = new Project({
|
|
102
|
+
name: name.trim(),
|
|
103
|
+
description: description ? description.trim() : "",
|
|
104
|
+
tags: tags ? tags.map((tag) => tag.toLowerCase().trim()) : [],
|
|
105
|
+
settings: {
|
|
106
|
+
defaultBranch: "main",
|
|
107
|
+
changelogTemplate: "default",
|
|
108
|
+
includeCommitsWithoutPR: true,
|
|
109
|
+
includeMergeCommits: false,
|
|
110
|
+
...settings,
|
|
111
|
+
},
|
|
112
|
+
ownerId: req.user._id,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await project.save();
|
|
116
|
+
|
|
117
|
+
// Update user's usage count (using local User model)
|
|
118
|
+
await User.findByIdAndUpdate(req.user._id, {
|
|
119
|
+
$inc: { "usage.projectsCount": 1 },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
logger.info(`Project created: ${project.name} by user ${req.user.email}`);
|
|
123
|
+
|
|
124
|
+
res.status(201).json({
|
|
125
|
+
success: true,
|
|
126
|
+
message: "Project created successfully.",
|
|
127
|
+
data: {
|
|
128
|
+
project: project.toJSON(),
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.error("Create project error:", error);
|
|
133
|
+
res.status(500).json({
|
|
134
|
+
success: false,
|
|
135
|
+
message: "Internal server error.",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Get all projects for the authenticated user
|
|
141
|
+
const getProjects = async (req, res) => {
|
|
142
|
+
try {
|
|
143
|
+
const { limit = 10, skip = 0, status = "active" } = req.query;
|
|
144
|
+
|
|
145
|
+
const projects = await Project.findByUser(req.user._id, {
|
|
146
|
+
status,
|
|
147
|
+
limit: parseInt(limit),
|
|
148
|
+
skip: parseInt(skip),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Get statistics for each project
|
|
152
|
+
const projectsWithStats = await Promise.all(
|
|
153
|
+
projects.map(async (project) => {
|
|
154
|
+
const stats = await project.getStatistics();
|
|
155
|
+
return {
|
|
156
|
+
...project.toJSON(),
|
|
157
|
+
statistics: stats,
|
|
158
|
+
};
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
res.json({
|
|
163
|
+
success: true,
|
|
164
|
+
data: {
|
|
165
|
+
projects: projectsWithStats,
|
|
166
|
+
pagination: {
|
|
167
|
+
limit: parseInt(limit),
|
|
168
|
+
skip: parseInt(skip),
|
|
169
|
+
total: projects.length,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.error("Get projects error:", error);
|
|
175
|
+
res.status(500).json({
|
|
176
|
+
success: false,
|
|
177
|
+
message: "Internal server error.",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Get a specific project by ID
|
|
183
|
+
const getProject = async (req, res) => {
|
|
184
|
+
try {
|
|
185
|
+
const { id } = req.params;
|
|
186
|
+
|
|
187
|
+
const project = await Project.findById(id).populate(
|
|
188
|
+
"ownerId",
|
|
189
|
+
"name email avatar",
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (!project) {
|
|
193
|
+
return res.status(404).json({
|
|
194
|
+
success: false,
|
|
195
|
+
message: "Project not found.",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check ownership
|
|
200
|
+
if (project.ownerId._id.toString() !== req.user._id.toString()) {
|
|
201
|
+
return res.status(403).json({
|
|
202
|
+
success: false,
|
|
203
|
+
message: "Access denied. You do not own this project.",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const stats = await project.getStatistics();
|
|
208
|
+
|
|
209
|
+
res.json({
|
|
210
|
+
success: true,
|
|
211
|
+
data: {
|
|
212
|
+
project: {
|
|
213
|
+
...project.toJSON(),
|
|
214
|
+
statistics: stats,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.error("Get project error:", error);
|
|
220
|
+
res.status(500).json({
|
|
221
|
+
success: false,
|
|
222
|
+
message: "Internal server error.",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Update a project
|
|
228
|
+
const updateProject = async (req, res) => {
|
|
229
|
+
try {
|
|
230
|
+
const { id } = req.params;
|
|
231
|
+
const { name, description, tags, settings, isPublic } = req.body;
|
|
232
|
+
|
|
233
|
+
const project = await Project.findById(id);
|
|
234
|
+
if (!project) {
|
|
235
|
+
return res.status(404).json({
|
|
236
|
+
success: false,
|
|
237
|
+
message: "Project not found.",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check ownership
|
|
242
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
243
|
+
return res.status(403).json({
|
|
244
|
+
success: false,
|
|
245
|
+
message: "Access denied. You do not own this project.",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Update fields
|
|
250
|
+
if (name) {
|
|
251
|
+
if (name.trim().length === 0) {
|
|
252
|
+
return res.status(400).json({
|
|
253
|
+
success: false,
|
|
254
|
+
message: "Project name cannot be empty.",
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (name.length > 100) {
|
|
258
|
+
return res.status(400).json({
|
|
259
|
+
success: false,
|
|
260
|
+
message: "Project name cannot exceed 100 characters.",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
project.name = name.trim();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (description !== undefined) {
|
|
267
|
+
project.description = description ? description.trim() : "";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (tags !== undefined) {
|
|
271
|
+
project.tags = tags.map((tag) => tag.toLowerCase().trim());
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (settings) {
|
|
275
|
+
project.settings = { ...project.settings, ...settings };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (isPublic !== undefined) {
|
|
279
|
+
project.isPublic = isPublic;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await project.save();
|
|
283
|
+
|
|
284
|
+
logger.info(`Project updated: ${project.name} by user ${req.user.email}`);
|
|
285
|
+
|
|
286
|
+
res.json({
|
|
287
|
+
success: true,
|
|
288
|
+
message: "Project updated successfully.",
|
|
289
|
+
data: {
|
|
290
|
+
project: project.toJSON(),
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
} catch (error) {
|
|
294
|
+
logger.error("Update project error:", error);
|
|
295
|
+
res.status(500).json({
|
|
296
|
+
success: false,
|
|
297
|
+
message: "Internal server error.",
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Delete a project (soft delete)
|
|
303
|
+
const deleteProject = async (req, res) => {
|
|
304
|
+
try {
|
|
305
|
+
const { id } = req.params;
|
|
306
|
+
|
|
307
|
+
const project = await Project.findById(id);
|
|
308
|
+
if (!project) {
|
|
309
|
+
return res.status(404).json({
|
|
310
|
+
success: false,
|
|
311
|
+
message: "Project not found.",
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Check ownership
|
|
316
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
317
|
+
return res.status(403).json({
|
|
318
|
+
success: false,
|
|
319
|
+
message: "Access denied. You do not own this project.",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Soft delete - set status to 'deleted'
|
|
324
|
+
project.status = "deleted";
|
|
325
|
+
await project.save();
|
|
326
|
+
|
|
327
|
+
// Update user's usage count
|
|
328
|
+
await User.findByIdAndUpdate(req.user._id, {
|
|
329
|
+
$inc: { "usage.projectsCount": -1 },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
logger.info(`Project deleted: ${project.name} by user ${req.user.email}`);
|
|
333
|
+
|
|
334
|
+
res.json({
|
|
335
|
+
success: true,
|
|
336
|
+
message: "Project deleted successfully.",
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
logger.error("Delete project error:", error);
|
|
340
|
+
res.status(500).json({
|
|
341
|
+
success: false,
|
|
342
|
+
message: "Internal server error.",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Archive a project
|
|
348
|
+
const archiveProject = async (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
const { id } = req.params;
|
|
351
|
+
|
|
352
|
+
const project = await Project.findById(id);
|
|
353
|
+
if (!project) {
|
|
354
|
+
return res.status(404).json({
|
|
355
|
+
success: false,
|
|
356
|
+
message: "Project not found.",
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Check ownership
|
|
361
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
362
|
+
return res.status(403).json({
|
|
363
|
+
success: false,
|
|
364
|
+
message: "Access denied. You do not own this project.",
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Archive project
|
|
369
|
+
project.status = "archived";
|
|
370
|
+
await project.save();
|
|
371
|
+
|
|
372
|
+
logger.info(`Project archived: ${project.name} by user ${req.user.email}`);
|
|
373
|
+
|
|
374
|
+
res.json({
|
|
375
|
+
success: true,
|
|
376
|
+
message: "Project archived successfully.",
|
|
377
|
+
});
|
|
378
|
+
} catch (error) {
|
|
379
|
+
logger.error("Archive project error:", error);
|
|
380
|
+
res.status(500).json({
|
|
381
|
+
success: false,
|
|
382
|
+
message: "Internal server error.",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Get project statistics
|
|
388
|
+
const getProjectStatistics = async (req, res) => {
|
|
389
|
+
try {
|
|
390
|
+
const { id } = req.params;
|
|
391
|
+
|
|
392
|
+
const project = await Project.findById(id);
|
|
393
|
+
if (!project) {
|
|
394
|
+
return res.status(404).json({
|
|
395
|
+
success: false,
|
|
396
|
+
message: "Project not found.",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Check ownership
|
|
401
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
402
|
+
return res.status(403).json({
|
|
403
|
+
success: false,
|
|
404
|
+
message: "Access denied. You do not own this project.",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const stats = await project.getStatistics();
|
|
409
|
+
|
|
410
|
+
res.json({
|
|
411
|
+
success: true,
|
|
412
|
+
data: {
|
|
413
|
+
statistics: stats,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
} catch (error) {
|
|
417
|
+
logger.error("Get project statistics error:", error);
|
|
418
|
+
res.status(500).json({
|
|
419
|
+
success: false,
|
|
420
|
+
message: "Internal server error.",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// Search projects
|
|
426
|
+
const searchProjects = async (req, res) => {
|
|
427
|
+
try {
|
|
428
|
+
const { q, tags, status = "active" } = req.query;
|
|
429
|
+
|
|
430
|
+
if (!q && !tags) {
|
|
431
|
+
return res.status(400).json({
|
|
432
|
+
success: false,
|
|
433
|
+
message: "Search query or tags are required.",
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const query = {
|
|
438
|
+
ownerId: req.user._id,
|
|
439
|
+
status,
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
if (q) {
|
|
443
|
+
query.$or = [
|
|
444
|
+
{ name: { $regex: q, $options: "i" } },
|
|
445
|
+
{ description: { $regex: q, $options: "i" } },
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (tags) {
|
|
450
|
+
const tagArray = Array.isArray(tags) ? tags : [tags];
|
|
451
|
+
query.tags = { $in: tagArray.map((tag) => tag.toLowerCase()) };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const projects = await Project.find(query)
|
|
455
|
+
.populate("ownerId", "name email avatar")
|
|
456
|
+
.sort({ updatedAt: -1 });
|
|
457
|
+
|
|
458
|
+
res.json({
|
|
459
|
+
success: true,
|
|
460
|
+
data: {
|
|
461
|
+
projects: projects.map((p) => p.toJSON()),
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
} catch (error) {
|
|
465
|
+
logger.error("Search projects error:", error);
|
|
466
|
+
res.status(500).json({
|
|
467
|
+
success: false,
|
|
468
|
+
message: "Internal server error.",
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Get project repositories
|
|
474
|
+
const getProjectRepositories = async (req, res) => {
|
|
475
|
+
try {
|
|
476
|
+
const { id } = req.params;
|
|
477
|
+
const { limit = 20, skip = 0 } = req.query;
|
|
478
|
+
|
|
479
|
+
const project = await Project.findById(id);
|
|
480
|
+
if (!project) {
|
|
481
|
+
return res.status(404).json({
|
|
482
|
+
success: false,
|
|
483
|
+
message: "Project not found.",
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Check ownership
|
|
488
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
489
|
+
return res.status(403).json({
|
|
490
|
+
success: false,
|
|
491
|
+
message: "Access denied. You do not own this project.",
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const repositories = await Repository.findByProject(id, {
|
|
496
|
+
limit: parseInt(limit),
|
|
497
|
+
skip: parseInt(skip),
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
res.json({
|
|
501
|
+
success: true,
|
|
502
|
+
data: {
|
|
503
|
+
repositories: repositories.map((r) => r.toJSON()),
|
|
504
|
+
pagination: {
|
|
505
|
+
limit: parseInt(limit),
|
|
506
|
+
skip: parseInt(skip),
|
|
507
|
+
total: repositories.length,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
} catch (error) {
|
|
512
|
+
logger.error("Get project repositories error:", error);
|
|
513
|
+
res.status(500).json({
|
|
514
|
+
success: false,
|
|
515
|
+
message: "Internal server error.",
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// Get project changelogs
|
|
521
|
+
const getProjectChangelogs = async (req, res) => {
|
|
522
|
+
try {
|
|
523
|
+
const { id } = req.params;
|
|
524
|
+
const { limit = 20, skip = 0, status = ["completed"] } = req.query;
|
|
525
|
+
|
|
526
|
+
const project = await Project.findById(id);
|
|
527
|
+
if (!project) {
|
|
528
|
+
return res.status(404).json({
|
|
529
|
+
success: false,
|
|
530
|
+
message: "Project not found.",
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Check ownership
|
|
535
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
536
|
+
return res.status(403).json({
|
|
537
|
+
success: false,
|
|
538
|
+
message: "Access denied. You do not own this project.",
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const changelogs = await Changelog.findByProject(id, {
|
|
543
|
+
status,
|
|
544
|
+
limit: parseInt(limit),
|
|
545
|
+
skip: parseInt(skip),
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
res.json({
|
|
549
|
+
success: true,
|
|
550
|
+
data: {
|
|
551
|
+
changelogs: changelogs.map((c) => c.toJSON()),
|
|
552
|
+
pagination: {
|
|
553
|
+
limit: parseInt(limit),
|
|
554
|
+
skip: parseInt(skip),
|
|
555
|
+
total: changelogs.length,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
} catch (error) {
|
|
560
|
+
logger.error("Get project changelogs error:", error);
|
|
561
|
+
res.status(500).json({
|
|
562
|
+
success: false,
|
|
563
|
+
message: "Internal server error.",
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
module.exports = {
|
|
569
|
+
createProject,
|
|
570
|
+
getProjects,
|
|
571
|
+
getProject,
|
|
572
|
+
updateProject,
|
|
573
|
+
deleteProject,
|
|
574
|
+
archiveProject,
|
|
575
|
+
getProjectStatistics,
|
|
576
|
+
searchProjects,
|
|
577
|
+
getProjectRepositories,
|
|
578
|
+
getProjectChangelogs,
|
|
579
|
+
getUserStatistics,
|
|
580
|
+
};
|