@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const User = require('../models/User');
|
|
2
2
|
const { generateAccessToken, generateRefreshToken, verifyRefreshToken } = require('../utils/jwt');
|
|
3
3
|
const asyncHandler = require('../utils/asyncHandler');
|
|
4
|
+
const githubService = require('../services/github.service');
|
|
4
5
|
|
|
5
6
|
// Register new user
|
|
6
7
|
const register = asyncHandler(async (req, res) => {
|
|
@@ -85,9 +86,155 @@ const me = asyncHandler(async (req, res) => {
|
|
|
85
86
|
res.json({ user: req.user.toJSON() });
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
// Initiate GitHub OAuth flow
|
|
90
|
+
const githubLogin = asyncHandler(async (req, res) => {
|
|
91
|
+
const state = githubService.generateState();
|
|
92
|
+
|
|
93
|
+
// Store state in session for CSRF protection (if sessions are enabled)
|
|
94
|
+
if (req.session) {
|
|
95
|
+
req.session.githubOAuthState = state;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const authUrl = githubService.getAuthURL(state);
|
|
99
|
+
|
|
100
|
+
// Return URL for frontend to redirect, or redirect directly
|
|
101
|
+
if (req.query.json === 'true' || req.headers.accept?.includes('application/json')) {
|
|
102
|
+
res.json({
|
|
103
|
+
success: true,
|
|
104
|
+
authUrl: authUrl,
|
|
105
|
+
state: state
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
res.redirect(authUrl);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Handle GitHub OAuth callback
|
|
113
|
+
const githubCallback = asyncHandler(async (req, res) => {
|
|
114
|
+
const { code, state } = req.query;
|
|
115
|
+
|
|
116
|
+
if (!code) {
|
|
117
|
+
return res.status(400).json({ error: 'Authorization code missing' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Verify state parameter for CSRF protection
|
|
121
|
+
if (req.session?.githubOAuthState) {
|
|
122
|
+
if (!state || state !== req.session.githubOAuthState) {
|
|
123
|
+
return res.status(400).json({ error: 'Invalid state parameter' });
|
|
124
|
+
}
|
|
125
|
+
// Clear used state
|
|
126
|
+
delete req.session.githubOAuthState;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Exchange code for access token
|
|
130
|
+
const tokenResponse = await githubService.getAccessToken(code, state);
|
|
131
|
+
|
|
132
|
+
if (!tokenResponse.accessToken) {
|
|
133
|
+
return res.status(400).json({ error: 'Failed to get access token' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get user info from GitHub
|
|
137
|
+
const githubUser = await githubService.getFullUserInfo(tokenResponse.accessToken);
|
|
138
|
+
|
|
139
|
+
// Check if user exists with this GitHub ID
|
|
140
|
+
let user = await User.findOne({ githubId: githubUser.id });
|
|
141
|
+
|
|
142
|
+
if (!user) {
|
|
143
|
+
// Check if user exists with this email
|
|
144
|
+
const emailToCheck = githubUser.email || `${githubUser.login}@users.noreply.github.com`;
|
|
145
|
+
user = await User.findOne({ email: emailToCheck.toLowerCase() });
|
|
146
|
+
|
|
147
|
+
if (user) {
|
|
148
|
+
// Link GitHub account to existing user
|
|
149
|
+
user.githubId = githubUser.id;
|
|
150
|
+
user.githubUsername = githubUser.login;
|
|
151
|
+
user.githubAccessToken = tokenResponse.accessToken;
|
|
152
|
+
if (tokenResponse.refreshToken) {
|
|
153
|
+
user.githubRefreshToken = tokenResponse.refreshToken;
|
|
154
|
+
}
|
|
155
|
+
user.githubEmail = githubUser.email;
|
|
156
|
+
user.avatar = githubUser.avatarUrl;
|
|
157
|
+
if (githubUser.name) user.name = githubUser.name;
|
|
158
|
+
user.emailVerified = githubUser.emailVerified || user.emailVerified;
|
|
159
|
+
await user.save();
|
|
160
|
+
} else {
|
|
161
|
+
// Create new user
|
|
162
|
+
user = new User({
|
|
163
|
+
email: emailToCheck.toLowerCase(),
|
|
164
|
+
name: githubUser.name || githubUser.login,
|
|
165
|
+
githubId: githubUser.id,
|
|
166
|
+
githubUsername: githubUser.login,
|
|
167
|
+
githubAccessToken: tokenResponse.accessToken,
|
|
168
|
+
githubRefreshToken: tokenResponse.refreshToken,
|
|
169
|
+
githubEmail: githubUser.email,
|
|
170
|
+
avatar: githubUser.avatarUrl,
|
|
171
|
+
emailVerified: githubUser.emailVerified,
|
|
172
|
+
role: 'user'
|
|
173
|
+
});
|
|
174
|
+
await user.save();
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
// Update existing user's tokens and info
|
|
178
|
+
user.githubAccessToken = tokenResponse.accessToken;
|
|
179
|
+
if (tokenResponse.refreshToken) {
|
|
180
|
+
user.githubRefreshToken = tokenResponse.refreshToken;
|
|
181
|
+
}
|
|
182
|
+
user.avatar = githubUser.avatarUrl;
|
|
183
|
+
if (githubUser.name) user.name = githubUser.name;
|
|
184
|
+
user.githubUsername = githubUser.login;
|
|
185
|
+
await user.save();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Generate JWT tokens
|
|
189
|
+
const token = generateAccessToken(user._id, user.role);
|
|
190
|
+
const refreshToken = generateRefreshToken(user._id);
|
|
191
|
+
|
|
192
|
+
// Update last login
|
|
193
|
+
user.lastLogin = new Date();
|
|
194
|
+
await user.save();
|
|
195
|
+
|
|
196
|
+
// Redirect to frontend with token in URL hash or return JSON
|
|
197
|
+
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000';
|
|
198
|
+
|
|
199
|
+
if (req.query.json === 'true' || req.headers.accept?.includes('application/json')) {
|
|
200
|
+
res.json({
|
|
201
|
+
success: true,
|
|
202
|
+
token,
|
|
203
|
+
refreshToken,
|
|
204
|
+
user: user.toJSON()
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
res.redirect(`${frontendUrl}/dashboard#token=${token}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Refresh GitHub access token
|
|
212
|
+
const githubRefreshToken = asyncHandler(async (req, res) => {
|
|
213
|
+
const { refreshToken } = req.body;
|
|
214
|
+
|
|
215
|
+
if (!refreshToken) {
|
|
216
|
+
return res.status(400).json({ error: 'Refresh token required' });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const tokenResponse = await githubService.refreshAccessToken(refreshToken);
|
|
221
|
+
|
|
222
|
+
res.json({
|
|
223
|
+
success: true,
|
|
224
|
+
accessToken: tokenResponse.accessToken,
|
|
225
|
+
refreshToken: tokenResponse.refreshToken
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
res.status(400).json({ error: error.message });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
88
232
|
module.exports = {
|
|
89
233
|
register,
|
|
90
234
|
login,
|
|
91
235
|
refresh,
|
|
92
|
-
me
|
|
236
|
+
me,
|
|
237
|
+
githubLogin,
|
|
238
|
+
githubCallback,
|
|
239
|
+
githubRefreshToken
|
|
93
240
|
};
|
|
@@ -129,12 +129,14 @@ exports.adminList = async (req, res) => {
|
|
|
129
129
|
const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
|
|
130
130
|
|
|
131
131
|
// Use JSON Configs service for admin data with filtering and pagination
|
|
132
|
+
// Always bypass cache for admin list to ensure fresh data
|
|
132
133
|
const result = await waitingListService.getWaitingListEntriesAdmin({
|
|
133
134
|
status,
|
|
134
135
|
type,
|
|
135
136
|
email,
|
|
136
137
|
limit: parsedLimit,
|
|
137
|
-
offset: parsedOffset
|
|
138
|
+
offset: parsedOffset,
|
|
139
|
+
bypassCache: true
|
|
138
140
|
});
|
|
139
141
|
|
|
140
142
|
return res.json(result);
|
|
@@ -143,3 +145,130 @@ exports.adminList = async (req, res) => {
|
|
|
143
145
|
return res.status(500).json({ error: 'Failed to list entries' });
|
|
144
146
|
}
|
|
145
147
|
};
|
|
148
|
+
|
|
149
|
+
// Get available types (for admin UI filters)
|
|
150
|
+
exports.getTypes = async (req, res) => {
|
|
151
|
+
try {
|
|
152
|
+
const result = await waitingListService.getAvailableTypes();
|
|
153
|
+
res.json(result);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Waiting list get types error:', error);
|
|
156
|
+
return res.status(500).json({ error: 'Failed to get types' });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Export waiting list entries as CSV (server-side, respects filters)
|
|
161
|
+
exports.exportCsv = async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const {
|
|
164
|
+
status,
|
|
165
|
+
type,
|
|
166
|
+
email,
|
|
167
|
+
} = req.query;
|
|
168
|
+
|
|
169
|
+
// Get ALL filtered entries (no pagination)
|
|
170
|
+
const result = await waitingListService.getWaitingListEntriesAdmin({
|
|
171
|
+
status,
|
|
172
|
+
type,
|
|
173
|
+
email,
|
|
174
|
+
limit: 100000, // Large limit to get all entries
|
|
175
|
+
offset: 0
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const entries = result.entries || [];
|
|
179
|
+
|
|
180
|
+
// Set CSV headers
|
|
181
|
+
res.setHeader('Content-Type', 'text/csv');
|
|
182
|
+
res.setHeader('Content-Disposition', `attachment; filename="waiting-list-${new Date().toISOString().split('T')[0]}.csv"`);
|
|
183
|
+
|
|
184
|
+
// CSV header row
|
|
185
|
+
const csvRows = [
|
|
186
|
+
['Email', 'Type', 'Status', 'Referral Source', 'Created At', 'Updated At']
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
// Data rows
|
|
190
|
+
entries.forEach(entry => {
|
|
191
|
+
csvRows.push([
|
|
192
|
+
entry.email || '',
|
|
193
|
+
entry.type || '',
|
|
194
|
+
entry.status || '',
|
|
195
|
+
entry.referralSource || '',
|
|
196
|
+
entry.createdAt ? new Date(entry.createdAt).toISOString() : '',
|
|
197
|
+
entry.updatedAt ? new Date(entry.updatedAt).toISOString() : ''
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Convert to CSV string with proper escaping
|
|
202
|
+
const csvContent = csvRows.map(row =>
|
|
203
|
+
row.map(cell => {
|
|
204
|
+
const str = String(cell || '');
|
|
205
|
+
// Escape quotes and wrap in quotes if contains comma, quote, or newline
|
|
206
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
207
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
208
|
+
}
|
|
209
|
+
return str;
|
|
210
|
+
}).join(',')
|
|
211
|
+
).join('\n');
|
|
212
|
+
|
|
213
|
+
res.send(csvContent);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('Waiting list export CSV error:', error);
|
|
216
|
+
return res.status(500).json({ error: 'Failed to export CSV' });
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Bulk remove waiting list entries
|
|
221
|
+
exports.bulkRemove = async (req, res) => {
|
|
222
|
+
try {
|
|
223
|
+
const { entryIds } = req.body;
|
|
224
|
+
|
|
225
|
+
if (!Array.isArray(entryIds) || entryIds.length === 0) {
|
|
226
|
+
return res.status(400).json({ error: 'entryIds must be a non-empty array' });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { removeWaitingListEntry, getWaitingListEntries, clearWaitingListCache } = require('../services/waitingListJson.service');
|
|
230
|
+
|
|
231
|
+
// Get all entries to verify IDs exist (bypass cache)
|
|
232
|
+
const { entries } = await getWaitingListEntries({ bypassCache: true });
|
|
233
|
+
const validIds = new Set(entries.map(e => e.id));
|
|
234
|
+
|
|
235
|
+
const removed = [];
|
|
236
|
+
const notFound = [];
|
|
237
|
+
|
|
238
|
+
// Remove each entry
|
|
239
|
+
for (const entryId of entryIds) {
|
|
240
|
+
if (!validIds.has(entryId)) {
|
|
241
|
+
notFound.push(entryId);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
await removeWaitingListEntry(entryId);
|
|
247
|
+
removed.push(entryId);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`Failed to remove entry ${entryId}:`, error.message);
|
|
250
|
+
notFound.push(entryId);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Force clear cache after all removals
|
|
255
|
+
await clearWaitingListCache();
|
|
256
|
+
|
|
257
|
+
console.log(`[BulkRemove] Removed ${removed.length} entries, ${notFound.length} not found`);
|
|
258
|
+
|
|
259
|
+
res.json({
|
|
260
|
+
message: `Successfully removed ${removed.length} entr${removed.length === 1 ? 'y' : 'ies'}`,
|
|
261
|
+
removed: {
|
|
262
|
+
count: removed.length,
|
|
263
|
+
ids: removed
|
|
264
|
+
},
|
|
265
|
+
notFound: {
|
|
266
|
+
count: notFound.length,
|
|
267
|
+
ids: notFound
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Waiting list bulk remove error:', error);
|
|
272
|
+
return res.status(500).json({ error: 'Failed to bulk remove entries' });
|
|
273
|
+
}
|
|
274
|
+
};
|
package/src/models/RbacRole.js
CHANGED
|
@@ -2,7 +2,7 @@ const mongoose = require('mongoose');
|
|
|
2
2
|
|
|
3
3
|
const rbacRoleSchema = new mongoose.Schema(
|
|
4
4
|
{
|
|
5
|
-
key: { type: String, required: true, trim: true, lowercase: true
|
|
5
|
+
key: { type: String, required: true, trim: true, lowercase: true },
|
|
6
6
|
name: { type: String, required: true, trim: true },
|
|
7
7
|
description: { type: String, default: '', trim: true },
|
|
8
8
|
status: { type: String, enum: ['active', 'disabled'], default: 'active', index: true },
|
package/src/models/User.js
CHANGED
|
@@ -7,24 +7,53 @@ const userSchema = new mongoose.Schema({
|
|
|
7
7
|
required: true,
|
|
8
8
|
unique: true,
|
|
9
9
|
lowercase: true,
|
|
10
|
-
trim: true
|
|
11
|
-
index: true
|
|
10
|
+
trim: true
|
|
12
11
|
},
|
|
13
12
|
clerkUserId: {
|
|
14
13
|
type: String,
|
|
15
|
-
index: true,
|
|
16
14
|
sparse: true
|
|
17
15
|
},
|
|
18
16
|
passwordHash: {
|
|
19
17
|
type: String,
|
|
20
18
|
required: function () {
|
|
21
|
-
|
|
19
|
+
// Password is optional for OAuth users (GitHub, Clerk)
|
|
20
|
+
return !this.clerkUserId && !this.githubId;
|
|
22
21
|
}
|
|
23
22
|
},
|
|
24
23
|
name: {
|
|
25
24
|
type: String,
|
|
26
25
|
trim: true
|
|
27
26
|
},
|
|
27
|
+
|
|
28
|
+
// GitHub OAuth Integration
|
|
29
|
+
githubId: {
|
|
30
|
+
type: String,
|
|
31
|
+
sparse: true
|
|
32
|
+
},
|
|
33
|
+
githubUsername: {
|
|
34
|
+
type: String,
|
|
35
|
+
sparse: true
|
|
36
|
+
},
|
|
37
|
+
githubAccessToken: {
|
|
38
|
+
type: String,
|
|
39
|
+
select: false // Don't return by default
|
|
40
|
+
},
|
|
41
|
+
githubRefreshToken: {
|
|
42
|
+
type: String,
|
|
43
|
+
select: false // Don't return by default
|
|
44
|
+
},
|
|
45
|
+
githubEmail: {
|
|
46
|
+
type: String,
|
|
47
|
+
sparse: true
|
|
48
|
+
},
|
|
49
|
+
avatar: {
|
|
50
|
+
type: String
|
|
51
|
+
},
|
|
52
|
+
emailVerified: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false
|
|
55
|
+
},
|
|
56
|
+
|
|
28
57
|
subscriptionStatus: {
|
|
29
58
|
type: String,
|
|
30
59
|
enum: ['none', 'active', 'cancelled', 'past_due', 'incomplete', 'incomplete_expired', 'trialing', 'unpaid'],
|
|
@@ -63,7 +92,10 @@ const userSchema = new mongoose.Schema({
|
|
|
63
92
|
timestamps: true
|
|
64
93
|
});
|
|
65
94
|
|
|
66
|
-
//
|
|
95
|
+
// Indexes
|
|
96
|
+
userSchema.index({ email: 1 });
|
|
97
|
+
userSchema.index({ githubId: 1 });
|
|
98
|
+
userSchema.index({ clerkUserId: 1 });
|
|
67
99
|
|
|
68
100
|
// Hash password before saving
|
|
69
101
|
userSchema.pre('save', async function(next) {
|
|
@@ -82,6 +114,8 @@ userSchema.methods.comparePassword = async function(candidatePassword) {
|
|
|
82
114
|
userSchema.methods.toJSON = function() {
|
|
83
115
|
const obj = this.toObject();
|
|
84
116
|
delete obj.passwordHash;
|
|
117
|
+
delete obj.githubAccessToken;
|
|
118
|
+
delete obj.githubRefreshToken;
|
|
85
119
|
delete obj.__v;
|
|
86
120
|
return obj;
|
|
87
121
|
};
|
|
@@ -4,9 +4,15 @@ const authController = require('../controllers/auth.controller');
|
|
|
4
4
|
const { authenticate } = require('../middleware/auth');
|
|
5
5
|
const { auditMiddleware } = require('../services/auditLogger');
|
|
6
6
|
|
|
7
|
+
// Email/Password Auth
|
|
7
8
|
router.post('/register', auditMiddleware('public.auth.register', { entityType: 'User' }), authController.register);
|
|
8
9
|
router.post('/login', auditMiddleware('public.auth.login', { entityType: 'User' }), authController.login);
|
|
9
10
|
router.post('/refresh-token', auditMiddleware('public.auth.refresh', { entityType: 'User' }), authController.refresh);
|
|
10
11
|
router.get('/me', authenticate, authController.me);
|
|
11
12
|
|
|
13
|
+
// GitHub OAuth Auth
|
|
14
|
+
router.get('/github', auditMiddleware('public.auth.github.init', { entityType: 'User' }), authController.githubLogin);
|
|
15
|
+
router.get('/github/callback', auditMiddleware('public.auth.github.callback', { entityType: 'User' }), authController.githubCallback);
|
|
16
|
+
router.post('/github/refresh-token', auditMiddleware('public.auth.github.refresh', { entityType: 'User' }), authController.githubRefreshToken);
|
|
17
|
+
|
|
12
18
|
module.exports = router;
|
|
@@ -3,11 +3,21 @@ const router = express.Router();
|
|
|
3
3
|
const waitingListController = require('../controllers/waitingList.controller');
|
|
4
4
|
const asyncHandler = require('../utils/asyncHandler');
|
|
5
5
|
const { auditMiddleware } = require('../services/auditLogger');
|
|
6
|
+
const rateLimiter = require('../services/rateLimiter.service');
|
|
6
7
|
|
|
7
8
|
// POST /api/waiting-list/subscribe - Subscribe to waiting list
|
|
8
|
-
|
|
9
|
+
// Rate limited by IP to prevent spam/abuse (1 request per minute)
|
|
10
|
+
router.post('/subscribe',
|
|
11
|
+
rateLimiter.limit('waitingListSubscribeLimiter'),
|
|
12
|
+
auditMiddleware('public.waiting_list.subscribe', { entityType: 'WaitingList' }),
|
|
13
|
+
asyncHandler(waitingListController.subscribe)
|
|
14
|
+
);
|
|
9
15
|
|
|
10
16
|
// GET /api/waiting-list/stats - Get waiting list statistics (public)
|
|
11
|
-
|
|
17
|
+
// Light rate limiting to prevent abuse (60 requests per minute)
|
|
18
|
+
router.get('/stats',
|
|
19
|
+
rateLimiter.limit('waitingListStatsLimiter'),
|
|
20
|
+
asyncHandler(waitingListController.getStats)
|
|
21
|
+
);
|
|
12
22
|
|
|
13
23
|
module.exports = router;
|
|
@@ -5,5 +5,8 @@ const waitingListController = require('../controllers/waitingList.controller');
|
|
|
5
5
|
const asyncHandler = require('../utils/asyncHandler');
|
|
6
6
|
|
|
7
7
|
router.get('/', adminSessionAuth, asyncHandler(waitingListController.adminList));
|
|
8
|
+
router.get('/types', adminSessionAuth, asyncHandler(waitingListController.getTypes));
|
|
9
|
+
router.get('/export-csv', adminSessionAuth, asyncHandler(waitingListController.exportCsv));
|
|
10
|
+
router.post('/bulk-remove', adminSessionAuth, asyncHandler(waitingListController.bulkRemove));
|
|
8
11
|
|
|
9
12
|
module.exports = router;
|