diff --git a/apps/api/src/modules/projects/infrastructure/repositories/prisma-project-development.repository.ts b/apps/api/src/modules/projects/infrastructure/repositories/prisma-project-development.repository.ts index 26d49f7..a41ef31 100644 --- a/apps/api/src/modules/projects/infrastructure/repositories/prisma-project-development.repository.ts +++ b/apps/api/src/modules/projects/infrastructure/repositories/prisma-project-development.repository.ts @@ -37,7 +37,7 @@ export class PrismaProjectDevelopmentRepository implements IProjectRepository { ST_X(p.location::geometry) as lng, COUNT(pr.id)::int as "propertyCount" FROM "ProjectDevelopment" p - LEFT JOIN "Property" pr ON pr."projectId" = p.id + LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id WHERE p.slug = ${slug} GROUP BY p.id LIMIT 1 @@ -52,7 +52,7 @@ export class PrismaProjectDevelopmentRepository implements IProjectRepository { ST_X(p.location::geometry) as lng, COUNT(pr.id)::int as "propertyCount" FROM "ProjectDevelopment" p - LEFT JOIN "Property" pr ON pr."projectId" = p.id + LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id WHERE p.id = ${id} GROUP BY p.id LIMIT 1 @@ -163,7 +163,7 @@ export class PrismaProjectDevelopmentRepository implements IProjectRepository { `SELECT p.*, ST_Y(p.location::geometry) as lat, ST_X(p.location::geometry) as lng, COUNT(pr.id)::int as "propertyCount" FROM "ProjectDevelopment" p - LEFT JOIN "Property" pr ON pr."projectId" = p.id + LEFT JOIN "Property" pr ON pr."projectDevelopmentId" = p.id WHERE ${where.replace(/\b(\$\d+)/g, (_, m) => m)} GROUP BY p.id ORDER BY p."createdAt" DESC diff --git a/apps/api/src/modules/projects/presentation/controllers/projects.controller.ts b/apps/api/src/modules/projects/presentation/controllers/projects.controller.ts index e08c82c..cd7b636 100644 --- a/apps/api/src/modules/projects/presentation/controllers/projects.controller.ts +++ b/apps/api/src/modules/projects/presentation/controllers/projects.controller.ts @@ -12,6 +12,29 @@ import { CreateProjectDto } from '../dto/create-project.dto'; import { SearchProjectsDto } from '../dto/search-projects.dto'; import { UpdateProjectDto } from '../dto/update-project.dto'; +interface RawProjectListItem { + id: string; + name: string; + slug: string; + developer: string; + developerLogo: string | null; + media?: { url: string; type?: string; order?: number }[] | null; + [k: string]: unknown; +} + +function shapeProject(row: T) { + const { developer, developerLogo, media, ...rest } = row; + const thumbnailUrl = + Array.isArray(media) && media.length > 0 ? (media[0]?.url ?? null) : null; + return { + ...rest, + developer: { id: developer, name: developer, logo: developerLogo ?? null }, + thumbnailUrl, + propertyTypes: [], + completionDate: (rest as { completionDate?: Date | null }).completionDate ?? null, + }; +} + @ApiTags('projects') @Controller('projects') export class ProjectsController { @@ -26,7 +49,10 @@ export class ProjectsController { @ApiResponse({ status: 200, description: 'Danh sách dự án phân trang' }) @Get() async listProjects(@Query() dto: SearchProjectsDto) { - return this.queryBus.execute( + const result = await this.queryBus.execute< + ListProjectsQuery, + { data: RawProjectListItem[]; total: number; page: number; limit: number; totalPages: number } + >( new ListProjectsQuery( dto.q, dto.status, @@ -38,6 +64,7 @@ export class ProjectsController { dto.limit ?? 20, ), ); + return { ...result, data: result.data.map(shapeProject) }; } @ApiOperation({ summary: 'Chi tiết dự án', description: 'Xem chi tiết dự án theo slug hoặc ID' }) @@ -45,11 +72,13 @@ export class ProjectsController { @ApiResponse({ status: 404, description: 'Không tìm thấy dự án' }) @Get(':slugOrId') async getProject(@Param('slugOrId') slugOrId: string) { - const result = await this.queryBus.execute(new GetProjectQuery(slugOrId)); + const result = await this.queryBus.execute( + new GetProjectQuery(slugOrId), + ); if (!result) { throw new NotFoundException('Dự án', slugOrId); } - return result; + return shapeProject(result); } // ── Admin endpoints ───────────────────────────────────────────────