@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,682 @@
|
|
|
1
|
+
const Changelog = require("../models/Changelog");
|
|
2
|
+
const Project = require("../models/Project");
|
|
3
|
+
const Repository = require("../models/Repository");
|
|
4
|
+
const ChangelogService = require("../services/changelog");
|
|
5
|
+
const { checkSubscription, checkUsageLimits } = require("../middleware/auth");
|
|
6
|
+
const logger = require("../../utils/logger");
|
|
7
|
+
|
|
8
|
+
// Get User model from SuperBackend or fallback to local
|
|
9
|
+
const getUserModel = () => {
|
|
10
|
+
const sb = globalThis.superbackend || globalThis.saasbackend;
|
|
11
|
+
return sb?.models?.User || require("../models/User");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Generate a new changelog
|
|
15
|
+
const generateChangelog = async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const {
|
|
18
|
+
projectId,
|
|
19
|
+
repositoryId,
|
|
20
|
+
month,
|
|
21
|
+
year,
|
|
22
|
+
format,
|
|
23
|
+
template,
|
|
24
|
+
includeCommitsWithoutPR,
|
|
25
|
+
} = req.body;
|
|
26
|
+
|
|
27
|
+
// Validation
|
|
28
|
+
if (!projectId || !repositoryId || !month || !year) {
|
|
29
|
+
return res.status(400).json({
|
|
30
|
+
success: false,
|
|
31
|
+
message: "Project ID, repository ID, month, and year are required.",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if project and repository exist and user owns them
|
|
36
|
+
const [project, repository] = await Promise.all([
|
|
37
|
+
Project.findById(projectId),
|
|
38
|
+
Repository.findById(repositoryId),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
if (!project || !repository) {
|
|
42
|
+
return res.status(404).json({
|
|
43
|
+
success: false,
|
|
44
|
+
message: "Project or repository not found.",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
project.ownerId.toString() !== req.user._id.toString() ||
|
|
50
|
+
repository.ownerId.toString() !== req.user._id.toString()
|
|
51
|
+
) {
|
|
52
|
+
return res.status(403).json({
|
|
53
|
+
success: false,
|
|
54
|
+
message: "Access denied. You do not own this project or repository.",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if repository is active
|
|
59
|
+
if (repository.status !== "active") {
|
|
60
|
+
return res.status(400).json({
|
|
61
|
+
success: false,
|
|
62
|
+
message: "Repository is not active.",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if user can create more changelogs
|
|
67
|
+
const User = getUserModel();
|
|
68
|
+
const user = await User.findById(req.user._id);
|
|
69
|
+
const currentChangelogs = await Changelog.countDocuments({
|
|
70
|
+
projectId,
|
|
71
|
+
status: { $in: ["completed", "failed"] },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (currentChangelogs >= user.subscription.changelogsLimit) {
|
|
75
|
+
return res.status(403).json({
|
|
76
|
+
success: false,
|
|
77
|
+
message: `You have reached your changelog limit of ${user.subscription.changelogsLimit} per month. Upgrade your plan or wait until next month.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check if changelog already exists for this period
|
|
82
|
+
const existingChangelog = await Changelog.existsForPeriod(
|
|
83
|
+
repositoryId,
|
|
84
|
+
month,
|
|
85
|
+
year,
|
|
86
|
+
);
|
|
87
|
+
if (existingChangelog) {
|
|
88
|
+
return res.status(400).json({
|
|
89
|
+
success: false,
|
|
90
|
+
message: "Changelog already exists for this period.",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate changelog
|
|
95
|
+
const changelog = await ChangelogService.generateChangelog(
|
|
96
|
+
projectId,
|
|
97
|
+
repositoryId,
|
|
98
|
+
month,
|
|
99
|
+
year,
|
|
100
|
+
{
|
|
101
|
+
format: format || "markdown",
|
|
102
|
+
template: template || "default",
|
|
103
|
+
includeCommitsWithoutPR: includeCommitsWithoutPR !== false,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
logger.info(
|
|
108
|
+
`Changelog generated: ${changelog.title} by user ${req.user.email}`,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
res.status(201).json({
|
|
112
|
+
success: true,
|
|
113
|
+
message: "Changelog generated successfully.",
|
|
114
|
+
data: {
|
|
115
|
+
changelog: changelog.toJSON(),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.error("Generate changelog error:", error);
|
|
120
|
+
res.status(500).json({
|
|
121
|
+
success: false,
|
|
122
|
+
message: "Internal server error.",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Get all changelogs for a project
|
|
128
|
+
const getChangelogs = async (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const { projectId } = req.params;
|
|
131
|
+
const {
|
|
132
|
+
limit = 20,
|
|
133
|
+
skip = 0,
|
|
134
|
+
status = ["completed"],
|
|
135
|
+
sortBy = "createdAt",
|
|
136
|
+
sortOrder = -1,
|
|
137
|
+
} = req.query;
|
|
138
|
+
|
|
139
|
+
// Check if project exists and user owns it
|
|
140
|
+
const project = await Project.findById(projectId);
|
|
141
|
+
if (!project) {
|
|
142
|
+
return res.status(404).json({
|
|
143
|
+
success: false,
|
|
144
|
+
message: "Project not found.",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (project.ownerId.toString() !== req.user._id.toString()) {
|
|
149
|
+
return res.status(403).json({
|
|
150
|
+
success: false,
|
|
151
|
+
message: "Access denied. You do not own this project.",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const changelogs = await Changelog.findByProject(projectId, {
|
|
156
|
+
status,
|
|
157
|
+
limit: parseInt(limit),
|
|
158
|
+
skip: parseInt(skip),
|
|
159
|
+
sortBy,
|
|
160
|
+
sortOrder: parseInt(sortOrder),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
res.json({
|
|
164
|
+
success: true,
|
|
165
|
+
data: {
|
|
166
|
+
changelogs: changelogs.map((c) => c.toJSON()),
|
|
167
|
+
pagination: {
|
|
168
|
+
limit: parseInt(limit),
|
|
169
|
+
skip: parseInt(skip),
|
|
170
|
+
total: changelogs.length,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
logger.error("Get changelogs error:", error);
|
|
176
|
+
res.status(500).json({
|
|
177
|
+
success: false,
|
|
178
|
+
message: "Internal server error.",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Get all changelogs for a repository
|
|
184
|
+
const getRepositoryChangelogs = async (req, res) => {
|
|
185
|
+
try {
|
|
186
|
+
const { repositoryId } = req.params;
|
|
187
|
+
const {
|
|
188
|
+
limit = 20,
|
|
189
|
+
skip = 0,
|
|
190
|
+
status = ["completed"],
|
|
191
|
+
sortBy = "createdAt",
|
|
192
|
+
sortOrder = -1,
|
|
193
|
+
} = req.query;
|
|
194
|
+
|
|
195
|
+
// Check if repository exists and user owns it
|
|
196
|
+
const repository = await Repository.findById(repositoryId);
|
|
197
|
+
if (!repository) {
|
|
198
|
+
return res.status(404).json({
|
|
199
|
+
success: false,
|
|
200
|
+
message: "Repository not found.",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check ownership through project
|
|
205
|
+
const project = await Project.findById(repository.projectId);
|
|
206
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
207
|
+
return res.status(403).json({
|
|
208
|
+
success: false,
|
|
209
|
+
message: "Access denied. You do not own this repository.",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const changelogs = await Changelog.findByRepository(repositoryId, {
|
|
214
|
+
status,
|
|
215
|
+
limit: parseInt(limit),
|
|
216
|
+
skip: parseInt(skip),
|
|
217
|
+
sortBy,
|
|
218
|
+
sortOrder: parseInt(sortOrder),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
res.json({
|
|
222
|
+
success: true,
|
|
223
|
+
data: {
|
|
224
|
+
changelogs: changelogs.map((c) => c.toJSON()),
|
|
225
|
+
pagination: {
|
|
226
|
+
limit: parseInt(limit),
|
|
227
|
+
skip: parseInt(skip),
|
|
228
|
+
total: changelogs.length,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
logger.error("Get repository changelogs error:", error);
|
|
234
|
+
res.status(500).json({
|
|
235
|
+
success: false,
|
|
236
|
+
message: "Internal server error.",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Get a specific changelog by ID
|
|
242
|
+
const getChangelog = async (req, res) => {
|
|
243
|
+
try {
|
|
244
|
+
const { id } = req.params;
|
|
245
|
+
|
|
246
|
+
const changelog = await Changelog.findById(id)
|
|
247
|
+
.populate("projectId", "name slug")
|
|
248
|
+
.populate("repositoryId", "name githubRepoName")
|
|
249
|
+
.populate("generatedBy", "name email");
|
|
250
|
+
|
|
251
|
+
if (!changelog) {
|
|
252
|
+
return res.status(404).json({
|
|
253
|
+
success: false,
|
|
254
|
+
message: "Changelog not found.",
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check ownership through project
|
|
259
|
+
const project = await Project.findById(changelog.projectId);
|
|
260
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
261
|
+
return res.status(403).json({
|
|
262
|
+
success: false,
|
|
263
|
+
message: "Access denied. You do not own this changelog.",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Increment view count for public changelogs
|
|
268
|
+
if (changelog.isPublic) {
|
|
269
|
+
await changelog.incrementViewCount();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
res.json({
|
|
273
|
+
success: true,
|
|
274
|
+
data: {
|
|
275
|
+
changelog: changelog.toJSON(),
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
} catch (error) {
|
|
279
|
+
logger.error("Get changelog error:", error);
|
|
280
|
+
res.status(500).json({
|
|
281
|
+
success: false,
|
|
282
|
+
message: "Internal server error.",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Update a changelog
|
|
288
|
+
const updateChangelog = async (req, res) => {
|
|
289
|
+
try {
|
|
290
|
+
const { id } = req.params;
|
|
291
|
+
const { title, description, format, template, isPublic, shareToken } =
|
|
292
|
+
req.body;
|
|
293
|
+
|
|
294
|
+
const changelog = await Changelog.findById(id);
|
|
295
|
+
if (!changelog) {
|
|
296
|
+
return res.status(404).json({
|
|
297
|
+
success: false,
|
|
298
|
+
message: "Changelog not found.",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check ownership through project
|
|
303
|
+
const project = await Project.findById(changelog.projectId);
|
|
304
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
305
|
+
return res.status(403).json({
|
|
306
|
+
success: false,
|
|
307
|
+
message: "Access denied. You do not own this changelog.",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Update fields
|
|
312
|
+
if (title) changelog.title = title.trim();
|
|
313
|
+
if (description !== undefined) changelog.description = description.trim();
|
|
314
|
+
if (format) changelog.format = format;
|
|
315
|
+
if (template) changelog.template = template;
|
|
316
|
+
if (isPublic !== undefined) changelog.isPublic = isPublic;
|
|
317
|
+
if (shareToken !== undefined) changelog.shareToken = shareToken;
|
|
318
|
+
|
|
319
|
+
await changelog.save();
|
|
320
|
+
|
|
321
|
+
logger.info(
|
|
322
|
+
`Changelog updated: ${changelog.title} by user ${req.user.email}`,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
res.json({
|
|
326
|
+
success: true,
|
|
327
|
+
message: "Changelog updated successfully.",
|
|
328
|
+
data: {
|
|
329
|
+
changelog: changelog.toJSON(),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
logger.error("Update changelog error:", error);
|
|
334
|
+
res.status(500).json({
|
|
335
|
+
success: false,
|
|
336
|
+
message: "Internal server error.",
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Delete a changelog (soft delete)
|
|
342
|
+
const deleteChangelog = async (req, res) => {
|
|
343
|
+
try {
|
|
344
|
+
const { id } = req.params;
|
|
345
|
+
|
|
346
|
+
const changelog = await Changelog.findById(id);
|
|
347
|
+
if (!changelog) {
|
|
348
|
+
return res.status(404).json({
|
|
349
|
+
success: false,
|
|
350
|
+
message: "Changelog not found.",
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check ownership through project
|
|
355
|
+
const project = await Project.findById(changelog.projectId);
|
|
356
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
357
|
+
return res.status(403).json({
|
|
358
|
+
success: false,
|
|
359
|
+
message: "Access denied. You do not own this changelog.",
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Soft delete - set status to 'deleted'
|
|
364
|
+
changelog.status = "deleted";
|
|
365
|
+
await changelog.save();
|
|
366
|
+
|
|
367
|
+
// Update user's usage count
|
|
368
|
+
const User = getUserModel();
|
|
369
|
+
await User.findByIdAndUpdate(req.user._id, {
|
|
370
|
+
$inc: { "usage.changelogsCount": -1 },
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
logger.info(
|
|
374
|
+
`Changelog deleted: ${changelog.title} by user ${req.user.email}`,
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
res.json({
|
|
378
|
+
success: true,
|
|
379
|
+
message: "Changelog deleted successfully.",
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
logger.error("Delete changelog error:", error);
|
|
383
|
+
res.status(500).json({
|
|
384
|
+
success: false,
|
|
385
|
+
message: "Internal server error.",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Get changelog statistics
|
|
391
|
+
const getChangelogStatistics = async (req, res) => {
|
|
392
|
+
try {
|
|
393
|
+
const { id } = req.params;
|
|
394
|
+
|
|
395
|
+
const changelog = await Changelog.findById(id);
|
|
396
|
+
if (!changelog) {
|
|
397
|
+
return res.status(404).json({
|
|
398
|
+
success: false,
|
|
399
|
+
message: "Changelog not found.",
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check ownership through project
|
|
404
|
+
const project = await Project.findById(changelog.projectId);
|
|
405
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
406
|
+
return res.status(403).json({
|
|
407
|
+
success: false,
|
|
408
|
+
message: "Access denied. You do not own this changelog.",
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const stats = await changelog.getStatistics();
|
|
413
|
+
|
|
414
|
+
res.json({
|
|
415
|
+
success: true,
|
|
416
|
+
data: {
|
|
417
|
+
statistics: stats,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
logger.error("Get changelog statistics error:", error);
|
|
422
|
+
res.status(500).json({
|
|
423
|
+
success: false,
|
|
424
|
+
message: "Internal server error.",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// Get available months for changelog generation
|
|
430
|
+
const getAvailableMonths = async (req, res) => {
|
|
431
|
+
try {
|
|
432
|
+
const { repositoryId } = req.params;
|
|
433
|
+
|
|
434
|
+
// Check if repository exists and user owns it
|
|
435
|
+
const repository = await Repository.findById(repositoryId);
|
|
436
|
+
if (!repository) {
|
|
437
|
+
return res.status(404).json({
|
|
438
|
+
success: false,
|
|
439
|
+
message: "Repository not found.",
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Check ownership through project
|
|
444
|
+
const project = await Project.findById(repository.projectId);
|
|
445
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
446
|
+
return res.status(403).json({
|
|
447
|
+
success: false,
|
|
448
|
+
message: "Access denied. You do not own this repository.",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const availableMonths =
|
|
453
|
+
await ChangelogService.getAvailableMonths(repositoryId);
|
|
454
|
+
|
|
455
|
+
res.json({
|
|
456
|
+
success: true,
|
|
457
|
+
data: {
|
|
458
|
+
availableMonths,
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
} catch (error) {
|
|
462
|
+
logger.error("Get available months error:", error);
|
|
463
|
+
res.status(500).json({
|
|
464
|
+
success: false,
|
|
465
|
+
message: "Internal server error.",
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// Share a changelog (generate share token)
|
|
471
|
+
const shareChangelog = async (req, res) => {
|
|
472
|
+
try {
|
|
473
|
+
const { id } = req.params;
|
|
474
|
+
|
|
475
|
+
const changelog = await Changelog.findById(id);
|
|
476
|
+
if (!changelog) {
|
|
477
|
+
return res.status(404).json({
|
|
478
|
+
success: false,
|
|
479
|
+
message: "Changelog not found.",
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Check ownership through project
|
|
484
|
+
const project = await Project.findById(changelog.projectId);
|
|
485
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
486
|
+
return res.status(403).json({
|
|
487
|
+
success: false,
|
|
488
|
+
message: "Access denied. You do not own this changelog.",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Make changelog public and generate share token
|
|
493
|
+
changelog.isPublic = true;
|
|
494
|
+
if (!changelog.shareToken) {
|
|
495
|
+
const crypto = require("crypto");
|
|
496
|
+
changelog.shareToken = crypto.randomBytes(16).toString("hex");
|
|
497
|
+
}
|
|
498
|
+
await changelog.save();
|
|
499
|
+
|
|
500
|
+
logger.info(
|
|
501
|
+
`Changelog shared: ${changelog.title} by user ${req.user.email}`,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
res.json({
|
|
505
|
+
success: true,
|
|
506
|
+
message: "Changelog shared successfully.",
|
|
507
|
+
data: {
|
|
508
|
+
shareUrl: `${process.env.FRONTEND_URL}/share/${changelog.shareToken}`,
|
|
509
|
+
shareToken: changelog.shareToken,
|
|
510
|
+
},
|
|
511
|
+
});
|
|
512
|
+
} catch (error) {
|
|
513
|
+
logger.error("Share changelog error:", error);
|
|
514
|
+
res.status(500).json({
|
|
515
|
+
success: false,
|
|
516
|
+
message: "Internal server error.",
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// Unshare a changelog
|
|
522
|
+
const unshareChangelog = async (req, res) => {
|
|
523
|
+
try {
|
|
524
|
+
const { id } = req.params;
|
|
525
|
+
|
|
526
|
+
const changelog = await Changelog.findById(id);
|
|
527
|
+
if (!changelog) {
|
|
528
|
+
return res.status(404).json({
|
|
529
|
+
success: false,
|
|
530
|
+
message: "Changelog not found.",
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Check ownership through project
|
|
535
|
+
const project = await Project.findById(changelog.projectId);
|
|
536
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
537
|
+
return res.status(403).json({
|
|
538
|
+
success: false,
|
|
539
|
+
message: "Access denied. You do not own this changelog.",
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Make changelog private and remove share token
|
|
544
|
+
changelog.isPublic = false;
|
|
545
|
+
changelog.shareToken = null;
|
|
546
|
+
await changelog.save();
|
|
547
|
+
|
|
548
|
+
logger.info(
|
|
549
|
+
`Changelog unshared: ${changelog.title} by user ${req.user.email}`,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
res.json({
|
|
553
|
+
success: true,
|
|
554
|
+
message: "Changelog unshared successfully.",
|
|
555
|
+
});
|
|
556
|
+
} catch (error) {
|
|
557
|
+
logger.error("Unshare changelog error:", error);
|
|
558
|
+
res.status(500).json({
|
|
559
|
+
success: false,
|
|
560
|
+
message: "Internal server error.",
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// Get public changelog by share token
|
|
566
|
+
const getPublicChangelog = async (req, res) => {
|
|
567
|
+
try {
|
|
568
|
+
const { shareToken } = req.params;
|
|
569
|
+
|
|
570
|
+
const changelog = await Changelog.findOne({ shareToken, isPublic: true })
|
|
571
|
+
.populate("projectId", "name slug")
|
|
572
|
+
.populate("repositoryId", "name githubRepoName")
|
|
573
|
+
.populate("generatedBy", "name email");
|
|
574
|
+
|
|
575
|
+
if (!changelog) {
|
|
576
|
+
return res.status(404).json({
|
|
577
|
+
success: false,
|
|
578
|
+
message: "Changelog not found or not shared.",
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Increment view count
|
|
583
|
+
await changelog.incrementViewCount();
|
|
584
|
+
|
|
585
|
+
res.json({
|
|
586
|
+
success: true,
|
|
587
|
+
data: {
|
|
588
|
+
changelog: changelog.toJSON(),
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
} catch (error) {
|
|
592
|
+
logger.error("Get public changelog error:", error);
|
|
593
|
+
res.status(500).json({
|
|
594
|
+
success: false,
|
|
595
|
+
message: "Internal server error.",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// Download changelog
|
|
601
|
+
const downloadChangelog = async (req, res) => {
|
|
602
|
+
try {
|
|
603
|
+
const { id } = req.params;
|
|
604
|
+
const { format = "markdown" } = req.query;
|
|
605
|
+
|
|
606
|
+
const changelog = await Changelog.findById(id);
|
|
607
|
+
if (!changelog) {
|
|
608
|
+
return res.status(404).json({
|
|
609
|
+
success: false,
|
|
610
|
+
message: "Changelog not found.",
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Check ownership through project
|
|
615
|
+
const project = await Project.findById(changelog.projectId);
|
|
616
|
+
if (!project || project.ownerId.toString() !== req.user._id.toString()) {
|
|
617
|
+
return res.status(403).json({
|
|
618
|
+
success: false,
|
|
619
|
+
message: "Access denied. You do not own this changelog.",
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Generate download URL or content
|
|
624
|
+
let downloadUrl = changelog.downloadUrl;
|
|
625
|
+
let content = changelog.content;
|
|
626
|
+
|
|
627
|
+
// If format is different, regenerate content
|
|
628
|
+
if (format !== changelog.format) {
|
|
629
|
+
const repository = await Repository.findById(changelog.repositoryId);
|
|
630
|
+
const month = changelog.month;
|
|
631
|
+
const year = changelog.year;
|
|
632
|
+
|
|
633
|
+
const changelogService = new ChangelogService();
|
|
634
|
+
content = await changelogService.generateContent(
|
|
635
|
+
repository,
|
|
636
|
+
month,
|
|
637
|
+
year,
|
|
638
|
+
{
|
|
639
|
+
format,
|
|
640
|
+
template: changelog.template,
|
|
641
|
+
},
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Set appropriate headers for download
|
|
646
|
+
res.setHeader(
|
|
647
|
+
"Content-Type",
|
|
648
|
+
format === "markdown"
|
|
649
|
+
? "text/markdown"
|
|
650
|
+
: format === "html"
|
|
651
|
+
? "text/html"
|
|
652
|
+
: "application/json",
|
|
653
|
+
);
|
|
654
|
+
res.setHeader(
|
|
655
|
+
"Content-Disposition",
|
|
656
|
+
`attachment; filename="${changelog.title.replace(/\s+/g, "_")}.${format}"`,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
res.send(content);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
logger.error("Download changelog error:", error);
|
|
662
|
+
res.status(500).json({
|
|
663
|
+
success: false,
|
|
664
|
+
message: "Internal server error.",
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
module.exports = {
|
|
670
|
+
generateChangelog,
|
|
671
|
+
getChangelogs,
|
|
672
|
+
getRepositoryChangelogs,
|
|
673
|
+
getChangelog,
|
|
674
|
+
updateChangelog,
|
|
675
|
+
deleteChangelog,
|
|
676
|
+
getChangelogStatistics,
|
|
677
|
+
getAvailableMonths,
|
|
678
|
+
shareChangelog,
|
|
679
|
+
unshareChangelog,
|
|
680
|
+
getPublicChangelog,
|
|
681
|
+
downloadChangelog,
|
|
682
|
+
};
|