feat: added cursor-based pagination to checklistitems
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsInt, IsISO8601, IsOptional, Max, Min } from "class-validator";
|
||||
|
||||
export class CursorPaginationDto {
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@ApiPropertyOptional({ type: "string", description: "Cursor (createdAt of last seen item)", example: "2026-07-01T10:00:00.000Z" })
|
||||
cursor?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(50)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ type: "number", required: false, default: 10 })
|
||||
limit?: number;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { CursorPaginatedResult } from "../interfaces/cursor-paginated-result.interface";
|
||||
|
||||
export function buildCursorPageFormat<T extends { createdAt: Date }>(
|
||||
data: T[],
|
||||
limit: number,
|
||||
previousCursor?: Date,
|
||||
): CursorPaginatedResult<T> {
|
||||
const hasMore = data.length > limit;
|
||||
|
||||
if (hasMore) data.pop();
|
||||
|
||||
const nextCursor = hasMore ? data[data.length - 1].createdAt.toISOString() : null;
|
||||
|
||||
const prevCursor = previousCursor ? previousCursor.toISOString() : null;
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
nextCursor,
|
||||
prevCursor,
|
||||
hasMore,
|
||||
limit,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface CursorPaginatedResult<T> {
|
||||
data: T[];
|
||||
meta: {
|
||||
nextCursor: string | null;
|
||||
prevCursor: string | null;
|
||||
hasMore: boolean;
|
||||
limit: number;
|
||||
};
|
||||
}
|
||||
@@ -1,56 +1,59 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||
import { CheckListItemService } from '../providers/check-list-item.service';
|
||||
import { CreateCheckListItemDto } from '../dto/check-list-item/create-check-list-item.dto';
|
||||
import { UpdateCheckListItemDto } from '../dto/check-list-item/update-check-list-item.dto';
|
||||
import { TMCheckListItem } from '../entities/check-list-item.entity';
|
||||
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
|
||||
import { CheckListItemService } from "../providers/check-list-item.service";
|
||||
import { CreateCheckListItemDto } from "../dto/check-list-item/create-check-list-item.dto";
|
||||
import { UpdateCheckListItemDto } from "../dto/check-list-item/update-check-list-item.dto";
|
||||
import { CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
||||
import { TMCheckListItem } from "../entities/check-list-item.entity";
|
||||
|
||||
@ApiTags('Task Manager - Check List Items')
|
||||
@Controller('task-manager/check-list-items')
|
||||
@ApiTags("Task Manager - Check List Items")
|
||||
@Controller("task-manager/check-list-items")
|
||||
export class CheckListItemController {
|
||||
constructor(private readonly checkListItemService: CheckListItemService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Get all check list items, optionally filtered by task' })
|
||||
@ApiQuery({ name: 'taskId', required: false, description: 'Filter check list items by task ID' })
|
||||
@ApiResponse({ status: 200, description: 'List of check list items', type: [TMCheckListItem] })
|
||||
findAll(@Query('taskId') taskId?: string): Promise<TMCheckListItem[]> {
|
||||
if (taskId) return this.checkListItemService.findByTask(taskId);
|
||||
return this.checkListItemService.findAll();
|
||||
@ApiOperation({ summary: "Get all check list items, optionally filtered by task" })
|
||||
@ApiQuery({ name: "taskId", required: false, description: "Filter check list items by task ID" })
|
||||
@ApiQuery({ name: "cursor", required: false, description: "Cursor for pagination (createdAt ISO string)" })
|
||||
@ApiQuery({ name: "limit", required: false, description: "Number of records to return (max 50)" })
|
||||
@ApiResponse({ status: 200, description: "Cursor-paginated list of check list items" })
|
||||
findAll(@Query() pagination: CursorPaginationDto, @Query("taskId") taskId?: string) {
|
||||
if (taskId) return this.checkListItemService.findByTask(taskId, pagination);
|
||||
return this.checkListItemService.findAll(pagination);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get a check list item by ID' })
|
||||
@ApiParam({ name: 'id', description: 'CheckListItem ID' })
|
||||
@ApiResponse({ status: 200, description: 'CheckListItem found', type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: 'CheckListItem not found' })
|
||||
findOne(@Param('id') id: string): Promise<TMCheckListItem> {
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a check list item by ID" })
|
||||
@ApiParam({ name: "id", description: "CheckListItem ID" })
|
||||
@ApiResponse({ status: 200, description: "CheckListItem found", type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: "CheckListItem not found" })
|
||||
findOne(@Param("id") id: string): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.findOne(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a new check list item' })
|
||||
@ApiResponse({ status: 201, description: 'CheckListItem created', type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: 'Task not found' })
|
||||
@ApiOperation({ summary: "Create a new check list item" })
|
||||
@ApiResponse({ status: 201, description: "CheckListItem created", type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
create(@Body() body: CreateCheckListItemDto): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.create(body);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: 'Update a check list item' })
|
||||
@ApiParam({ name: 'id', description: 'CheckListItem ID' })
|
||||
@ApiResponse({ status: 200, description: 'CheckListItem updated', type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: 'CheckListItem or Task not found' })
|
||||
update(@Param('id') id: string, @Body() body: UpdateCheckListItemDto): Promise<TMCheckListItem> {
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "Update a check list item" })
|
||||
@ApiParam({ name: "id", description: "CheckListItem ID" })
|
||||
@ApiResponse({ status: 200, description: "CheckListItem updated", type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: "CheckListItem or Task not found" })
|
||||
update(@Param("id") id: string, @Body() body: UpdateCheckListItemDto): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.update(id, body);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete a check list item' })
|
||||
@ApiParam({ name: 'id', description: 'CheckListItem ID' })
|
||||
@ApiResponse({ status: 200, description: 'CheckListItem deleted' })
|
||||
@ApiResponse({ status: 404, description: 'CheckListItem not found' })
|
||||
remove(@Param('id') id: string): Promise<void> {
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Delete a check list item" })
|
||||
@ApiParam({ name: "id", description: "CheckListItem ID" })
|
||||
@ApiResponse({ status: 200, description: "CheckListItem deleted", type: TMCheckListItem })
|
||||
@ApiResponse({ status: 404, description: "CheckListItem not found" })
|
||||
remove(@Param("id") id: string): Promise<TMCheckListItem> {
|
||||
return this.checkListItemService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { CheckListItemRepository } from "../repositories/check-list-item.repository";
|
||||
import { TaskRepository } from "../repositories/task.repository";
|
||||
import { TMCheckListItem } from "../entities/check-list-item.entity";
|
||||
import { CreateCheckListItemDto } from "../dto/check-list-item/create-check-list-item.dto";
|
||||
import { UpdateCheckListItemDto } from "../dto/check-list-item/update-check-list-item.dto";
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CheckListItemRepository } from '../repositories/check-list-item.repository';
|
||||
import { TaskRepository } from '../repositories/task.repository';
|
||||
import { TMCheckListItem } from '../entities/check-list-item.entity';
|
||||
import { CreateCheckListItemDto } from '../dto/check-list-item/create-check-list-item.dto';
|
||||
import { UpdateCheckListItemDto } from '../dto/check-list-item/update-check-list-item.dto';
|
||||
import { CursorPaginationDto } from '../../../common/DTO/cursor-pagination.dto';
|
||||
import { CursorPaginatedResult } from '../../../common/interfaces/cursor-paginated-result.interface';
|
||||
import { buildCursorPageFormat } from '../../../common/helpers/cursor-paginated.helper';
|
||||
|
||||
@Injectable()
|
||||
export class CheckListItemService {
|
||||
@@ -12,12 +15,25 @@ export class CheckListItemService {
|
||||
private readonly taskRepository: TaskRepository,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMCheckListItem[]> {
|
||||
return this.checkListItemRepository.findAll();
|
||||
async findAll(pagination: CursorPaginationDto): Promise<CursorPaginatedResult<TMCheckListItem>> {
|
||||
const limit = pagination.limit ?? 10;
|
||||
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
|
||||
|
||||
const [data] = await this.checkListItemRepository.findAll(limit, cursorDate);
|
||||
|
||||
return buildCursorPageFormat(data, limit, cursorDate);
|
||||
}
|
||||
|
||||
findByTask(taskId: string): Promise<TMCheckListItem[]> {
|
||||
return this.checkListItemRepository.findByTask(taskId);
|
||||
async findByTask(
|
||||
taskId: string,
|
||||
pagination: CursorPaginationDto,
|
||||
): Promise<CursorPaginatedResult<TMCheckListItem>> {
|
||||
const limit = pagination.limit ?? 10;
|
||||
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
|
||||
|
||||
const [data] = await this.checkListItemRepository.findByTask(taskId, limit, cursorDate);
|
||||
|
||||
return buildCursorPageFormat(data, limit, cursorDate);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMCheckListItem> {
|
||||
@@ -46,8 +62,9 @@ export class CheckListItemService {
|
||||
return this.checkListItemRepository.save(checkListItem);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOne(id);
|
||||
async remove(id: string): Promise<TMCheckListItem> {
|
||||
const checkListItem = await this.findOne(id);
|
||||
await this.checkListItemRepository.delete(id);
|
||||
return checkListItem;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Repository, MoreThan } from 'typeorm';
|
||||
import { TMCheckListItem } from '../entities/check-list-item.entity';
|
||||
|
||||
@Injectable()
|
||||
@@ -10,15 +10,23 @@ export class CheckListItemRepository {
|
||||
private readonly repository: Repository<TMCheckListItem>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
findAll(limit: number, cursor?: Date) {
|
||||
return this.repository.findAndCount({
|
||||
where: cursor ? { createdAt: MoreThan(cursor) } : {},
|
||||
order: { createdAt: 'ASC' },
|
||||
take: limit + 1,
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
findByTask(taskId: string) {
|
||||
return this.repository.find({
|
||||
where: { taskId },
|
||||
findByTask(taskId: string, limit: number, cursor?: Date) {
|
||||
return this.repository.findAndCount({
|
||||
where: cursor
|
||||
? { taskId, createdAt: MoreThan(cursor) }
|
||||
: { taskId },
|
||||
order: { createdAt: 'ASC' },
|
||||
take: limit + 1,
|
||||
relations: ['task'],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +37,13 @@ export class CheckListItemRepository {
|
||||
});
|
||||
}
|
||||
|
||||
findOneByCreatedAt(createdAt: Date) {
|
||||
return this.repository.findOne({
|
||||
where: { createdAt },
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMCheckListItem>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user