user.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import express from 'express';
  2. import { userDb } from '../database/db.js';
  3. import { authenticateToken } from '../middleware/auth.js';
  4. import { getSystemGitConfig } from '../utils/gitConfig.js';
  5. import { readPilotDeckConfigFile } from '../services/pilotdeckConfig.js';
  6. import { spawn } from 'child_process';
  7. const router = express.Router();
  8. // Sentinel api-key written by scripts/bootstrap-pilotdeck-config.mjs so the
  9. // engine can boot. Treated as "not configured" so the UI routes to onboarding.
  10. const PLACEHOLDER_API_KEY = 'PLACEHOLDER_RUN_ONBOARDING_TO_REPLACE';
  11. function hasUsablePilotDeckConfig() {
  12. const record = readPilotDeckConfigFile();
  13. if (!record.exists) return false;
  14. const mainRef = typeof record.config?.agent?.model === 'string'
  15. ? record.config.agent.model.trim()
  16. : '';
  17. if (!mainRef) return false;
  18. const slash = mainRef.indexOf('/');
  19. if (slash <= 0 || slash === mainRef.length - 1) return false;
  20. const providerId = mainRef.slice(0, slash);
  21. const modelId = mainRef.slice(slash + 1);
  22. const provider = record.config?.model?.providers?.[providerId];
  23. if (!provider || typeof provider !== 'object') return false;
  24. const hasUrl = typeof provider.url === 'string' && provider.url.trim();
  25. const apiKey = typeof provider.apiKey === 'string' ? provider.apiKey.trim() : '';
  26. const hasRealKey = Boolean(apiKey) && apiKey !== PLACEHOLDER_API_KEY;
  27. const hasModel = provider.models && typeof provider.models === 'object' && modelId in provider.models;
  28. return Boolean(hasUrl && hasRealKey && hasModel);
  29. }
  30. function spawnAsync(command, args, options = {}) {
  31. return new Promise((resolve, reject) => {
  32. const child = spawn(command, args, { ...options, shell: false });
  33. let stdout = '';
  34. let stderr = '';
  35. child.stdout.on('data', (data) => { stdout += data.toString(); });
  36. child.stderr.on('data', (data) => { stderr += data.toString(); });
  37. child.on('error', (error) => { reject(error); });
  38. child.on('close', (code) => {
  39. if (code === 0) { resolve({ stdout, stderr }); return; }
  40. const error = new Error(`Command failed: ${command} ${args.join(' ')}`);
  41. error.code = code;
  42. error.stdout = stdout;
  43. error.stderr = stderr;
  44. reject(error);
  45. });
  46. });
  47. }
  48. router.get('/git-config', authenticateToken, async (req, res) => {
  49. try {
  50. const userId = req.user.id;
  51. let gitConfig = userDb.getGitConfig(userId);
  52. // If database is empty, try to get from system git config
  53. if (!gitConfig || (!gitConfig.git_name && !gitConfig.git_email)) {
  54. const systemConfig = await getSystemGitConfig();
  55. // If system has values, save them to database for this user
  56. if (systemConfig.git_name || systemConfig.git_email) {
  57. userDb.updateGitConfig(userId, systemConfig.git_name, systemConfig.git_email);
  58. gitConfig = systemConfig;
  59. console.log(`Auto-populated git config from system for user ${userId}: ${systemConfig.git_name} <${systemConfig.git_email}>`);
  60. }
  61. }
  62. res.json({
  63. success: true,
  64. gitName: gitConfig?.git_name || null,
  65. gitEmail: gitConfig?.git_email || null
  66. });
  67. } catch (error) {
  68. console.error('Error getting git config:', error);
  69. res.status(500).json({ error: 'Failed to get git configuration' });
  70. }
  71. });
  72. // Apply git config globally via git config --global
  73. router.post('/git-config', authenticateToken, async (req, res) => {
  74. try {
  75. const userId = req.user.id;
  76. const { gitName, gitEmail } = req.body;
  77. if (!gitName || !gitEmail) {
  78. return res.status(400).json({ error: 'Git name and email are required' });
  79. }
  80. // Validate email format
  81. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  82. if (!emailRegex.test(gitEmail)) {
  83. return res.status(400).json({ error: 'Invalid email format' });
  84. }
  85. userDb.updateGitConfig(userId, gitName, gitEmail);
  86. try {
  87. await spawnAsync('git', ['config', '--global', 'user.name', gitName]);
  88. await spawnAsync('git', ['config', '--global', 'user.email', gitEmail]);
  89. console.log(`Applied git config globally: ${gitName} <${gitEmail}>`);
  90. } catch (gitError) {
  91. console.error('Error applying git config:', gitError);
  92. }
  93. res.json({
  94. success: true,
  95. gitName,
  96. gitEmail
  97. });
  98. } catch (error) {
  99. console.error('Error updating git config:', error);
  100. res.status(500).json({ error: 'Failed to update git configuration' });
  101. }
  102. });
  103. router.post('/complete-onboarding', authenticateToken, async (req, res) => {
  104. try {
  105. res.json({
  106. success: true,
  107. message: 'Onboarding completed successfully'
  108. });
  109. } catch (error) {
  110. console.error('Error completing onboarding:', error);
  111. res.status(500).json({ error: 'Failed to complete onboarding' });
  112. }
  113. });
  114. router.get('/onboarding-status', authenticateToken, async (req, res) => {
  115. try {
  116. const hasCompleted = hasUsablePilotDeckConfig();
  117. res.json({
  118. success: true,
  119. hasCompletedOnboarding: hasCompleted
  120. });
  121. } catch (error) {
  122. console.error('Error checking onboarding status:', error);
  123. res.status(500).json({ error: 'Failed to check onboarding status' });
  124. }
  125. });
  126. export default router;