191 lines
5.0 KiB
TypeScript
191 lines
5.0 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Put,
|
|
Post,
|
|
Delete,
|
|
Param,
|
|
Body,
|
|
Req,
|
|
UseGuards,
|
|
NotFoundException,
|
|
} from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { RolesGuard, Roles } from '@it0/common';
|
|
import { User } from '../../../domain/entities/user.entity';
|
|
import { ApiKey } from '../../../domain/entities/api-key.entity';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import * as crypto from 'crypto';
|
|
|
|
@Controller('api/v1/admin/settings')
|
|
@UseGuards(RolesGuard)
|
|
@Roles('admin')
|
|
export class SettingsController {
|
|
// In-memory store for platform settings (would be a DB table in production)
|
|
private generalSettings: Record<string, any> = {
|
|
platformName: 'IT0',
|
|
defaultTimezone: 'UTC',
|
|
defaultLanguage: 'en',
|
|
autoApproveThreshold: 1,
|
|
};
|
|
|
|
private notificationSettings: Record<string, any> = {
|
|
emailEnabled: false,
|
|
smsEnabled: false,
|
|
pushEnabled: true,
|
|
defaultEscalationPolicy: 'immediate',
|
|
};
|
|
|
|
private themeSettings: Record<string, any> = {
|
|
mode: 'dark',
|
|
primaryColor: '#3b82f6',
|
|
};
|
|
|
|
constructor(
|
|
@InjectRepository(User)
|
|
private readonly userRepository: Repository<User>,
|
|
@InjectRepository(ApiKey)
|
|
private readonly apiKeyRepository: Repository<ApiKey>,
|
|
) {}
|
|
|
|
// --- General Settings ---
|
|
|
|
@Get('general')
|
|
async getGeneral() {
|
|
return this.generalSettings;
|
|
}
|
|
|
|
@Put('general')
|
|
async updateGeneral(@Body() body: any) {
|
|
Object.assign(this.generalSettings, body);
|
|
return this.generalSettings;
|
|
}
|
|
|
|
// --- Notification Settings ---
|
|
|
|
@Get('notifications')
|
|
async getNotifications() {
|
|
return this.notificationSettings;
|
|
}
|
|
|
|
@Put('notifications')
|
|
async updateNotifications(@Body() body: any) {
|
|
Object.assign(this.notificationSettings, body);
|
|
return this.notificationSettings;
|
|
}
|
|
|
|
// --- API Keys ---
|
|
|
|
@Get('api-keys')
|
|
async listApiKeys(@Req() req: any) {
|
|
const userId = req.user?.sub || req.user?.id;
|
|
const keys = await this.apiKeyRepository.find({
|
|
where: userId ? { userId } : {},
|
|
order: { createdAt: 'DESC' },
|
|
});
|
|
|
|
return keys.map((k) => ({
|
|
id: k.id,
|
|
name: k.name,
|
|
key: '****' + k.keyHash.slice(-8),
|
|
createdAt: k.createdAt.toISOString(),
|
|
lastUsedAt: k.lastUsedAt?.toISOString() ?? null,
|
|
}));
|
|
}
|
|
|
|
@Post('api-keys')
|
|
async createApiKey(@Req() req: any, @Body() body: { name: string }) {
|
|
const userId = req.user?.sub || req.user?.id;
|
|
const tenantId = req.user?.tenantId || 'default';
|
|
const rawKey = `it0_${crypto.randomBytes(32).toString('hex')}`;
|
|
const keyHash = await bcrypt.hash(rawKey, 10);
|
|
|
|
const apiKey = this.apiKeyRepository.create({
|
|
id: crypto.randomUUID(),
|
|
tenantId,
|
|
userId,
|
|
keyHash,
|
|
name: body.name,
|
|
permissions: [],
|
|
isActive: true,
|
|
});
|
|
|
|
await this.apiKeyRepository.save(apiKey);
|
|
return { key: rawKey };
|
|
}
|
|
|
|
@Delete('api-keys/:id')
|
|
async deleteApiKey(@Param('id') id: string) {
|
|
const key = await this.apiKeyRepository.findOne({ where: { id } });
|
|
if (!key) throw new NotFoundException(`API key "${id}" not found`);
|
|
await this.apiKeyRepository.remove(key);
|
|
return { success: true };
|
|
}
|
|
|
|
// --- Theme Settings ---
|
|
|
|
@Get('theme')
|
|
async getTheme() {
|
|
return this.themeSettings;
|
|
}
|
|
|
|
@Put('theme')
|
|
async updateTheme(@Body() body: any) {
|
|
Object.assign(this.themeSettings, body);
|
|
return this.themeSettings;
|
|
}
|
|
|
|
// --- Account Settings ---
|
|
|
|
@Get('account')
|
|
async getAccount(@Req() req: any) {
|
|
const userId = req.user?.sub || req.user?.id;
|
|
if (!userId) return { displayName: 'Admin', email: '' };
|
|
|
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
if (!user) return { displayName: 'Admin', email: '' };
|
|
|
|
return {
|
|
displayName: user.name,
|
|
email: user.email,
|
|
};
|
|
}
|
|
|
|
@Put('account')
|
|
async updateAccount(@Req() req: any, @Body() body: { displayName: string }) {
|
|
const userId = req.user?.sub || req.user?.id;
|
|
if (!userId) throw new NotFoundException('User not found');
|
|
|
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
if (!user) throw new NotFoundException('User not found');
|
|
|
|
user.name = body.displayName;
|
|
const saved = await this.userRepository.save(user);
|
|
|
|
return { displayName: saved.name, email: saved.email };
|
|
}
|
|
|
|
@Put('account/password')
|
|
async changePassword(
|
|
@Req() req: any,
|
|
@Body() body: { currentPassword: string; newPassword: string },
|
|
) {
|
|
const userId = req.user?.sub || req.user?.id;
|
|
if (!userId) throw new NotFoundException('User not found');
|
|
|
|
const user = await this.userRepository.findOne({ where: { id: userId } });
|
|
if (!user) throw new NotFoundException('User not found');
|
|
|
|
const valid = await bcrypt.compare(body.currentPassword, user.passwordHash);
|
|
if (!valid) {
|
|
return { success: false, message: 'Current password is incorrect' };
|
|
}
|
|
|
|
user.passwordHash = await bcrypt.hash(body.newPassword, 12);
|
|
await this.userRepository.save(user);
|
|
|
|
return { success: true };
|
|
}
|
|
}
|