fix: format tenant API response to match frontend DTO

Map flat quota fields to nested quota object and add userCount field
to match the frontend's expected Tenant interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-22 00:41:19 -08:00
parent 3816d6841d
commit 8f89b8121c
1 changed files with 43 additions and 20 deletions

View File

@ -28,13 +28,34 @@ export class TenantController {
private readonly tenantProvisioningService: TenantProvisioningService,
) {}
private toDto(t: Tenant) {
return {
id: t.id,
name: t.name,
slug: t.slug,
plan: t.plan,
status: t.status,
adminEmail: t.adminEmail,
userCount: 0,
quota: {
maxServers: t.maxServers,
maxUsers: t.maxUsers,
maxStandingOrders: t.maxStandingOrders,
maxAgentTokensPerMonth: t.maxAgentTokensPerMonth,
},
createdAt: t.createdAt,
updatedAt: t.updatedAt,
};
}
/**
* GET /api/v1/admin/tenants
* Returns all tenants from the public schema's tenants table.
*/
@Get()
async listTenants() {
return this.tenantRepository.find({ order: { createdAt: 'DESC' } });
const tenants = await this.tenantRepository.find({ order: { createdAt: 'DESC' } });
return tenants.map((t) => this.toDto(t));
}
/**
@ -47,7 +68,7 @@ export class TenantController {
if (!tenant) {
throw new NotFoundException(`Tenant with id "${id}" not found`);
}
return tenant;
return this.toDto(tenant);
}
/**
@ -95,7 +116,7 @@ export class TenantController {
// The tenant record is already saved; schema provisioning can be retried
}
return savedTenant;
return this.toDto(savedTenant);
}
/**
@ -103,30 +124,32 @@ export class TenantController {
* Updates tenant metadata.
*/
@Patch(':id')
async updateTenant(@Param('id') id: string, @Body() body: Partial<Tenant>) {
async updateTenant(@Param('id') id: string, @Body() body: any) {
const tenant = await this.tenantRepository.findOne({ where: { id } });
if (!tenant) {
throw new NotFoundException(`Tenant with id "${id}" not found`);
}
// Only allow updating specific fields
const allowedFields: (keyof Tenant)[] = [
'name',
'plan',
'status',
'adminEmail',
'maxServers',
'maxUsers',
'maxStandingOrders',
'maxAgentTokensPerMonth',
];
if (body.name !== undefined) tenant.name = body.name;
if (body.plan !== undefined) tenant.plan = body.plan;
if (body.status !== undefined) tenant.status = body.status;
if (body.adminEmail !== undefined) tenant.adminEmail = body.adminEmail;
for (const field of allowedFields) {
if (body[field] !== undefined) {
(tenant as any)[field] = body[field];
}
// Support nested quota object from frontend
if (body.quota) {
if (body.quota.maxServers !== undefined) tenant.maxServers = body.quota.maxServers;
if (body.quota.maxUsers !== undefined) tenant.maxUsers = body.quota.maxUsers;
if (body.quota.maxStandingOrders !== undefined) tenant.maxStandingOrders = body.quota.maxStandingOrders;
if (body.quota.maxAgentTokensPerMonth !== undefined) tenant.maxAgentTokensPerMonth = body.quota.maxAgentTokensPerMonth;
}
return this.tenantRepository.save(tenant);
// Also support flat fields
if (body.maxServers !== undefined) tenant.maxServers = body.maxServers;
if (body.maxUsers !== undefined) tenant.maxUsers = body.maxUsers;
if (body.maxStandingOrders !== undefined) tenant.maxStandingOrders = body.maxStandingOrders;
if (body.maxAgentTokensPerMonth !== undefined) tenant.maxAgentTokensPerMonth = body.maxAgentTokensPerMonth;
const saved = await this.tenantRepository.save(tenant);
return this.toDto(saved);
}
}