WIP - Added pagination for list view
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user