You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
586 lines
15 KiB
586 lines
15 KiB
import { Response } from 'express'; |
|
import { AuthRequest } from '../types/index.js'; |
|
import { db } from '../config/database.js'; |
|
import { generateMagicCode } from '../utils/magicCodeGenerator.js'; |
|
|
|
export async function getAllProfiles(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const result = await db.execute({ |
|
sql: ` |
|
SELECT |
|
sp.id, |
|
sp.magic_code, |
|
sp.name, |
|
sp.description, |
|
sp.created_at, |
|
sp.updated_at, |
|
sp.is_active, |
|
spv.setting_key, |
|
spv.setting_value |
|
FROM settings_profiles sp |
|
LEFT JOIN settings_profile_values spv ON sp.id = spv.profile_id |
|
WHERE sp.created_by = ? |
|
ORDER BY sp.created_at DESC |
|
`, |
|
args: [req.userId] |
|
}); |
|
|
|
// Group settings by profile |
|
const profilesMap = new Map<number, any>(); |
|
|
|
for (const row of result.rows) { |
|
const profileId = row.id as number; |
|
|
|
if (!profilesMap.has(profileId)) { |
|
profilesMap.set(profileId, { |
|
id: profileId, |
|
magicCode: row.magic_code, |
|
name: row.name, |
|
description: row.description, |
|
createdAt: row.created_at, |
|
updatedAt: row.updated_at, |
|
isActive: row.is_active === 1, |
|
settings: {} |
|
}); |
|
} |
|
|
|
const profile = profilesMap.get(profileId)!; |
|
const settingKey = row.setting_key as string | null; |
|
if (settingKey) { |
|
profile.settings[settingKey] = row.setting_value as string; |
|
} |
|
} |
|
|
|
const profiles = Array.from(profilesMap.values()).map(profile => { |
|
let enabledApps: string[] = []; |
|
if (profile.settings.enabled_apps) { |
|
try { |
|
enabledApps = JSON.parse(profile.settings.enabled_apps); |
|
} catch (e) { |
|
console.warn('Failed to parse enabled_apps for profile', profile.id); |
|
} |
|
} |
|
|
|
return { |
|
...profile, |
|
enabledApps |
|
}; |
|
}); |
|
|
|
res.json({ |
|
success: true, |
|
data: profiles |
|
}); |
|
} catch (error: any) { |
|
console.error('Get all profiles error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'GET_PROFILES_ERROR', |
|
message: 'Error fetching settings profiles' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function getProfile(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const profileId = parseInt(req.params.id); |
|
if (!profileId || isNaN(profileId)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_PROFILE_ID', |
|
message: 'Invalid profile ID' |
|
} |
|
}); |
|
} |
|
|
|
// Get profile and verify ownership |
|
const profileResult = await db.execute({ |
|
sql: 'SELECT * FROM settings_profiles WHERE id = ? AND created_by = ?', |
|
args: [profileId, req.userId] |
|
}); |
|
|
|
if (!profileResult.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'PROFILE_NOT_FOUND', |
|
message: 'Profile not found or you do not have permission to access it' |
|
} |
|
}); |
|
} |
|
|
|
const profile = profileResult.rows[0]; |
|
|
|
// Get settings |
|
const settingsResult = await db.execute({ |
|
sql: 'SELECT setting_key, setting_value FROM settings_profile_values WHERE profile_id = ?', |
|
args: [profileId] |
|
}); |
|
|
|
const settings: Record<string, string> = {}; |
|
for (const row of settingsResult.rows) { |
|
settings[row.setting_key as string] = row.setting_value as string; |
|
} |
|
|
|
let enabledApps: string[] = []; |
|
if (settings.enabled_apps) { |
|
try { |
|
enabledApps = JSON.parse(settings.enabled_apps); |
|
} catch (e) { |
|
console.warn('Failed to parse enabled_apps for profile', profileId); |
|
} |
|
} |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
id: profile.id, |
|
magicCode: profile.magic_code, |
|
name: profile.name, |
|
description: profile.description, |
|
createdAt: profile.created_at, |
|
updatedAt: profile.updated_at, |
|
isActive: profile.is_active === 1, |
|
settings, |
|
enabledApps |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Get profile error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'GET_PROFILE_ERROR', |
|
message: 'Error fetching settings profile' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function createProfile(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const { name, description, enabledApps } = req.body; |
|
|
|
if (!name || typeof name !== 'string' || name.trim().length === 0) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_NAME', |
|
message: 'Name is required' |
|
} |
|
}); |
|
} |
|
|
|
// Generate unique magic code |
|
const magicCode = await generateMagicCode(); |
|
|
|
// Create profile |
|
const profileResult = await db.execute({ |
|
sql: ` |
|
INSERT INTO settings_profiles (magic_code, name, description, created_by) |
|
VALUES (?, ?, ?, ?) |
|
`, |
|
args: [magicCode, name.trim(), description?.trim() || null, req.userId] |
|
}); |
|
|
|
const profileId = Number(profileResult.lastInsertRowid); |
|
|
|
// Add enabled apps if provided |
|
if (enabledApps && Array.isArray(enabledApps)) { |
|
await db.execute({ |
|
sql: ` |
|
INSERT INTO settings_profile_values (profile_id, setting_key, setting_value) |
|
VALUES (?, ?, ?) |
|
`, |
|
args: [profileId, 'enabled_apps', JSON.stringify(enabledApps)] |
|
}); |
|
} |
|
|
|
// Get created profile |
|
const createdProfile = await db.execute({ |
|
sql: 'SELECT * FROM settings_profiles WHERE id = ?', |
|
args: [profileId] |
|
}); |
|
|
|
res.status(201).json({ |
|
success: true, |
|
data: { |
|
id: createdProfile.rows[0].id, |
|
magicCode: createdProfile.rows[0].magic_code, |
|
name: createdProfile.rows[0].name, |
|
description: createdProfile.rows[0].description, |
|
createdAt: createdProfile.rows[0].created_at, |
|
updatedAt: createdProfile.rows[0].updated_at, |
|
isActive: createdProfile.rows[0].is_active === 1 |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Create profile error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'CREATE_PROFILE_ERROR', |
|
message: 'Error creating settings profile' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function updateProfile(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const profileId = parseInt(req.params.id); |
|
if (!profileId || isNaN(profileId)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_PROFILE_ID', |
|
message: 'Invalid profile ID' |
|
} |
|
}); |
|
} |
|
|
|
const { name, description, isActive } = req.body; |
|
|
|
// Verify ownership |
|
const existing = await db.execute({ |
|
sql: 'SELECT id FROM settings_profiles WHERE id = ? AND created_by = ?', |
|
args: [profileId, req.userId] |
|
}); |
|
|
|
if (!existing.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'PROFILE_NOT_FOUND', |
|
message: 'Profile not found or you do not have permission to update it' |
|
} |
|
}); |
|
} |
|
|
|
// Build update query |
|
const updates: string[] = []; |
|
const args: any[] = []; |
|
|
|
if (name !== undefined) { |
|
if (typeof name !== 'string' || name.trim().length === 0) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_NAME', |
|
message: 'Name must be a non-empty string' |
|
} |
|
}); |
|
} |
|
updates.push('name = ?'); |
|
args.push(name.trim()); |
|
} |
|
|
|
if (description !== undefined) { |
|
updates.push('description = ?'); |
|
args.push(description?.trim() || null); |
|
} |
|
|
|
if (isActive !== undefined) { |
|
updates.push('is_active = ?'); |
|
args.push(isActive ? 1 : 0); |
|
} |
|
|
|
if (updates.length === 0) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'NO_UPDATES', |
|
message: 'No fields to update' |
|
} |
|
}); |
|
} |
|
|
|
updates.push('updated_at = ?'); |
|
args.push(new Date().toISOString()); |
|
args.push(profileId); |
|
|
|
await db.execute({ |
|
sql: `UPDATE settings_profiles SET ${updates.join(', ')} WHERE id = ?`, |
|
args |
|
}); |
|
|
|
// Get updated profile |
|
const updated = await db.execute({ |
|
sql: 'SELECT * FROM settings_profiles WHERE id = ?', |
|
args: [profileId] |
|
}); |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
id: updated.rows[0].id, |
|
magicCode: updated.rows[0].magic_code, |
|
name: updated.rows[0].name, |
|
description: updated.rows[0].description, |
|
createdAt: updated.rows[0].created_at, |
|
updatedAt: updated.rows[0].updated_at, |
|
isActive: updated.rows[0].is_active === 1 |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Update profile error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'UPDATE_PROFILE_ERROR', |
|
message: 'Error updating settings profile' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function deleteProfile(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const profileId = parseInt(req.params.id); |
|
if (!profileId || isNaN(profileId)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_PROFILE_ID', |
|
message: 'Invalid profile ID' |
|
} |
|
}); |
|
} |
|
|
|
// Verify ownership |
|
const existing = await db.execute({ |
|
sql: 'SELECT id FROM settings_profiles WHERE id = ? AND created_by = ?', |
|
args: [profileId, req.userId] |
|
}); |
|
|
|
if (!existing.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'PROFILE_NOT_FOUND', |
|
message: 'Profile not found or you do not have permission to delete it' |
|
} |
|
}); |
|
} |
|
|
|
// Delete profile (cascade will handle settings) |
|
await db.execute({ |
|
sql: 'DELETE FROM settings_profiles WHERE id = ?', |
|
args: [profileId] |
|
}); |
|
|
|
res.json({ |
|
success: true, |
|
data: { message: 'Profile deleted successfully' } |
|
}); |
|
} catch (error: any) { |
|
console.error('Delete profile error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'DELETE_PROFILE_ERROR', |
|
message: 'Error deleting settings profile' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function updateProfileSettings(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const profileId = parseInt(req.params.id); |
|
if (!profileId || isNaN(profileId)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_PROFILE_ID', |
|
message: 'Invalid profile ID' |
|
} |
|
}); |
|
} |
|
|
|
const { enabledApps } = req.body; |
|
|
|
// Verify ownership |
|
const existing = await db.execute({ |
|
sql: 'SELECT id FROM settings_profiles WHERE id = ? AND created_by = ?', |
|
args: [profileId, req.userId] |
|
}); |
|
|
|
if (!existing.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'PROFILE_NOT_FOUND', |
|
message: 'Profile not found or you do not have permission to update it' |
|
} |
|
}); |
|
} |
|
|
|
if (enabledApps !== undefined) { |
|
if (!Array.isArray(enabledApps)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_ENABLED_APPS', |
|
message: 'enabledApps must be an array' |
|
} |
|
}); |
|
} |
|
|
|
// Update or insert setting |
|
await db.execute({ |
|
sql: ` |
|
INSERT INTO settings_profile_values (profile_id, setting_key, setting_value, updated_at) |
|
VALUES (?, ?, ?, ?) |
|
ON CONFLICT(profile_id, setting_key) DO UPDATE SET |
|
setting_value = excluded.setting_value, |
|
updated_at = excluded.updated_at |
|
`, |
|
args: [profileId, 'enabled_apps', JSON.stringify(enabledApps), new Date().toISOString()] |
|
}); |
|
} |
|
|
|
res.json({ |
|
success: true, |
|
data: {} |
|
}); |
|
} catch (error: any) { |
|
console.error('Update profile settings error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'UPDATE_SETTINGS_ERROR', |
|
message: 'Error updating profile settings' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function regenerateMagicCode(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'Authentication required' |
|
} |
|
}); |
|
} |
|
|
|
const profileId = parseInt(req.params.id); |
|
if (!profileId || isNaN(profileId)) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_PROFILE_ID', |
|
message: 'Invalid profile ID' |
|
} |
|
}); |
|
} |
|
|
|
// Verify ownership |
|
const existing = await db.execute({ |
|
sql: 'SELECT id FROM settings_profiles WHERE id = ? AND created_by = ?', |
|
args: [profileId, req.userId] |
|
}); |
|
|
|
if (!existing.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'PROFILE_NOT_FOUND', |
|
message: 'Profile not found or you do not have permission to regenerate its code' |
|
} |
|
}); |
|
} |
|
|
|
// Generate new magic code |
|
const newMagicCode = await generateMagicCode(); |
|
|
|
// Update profile |
|
await db.execute({ |
|
sql: 'UPDATE settings_profiles SET magic_code = ?, updated_at = ? WHERE id = ?', |
|
args: [newMagicCode, new Date().toISOString(), profileId] |
|
}); |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
magicCode: newMagicCode |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Regenerate magic code error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'REGENERATE_CODE_ERROR', |
|
message: 'Error regenerating magic code' |
|
} |
|
}); |
|
} |
|
}
|
|
|