fix(projects): replace \$queryRawUnsafe with Prisma.sql tagged templates in search

- Replace both \$queryRawUnsafe calls in search() with \$queryRaw + Prisma.sql/Prisma.join
- Remove no-op .replace() regex on positional parameters (A-23)
- Change import type { Prisma } to import { Prisma } so Prisma.sql/Prisma.join
  are available as runtime values

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-22 23:39:29 +07:00
parent 7e2ccdfb7c
commit 23af73496d

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import { Prisma } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { ProjectDevelopmentEntity } from '../../domain/entities/project-development.entity';
import type {
@@ -132,58 +132,49 @@ export class PrismaProjectDevelopmentRepository implements IProjectRepository {
const limit = params.limit ?? 20;
const offset = (page - 1) * limit;
const conditions: string[] = ['1=1'];
const values: unknown[] = [];
let paramIndex = 1;
const clauses: Prisma.Sql[] = [Prisma.sql`1=1`];
if (params.status) {
conditions.push(`status = $${paramIndex++}::"ProjectDevelopmentStatus"`);
values.push(params.status);
clauses.push(Prisma.sql`status = ${params.status}::"ProjectDevelopmentStatus"`);
}
if (params.city) {
conditions.push(`city = $${paramIndex++}`);
values.push(params.city);
clauses.push(Prisma.sql`city = ${params.city}`);
}
if (params.district) {
conditions.push(`district = $${paramIndex++}`);
values.push(params.district);
clauses.push(Prisma.sql`district = ${params.district}`);
}
if (params.developer) {
conditions.push(`developer ILIKE $${paramIndex++}`);
values.push(`%${params.developer}%`);
clauses.push(Prisma.sql`developer ILIKE ${'%' + params.developer + '%'}`);
}
if (params.isVerified !== undefined) {
conditions.push(`"isVerified" = $${paramIndex++}`);
values.push(params.isVerified);
clauses.push(Prisma.sql`"isVerified" = ${params.isVerified}`);
}
if (params.ownerId) {
conditions.push(`"ownerId" = $${paramIndex++}`);
values.push(params.ownerId);
clauses.push(Prisma.sql`"ownerId" = ${params.ownerId}`);
}
if (params.query) {
conditions.push(`(name ILIKE $${paramIndex} OR developer ILIKE $${paramIndex} OR district ILIKE $${paramIndex} OR city ILIKE $${paramIndex})`);
values.push(`%${params.query}%`);
paramIndex++;
const like = `%${params.query}%`;
clauses.push(Prisma.sql`(name ILIKE ${like} OR developer ILIKE ${like} OR district ILIKE ${like} OR city ILIKE ${like})`);
}
const where = conditions.join(' AND ');
const where = Prisma.join(clauses, ' AND ');
const countResult = await this.prisma.$queryRawUnsafe<[{ count: bigint }]>(
`SELECT COUNT(*)::bigint as count FROM "ProjectDevelopment" WHERE ${where}`,
...values,
const countResult = await this.prisma.$queryRaw<[{ count: bigint }]>(
Prisma.sql`SELECT COUNT(*)::bigint as count FROM "ProjectDevelopment" WHERE ${where}`,
);
const total = Number(countResult[0].count);
const rows = await this.prisma.$queryRawUnsafe<RawProjectDetail[]>(
`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."projectDevelopmentId" = p.id
WHERE ${where.replace(/\b(\$\d+)/g, (_, m) => m)}
GROUP BY p.id
ORDER BY p."createdAt" DESC
LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
...values, limit, offset,
const rows = await this.prisma.$queryRaw<RawProjectDetail[]>(
Prisma.sql`
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."projectDevelopmentId" = p.id
WHERE ${where}
GROUP BY p.id
ORDER BY p."createdAt" DESC
LIMIT ${limit} OFFSET ${offset}
`,
);
return {