WIP - Added pagination for list view

This commit is contained in:
Francisco Gaona
2026-01-13 09:03:11 +01:00
parent 730fddd181
commit 47fa72451d
9 changed files with 357 additions and 22 deletions

View File

@@ -576,11 +576,48 @@ export class ObjectService {
}
}
// Apply additional filters
if (filters) {
query = query.where(filters);
// Extract pagination and sorting parameters from query string
const {
page,
pageSize,
sortField,
sortDirection,
...rawFilters
} = filters || {};
const reservedFilterKeys = new Set(['page', 'pageSize', 'sortField', 'sortDirection']);
const filterEntries = Object.entries(rawFilters || {}).filter(
([key, value]) =>
!reservedFilterKeys.has(key) &&
value !== undefined &&
value !== null &&
value !== '',
);
if (filterEntries.length > 0) {
query = query.where(builder => {
for (const [key, value] of filterEntries) {
builder.where(key, value as any);
}
});
}
if (sortField) {
query = query.orderBy(sortField, sortDirection === 'desc' ? 'desc' : 'asc');
}
const parsedPage = Number.isFinite(Number(page)) ? Number(page) : 1;
const parsedPageSize = Number.isFinite(Number(pageSize)) ? Number(pageSize) : 0;
const safePage = parsedPage > 0 ? parsedPage : 1;
const safePageSize = parsedPageSize > 0 ? Math.min(parsedPageSize, 500) : 0;
const shouldPaginate = safePageSize > 0;
const totalCount = await query.clone().resultSize();
if (shouldPaginate) {
query = query.offset((safePage - 1) * safePageSize).limit(safePageSize);
}
const records = await query.select('*');
// Filter fields based on field-level permissions
@@ -590,7 +627,12 @@ export class ObjectService {
)
);
return filteredRecords;
return {
data: filteredRecords,
totalCount,
page: shouldPaginate ? safePage : 1,
pageSize: shouldPaginate ? safePageSize : filteredRecords.length,
};
}
async getRecord(
@@ -952,6 +994,73 @@ export class ObjectService {
return { success: true };
}
async deleteRecords(
tenantId: string,
objectApiName: string,
recordIds: string[],
userId: string,
) {
if (!Array.isArray(recordIds) || recordIds.length === 0) {
throw new BadRequestException('No record IDs provided');
}
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
const knex = await this.tenantDbService.getTenantKnexById(resolvedTenantId);
// Get user with roles and permissions
const user = await User.query(knex)
.findById(userId)
.withGraphFetched('[roles.[objectPermissions, fieldPermissions]]');
if (!user) {
throw new NotFoundException('User not found');
}
// Get object definition with authorization settings
const objectDefModel = await ObjectDefinition.query(knex)
.findOne({ apiName: objectApiName });
if (!objectDefModel) {
throw new NotFoundException(`Object ${objectApiName} not found`);
}
const tableName = this.getTableName(
objectDefModel.apiName,
objectDefModel.label,
objectDefModel.pluralLabel,
);
const records = await knex(tableName).whereIn('id', recordIds);
if (records.length === 0) {
throw new NotFoundException('No records found to delete');
}
const foundIds = new Set(records.map((record: any) => record.id));
const missingIds = recordIds.filter(id => !foundIds.has(id));
if (missingIds.length > 0) {
throw new NotFoundException(`Records not found: ${missingIds.join(', ')}`);
}
// Check if user can delete each record
for (const record of records) {
await this.authService.assertCanPerformAction('delete', objectDefModel, record, user, knex);
}
// Ensure model is registered
await this.ensureModelRegistered(resolvedTenantId, objectApiName, objectDefModel);
// Use Objection model
const boundModel = await this.modelService.getBoundModel(resolvedTenantId, objectApiName);
await boundModel.query().whereIn('id', recordIds).delete();
// Remove from search index
await Promise.all(
recordIds.map((id) => this.removeIndexedRecord(resolvedTenantId, objectApiName, id)),
);
return { success: true, deleted: recordIds.length };
}
private async indexRecord(
tenantId: string,
objectApiName: string,

View File

@@ -95,4 +95,20 @@ export class RuntimeObjectController {
user.userId,
);
}
@Post(':objectApiName/records/bulk-delete')
async deleteRecords(
@TenantId() tenantId: string,
@Param('objectApiName') objectApiName: string,
@Body() body: { recordIds?: string[]; ids?: string[] },
@CurrentUser() user: any,
) {
const recordIds: string[] = body?.recordIds || body?.ids || [];
return this.objectService.deleteRecords(
tenantId,
objectApiName,
recordIds,
user.userId,
);
}
}