@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.
Files changed (119) hide show
  1. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
  2. package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
  3. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
  4. package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
  5. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
  6. package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
  7. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
  8. package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
  9. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
  10. package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
  11. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
  12. package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
  13. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
  14. package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
  15. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
  16. package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
  17. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
  18. package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
  19. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
  20. package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
  21. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
  22. package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
  23. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
  24. package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
  25. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
  26. package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
  27. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
  28. package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
  29. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
  30. package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
  31. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
  32. package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
  33. package/.beads/config.yaml +4 -0
  34. package/.beads/issues.jsonl +8 -0
  35. package/.beads/metadata.json +4 -0
  36. package/.env.example +8 -0
  37. package/autochangelog/.env.example +36 -0
  38. package/autochangelog/README.md +412 -0
  39. package/autochangelog/config/database.js +27 -0
  40. package/autochangelog/package.json +47 -0
  41. package/autochangelog/public/landing.html +581 -0
  42. package/autochangelog/server.js +104 -0
  43. package/autochangelog/src/app.js +181 -0
  44. package/autochangelog/src/config/database.js +26 -0
  45. package/autochangelog/src/controllers/auth.js +488 -0
  46. package/autochangelog/src/controllers/changelog.js +682 -0
  47. package/autochangelog/src/controllers/project.js +580 -0
  48. package/autochangelog/src/controllers/repository.js +780 -0
  49. package/autochangelog/src/middleware/auth.js +386 -0
  50. package/autochangelog/src/models/Changelog.js +443 -0
  51. package/autochangelog/src/models/Project.js +226 -0
  52. package/autochangelog/src/models/Repository.js +366 -0
  53. package/autochangelog/src/models/User.js +223 -0
  54. package/autochangelog/src/routes/auth.routes.js +32 -0
  55. package/autochangelog/src/routes/changelog.routes.js +42 -0
  56. package/autochangelog/src/routes/github-auth.routes.js +102 -0
  57. package/autochangelog/src/routes/project.routes.js +50 -0
  58. package/autochangelog/src/routes/repository.routes.js +54 -0
  59. package/autochangelog/src/services/changelog.js +722 -0
  60. package/autochangelog/src/services/github.js +243 -0
  61. package/autochangelog/utils/logger.js +77 -0
  62. package/autochangelog/views/404.ejs +18 -0
  63. package/autochangelog/views/dashboard.ejs +596 -0
  64. package/autochangelog/views/index.ejs +231 -0
  65. package/autochangelog/views/layouts/main.ejs +44 -0
  66. package/autochangelog/views/login.ejs +104 -0
  67. package/autochangelog/views/partials/footer.ejs +20 -0
  68. package/autochangelog/views/partials/navbar.ejs +51 -0
  69. package/autochangelog/views/register.ejs +109 -0
  70. package/autochangelog-cli/README.md +266 -0
  71. package/autochangelog-cli/bin/autochangelog +120 -0
  72. package/autochangelog-cli/package.json +46 -0
  73. package/autochangelog-cli/src/cli/commands/auth.js +291 -0
  74. package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
  75. package/autochangelog-cli/src/cli/commands/project.js +427 -0
  76. package/autochangelog-cli/src/cli/commands/repo.js +557 -0
  77. package/autochangelog-cli/src/cli/commands/stats.js +706 -0
  78. package/autochangelog-cli/src/cli/utils/config.js +277 -0
  79. package/autochangelog-cli/src/cli/utils/errors.js +307 -0
  80. package/autochangelog-cli/src/cli/utils/logger.js +75 -0
  81. package/autochangelog-cli/src/cli/utils/output.js +357 -0
  82. package/package.json +8 -3
  83. package/plugins/supercli/README.md +108 -0
  84. package/plugins/supercli/plugin.json +123 -0
  85. package/server.js +1 -1
  86. package/src/cli/api.js +380 -0
  87. package/src/cli/direct/agent-utils.js +61 -0
  88. package/src/cli/direct/cli-utils.js +112 -0
  89. package/src/cli/direct/data-seeding.js +307 -0
  90. package/src/cli/direct/db-admin.js +84 -0
  91. package/src/cli/direct/db-advanced.js +372 -0
  92. package/src/cli/direct/db-utils.js +558 -0
  93. package/src/cli/direct/help.js +195 -0
  94. package/src/cli/direct/migration.js +107 -0
  95. package/src/cli/direct/rbac-advanced.js +132 -0
  96. package/src/cli/direct/resources-additional.js +400 -0
  97. package/src/cli/direct/resources-cms-advanced.js +173 -0
  98. package/src/cli/direct/resources-cms.js +247 -0
  99. package/src/cli/direct/resources-core.js +253 -0
  100. package/src/cli/direct/resources-execution.js +367 -0
  101. package/src/cli/direct/resources-health.js +152 -0
  102. package/src/cli/direct/resources-integrations.js +182 -0
  103. package/src/cli/direct/resources-logs.js +204 -0
  104. package/src/cli/direct/resources-org-rbac.js +187 -0
  105. package/src/cli/direct/resources-system.js +236 -0
  106. package/src/cli/direct.js +556 -0
  107. package/src/controllers/admin.controller.js +4 -0
  108. package/src/controllers/auth.controller.js +148 -1
  109. package/src/controllers/waitingList.controller.js +130 -1
  110. package/src/models/RbacRole.js +1 -1
  111. package/src/models/User.js +39 -5
  112. package/src/routes/auth.routes.js +6 -0
  113. package/src/routes/waitingList.routes.js +12 -2
  114. package/src/routes/waitingListAdmin.routes.js +3 -0
  115. package/src/services/email.service.js +1 -0
  116. package/src/services/github.service.js +255 -0
  117. package/src/services/rateLimiter.service.js +29 -1
  118. package/src/services/waitingListJson.service.js +32 -3
  119. package/views/admin-waiting-list.ejs +386 -3
@@ -0,0 +1,181 @@
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const helmet = require("helmet");
4
+ const morgan = require("morgan");
5
+ const rateLimit = require("express-rate-limit");
6
+ const path = require("path");
7
+ const logger = require("../utils/logger");
8
+
9
+ // Import autochangelog-specific routes
10
+ const projectRoutes = require("./routes/project.routes");
11
+ const repositoryRoutes = require("./routes/repository.routes");
12
+ const changelogRoutes = require("./routes/changelog.routes");
13
+ const githubAuthRoutes = require("./routes/github-auth.routes"); // GitHub OAuth routes
14
+
15
+ const app = express();
16
+
17
+ // Security middleware
18
+ app.use(
19
+ helmet({
20
+ crossOriginEmbedderPolicy: false,
21
+ contentSecurityPolicy: {
22
+ directives: {
23
+ defaultSrc: ["'self'"],
24
+ styleSrc: [
25
+ "'self'",
26
+ "'unsafe-inline'",
27
+ "https://cdn.tailwindcss.com",
28
+ "https://cdn.jsdelivr.net",
29
+ ],
30
+ scriptSrc: [
31
+ "'self'",
32
+ "'unsafe-inline'",
33
+ "'unsafe-eval'",
34
+ "https://cdn.tailwindcss.com",
35
+ "https://unpkg.com",
36
+ "https://cdn.jsdelivr.net",
37
+ ],
38
+ imgSrc: ["'self'", "data:", "https:", "http:"],
39
+ connectSrc: ["'self'", "https://api.github.com"],
40
+ fontSrc: [
41
+ "'self'",
42
+ "https://fonts.gstatic.com",
43
+ "https://cdn.jsdelivr.net",
44
+ ],
45
+ objectSrc: ["'none'"],
46
+ mediaSrc: ["'self'"],
47
+ frameSrc: ["'none'"],
48
+ },
49
+ },
50
+ }),
51
+ );
52
+
53
+ // CORS configuration
54
+ app.use(
55
+ cors({
56
+ origin: process.env.FRONTEND_URL || "http://localhost:3000",
57
+ credentials: true,
58
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
59
+ allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
60
+ }),
61
+ );
62
+
63
+ // Rate limiting
64
+ const globalRateLimit = rateLimit({
65
+ windowMs: 15 * 60 * 1000, // 15 minutes
66
+ max: 1000, // limit each IP to 1000 requests per windowMs
67
+ message: {
68
+ success: false,
69
+ message: "Too many requests from this IP, please try again later.",
70
+ },
71
+ standardHeaders: true,
72
+ legacyHeaders: false,
73
+ });
74
+
75
+ app.use(globalRateLimit);
76
+
77
+ // Body parsing middleware
78
+ app.use(express.json({ limit: "10mb" }));
79
+ app.use(express.urlencoded({ extended: true, limit: "10mb" }));
80
+
81
+ // Static files
82
+ app.use(express.static(path.join(__dirname, "../public")));
83
+
84
+ // View engine setup
85
+ app.set("view engine", "ejs");
86
+ app.set("views", path.join(__dirname, "../views"));
87
+
88
+ // Logging middleware
89
+ if (process.env.NODE_ENV === "development") {
90
+ app.use(morgan("dev"));
91
+ } else {
92
+ app.use(
93
+ morgan("combined", {
94
+ stream: {
95
+ write: (message) => logger.info(message.trim()),
96
+ },
97
+ }),
98
+ );
99
+ }
100
+
101
+ // Request logging middleware
102
+ app.use((req, res, next) => {
103
+ logger.info(`${req.method} ${req.path} - ${req.ip}`);
104
+ next();
105
+ });
106
+
107
+ // Health check endpoint
108
+ app.get("/health", (req, res) => {
109
+ res.json({
110
+ success: true,
111
+ message: "AutoChangelog API is running",
112
+ timestamp: new Date().toISOString(),
113
+ uptime: process.uptime(),
114
+ });
115
+ });
116
+
117
+ // Landing page route
118
+ app.get("/", (req, res) => {
119
+ res.render("index", {
120
+ title: "AutoChangelog - Automated Changelog Generation",
121
+ user: req.user || null,
122
+ });
123
+ });
124
+
125
+ // Auth pages (EJS views - API calls go to /api/auth/* provided by SuperBackend)
126
+ app.get("/auth/login", (req, res) => {
127
+ res.render("login", { title: "Login - AutoChangelog" });
128
+ });
129
+
130
+ app.get("/auth/register", (req, res) => {
131
+ res.render("register", { title: "Create Account - AutoChangelog" });
132
+ });
133
+
134
+ app.get("/auth/logout", (req, res) => {
135
+ res.redirect("/auth/login");
136
+ });
137
+
138
+ // Dashboard route
139
+ app.get("/dashboard", (req, res) => {
140
+ res.render("dashboard", {
141
+ title: "Dashboard - AutoChangelog",
142
+ user: req.user || null,
143
+ });
144
+ });
145
+
146
+ // AutoChangelog-specific API routes
147
+ // Note: SuperBackend provides /api/auth/*, /api/users/*, /api/orgs/*, /api/billing/*
148
+ // SuperBackend middleware is mounted in server.js AFTER this app is built,
149
+ // so we must NOT have a catch-all 404 here — it's registered in server.js instead
150
+ app.use("/api/projects", projectRoutes);
151
+ app.use("/api/repositories", repositoryRoutes);
152
+ app.use("/api/changelogs", changelogRoutes);
153
+ app.use("/auth", githubAuthRoutes); // GitHub OAuth: /auth/github and /auth/github/callback
154
+
155
+ // GitHub webhook endpoint (public)
156
+ app.post("/api/webhooks/github", (req, res) => {
157
+ logger.info("GitHub webhook received");
158
+ res.status(200).json({ success: true, message: "Webhook received" });
159
+ });
160
+
161
+ // Global error handler
162
+ app.use((err, req, res, next) => {
163
+ logger.error("Unhandled error:", err);
164
+
165
+ const errorResponse = {
166
+ success: false,
167
+ message:
168
+ process.env.NODE_ENV === "production"
169
+ ? "Internal server error"
170
+ : err.message,
171
+ };
172
+
173
+ if (process.env.NODE_ENV !== "production") {
174
+ errorResponse.stack = err.stack;
175
+ errorResponse.details = err;
176
+ }
177
+
178
+ res.status(err.status || 500).json(errorResponse);
179
+ });
180
+
181
+ module.exports = app;
@@ -0,0 +1,26 @@
1
+ const mongoose = require("mongoose");
2
+ const logger = require("../../utils/logger");
3
+
4
+ const connectDB = async () => {
5
+ try {
6
+ const conn = await mongoose.connect(process.env.MONGODB_URI);
7
+
8
+ logger.info(`MongoDB Connected: ${conn.connection.host}`);
9
+ } catch (error) {
10
+ logger.error("Database connection error:", error);
11
+ process.exit(1);
12
+ }
13
+ };
14
+
15
+ const disconnectDB = async () => {
16
+ try {
17
+ await mongoose.disconnect();
18
+ logger.info("MongoDB disconnected");
19
+ } catch (error) {
20
+ logger.error("Error disconnecting from MongoDB:", error);
21
+ }
22
+ };
23
+
24
+ module.exports = connectDB;
25
+ module.exports.connectDB = connectDB;
26
+ module.exports.disconnectDB = disconnectDB;
@@ -0,0 +1,488 @@
1
+ const bcrypt = require('bcryptjs');
2
+ const jwt = require('jsonwebtoken');
3
+ const User = require('../models/User');
4
+ const GitHubService = require('../services/github');
5
+ const logger = require('../../utils/logger');
6
+
7
+ // Register a new user
8
+ const register = async (req, res) => {
9
+ try {
10
+ const { email, password, name } = req.body;
11
+
12
+ // Validation
13
+ if (!email || !password) {
14
+ return res.status(400).json({
15
+ success: false,
16
+ message: 'Email and password are required.',
17
+ });
18
+ }
19
+
20
+ if (password.length < 6) {
21
+ return res.status(400).json({
22
+ success: false,
23
+ message: 'Password must be at least 6 characters long.',
24
+ });
25
+ }
26
+
27
+ // Check if user already exists
28
+ const existingUser = await User.findOne({ email: email.toLowerCase() });
29
+ if (existingUser) {
30
+ return res.status(400).json({
31
+ success: false,
32
+ message: 'User with this email already exists.',
33
+ });
34
+ }
35
+
36
+ // Create new user
37
+ const user = new User({
38
+ email: email.toLowerCase(),
39
+ password,
40
+ name,
41
+ });
42
+
43
+ await user.save();
44
+
45
+ // Generate JWT token
46
+ const token = jwt.sign(
47
+ { userId: user._id },
48
+ process.env.JWT_SECRET,
49
+ { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
50
+ );
51
+
52
+ logger.info(`User registered successfully: ${user.email}`);
53
+
54
+ res.status(201).json({
55
+ success: true,
56
+ message: 'User registered successfully.',
57
+ data: {
58
+ user: user.toJSON(),
59
+ token,
60
+ },
61
+ });
62
+ } catch (error) {
63
+ logger.error('Registration error:', error);
64
+ res.status(500).json({
65
+ success: false,
66
+ message: 'Internal server error.',
67
+ });
68
+ }
69
+ };
70
+
71
+ // Login user
72
+ const login = async (req, res) => {
73
+ try {
74
+ const { email, password } = req.body;
75
+
76
+ // Validation
77
+ if (!email || !password) {
78
+ return res.status(400).json({
79
+ success: false,
80
+ message: 'Email and password are required.',
81
+ });
82
+ }
83
+
84
+ // Find user
85
+ const user = await User.findOne({ email: email.toLowerCase() });
86
+ if (!user) {
87
+ return res.status(401).json({
88
+ success: false,
89
+ message: 'Invalid email or password.',
90
+ });
91
+ }
92
+
93
+ // Check if account is active
94
+ if (!user.isActive) {
95
+ return res.status(401).json({
96
+ success: false,
97
+ message: 'Account is inactive.',
98
+ });
99
+ }
100
+
101
+ // Check password
102
+ const isPasswordValid = await user.comparePassword(password);
103
+ if (!isPasswordValid) {
104
+ // Increment login attempts
105
+ await user.incLoginAttempts();
106
+ return res.status(401).json({
107
+ success: false,
108
+ message: 'Invalid email or password.',
109
+ });
110
+ }
111
+
112
+ // Reset login attempts on successful login
113
+ await user.resetLoginAttempts();
114
+
115
+ // Update last login
116
+ await user.updateOne({ lastLogin: new Date() });
117
+
118
+ // Generate JWT token
119
+ const token = jwt.sign(
120
+ { userId: user._id },
121
+ process.env.JWT_SECRET,
122
+ { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
123
+ );
124
+
125
+ logger.info(`User logged in successfully: ${user.email}`);
126
+
127
+ res.json({
128
+ success: true,
129
+ message: 'Login successful.',
130
+ data: {
131
+ user: user.toJSON(),
132
+ token,
133
+ },
134
+ });
135
+ } catch (error) {
136
+ logger.error('Login error:', error);
137
+ res.status(500).json({
138
+ success: false,
139
+ message: 'Internal server error.',
140
+ });
141
+ }
142
+ };
143
+
144
+ // GitHub OAuth login
145
+ const githubLogin = async (req, res) => {
146
+ try {
147
+ const authUrl = GitHubService.getAuthURL();
148
+ res.json({
149
+ success: true,
150
+ data: {
151
+ authUrl,
152
+ },
153
+ });
154
+ } catch (error) {
155
+ logger.error('GitHub login error:', error);
156
+ res.status(500).json({
157
+ success: false,
158
+ message: 'Internal server error.',
159
+ });
160
+ }
161
+ };
162
+
163
+ // GitHub OAuth callback
164
+ const githubCallback = async (req, res) => {
165
+ try {
166
+ const { code, state } = req.query;
167
+
168
+ if (!code) {
169
+ return res.status(400).json({
170
+ success: false,
171
+ message: 'Authorization code not provided.',
172
+ });
173
+ }
174
+
175
+ // Exchange code for access token
176
+ const tokenResponse = await GitHubService.getAccessToken(code);
177
+
178
+ if (!tokenResponse.access_token) {
179
+ return res.status(400).json({
180
+ success: false,
181
+ message: 'Failed to get access token from GitHub.',
182
+ });
183
+ }
184
+
185
+ const accessToken = tokenResponse.access_token;
186
+
187
+ // Get user information from GitHub
188
+ const githubUser = await GitHubService.getUser(accessToken);
189
+
190
+ // Find or create user in our database
191
+ let user = await User.findOne({ githubId: githubUser.id });
192
+
193
+ if (!user) {
194
+ // Check if user exists with email
195
+ user = await User.findOne({ email: githubUser.email });
196
+
197
+ if (user) {
198
+ // Update existing user with GitHub info
199
+ user.githubId = githubUser.id;
200
+ user.githubUsername = githubUser.login;
201
+ user.githubAccessToken = accessToken;
202
+ user.githubRefreshToken = tokenResponse.refresh_token;
203
+ user.githubEmail = githubUser.email;
204
+ user.name = user.name || githubUser.name || githubUser.login;
205
+ user.avatar = githubUser.avatar_url;
206
+ } else {
207
+ // Create new user
208
+ user = new User({
209
+ email: githubUser.email,
210
+ name: githubUser.name || githubUser.login,
211
+ githubId: githubUser.id,
212
+ githubUsername: githubUser.login,
213
+ githubAccessToken: accessToken,
214
+ githubRefreshToken: tokenResponse.refresh_token,
215
+ githubEmail: githubUser.email,
216
+ avatar: githubUser.avatar_url,
217
+ // Password is not required for GitHub auth, but we'll set a random one
218
+ password: Math.random().toString(36).slice(-8),
219
+ });
220
+ }
221
+
222
+ await user.save();
223
+ } else {
224
+ // Update existing GitHub user
225
+ user.githubAccessToken = accessToken;
226
+ user.githubRefreshToken = tokenResponse.refresh_token;
227
+ user.name = user.name || githubUser.name || githubUser.login;
228
+ user.avatar = githubUser.avatar_url;
229
+ await user.save();
230
+ }
231
+
232
+ // Generate JWT token
233
+ const token = jwt.sign(
234
+ { userId: user._id },
235
+ process.env.JWT_SECRET,
236
+ { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
237
+ );
238
+
239
+ logger.info(`User logged in via GitHub: ${user.email}`);
240
+
241
+ res.json({
242
+ success: true,
243
+ message: 'GitHub authentication successful.',
244
+ data: {
245
+ user: user.toJSON(),
246
+ token,
247
+ },
248
+ });
249
+ } catch (error) {
250
+ logger.error('GitHub callback error:', error);
251
+ res.status(500).json({
252
+ success: false,
253
+ message: 'Internal server error.',
254
+ });
255
+ }
256
+ };
257
+
258
+ // Get current user profile
259
+ const getProfile = async (req, res) => {
260
+ try {
261
+ const user = await User.findById(req.user._id).select('-password -githubAccessToken -githubRefreshToken');
262
+
263
+ if (!user) {
264
+ return res.status(404).json({
265
+ success: false,
266
+ message: 'User not found.',
267
+ });
268
+ }
269
+
270
+ res.json({
271
+ success: true,
272
+ data: {
273
+ user: user.toJSON(),
274
+ },
275
+ });
276
+ } catch (error) {
277
+ logger.error('Get profile error:', error);
278
+ res.status(500).json({
279
+ success: false,
280
+ message: 'Internal server error.',
281
+ });
282
+ }
283
+ };
284
+
285
+ // Update user profile
286
+ const updateProfile = async (req, res) => {
287
+ try {
288
+ const { name, email, preferences } = req.body;
289
+
290
+ const user = await User.findById(req.user._id);
291
+ if (!user) {
292
+ return res.status(404).json({
293
+ success: false,
294
+ message: 'User not found.',
295
+ });
296
+ }
297
+
298
+ // Update fields
299
+ if (name) user.name = name;
300
+ if (email && email !== user.email) {
301
+ // Check if email is already taken
302
+ const existingUser = await User.findOne({ email: email.toLowerCase(), _id: { $ne: user._id } });
303
+ if (existingUser) {
304
+ return res.status(400).json({
305
+ success: false,
306
+ message: 'Email is already taken.',
307
+ });
308
+ }
309
+ user.email = email.toLowerCase();
310
+ }
311
+ if (preferences) {
312
+ user.preferences = { ...user.preferences, ...preferences };
313
+ }
314
+
315
+ await user.save();
316
+
317
+ logger.info(`User profile updated: ${user.email}`);
318
+
319
+ res.json({
320
+ success: true,
321
+ message: 'Profile updated successfully.',
322
+ data: {
323
+ user: user.toJSON(),
324
+ },
325
+ });
326
+ } catch (error) {
327
+ logger.error('Update profile error:', error);
328
+ res.status(500).json({
329
+ success: false,
330
+ message: 'Internal server error.',
331
+ });
332
+ }
333
+ };
334
+
335
+ // Change password
336
+ const changePassword = async (req, res) => {
337
+ try {
338
+ const { currentPassword, newPassword } = req.body;
339
+
340
+ if (!currentPassword || !newPassword) {
341
+ return res.status(400).json({
342
+ success: false,
343
+ message: 'Current password and new password are required.',
344
+ });
345
+ }
346
+
347
+ if (newPassword.length < 6) {
348
+ return res.status(400).json({
349
+ success: false,
350
+ message: 'New password must be at least 6 characters long.',
351
+ });
352
+ }
353
+
354
+ const user = await User.findById(req.user._id);
355
+ if (!user) {
356
+ return res.status(404).json({
357
+ success: false,
358
+ message: 'User not found.',
359
+ });
360
+ }
361
+
362
+ // Check current password
363
+ const isCurrentPasswordValid = await user.comparePassword(currentPassword);
364
+ if (!isCurrentPasswordValid) {
365
+ return res.status(400).json({
366
+ success: false,
367
+ message: 'Current password is incorrect.',
368
+ });
369
+ }
370
+
371
+ // Update password
372
+ user.password = newPassword;
373
+ await user.save();
374
+
375
+ logger.info(`Password changed for user: ${user.email}`);
376
+
377
+ res.json({
378
+ success: true,
379
+ message: 'Password changed successfully.',
380
+ });
381
+ } catch (error) {
382
+ logger.error('Change password error:', error);
383
+ res.status(500).json({
384
+ success: false,
385
+ message: 'Internal server error.',
386
+ });
387
+ }
388
+ };
389
+
390
+ // Logout (client-side token removal, server-side we can implement token blacklisting if needed)
391
+ const logout = async (req, res) => {
392
+ try {
393
+ // In a more sophisticated implementation, you might want to blacklist the token
394
+ // For now, we'll just return success and let the client handle token removal
395
+
396
+ logger.info(`User logged out: ${req.user.email}`);
397
+
398
+ res.json({
399
+ success: true,
400
+ message: 'Logout successful.',
401
+ });
402
+ } catch (error) {
403
+ logger.error('Logout error:', error);
404
+ res.status(500).json({
405
+ success: false,
406
+ message: 'Internal server error.',
407
+ });
408
+ }
409
+ };
410
+
411
+ // Refresh token
412
+ const refreshToken = async (req, res) => {
413
+ try {
414
+ const user = await User.findById(req.user._id);
415
+ if (!user || !user.isActive) {
416
+ return res.status(401).json({
417
+ success: false,
418
+ message: 'Invalid user or inactive account.',
419
+ });
420
+ }
421
+
422
+ // Generate new token
423
+ const token = jwt.sign(
424
+ { userId: user._id },
425
+ process.env.JWT_SECRET,
426
+ { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
427
+ );
428
+
429
+ res.json({
430
+ success: true,
431
+ message: 'Token refreshed successfully.',
432
+ data: {
433
+ token,
434
+ },
435
+ });
436
+ } catch (error) {
437
+ logger.error('Refresh token error:', error);
438
+ res.status(500).json({
439
+ success: false,
440
+ message: 'Internal server error.',
441
+ });
442
+ }
443
+ };
444
+
445
+ // Delete account
446
+ const deleteAccount = async (req, res) => {
447
+ try {
448
+ const user = await User.findById(req.user._id);
449
+ if (!user) {
450
+ return res.status(404).json({
451
+ success: false,
452
+ message: 'User not found.',
453
+ });
454
+ }
455
+
456
+ // TODO: Handle cleanup of user data (projects, repositories, changelogs)
457
+ // For now, we'll just deactivate the account
458
+ user.isActive = false;
459
+ user.email = `deleted_${Date.now()}_${user.email}`;
460
+ await user.save();
461
+
462
+ logger.info(`User account deactivated: ${user.email}`);
463
+
464
+ res.json({
465
+ success: true,
466
+ message: 'Account deactivated successfully.',
467
+ });
468
+ } catch (error) {
469
+ logger.error('Delete account error:', error);
470
+ res.status(500).json({
471
+ success: false,
472
+ message: 'Internal server error.',
473
+ });
474
+ }
475
+ };
476
+
477
+ module.exports = {
478
+ register,
479
+ login,
480
+ githubLogin,
481
+ githubCallback,
482
+ getProfile,
483
+ updateProfile,
484
+ changePassword,
485
+ logout,
486
+ refreshToken,
487
+ deleteAccount,
488
+ };