@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,366 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
const repositorySchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
projectId: {
|
|
6
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
7
|
+
ref: "Project",
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
// GitHub Information
|
|
12
|
+
githubRepoId: {
|
|
13
|
+
type: String,
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
githubRepoName: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: true,
|
|
19
|
+
trim: true,
|
|
20
|
+
},
|
|
21
|
+
githubRepoUrl: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true,
|
|
24
|
+
trim: true,
|
|
25
|
+
},
|
|
26
|
+
githubRepoOwner: {
|
|
27
|
+
type: String,
|
|
28
|
+
required: true,
|
|
29
|
+
trim: true,
|
|
30
|
+
},
|
|
31
|
+
githubRepoFullName: {
|
|
32
|
+
type: String,
|
|
33
|
+
required: true,
|
|
34
|
+
trim: true,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Repository Settings
|
|
38
|
+
name: {
|
|
39
|
+
type: String,
|
|
40
|
+
required: true,
|
|
41
|
+
trim: true,
|
|
42
|
+
maxlength: [100, "Repository name cannot exceed 100 characters"],
|
|
43
|
+
},
|
|
44
|
+
description: {
|
|
45
|
+
type: String,
|
|
46
|
+
trim: true,
|
|
47
|
+
maxlength: [500, "Description cannot exceed 500 characters"],
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// Authentication
|
|
51
|
+
githubAccessToken: {
|
|
52
|
+
type: String,
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
githubRefreshToken: {
|
|
56
|
+
type: String,
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Configuration
|
|
60
|
+
defaultBranch: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: "main",
|
|
63
|
+
trim: true,
|
|
64
|
+
},
|
|
65
|
+
includeCommitsWithoutPR: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: true,
|
|
68
|
+
},
|
|
69
|
+
includeMergeCommits: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
includeDraftPRs: {
|
|
74
|
+
type: Boolean,
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
includeClosedPRs: {
|
|
78
|
+
type: Boolean,
|
|
79
|
+
default: true,
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Status
|
|
83
|
+
status: {
|
|
84
|
+
type: String,
|
|
85
|
+
enum: ["active", "inactive", "error", "deleted"],
|
|
86
|
+
default: "active",
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Sync Information
|
|
90
|
+
lastSyncedAt: {
|
|
91
|
+
type: Date,
|
|
92
|
+
},
|
|
93
|
+
lastSyncStatus: {
|
|
94
|
+
type: String,
|
|
95
|
+
enum: ["success", "failed", "partial"],
|
|
96
|
+
default: "success",
|
|
97
|
+
},
|
|
98
|
+
lastSyncError: {
|
|
99
|
+
type: String,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// Metadata
|
|
103
|
+
metadata: {
|
|
104
|
+
totalCommits: {
|
|
105
|
+
type: Number,
|
|
106
|
+
default: 0,
|
|
107
|
+
},
|
|
108
|
+
totalPRs: {
|
|
109
|
+
type: Number,
|
|
110
|
+
default: 0,
|
|
111
|
+
},
|
|
112
|
+
totalIssues: {
|
|
113
|
+
type: Number,
|
|
114
|
+
default: 0,
|
|
115
|
+
},
|
|
116
|
+
lastCommitSha: {
|
|
117
|
+
type: String,
|
|
118
|
+
},
|
|
119
|
+
lastCommitDate: {
|
|
120
|
+
type: Date,
|
|
121
|
+
},
|
|
122
|
+
branches: [
|
|
123
|
+
{
|
|
124
|
+
name: String,
|
|
125
|
+
lastCommitSha: String,
|
|
126
|
+
lastCommitDate: Date,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Webhook
|
|
132
|
+
webhook: {
|
|
133
|
+
enabled: {
|
|
134
|
+
type: Boolean,
|
|
135
|
+
default: false,
|
|
136
|
+
},
|
|
137
|
+
webhookId: {
|
|
138
|
+
type: String,
|
|
139
|
+
},
|
|
140
|
+
webhookUrl: {
|
|
141
|
+
type: String,
|
|
142
|
+
},
|
|
143
|
+
secret: {
|
|
144
|
+
type: String,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Tags
|
|
149
|
+
tags: [
|
|
150
|
+
{
|
|
151
|
+
type: String,
|
|
152
|
+
trim: true,
|
|
153
|
+
lowercase: true,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
// Privacy
|
|
158
|
+
isPrivate: {
|
|
159
|
+
type: Boolean,
|
|
160
|
+
default: false,
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// GitHub Features
|
|
164
|
+
features: {
|
|
165
|
+
issues: {
|
|
166
|
+
type: Boolean,
|
|
167
|
+
default: true,
|
|
168
|
+
},
|
|
169
|
+
pullRequests: {
|
|
170
|
+
type: Boolean,
|
|
171
|
+
default: true,
|
|
172
|
+
},
|
|
173
|
+
releases: {
|
|
174
|
+
type: Boolean,
|
|
175
|
+
default: true,
|
|
176
|
+
},
|
|
177
|
+
discussions: {
|
|
178
|
+
type: Boolean,
|
|
179
|
+
default: false,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
timestamps: true,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Indexes
|
|
189
|
+
repositorySchema.index({ projectId: 1, status: 1 });
|
|
190
|
+
repositorySchema.index({ githubRepoId: 1 });
|
|
191
|
+
repositorySchema.index({ githubRepoFullName: 1 });
|
|
192
|
+
repositorySchema.index({ "metadata.lastCommitDate": 1 });
|
|
193
|
+
|
|
194
|
+
// Pre-save middleware to validate GitHub URL
|
|
195
|
+
repositorySchema.pre("save", function (next) {
|
|
196
|
+
if (this.isModified("githubRepoUrl")) {
|
|
197
|
+
try {
|
|
198
|
+
new URL(this.githubRepoUrl);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const err = new Error("Invalid GitHub repository URL");
|
|
201
|
+
return next(err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
next();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Static method to find repositories by project
|
|
208
|
+
repositorySchema.statics.findByProject = function (projectId, options = {}) {
|
|
209
|
+
const { status = "active", limit = 20, skip = 0 } = options;
|
|
210
|
+
|
|
211
|
+
return this.find({
|
|
212
|
+
projectId,
|
|
213
|
+
status,
|
|
214
|
+
})
|
|
215
|
+
.sort({ updatedAt: -1 })
|
|
216
|
+
.limit(limit)
|
|
217
|
+
.skip(skip)
|
|
218
|
+
.populate("projectId", "name slug");
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Static method to check if repository exists for project
|
|
222
|
+
repositorySchema.statics.existsForProject = function (projectId, githubRepoId) {
|
|
223
|
+
return this.exists({
|
|
224
|
+
projectId,
|
|
225
|
+
githubRepoId,
|
|
226
|
+
status: { $ne: "deleted" },
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// Static method to get repository by GitHub ID
|
|
231
|
+
repositorySchema.statics.findByGithubId = function (githubRepoId) {
|
|
232
|
+
return this.findOne({
|
|
233
|
+
githubRepoId,
|
|
234
|
+
status: { $ne: "deleted" },
|
|
235
|
+
}).populate("projectId", "name slug ownerId");
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Instance method to update sync status
|
|
239
|
+
repositorySchema.methods.updateSyncStatus = async function (
|
|
240
|
+
status,
|
|
241
|
+
error = null,
|
|
242
|
+
) {
|
|
243
|
+
const updates = {
|
|
244
|
+
lastSyncedAt: new Date(),
|
|
245
|
+
lastSyncStatus: status,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (error) {
|
|
249
|
+
updates.lastSyncError = error;
|
|
250
|
+
} else {
|
|
251
|
+
updates.lastSyncError = null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return this.updateOne(updates);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Instance method to update metadata
|
|
258
|
+
repositorySchema.methods.updateMetadata = async function (metadata) {
|
|
259
|
+
return this.updateOne({
|
|
260
|
+
$set: {
|
|
261
|
+
"metadata.totalCommits":
|
|
262
|
+
metadata.totalCommits || this.metadata.totalCommits,
|
|
263
|
+
"metadata.totalPRs": metadata.totalPRs || this.metadata.totalPRs,
|
|
264
|
+
"metadata.totalIssues": metadata.totalIssues || this.metadata.totalIssues,
|
|
265
|
+
"metadata.lastCommitSha":
|
|
266
|
+
metadata.lastCommitSha || this.metadata.lastCommitSha,
|
|
267
|
+
"metadata.lastCommitDate":
|
|
268
|
+
metadata.lastCommitDate || this.metadata.lastCommitDate,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Instance method to get available months for changelog generation
|
|
274
|
+
repositorySchema.methods.getAvailableMonths = async function () {
|
|
275
|
+
const Changelog = mongoose.model("Changelog");
|
|
276
|
+
|
|
277
|
+
// Get all generated changelogs for this repository
|
|
278
|
+
const generatedChangelogs = await Changelog.find({
|
|
279
|
+
repositoryId: this._id,
|
|
280
|
+
status: "completed",
|
|
281
|
+
})
|
|
282
|
+
.select("month year")
|
|
283
|
+
.lean();
|
|
284
|
+
|
|
285
|
+
const generatedMonths = new Set(
|
|
286
|
+
generatedChangelogs.map((c) => `${c.year}-${c.month}`),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Get commit history from metadata or fetch from GitHub
|
|
290
|
+
const commits = await this.fetchCommitHistory();
|
|
291
|
+
|
|
292
|
+
const availableMonths = new Set();
|
|
293
|
+
|
|
294
|
+
commits.forEach((commit) => {
|
|
295
|
+
const date = new Date(commit.date);
|
|
296
|
+
const year = date.getFullYear();
|
|
297
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
298
|
+
const key = `${year}-${month}`;
|
|
299
|
+
availableMonths.add(key);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Return months that have commits but no generated changelog
|
|
303
|
+
const result = Array.from(availableMonths)
|
|
304
|
+
.filter((month) => !generatedMonths.has(month))
|
|
305
|
+
.sort()
|
|
306
|
+
.reverse();
|
|
307
|
+
|
|
308
|
+
return result;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// Instance method to fetch commit history (placeholder for GitHub API integration)
|
|
312
|
+
repositorySchema.methods.fetchCommitHistory = async function () {
|
|
313
|
+
// This would integrate with GitHub API or local git repository
|
|
314
|
+
// For now, return empty array
|
|
315
|
+
return [];
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Instance method to validate repository access
|
|
319
|
+
repositorySchema.methods.validateAccess = async function () {
|
|
320
|
+
try {
|
|
321
|
+
// This would make a test API call to GitHub to validate access
|
|
322
|
+
// For now, return true
|
|
323
|
+
return { valid: true, error: null };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return { valid: false, error: error.message };
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Instance method to get repository statistics
|
|
330
|
+
repositorySchema.methods.getStatistics = async function () {
|
|
331
|
+
const Changelog = mongoose.model("Changelog");
|
|
332
|
+
|
|
333
|
+
const [totalChangelogs, recentChangelogs] = await Promise.all([
|
|
334
|
+
Changelog.countDocuments({
|
|
335
|
+
repositoryId: this._id,
|
|
336
|
+
status: { $in: ["completed", "failed"] },
|
|
337
|
+
}),
|
|
338
|
+
Changelog.find({
|
|
339
|
+
repositoryId: this._id,
|
|
340
|
+
})
|
|
341
|
+
.sort({ createdAt: -1 })
|
|
342
|
+
.limit(5)
|
|
343
|
+
.select("month year status createdAt")
|
|
344
|
+
.lean(),
|
|
345
|
+
]);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
totalChangelogs,
|
|
349
|
+
recentChangelogs,
|
|
350
|
+
lastSyncedAt: this.lastSyncedAt,
|
|
351
|
+
lastSyncStatus: this.lastSyncStatus,
|
|
352
|
+
metadata: this.metadata,
|
|
353
|
+
};
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Clean up response
|
|
357
|
+
repositorySchema.methods.toJSON = function () {
|
|
358
|
+
const obj = this.toObject();
|
|
359
|
+
delete obj.githubAccessToken;
|
|
360
|
+
delete obj.githubRefreshToken;
|
|
361
|
+
delete obj.webhook.secret;
|
|
362
|
+
delete obj.__v;
|
|
363
|
+
return obj;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
module.exports = mongoose.model("Repository", repositorySchema);
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
const bcrypt = require("bcryptjs");
|
|
3
|
+
|
|
4
|
+
const userSchema = new mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
email: {
|
|
7
|
+
type: String,
|
|
8
|
+
required: [true, "Email is required"],
|
|
9
|
+
unique: true,
|
|
10
|
+
lowercase: true,
|
|
11
|
+
trim: true,
|
|
12
|
+
match: [
|
|
13
|
+
/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
|
|
14
|
+
"Please enter a valid email",
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
password: {
|
|
18
|
+
type: String,
|
|
19
|
+
minlength: [6, "Password must be at least 6 characters long"],
|
|
20
|
+
// Password is optional for OAuth users (GitHub, etc.)
|
|
21
|
+
validate: {
|
|
22
|
+
validator: function (v) {
|
|
23
|
+
// If user has a githubId, password is optional
|
|
24
|
+
if (this.githubId) return true;
|
|
25
|
+
// Otherwise, password is required
|
|
26
|
+
return v && v.length >= 6;
|
|
27
|
+
},
|
|
28
|
+
message: "Password is required (min 6 characters)",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
name: {
|
|
32
|
+
type: String,
|
|
33
|
+
trim: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// GitHub Integration
|
|
37
|
+
githubId: {
|
|
38
|
+
type: String,
|
|
39
|
+
sparse: true,
|
|
40
|
+
},
|
|
41
|
+
githubUsername: {
|
|
42
|
+
type: String,
|
|
43
|
+
sparse: true,
|
|
44
|
+
},
|
|
45
|
+
githubAccessToken: {
|
|
46
|
+
type: String,
|
|
47
|
+
},
|
|
48
|
+
githubRefreshToken: {
|
|
49
|
+
type: String,
|
|
50
|
+
},
|
|
51
|
+
githubEmail: {
|
|
52
|
+
type: String,
|
|
53
|
+
sparse: true,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Profile
|
|
57
|
+
avatar: {
|
|
58
|
+
type: String,
|
|
59
|
+
},
|
|
60
|
+
isActive: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: true,
|
|
63
|
+
},
|
|
64
|
+
emailVerified: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Subscription
|
|
70
|
+
subscription: {
|
|
71
|
+
plan: {
|
|
72
|
+
type: String,
|
|
73
|
+
enum: ["free", "pro", "enterprise"],
|
|
74
|
+
default: "free",
|
|
75
|
+
},
|
|
76
|
+
projectsLimit: {
|
|
77
|
+
type: Number,
|
|
78
|
+
default: 3,
|
|
79
|
+
},
|
|
80
|
+
repositoriesLimit: {
|
|
81
|
+
type: Number,
|
|
82
|
+
default: 5,
|
|
83
|
+
},
|
|
84
|
+
changelogsLimit: {
|
|
85
|
+
type: Number,
|
|
86
|
+
default: 10,
|
|
87
|
+
},
|
|
88
|
+
lastResetDate: {
|
|
89
|
+
type: Date,
|
|
90
|
+
default: Date.now,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Usage tracking
|
|
95
|
+
usage: {
|
|
96
|
+
projectsCount: {
|
|
97
|
+
type: Number,
|
|
98
|
+
default: 0,
|
|
99
|
+
},
|
|
100
|
+
repositoriesCount: {
|
|
101
|
+
type: Number,
|
|
102
|
+
default: 0,
|
|
103
|
+
},
|
|
104
|
+
changelogsCount: {
|
|
105
|
+
type: Number,
|
|
106
|
+
default: 0,
|
|
107
|
+
},
|
|
108
|
+
lastActivity: {
|
|
109
|
+
type: Date,
|
|
110
|
+
default: Date.now,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Security
|
|
115
|
+
lastLogin: {
|
|
116
|
+
type: Date,
|
|
117
|
+
},
|
|
118
|
+
loginAttempts: {
|
|
119
|
+
type: Number,
|
|
120
|
+
default: 0,
|
|
121
|
+
},
|
|
122
|
+
lockUntil: {
|
|
123
|
+
type: Date,
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Preferences
|
|
127
|
+
preferences: {
|
|
128
|
+
defaultChangelogFormat: {
|
|
129
|
+
type: String,
|
|
130
|
+
enum: ["markdown", "html", "json"],
|
|
131
|
+
default: "markdown",
|
|
132
|
+
},
|
|
133
|
+
notifications: {
|
|
134
|
+
email: {
|
|
135
|
+
type: Boolean,
|
|
136
|
+
default: true,
|
|
137
|
+
},
|
|
138
|
+
changelogGenerated: {
|
|
139
|
+
type: Boolean,
|
|
140
|
+
default: true,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
timestamps: true,
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Indexes
|
|
151
|
+
userSchema.index({ githubId: 1 });
|
|
152
|
+
userSchema.index({ email: 1 });
|
|
153
|
+
|
|
154
|
+
// Virtual for account lockout
|
|
155
|
+
userSchema.virtual("isLocked").get(function () {
|
|
156
|
+
return !!(this.lockUntil && this.lockUntil > Date.now());
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Pre-save middleware to hash password
|
|
160
|
+
userSchema.pre("save", async function (next) {
|
|
161
|
+
// Only hash the password if it has been modified (or is new) AND exists
|
|
162
|
+
if (!this.isModified("password") || !this.password) return next();
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Hash the password with cost of 12
|
|
166
|
+
const salt = await bcrypt.genSalt(12);
|
|
167
|
+
this.password = await bcrypt.hash(this.password, salt);
|
|
168
|
+
next();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
next(error);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Instance method to check password
|
|
175
|
+
userSchema.methods.comparePassword = async function (candidatePassword) {
|
|
176
|
+
return bcrypt.compare(candidatePassword, this.password);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Instance method to increment login attempts
|
|
180
|
+
userSchema.methods.incLoginAttempts = function () {
|
|
181
|
+
// If we have a previous lock that has expired, restart at 1
|
|
182
|
+
if (this.lockUntil && this.lockUntil < Date.now()) {
|
|
183
|
+
return this.updateOne({
|
|
184
|
+
$unset: { lockUntil: 1 },
|
|
185
|
+
$set: { loginAttempts: 1 },
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const updates = { $inc: { loginAttempts: 1 } };
|
|
190
|
+
|
|
191
|
+
// If we're at max attempts and not locked, lock the account
|
|
192
|
+
if (this.loginAttempts + 1 >= 5 && !this.isLocked) {
|
|
193
|
+
updates.$set = { lockUntil: Date.now() + 2 * 60 * 60 * 1000 }; // Lock for 2 hours
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return this.updateOne(updates);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Instance method to reset login attempts
|
|
200
|
+
userSchema.methods.resetLoginAttempts = function () {
|
|
201
|
+
return this.updateOne({
|
|
202
|
+
$unset: { loginAttempts: 1, lockUntil: 1 },
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Instance method to update last activity
|
|
207
|
+
userSchema.methods.updateLastActivity = function () {
|
|
208
|
+
return this.updateOne({ lastActivity: new Date() });
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Clean up response
|
|
212
|
+
userSchema.methods.toJSON = function () {
|
|
213
|
+
const obj = this.toObject();
|
|
214
|
+
delete obj.password;
|
|
215
|
+
delete obj.githubAccessToken;
|
|
216
|
+
delete obj.githubRefreshToken;
|
|
217
|
+
delete obj.loginAttempts;
|
|
218
|
+
delete obj.lockUntil;
|
|
219
|
+
delete obj.__v;
|
|
220
|
+
return obj;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
module.exports = mongoose.model("users", userSchema);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const {
|
|
3
|
+
register,
|
|
4
|
+
login,
|
|
5
|
+
githubLogin,
|
|
6
|
+
githubCallback,
|
|
7
|
+
getProfile,
|
|
8
|
+
updateProfile,
|
|
9
|
+
changePassword,
|
|
10
|
+
logout,
|
|
11
|
+
refreshToken,
|
|
12
|
+
deleteAccount
|
|
13
|
+
} = require('../controllers/auth');
|
|
14
|
+
const { authenticate, authRateLimit } = require('../middleware/auth');
|
|
15
|
+
|
|
16
|
+
const router = express.Router();
|
|
17
|
+
|
|
18
|
+
// Public routes
|
|
19
|
+
router.post('/register', authRateLimit, register);
|
|
20
|
+
router.post('/login', authRateLimit, login);
|
|
21
|
+
router.get('/github/login', githubLogin);
|
|
22
|
+
router.get('/github/callback', githubCallback);
|
|
23
|
+
router.post('/refresh', refreshToken);
|
|
24
|
+
|
|
25
|
+
// Protected routes
|
|
26
|
+
router.get('/profile', authenticate, getProfile);
|
|
27
|
+
router.put('/profile', authenticate, updateProfile);
|
|
28
|
+
router.post('/change-password', authenticate, changePassword);
|
|
29
|
+
router.post('/logout', authenticate, logout);
|
|
30
|
+
router.delete('/account', authenticate, deleteAccount);
|
|
31
|
+
|
|
32
|
+
module.exports = router;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const {
|
|
3
|
+
generateChangelog,
|
|
4
|
+
getChangelogs,
|
|
5
|
+
getRepositoryChangelogs,
|
|
6
|
+
getChangelog,
|
|
7
|
+
updateChangelog,
|
|
8
|
+
deleteChangelog,
|
|
9
|
+
getChangelogStatistics,
|
|
10
|
+
getAvailableMonths,
|
|
11
|
+
shareChangelog,
|
|
12
|
+
unshareChangelog,
|
|
13
|
+
getPublicChangelog,
|
|
14
|
+
downloadChangelog
|
|
15
|
+
} = require('../controllers/changelog');
|
|
16
|
+
const { authenticate, checkOwnership, checkSubscription, checkUsageLimits, apiRateLimit } = require('../middleware/auth');
|
|
17
|
+
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
|
|
20
|
+
// Protected routes - require authentication
|
|
21
|
+
router.use(authenticate);
|
|
22
|
+
router.use(apiRateLimit);
|
|
23
|
+
|
|
24
|
+
// Changelog CRUD operations
|
|
25
|
+
router.post('/', checkUsageLimits, generateChangelog);
|
|
26
|
+
router.get('/project/:projectId', getChangelogs);
|
|
27
|
+
router.get('/repository/:repositoryId', getRepositoryChangelogs);
|
|
28
|
+
router.get('/:id', getChangelog);
|
|
29
|
+
router.put('/:id', checkOwnership('Changelog'), updateChangelog);
|
|
30
|
+
router.delete('/:id', checkOwnership('Changelog'), deleteChangelog);
|
|
31
|
+
|
|
32
|
+
// Changelog management
|
|
33
|
+
router.get('/:id/statistics', checkOwnership('Changelog'), getChangelogStatistics);
|
|
34
|
+
router.get('/:repositoryId/available-months', getAvailableMonths);
|
|
35
|
+
router.post('/:id/share', checkOwnership('Changelog'), shareChangelog);
|
|
36
|
+
router.post('/:id/unshare', checkOwnership('Changelog'), unshareChangelog);
|
|
37
|
+
router.get('/download/:id', checkOwnership('Changelog'), downloadChangelog);
|
|
38
|
+
|
|
39
|
+
// Public routes - for shared changelogs
|
|
40
|
+
router.get('/share/:shareToken', getPublicChangelog);
|
|
41
|
+
|
|
42
|
+
module.exports = router;
|