@intranefr/superbackend 1.6.7 → 1.7.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/.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 +9 -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,780 @@
|
|
|
1
|
+
const Repository = require("../models/Repository");
|
|
2
|
+
const Project = require("../models/Project");
|
|
3
|
+
const User = require("../models/User");
|
|
4
|
+
const GitHubService = require("../services/github");
|
|
5
|
+
const { checkSubscription, checkUsageLimits } = require("../middleware/auth");
|
|
6
|
+
const logger = require("../../utils/logger");
|
|
7
|
+
|
|
8
|
+
// Create a new repository
|
|
9
|
+
const createRepository = async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const {
|
|
12
|
+
projectId,
|
|
13
|
+
githubRepoId,
|
|
14
|
+
githubRepoName,
|
|
15
|
+
githubRepoUrl,
|
|
16
|
+
githubRepoOwner,
|
|
17
|
+
githubRepoFullName,
|
|
18
|
+
name,
|
|
19
|
+
description,
|
|
20
|
+
settings,
|
|
21
|
+
} = req.body;
|
|
22
|
+
|
|
23
|
+
// Validation
|
|
24
|
+
if (
|
|
25
|
+
!projectId ||
|
|
26
|
+
!githubRepoId ||
|
|
27
|
+
!githubRepoName ||
|
|
28
|
+
!githubRepoUrl ||
|
|
29
|
+
!githubRepoOwner ||
|
|
30
|
+
!githubRepoFullName
|
|
31
|
+
) {
|
|
32
|
+
return res.status(400).json({
|
|
33
|
+
success: false,
|
|
34
|
+
message: "Project ID and GitHub repository information are required.",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if project exists and user owns it
|
|
39
|
+
const project = await Project.findById(projectId);
|
|
40
|
+
if (!project) {
|
|
41
|
+
return res.status(404).json({
|
|
42
|
+
success: false,
|
|
43
|
+
message: "Project not found.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
48
|
+
return res.status(403).json({
|
|
49
|
+
success: false,
|
|
50
|
+
message: "Access denied. You do not own this project.",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if user can create more repositories
|
|
55
|
+
const user = await User.findById(req.user._id);
|
|
56
|
+
const currentRepositories = await Repository.countDocuments({
|
|
57
|
+
projectId,
|
|
58
|
+
status: "active",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (currentRepositories >= user.subscription.repositoriesLimit) {
|
|
62
|
+
return res.status(403).json({
|
|
63
|
+
success: false,
|
|
64
|
+
message: `You have reached your repository limit of ${user.subscription.repositoriesLimit}. Upgrade your plan to connect more repositories.`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check if repository already exists for this project
|
|
69
|
+
const existingRepo = await Repository.existsForProject(
|
|
70
|
+
projectId,
|
|
71
|
+
githubRepoId,
|
|
72
|
+
);
|
|
73
|
+
if (existingRepo) {
|
|
74
|
+
return res.status(400).json({
|
|
75
|
+
success: false,
|
|
76
|
+
message: "This repository is already connected to this project.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Validate GitHub repository access
|
|
81
|
+
const userWithGithub = await User.findById(req.user._id);
|
|
82
|
+
if (!userWithGithub.githubAccessToken) {
|
|
83
|
+
return res.status(400).json({
|
|
84
|
+
success: false,
|
|
85
|
+
message:
|
|
86
|
+
"GitHub access token not found. Please connect your GitHub account first.",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const validation = await GitHubService.validateRepositoryAccess(
|
|
91
|
+
userWithGithub.githubAccessToken,
|
|
92
|
+
githubRepoOwner,
|
|
93
|
+
githubRepoName,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!validation.valid) {
|
|
97
|
+
return res.status(400).json({
|
|
98
|
+
success: false,
|
|
99
|
+
message: `Failed to access repository: ${validation.error}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create repository
|
|
104
|
+
const repository = new Repository({
|
|
105
|
+
projectId,
|
|
106
|
+
githubRepoId,
|
|
107
|
+
githubRepoName,
|
|
108
|
+
githubRepoUrl,
|
|
109
|
+
githubRepoOwner,
|
|
110
|
+
githubRepoFullName,
|
|
111
|
+
name: name || githubRepoName,
|
|
112
|
+
description: description || "",
|
|
113
|
+
githubAccessToken: userWithGithub.githubAccessToken,
|
|
114
|
+
githubRefreshToken: userWithGithub.githubRefreshToken,
|
|
115
|
+
defaultBranch: settings?.defaultBranch || "main",
|
|
116
|
+
includeCommitsWithoutPR: settings?.includeCommitsWithoutPR !== false,
|
|
117
|
+
includeMergeCommits: settings?.includeMergeCommits || false,
|
|
118
|
+
includeDraftPRs: settings?.includeDraftPRs || false,
|
|
119
|
+
includeClosedPRs: settings?.includeClosedPRs !== false,
|
|
120
|
+
features: {
|
|
121
|
+
issues: settings?.features?.issues !== false,
|
|
122
|
+
pullRequests: settings?.features?.pullRequests !== false,
|
|
123
|
+
releases: settings?.features?.releases !== false,
|
|
124
|
+
discussions: settings?.features?.discussions || false,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await repository.save();
|
|
129
|
+
|
|
130
|
+
// Update project's repository count
|
|
131
|
+
await project.updateRepositoryCount();
|
|
132
|
+
|
|
133
|
+
// Update user's usage count
|
|
134
|
+
await User.findByIdAndUpdate(req.user._id, {
|
|
135
|
+
$inc: { "usage.repositoriesCount": 1 },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Populate project info
|
|
139
|
+
await repository.populate("projectId", "name slug");
|
|
140
|
+
|
|
141
|
+
logger.info(
|
|
142
|
+
`Repository created: ${repository.name} for project ${project.name} by user ${req.user.email}`,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
res.status(201).json({
|
|
146
|
+
success: true,
|
|
147
|
+
message: "Repository created successfully.",
|
|
148
|
+
data: {
|
|
149
|
+
repository: repository.toJSON(),
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.error("Create repository error:", error);
|
|
154
|
+
res.status(500).json({
|
|
155
|
+
success: false,
|
|
156
|
+
message: "Internal server error.",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Get all repositories for a project
|
|
162
|
+
const getRepositories = async (req, res) => {
|
|
163
|
+
try {
|
|
164
|
+
const { projectId } = req.params;
|
|
165
|
+
const { limit = 20, skip = 0, status = "active" } = req.query;
|
|
166
|
+
|
|
167
|
+
// Check if project exists and user owns it
|
|
168
|
+
const project = await Project.findById(projectId);
|
|
169
|
+
if (!project) {
|
|
170
|
+
return res.status(404).json({
|
|
171
|
+
success: false,
|
|
172
|
+
message: "Project not found.",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
177
|
+
return res.status(403).json({
|
|
178
|
+
success: false,
|
|
179
|
+
message: "Access denied. You do not own this project.",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const repositories = await Repository.findByProject(projectId, {
|
|
184
|
+
status,
|
|
185
|
+
limit: parseInt(limit),
|
|
186
|
+
skip: parseInt(skip),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
res.json({
|
|
190
|
+
success: true,
|
|
191
|
+
data: {
|
|
192
|
+
repositories: repositories.map((r) => r.toJSON()),
|
|
193
|
+
pagination: {
|
|
194
|
+
limit: parseInt(limit),
|
|
195
|
+
skip: parseInt(skip),
|
|
196
|
+
total: repositories.length,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.error("Get repositories error:", error);
|
|
202
|
+
res.status(500).json({
|
|
203
|
+
success: false,
|
|
204
|
+
message: "Internal server error.",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Get a specific repository by ID
|
|
210
|
+
const getRepository = async (req, res) => {
|
|
211
|
+
try {
|
|
212
|
+
const { id } = req.params;
|
|
213
|
+
|
|
214
|
+
const repository = await Repository.findById(id).populate(
|
|
215
|
+
"projectId",
|
|
216
|
+
"name slug",
|
|
217
|
+
);
|
|
218
|
+
if (!repository) {
|
|
219
|
+
return res.status(404).json({
|
|
220
|
+
success: false,
|
|
221
|
+
message: "Repository not found.",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check ownership through project
|
|
226
|
+
const project = await Project.findById(repository.projectId);
|
|
227
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
228
|
+
return res.status(403).json({
|
|
229
|
+
success: false,
|
|
230
|
+
message: "Access denied. You do not own this repository.",
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const stats = await repository.getStatistics();
|
|
235
|
+
|
|
236
|
+
res.json({
|
|
237
|
+
success: true,
|
|
238
|
+
data: {
|
|
239
|
+
repository: {
|
|
240
|
+
...repository.toJSON(),
|
|
241
|
+
statistics: stats,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger.error("Get repository error:", error);
|
|
247
|
+
res.status(500).json({
|
|
248
|
+
success: false,
|
|
249
|
+
message: "Internal server error.",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Update a repository
|
|
255
|
+
const updateRepository = async (req, res) => {
|
|
256
|
+
try {
|
|
257
|
+
const { id } = req.params;
|
|
258
|
+
const { name, description, settings, tags, isPrivate } = req.body;
|
|
259
|
+
|
|
260
|
+
const repository = await Repository.findById(id);
|
|
261
|
+
if (!repository) {
|
|
262
|
+
return res.status(404).json({
|
|
263
|
+
success: false,
|
|
264
|
+
message: "Repository not found.",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check ownership through project
|
|
269
|
+
const project = await Project.findById(repository.projectId);
|
|
270
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
271
|
+
return res.status(403).json({
|
|
272
|
+
success: false,
|
|
273
|
+
message: "Access denied. You do not own this repository.",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Update fields
|
|
278
|
+
if (name) repository.name = name.trim();
|
|
279
|
+
if (description !== undefined) repository.description = description.trim();
|
|
280
|
+
if (tags !== undefined)
|
|
281
|
+
repository.tags = tags.map((tag) => tag.toLowerCase().trim());
|
|
282
|
+
if (isPrivate !== undefined) repository.isPrivate = isPrivate;
|
|
283
|
+
|
|
284
|
+
if (settings) {
|
|
285
|
+
if (settings.defaultBranch)
|
|
286
|
+
repository.defaultBranch = settings.defaultBranch;
|
|
287
|
+
if (settings.includeCommitsWithoutPR !== undefined)
|
|
288
|
+
repository.includeCommitsWithoutPR = settings.includeCommitsWithoutPR;
|
|
289
|
+
if (settings.includeMergeCommits !== undefined)
|
|
290
|
+
repository.includeMergeCommits = settings.includeMergeCommits;
|
|
291
|
+
if (settings.includeDraftPRs !== undefined)
|
|
292
|
+
repository.includeDraftPRs = settings.includeDraftPRs;
|
|
293
|
+
if (settings.includeClosedPRs !== undefined)
|
|
294
|
+
repository.includeClosedPRs = settings.includeClosedPRs;
|
|
295
|
+
|
|
296
|
+
if (settings.features) {
|
|
297
|
+
repository.features = { ...repository.features, ...settings.features };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await repository.save();
|
|
302
|
+
|
|
303
|
+
logger.info(
|
|
304
|
+
`Repository updated: ${repository.name} by user ${req.user.email}`,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
res.json({
|
|
308
|
+
success: true,
|
|
309
|
+
message: "Repository updated successfully.",
|
|
310
|
+
data: {
|
|
311
|
+
repository: repository.toJSON(),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
} catch (error) {
|
|
315
|
+
logger.error("Update repository error:", error);
|
|
316
|
+
res.status(500).json({
|
|
317
|
+
success: false,
|
|
318
|
+
message: "Internal server error.",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// Delete a repository (soft delete)
|
|
324
|
+
const deleteRepository = async (req, res) => {
|
|
325
|
+
try {
|
|
326
|
+
const { id } = req.params;
|
|
327
|
+
|
|
328
|
+
const repository = await Repository.findById(id);
|
|
329
|
+
if (!repository) {
|
|
330
|
+
return res.status(404).json({
|
|
331
|
+
success: false,
|
|
332
|
+
message: "Repository not found.",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Check ownership through project
|
|
337
|
+
const project = await Project.findById(repository.projectId);
|
|
338
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
339
|
+
return res.status(403).json({
|
|
340
|
+
success: false,
|
|
341
|
+
message: "Access denied. You do not own this repository.",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Soft delete - set status to 'deleted'
|
|
346
|
+
repository.status = "deleted";
|
|
347
|
+
await repository.save();
|
|
348
|
+
|
|
349
|
+
// Update project's repository count
|
|
350
|
+
await project.updateRepositoryCount();
|
|
351
|
+
|
|
352
|
+
// Update user's usage count
|
|
353
|
+
|
|
354
|
+
await User.findByIdAndUpdate(req.user._id, {
|
|
355
|
+
$inc: { "usage.repositoriesCount": -1 },
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
logger.info(
|
|
359
|
+
`Repository deleted: ${repository.name} by user ${req.user.email}`,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
res.json({
|
|
363
|
+
success: true,
|
|
364
|
+
message: "Repository deleted successfully.",
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
logger.error("Delete repository error:", error);
|
|
368
|
+
res.status(500).json({
|
|
369
|
+
success: false,
|
|
370
|
+
message: "Internal server error.",
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Sync repository data from GitHub
|
|
376
|
+
const syncRepository = async (req, res) => {
|
|
377
|
+
try {
|
|
378
|
+
const { id } = req.params;
|
|
379
|
+
|
|
380
|
+
const repository = await Repository.findById(id);
|
|
381
|
+
if (!repository) {
|
|
382
|
+
return res.status(404).json({
|
|
383
|
+
success: false,
|
|
384
|
+
message: "Repository not found.",
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check ownership through project
|
|
389
|
+
const project = await Project.findById(repository.projectId);
|
|
390
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
391
|
+
return res.status(403).json({
|
|
392
|
+
success: false,
|
|
393
|
+
message: "Access denied. You do not own this repository.",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Update sync status to in_progress
|
|
398
|
+
await repository.updateSyncStatus("partial");
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
// Get repository information from GitHub
|
|
402
|
+
const githubRepo = await GitHubService.getRepository(
|
|
403
|
+
repository.githubAccessToken,
|
|
404
|
+
repository.githubRepoOwner,
|
|
405
|
+
repository.githubRepoName,
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// Update repository metadata
|
|
409
|
+
await repository.updateMetadata({
|
|
410
|
+
totalCommits: githubRepo.size, // Approximate
|
|
411
|
+
totalPRs: 0, // Would need to fetch PRs
|
|
412
|
+
totalIssues: githubRepo.open_issues_count,
|
|
413
|
+
lastCommitSha: null, // Would need to fetch latest commit
|
|
414
|
+
lastCommitDate: new Date(),
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Update sync status to success
|
|
418
|
+
await repository.updateSyncStatus("success");
|
|
419
|
+
|
|
420
|
+
logger.info(
|
|
421
|
+
`Repository synced: ${repository.name} by user ${req.user.email}`,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
res.json({
|
|
425
|
+
success: true,
|
|
426
|
+
message: "Repository synced successfully.",
|
|
427
|
+
data: {
|
|
428
|
+
repository: repository.toJSON(),
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
} catch (syncError) {
|
|
432
|
+
// Update sync status to failed
|
|
433
|
+
await repository.updateSyncStatus("failed", syncError.message);
|
|
434
|
+
|
|
435
|
+
logger.error(`Repository sync failed: ${repository.name}`, syncError);
|
|
436
|
+
|
|
437
|
+
res.status(500).json({
|
|
438
|
+
success: false,
|
|
439
|
+
message: "Failed to sync repository data from GitHub.",
|
|
440
|
+
error: syncError.message,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
} catch (error) {
|
|
444
|
+
logger.error("Sync repository error:", error);
|
|
445
|
+
res.status(500).json({
|
|
446
|
+
success: false,
|
|
447
|
+
message: "Internal server error.",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Get available months for changelog generation
|
|
453
|
+
const getAvailableMonths = async (req, res) => {
|
|
454
|
+
try {
|
|
455
|
+
const { id } = req.params;
|
|
456
|
+
|
|
457
|
+
const repository = await Repository.findById(id);
|
|
458
|
+
if (!repository) {
|
|
459
|
+
return res.status(404).json({
|
|
460
|
+
success: false,
|
|
461
|
+
message: "Repository not found.",
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Check ownership through project
|
|
466
|
+
const project = await Project.findById(repository.projectId);
|
|
467
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
468
|
+
return res.status(403).json({
|
|
469
|
+
success: false,
|
|
470
|
+
message: "Access denied. You do not own this repository.",
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const availableMonths = await repository.getAvailableMonths();
|
|
475
|
+
|
|
476
|
+
res.json({
|
|
477
|
+
success: true,
|
|
478
|
+
data: {
|
|
479
|
+
availableMonths,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
} catch (error) {
|
|
483
|
+
logger.error("Get available months error:", error);
|
|
484
|
+
res.status(500).json({
|
|
485
|
+
success: false,
|
|
486
|
+
message: "Internal server error.",
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// Validate repository access
|
|
492
|
+
const validateRepository = async (req, res) => {
|
|
493
|
+
try {
|
|
494
|
+
const { id } = req.params;
|
|
495
|
+
|
|
496
|
+
const repository = await Repository.findById(id);
|
|
497
|
+
if (!repository) {
|
|
498
|
+
return res.status(404).json({
|
|
499
|
+
success: false,
|
|
500
|
+
message: "Repository not found.",
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Check ownership through project
|
|
505
|
+
const project = await Project.findById(repository.projectId);
|
|
506
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
507
|
+
return res.status(403).json({
|
|
508
|
+
success: false,
|
|
509
|
+
message: "Access denied. You do not own this repository.",
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const validation = await GitHubService.validateRepositoryAccess(
|
|
514
|
+
repository.githubAccessToken,
|
|
515
|
+
repository.githubRepoOwner,
|
|
516
|
+
repository.githubRepoName,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
res.json({
|
|
520
|
+
success: true,
|
|
521
|
+
data: {
|
|
522
|
+
valid: validation.valid,
|
|
523
|
+
error: validation.error,
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
} catch (error) {
|
|
527
|
+
logger.error("Validate repository error:", error);
|
|
528
|
+
res.status(500).json({
|
|
529
|
+
success: false,
|
|
530
|
+
message: "Internal server error.",
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// Get repository statistics
|
|
536
|
+
const getRepositoryStatistics = async (req, res) => {
|
|
537
|
+
try {
|
|
538
|
+
const { id } = req.params;
|
|
539
|
+
|
|
540
|
+
const repository = await Repository.findById(id);
|
|
541
|
+
if (!repository) {
|
|
542
|
+
return res.status(404).json({
|
|
543
|
+
success: false,
|
|
544
|
+
message: "Repository not found.",
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Check ownership through project
|
|
549
|
+
const project = await Project.findById(repository.projectId);
|
|
550
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
551
|
+
return res.status(403).json({
|
|
552
|
+
success: false,
|
|
553
|
+
message: "Access denied. You do not own this repository.",
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const stats = await repository.getStatistics();
|
|
558
|
+
|
|
559
|
+
res.json({
|
|
560
|
+
success: true,
|
|
561
|
+
data: {
|
|
562
|
+
statistics: stats,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logger.error("Get repository statistics error:", error);
|
|
567
|
+
res.status(500).json({
|
|
568
|
+
success: false,
|
|
569
|
+
message: "Internal server error.",
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Get user's GitHub repositories
|
|
575
|
+
const getUserGithubRepositories = async (req, res) => {
|
|
576
|
+
try {
|
|
577
|
+
const { page = 1, per_page = 100, type = "owner" } = req.query;
|
|
578
|
+
|
|
579
|
+
const user = await User.findById(req.user._id);
|
|
580
|
+
if (!user || !user.githubAccessToken) {
|
|
581
|
+
return res.status(400).json({
|
|
582
|
+
success: false,
|
|
583
|
+
message:
|
|
584
|
+
"GitHub access token not found. Please connect your GitHub account first.",
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Instantiate GitHub Service with user's access token
|
|
589
|
+
const githubService = new GitHubService(user.githubAccessToken);
|
|
590
|
+
const reposResponse = await githubService.getUserRepos({
|
|
591
|
+
page: parseInt(page),
|
|
592
|
+
per_page: parseInt(per_page),
|
|
593
|
+
sort: "updated",
|
|
594
|
+
visibility: "all",
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
if (!reposResponse.success) {
|
|
598
|
+
return res.status(400).json({
|
|
599
|
+
success: false,
|
|
600
|
+
message:
|
|
601
|
+
reposResponse.error?.message ||
|
|
602
|
+
"Failed to fetch repositories from GitHub",
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Filter out repositories already connected to any of user's projects
|
|
607
|
+
const userProjects = await Project.find({ ownerId: req.user._id });
|
|
608
|
+
const connectedRepoIds = new Set();
|
|
609
|
+
|
|
610
|
+
for (const project of userProjects) {
|
|
611
|
+
const projectRepos = await Repository.find({
|
|
612
|
+
projectId: project._id,
|
|
613
|
+
status: { $ne: "deleted" },
|
|
614
|
+
});
|
|
615
|
+
projectRepos.forEach((repo) => connectedRepoIds.add(repo.githubRepoId));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const filteredRepos = reposResponse.data.filter(
|
|
619
|
+
(repo) => !connectedRepoIds.has(repo.id.toString()),
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
res.json({
|
|
623
|
+
success: true,
|
|
624
|
+
data: {
|
|
625
|
+
repositories: filteredRepos,
|
|
626
|
+
connectedCount: connectedRepoIds.size,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
} catch (error) {
|
|
630
|
+
logger.error("Get user GitHub repositories error:", error);
|
|
631
|
+
res.status(500).json({
|
|
632
|
+
success: false,
|
|
633
|
+
message: "Internal server error.",
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// Connect a GitHub repository to a project
|
|
639
|
+
const connectGithubRepository = async (req, res) => {
|
|
640
|
+
try {
|
|
641
|
+
const { projectId, repoFullName } = req.body;
|
|
642
|
+
|
|
643
|
+
// Validation
|
|
644
|
+
if (!projectId || !repoFullName) {
|
|
645
|
+
return res.status(400).json({
|
|
646
|
+
success: false,
|
|
647
|
+
message:
|
|
648
|
+
"Project ID and repository full name (owner/repo) are required.",
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Check if project exists and user owns it
|
|
653
|
+
const project = await Project.findById(projectId);
|
|
654
|
+
if (!project) {
|
|
655
|
+
return res.status(404).json({
|
|
656
|
+
success: false,
|
|
657
|
+
message: "Project not found.",
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
662
|
+
return res.status(403).json({
|
|
663
|
+
success: false,
|
|
664
|
+
message: "Access denied. You do not own this project.",
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Get user's GitHub access token
|
|
669
|
+
|
|
670
|
+
const user = await User.findById(req.user._id);
|
|
671
|
+
if (!user || !user.githubAccessToken) {
|
|
672
|
+
return res.status(400).json({
|
|
673
|
+
success: false,
|
|
674
|
+
message:
|
|
675
|
+
"GitHub access token not found. Please connect your GitHub account first.",
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Fetch repository details from GitHub
|
|
680
|
+
const githubService = new GitHubService(user.githubAccessToken);
|
|
681
|
+
const repoResponse = await githubService.getRepo(repoFullName);
|
|
682
|
+
|
|
683
|
+
if (!repoResponse.success) {
|
|
684
|
+
return res.status(400).json({
|
|
685
|
+
success: false,
|
|
686
|
+
message:
|
|
687
|
+
repoResponse.error?.message ||
|
|
688
|
+
"Failed to fetch repository from GitHub",
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const ghRepo = repoResponse.data;
|
|
693
|
+
|
|
694
|
+
// Check if repository already exists for this project
|
|
695
|
+
const existingRepo = await Repository.existsForProject(
|
|
696
|
+
projectId,
|
|
697
|
+
ghRepo.id.toString(),
|
|
698
|
+
);
|
|
699
|
+
if (existingRepo) {
|
|
700
|
+
return res.status(400).json({
|
|
701
|
+
success: false,
|
|
702
|
+
message: "This repository is already connected to this project.",
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Check repository limit
|
|
707
|
+
const currentRepositories = await Repository.countDocuments({
|
|
708
|
+
projectId,
|
|
709
|
+
status: "active",
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// Free plan allows 5 repositories per project
|
|
713
|
+
const repoLimit = 5;
|
|
714
|
+
if (currentRepositories >= repoLimit) {
|
|
715
|
+
return res.status(403).json({
|
|
716
|
+
success: false,
|
|
717
|
+
message: `You have reached the repository limit of ${repoLimit} for this project. Upgrade your plan to connect more repositories.`,
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Create the repository
|
|
722
|
+
const repository = new Repository({
|
|
723
|
+
projectId,
|
|
724
|
+
githubRepoId: ghRepo.id.toString(),
|
|
725
|
+
githubRepoName: ghRepo.name,
|
|
726
|
+
githubRepoUrl: ghRepo.html_url,
|
|
727
|
+
githubRepoOwner: ghRepo.owner.login,
|
|
728
|
+
githubRepoFullName: ghRepo.full_name,
|
|
729
|
+
name: ghRepo.name,
|
|
730
|
+
description: ghRepo.description || "",
|
|
731
|
+
defaultBranch: ghRepo.default_branch || "main",
|
|
732
|
+
githubAccessToken: user.githubAccessToken,
|
|
733
|
+
isPrivate: ghRepo.private,
|
|
734
|
+
metadata: {
|
|
735
|
+
totalCommits: 0,
|
|
736
|
+
totalPRs: 0,
|
|
737
|
+
totalIssues: ghRepo.open_issues_count || 0,
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
await repository.save();
|
|
742
|
+
|
|
743
|
+
// Update project's repository count
|
|
744
|
+
await Project.findByIdAndUpdate(projectId, {
|
|
745
|
+
$inc: { "metadata.totalRepositories": 1 },
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
logger.info(
|
|
749
|
+
`Repository connected: ${repository.name} for project ${project.name} by user ${req.user.email}`,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
res.status(201).json({
|
|
753
|
+
success: true,
|
|
754
|
+
message: "Repository connected successfully.",
|
|
755
|
+
data: {
|
|
756
|
+
repository: repository.toJSON(),
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
} catch (error) {
|
|
760
|
+
logger.error("Connect GitHub repository error:", error);
|
|
761
|
+
res.status(500).json({
|
|
762
|
+
success: false,
|
|
763
|
+
message: "Internal server error.",
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
module.exports = {
|
|
769
|
+
createRepository,
|
|
770
|
+
getRepositories,
|
|
771
|
+
getRepository,
|
|
772
|
+
updateRepository,
|
|
773
|
+
deleteRepository,
|
|
774
|
+
syncRepository,
|
|
775
|
+
getAvailableMonths,
|
|
776
|
+
validateRepository,
|
|
777
|
+
getRepositoryStatistics,
|
|
778
|
+
getUserGithubRepositories,
|
|
779
|
+
connectGithubRepository,
|
|
780
|
+
};
|