Enhance pagination and ordering capabilities in API responses
- Updated pagination-response and read-write-controllers documentation to include `orderBy` and `orderType` parameters for sorting results. - Introduced new `order-clause` module to handle ordering logic, including validation for order types and columns. - Enhanced `PaginationQueryDto` to support ordering fields in API requests. - Updated various repository and service classes to implement ordering in database queries. - Added unit tests for new ordering functionality and ensured existing tests cover the updated behavior. - Refactored related DTOs to include user and code relations for better data representation in responses.
This commit is contained in:
@@ -4,7 +4,9 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { and, asc, count, eq, ilike, inArray, or, SQL } from 'drizzle-orm';
|
||||
import { and, count, eq, ilike, inArray, or, SQL } from 'drizzle-orm';
|
||||
import { toOrderClauses } from '../../../common/http/response';
|
||||
import { attachAuditUsers } from '../../../database/load-user-refs';
|
||||
import { DateTime } from '../../../common/value-objects/date-time/date-time';
|
||||
import { PhoneNumber } from '../../../common/value-objects/phone-number/phone-number';
|
||||
import { Status } from '../../../common/value-objects/status/status';
|
||||
@@ -19,6 +21,17 @@ import type {
|
||||
UpdateEmployeeInput,
|
||||
} from './employee';
|
||||
|
||||
const EMPLOYEE_ORDER_COLUMNS = {
|
||||
id: employees.id,
|
||||
code: employees.code,
|
||||
name: employees.name,
|
||||
phone: employees.phone,
|
||||
position: employees.position,
|
||||
status: employees.status,
|
||||
createdAt: employees.createdAt,
|
||||
updatedAt: employees.updatedAt,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class EmployeesRepository {
|
||||
constructor(@Inject(DRIZZLE) private readonly db: DrizzleDB) {}
|
||||
@@ -37,16 +50,21 @@ export class EmployeesRepository {
|
||||
qb = this.extendListQuery(qb, filters);
|
||||
const rows = await qb
|
||||
.where(where)
|
||||
.orderBy(asc(employees.code))
|
||||
.orderBy(
|
||||
...toOrderClauses(EMPLOYEE_ORDER_COLUMNS, filters, [
|
||||
{ column: 'code', type: 'ASC' },
|
||||
]),
|
||||
)
|
||||
.limit(filters.limit)
|
||||
.offset(filters.offset);
|
||||
|
||||
return {
|
||||
data: await Promise.all(
|
||||
rows.map(async (row) =>
|
||||
this.toDomain(row, await this.loadAssignedUser(row.userId)),
|
||||
),
|
||||
const mapped = await Promise.all(
|
||||
rows.map(async (row) =>
|
||||
this.hydrateOne(row, await this.loadAssignedUser(row.userId)),
|
||||
),
|
||||
);
|
||||
return {
|
||||
data: mapped,
|
||||
total: Number(totalRow?.total ?? 0),
|
||||
};
|
||||
}
|
||||
@@ -67,7 +85,7 @@ export class EmployeesRepository {
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
return row
|
||||
? this.toDomain(row, await this.loadAssignedUser(row.userId))
|
||||
? this.hydrateOne(row, await this.loadAssignedUser(row.userId))
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -79,7 +97,7 @@ export class EmployeesRepository {
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
return row
|
||||
? this.toDomain(row, await this.loadAssignedUser(row.userId))
|
||||
? this.hydrateOne(row, await this.loadAssignedUser(row.userId))
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -91,7 +109,7 @@ export class EmployeesRepository {
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
return row
|
||||
? this.toDomain(row, await this.loadAssignedUser(row.userId))
|
||||
? this.hydrateOne(row, await this.loadAssignedUser(row.userId))
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -104,7 +122,7 @@ export class EmployeesRepository {
|
||||
.values(this.toInsertValues(input, status, now, input.userId))
|
||||
.returning();
|
||||
const row = inserted[0];
|
||||
return this.toDomain(row, await this.loadAssignedUser(row.userId));
|
||||
return this.hydrateOne(row, await this.loadAssignedUser(row.userId));
|
||||
} catch (error) {
|
||||
this.rethrowUniqueViolation(error);
|
||||
}
|
||||
@@ -153,7 +171,7 @@ export class EmployeesRepository {
|
||||
.where(eq(employees.id, id))
|
||||
.returning();
|
||||
const row = updated[0];
|
||||
return this.toDomain(row, await this.loadAssignedUser(row.userId));
|
||||
return this.hydrateOne(row, await this.loadAssignedUser(row.userId));
|
||||
} catch (error) {
|
||||
this.rethrowUniqueViolation(error);
|
||||
}
|
||||
@@ -178,7 +196,7 @@ export class EmployeesRepository {
|
||||
if (!row) {
|
||||
throw new NotFoundException('Employee not found');
|
||||
}
|
||||
return this.toDomain(row, await this.loadAssignedUser(row.userId));
|
||||
return this.hydrateOne(row, await this.loadAssignedUser(row.userId));
|
||||
}
|
||||
|
||||
async bulkUpdateStatus(
|
||||
@@ -308,7 +326,7 @@ export class EmployeesRepository {
|
||||
private toDomain(
|
||||
row: EmployeeRow,
|
||||
user: { id: string; username: string } | null,
|
||||
): Employee {
|
||||
) {
|
||||
return {
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
@@ -325,6 +343,14 @@ export class EmployeesRepository {
|
||||
};
|
||||
}
|
||||
|
||||
private async hydrateOne(
|
||||
row: EmployeeRow,
|
||||
user: { id: string; username: string } | null,
|
||||
): Promise<Employee> {
|
||||
const [item] = await attachAuditUsers(this.db, [this.toDomain(row, user)]);
|
||||
return item;
|
||||
}
|
||||
|
||||
private rethrowUniqueViolation(error: unknown): never {
|
||||
const err = this.unwrapDbError(error);
|
||||
if (err.code === '23505') {
|
||||
|
||||
Reference in New Issue
Block a user