feat(listings): allow admin to PATCH /listings/:id (TEC-2746)

- UpdateListingCommand accepts userRole; ADMIN bypasses owner/agent check
- Controller forwards user.role from JwtPayload
- Adds unit test covering admin-authorized edit path

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-18 01:17:06 +07:00
parent 632efbe2c6
commit 0cd3dc82fd
4 changed files with 30 additions and 4 deletions

View File

@@ -137,7 +137,30 @@ describe('UpdateListingHandler', () => {
const command = new UpdateListingCommand('listing-1', 'stranger', 'Tiêu đề mới');
await expect(handler.execute(command)).rejects.toThrow(/người bán|môi giới/);
await expect(handler.execute(command)).rejects.toThrow(/người bán|môi giới|quản trị/);
});
it('allows an admin to update any listing', async () => {
const listing = createListing('listing-1', 'seller-1');
const property = createProperty('prop-1');
mockListingRepo.findById.mockResolvedValue(listing);
mockPropertyRepo.findById.mockResolvedValue(property);
const command = new UpdateListingCommand(
'listing-1',
'admin-user',
'Tiêu đề do admin sửa',
undefined,
undefined,
undefined,
undefined,
undefined,
'ADMIN',
);
const result = await handler.execute(command);
expect(result.listingId).toBe('listing-1');
expect(result.updatedFields).toContain('title');
});
});

View File

@@ -8,5 +8,6 @@ export class UpdateListingCommand {
public readonly rentPriceMonthly?: bigint,
public readonly amenities?: string[],
public readonly mediaOrder?: { mediaId: string; order: number }[],
public readonly userRole?: string,
) {}
}

View File

@@ -38,12 +38,13 @@ export class UpdateListingHandler implements ICommandHandler<UpdateListingComman
throw new NotFoundException('Listing', command.listingId);
}
// 2. Ownership check: only the seller or assigned agent can edit
// 2. Ownership check: only the seller, assigned agent, or admin can edit
const isOwner = listing.sellerId === command.userId;
const isAgent = listing.agentId !== null && listing.agentId === command.userId;
if (!isOwner && !isAgent) {
const isAdmin = command.userRole === 'ADMIN';
if (!isOwner && !isAgent && !isAdmin) {
throw new ForbiddenException(
'Chỉ người bán hoặc môi giới được giao mới có thể chỉnh sửa tin đăng',
'Chỉ người bán, môi giới được giao hoặc quản trị viên mới có thể chỉnh sửa tin đăng',
);
}

View File

@@ -231,6 +231,7 @@ export class ListingsController {
dto.rentPriceMonthly,
dto.amenities,
dto.mediaOrder,
user.role,
),
);
}