fix: add users endpoint, admin route, and fix agent-config paths
- Add UsersController to auth-service for user CRUD (GET/POST/PUT/DELETE /api/v1/auth/users) - Add Kong route /api/v1/admin -> auth-service for tenant management - Remove AuthGuard from TenantController (Kong handles JWT) - Fix frontend agent-config API paths from /api/v1/agent/config to /api/v1/agent-config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
52b85f085e
commit
3816d6841d
|
|
@ -2,25 +2,25 @@ import { apiClient } from '@/infrastructure/api/api-client';
|
|||
import type { HookScriptDto } from '@/application/dto/agent-config.dto';
|
||||
|
||||
export async function getHooks(): Promise<HookScriptDto[]> {
|
||||
return apiClient<HookScriptDto[]>('/api/v1/agent/config/hooks');
|
||||
return apiClient<HookScriptDto[]>('/api/v1/agent-config/hooks');
|
||||
}
|
||||
|
||||
export async function updateHook(hook: HookScriptDto): Promise<void> {
|
||||
await apiClient(`/api/v1/agent/config/hooks/${hook.id}`, {
|
||||
await apiClient(`/api/v1/agent-config/hooks/${hook.id}`, {
|
||||
method: 'PUT',
|
||||
body: hook,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createHook(hook: Omit<HookScriptDto, 'id'>): Promise<HookScriptDto> {
|
||||
return apiClient<HookScriptDto>('/api/v1/agent/config/hooks', {
|
||||
return apiClient<HookScriptDto>('/api/v1/agent-config/hooks', {
|
||||
method: 'POST',
|
||||
body: hook,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteHook(hookId: string): Promise<void> {
|
||||
await apiClient(`/api/v1/agent/config/hooks/${hookId}`, {
|
||||
await apiClient(`/api/v1/agent-config/hooks/${hookId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { apiClient } from '@/infrastructure/api/api-client';
|
||||
|
||||
export async function switchEngine(engineType: string): Promise<void> {
|
||||
await apiClient('/api/v1/agent/config/engine', {
|
||||
await apiClient('/api/v1/agent-config/engine', {
|
||||
method: 'PUT',
|
||||
body: { engineType },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { apiClient } from '@/infrastructure/api/api-client';
|
||||
|
||||
export async function updateSystemPrompt(prompt: string): Promise<void> {
|
||||
await apiClient('/api/v1/agent/config/system-prompt', {
|
||||
await apiClient('/api/v1/agent-config/system-prompt', {
|
||||
method: 'PUT',
|
||||
body: { prompt },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,41 +4,41 @@ import type { AgentConfigDto } from '@/application/dto/agent-config.dto';
|
|||
|
||||
export const apiAgentConfigRepository: AgentConfigRepository = {
|
||||
async getConfig() {
|
||||
return apiClient<AgentConfigDto>('/api/v1/agent/config');
|
||||
return apiClient<AgentConfigDto>('/api/v1/agent-config');
|
||||
},
|
||||
|
||||
async saveConfig(config) {
|
||||
await apiClient('/api/v1/agent/config', {
|
||||
await apiClient('/api/v1/agent-config', {
|
||||
method: 'PUT',
|
||||
body: config,
|
||||
});
|
||||
},
|
||||
|
||||
async switchEngine(engineType) {
|
||||
await apiClient('/api/v1/agent/config/engine', {
|
||||
await apiClient('/api/v1/agent-config/engine', {
|
||||
method: 'PUT',
|
||||
body: { engineType },
|
||||
});
|
||||
},
|
||||
|
||||
async getSystemPrompt() {
|
||||
const config = await apiClient<AgentConfigDto>('/api/v1/agent/config');
|
||||
const config = await apiClient<AgentConfigDto>('/api/v1/agent-config');
|
||||
return config.systemPrompt ?? '';
|
||||
},
|
||||
|
||||
async updateSystemPrompt(prompt) {
|
||||
await apiClient('/api/v1/agent/config/system-prompt', {
|
||||
await apiClient('/api/v1/agent-config/system-prompt', {
|
||||
method: 'PUT',
|
||||
body: { prompt },
|
||||
});
|
||||
},
|
||||
|
||||
async getHooks() {
|
||||
return apiClient('/api/v1/agent/config/hooks');
|
||||
return apiClient('/api/v1/agent-config/hooks');
|
||||
},
|
||||
|
||||
async updateHook(hook) {
|
||||
await apiClient(`/api/v1/agent/config/hooks/${hook.id}`, {
|
||||
await apiClient(`/api/v1/agent-config/hooks/${hook.id}`, {
|
||||
method: 'PUT',
|
||||
body: hook,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ services:
|
|||
paths:
|
||||
- /api/v1/auth
|
||||
strip_path: false
|
||||
- name: admin-routes
|
||||
paths:
|
||||
- /api/v1/admin
|
||||
strip_path: false
|
||||
|
||||
- name: agent-service
|
||||
url: http://agent-service:3002
|
||||
|
|
@ -195,6 +199,13 @@ plugins:
|
|||
claims_to_verify:
|
||||
- exp
|
||||
|
||||
- name: jwt
|
||||
route: admin-routes
|
||||
config:
|
||||
key_claim_name: kid
|
||||
claims_to_verify:
|
||||
- exp
|
||||
|
||||
# ===== Route-specific overrides =====
|
||||
- name: rate-limiting
|
||||
route: agent-ws
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { PassportModule } from '@nestjs/passport';
|
|||
import { DatabaseModule, TenantProvisioningService } from '@it0/database';
|
||||
import { AuthController } from './interfaces/rest/controllers/auth.controller';
|
||||
import { TenantController } from './interfaces/rest/controllers/tenant.controller';
|
||||
import { UserController } from './interfaces/rest/controllers/user.controller';
|
||||
import { JwtStrategy } from './infrastructure/strategies/jwt.strategy';
|
||||
import { RbacGuard } from './infrastructure/guards/rbac.guard';
|
||||
import { AuthService } from './application/services/auth.service';
|
||||
|
|
@ -29,7 +30,7 @@ import { Tenant } from './domain/entities/tenant.entity';
|
|||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController, TenantController],
|
||||
controllers: [AuthController, TenantController, UserController],
|
||||
providers: [
|
||||
JwtStrategy,
|
||||
RbacGuard,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
NotFoundException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { RolesGuard, Roles } from '@it0/common';
|
||||
|
|
@ -18,7 +17,7 @@ import { Tenant } from '../../../domain/entities/tenant.entity';
|
|||
import * as crypto from 'crypto';
|
||||
|
||||
@Controller('api/v1/admin/tenants')
|
||||
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||
@UseGuards(RolesGuard)
|
||||
@Roles('admin')
|
||||
export class TenantController {
|
||||
private readonly logger = new Logger(TenantController.name);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Param,
|
||||
Body,
|
||||
UseGuards,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} 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 * as bcrypt from 'bcryptjs';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
@Controller('api/v1/auth/users')
|
||||
@UseGuards(RolesGuard)
|
||||
@Roles('admin')
|
||||
export class UserController {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
async listUsers() {
|
||||
const users = await this.userRepository.find({
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
const data = users.map((u) => ({
|
||||
id: u.id,
|
||||
displayName: u.name,
|
||||
email: u.email,
|
||||
role: u.roles?.[0] ?? 'viewer',
|
||||
tenantId: u.tenantId,
|
||||
tenantName: u.tenantId,
|
||||
status: u.isActive ? 'active' : 'disabled',
|
||||
lastLoginAt: u.lastLoginAt?.toISOString() ?? null,
|
||||
createdAt: u.createdAt.toISOString(),
|
||||
}));
|
||||
|
||||
return { data, total: data.length };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getUser(@Param('id') id: string) {
|
||||
const user = await this.userRepository.findOne({ where: { id } });
|
||||
if (!user) throw new NotFoundException(`User "${id}" not found`);
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
displayName: user.name,
|
||||
email: user.email,
|
||||
role: user.roles?.[0] ?? 'viewer',
|
||||
tenantId: user.tenantId,
|
||||
tenantName: user.tenantId,
|
||||
status: user.isActive ? 'active' : 'disabled',
|
||||
lastLoginAt: user.lastLoginAt?.toISOString() ?? null,
|
||||
createdAt: user.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createUser(
|
||||
@Body()
|
||||
body: {
|
||||
displayName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
role?: string;
|
||||
tenantId?: string;
|
||||
},
|
||||
) {
|
||||
const existing = await this.userRepository.findOne({
|
||||
where: { email: body.email },
|
||||
});
|
||||
if (existing) throw new ConflictException('Email already registered');
|
||||
|
||||
const passwordHash = await bcrypt.hash(body.password, 12);
|
||||
|
||||
const user = this.userRepository.create({
|
||||
id: crypto.randomUUID(),
|
||||
tenantId: body.tenantId || 'default',
|
||||
email: body.email,
|
||||
passwordHash,
|
||||
name: body.displayName,
|
||||
roles: [body.role || 'viewer'],
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const saved = await this.userRepository.save(user);
|
||||
|
||||
return {
|
||||
id: saved.id,
|
||||
displayName: saved.name,
|
||||
email: saved.email,
|
||||
role: saved.roles?.[0] ?? 'viewer',
|
||||
tenantId: saved.tenantId,
|
||||
status: 'active',
|
||||
createdAt: saved.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async updateUser(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { role?: string; status?: string },
|
||||
) {
|
||||
const user = await this.userRepository.findOne({ where: { id } });
|
||||
if (!user) throw new NotFoundException(`User "${id}" not found`);
|
||||
|
||||
if (body.role) user.roles = [body.role];
|
||||
if (body.status) user.isActive = body.status === 'active';
|
||||
|
||||
const saved = await this.userRepository.save(user);
|
||||
|
||||
return {
|
||||
id: saved.id,
|
||||
displayName: saved.name,
|
||||
email: saved.email,
|
||||
role: saved.roles?.[0] ?? 'viewer',
|
||||
tenantId: saved.tenantId,
|
||||
status: saved.isActive ? 'active' : 'disabled',
|
||||
};
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async deleteUser(@Param('id') id: string) {
|
||||
const user = await this.userRepository.findOne({ where: { id } });
|
||||
if (!user) throw new NotFoundException(`User "${id}" not found`);
|
||||
await this.userRepository.remove(user);
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue