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,10 +4,12 @@ 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 { DateTime } from '../../../common/value-objects/date-time/date-time';
|
||||
import { Status } from '../../../common/value-objects/status/status';
|
||||
import { DRIZZLE, type DrizzleDB } from '../../../database/database.module';
|
||||
import { attachAuditUsers } from '../../../database/load-user-refs';
|
||||
import { divisions, type DivisionRow } from '../../../database/schema';
|
||||
import type {
|
||||
CreateDivisionInput,
|
||||
@@ -16,6 +18,15 @@ import type {
|
||||
UpdateDivisionInput,
|
||||
} from './division';
|
||||
|
||||
const DIVISION_ORDER_COLUMNS = {
|
||||
id: divisions.id,
|
||||
name: divisions.name,
|
||||
code: divisions.code,
|
||||
status: divisions.status,
|
||||
createdAt: divisions.createdAt,
|
||||
updatedAt: divisions.updatedAt,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class DivisionsRepository {
|
||||
constructor(@Inject(DRIZZLE) private readonly db: DrizzleDB) {}
|
||||
@@ -34,12 +45,16 @@ export class DivisionsRepository {
|
||||
qb = this.extendListQuery(qb, filters);
|
||||
const rows = await qb
|
||||
.where(where)
|
||||
.orderBy(asc(divisions.code))
|
||||
.orderBy(
|
||||
...toOrderClauses(DIVISION_ORDER_COLUMNS, filters, [
|
||||
{ column: 'code', type: 'ASC' },
|
||||
]),
|
||||
)
|
||||
.limit(filters.limit)
|
||||
.offset(filters.offset);
|
||||
|
||||
return {
|
||||
data: rows.map((row) => this.toDomain(row)),
|
||||
data: await this.hydrate(rows),
|
||||
total: Number(totalRow?.total ?? 0),
|
||||
};
|
||||
}
|
||||
@@ -59,7 +74,7 @@ export class DivisionsRepository {
|
||||
.where(eq(divisions.id, id))
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
return row ? this.toDomain(row) : null;
|
||||
return row ? this.hydrateOne(row) : null;
|
||||
}
|
||||
|
||||
async findByCode(code: string): Promise<Division | null> {
|
||||
@@ -69,7 +84,7 @@ export class DivisionsRepository {
|
||||
.where(eq(divisions.code, code))
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
return row ? this.toDomain(row) : null;
|
||||
return row ? this.hydrateOne(row) : null;
|
||||
}
|
||||
|
||||
async create(input: CreateDivisionInput): Promise<Division> {
|
||||
@@ -89,7 +104,7 @@ export class DivisionsRepository {
|
||||
})
|
||||
.returning();
|
||||
const row = inserted[0];
|
||||
return this.toDomain(row);
|
||||
return this.hydrateOne(row);
|
||||
} catch (error) {
|
||||
this.rethrowUniqueViolation(error);
|
||||
}
|
||||
@@ -139,7 +154,7 @@ export class DivisionsRepository {
|
||||
.where(eq(divisions.id, id))
|
||||
.returning();
|
||||
const row = updated[0];
|
||||
return this.toDomain(row);
|
||||
return this.hydrateOne(row);
|
||||
} catch (error) {
|
||||
this.rethrowUniqueViolation(error);
|
||||
}
|
||||
@@ -164,7 +179,7 @@ export class DivisionsRepository {
|
||||
if (!row) {
|
||||
throw new NotFoundException('Division not found');
|
||||
}
|
||||
return this.toDomain(row);
|
||||
return this.hydrateOne(row);
|
||||
}
|
||||
|
||||
async bulkUpdateStatus(
|
||||
@@ -243,17 +258,25 @@ export class DivisionsRepository {
|
||||
return parts.length === 1 ? parts[0] : and(...parts);
|
||||
}
|
||||
|
||||
private toDomain(row: DivisionRow): Division {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
status: Status.create(row.status),
|
||||
createdAt: DateTime.fromUnixMs(row.createdAt),
|
||||
updatedAt: DateTime.fromUnixMs(row.updatedAt),
|
||||
createdBy: row.createdBy,
|
||||
updatedBy: row.updatedBy,
|
||||
};
|
||||
private async hydrate(rows: DivisionRow[]): Promise<Division[]> {
|
||||
return attachAuditUsers(
|
||||
this.db,
|
||||
rows.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
status: Status.create(row.status),
|
||||
createdAt: DateTime.fromUnixMs(row.createdAt),
|
||||
updatedAt: DateTime.fromUnixMs(row.updatedAt),
|
||||
createdBy: row.createdBy,
|
||||
updatedBy: row.updatedBy,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
private async hydrateOne(row: DivisionRow): Promise<Division> {
|
||||
const [item] = await this.hydrate([row]);
|
||||
return item;
|
||||
}
|
||||
|
||||
private rethrowUniqueViolation(error: unknown): never {
|
||||
|
||||
Reference in New Issue
Block a user