Farm Owner Assignment Design
Date: 2026-04-23
Status: Approved
Business Logic
- Admin tạo trang trại (Area), sau đó giao cho Owner quản lý
- Admin có thể tạo account Owner, Staff và assign cho trang trại/ao
- Owner: giao theo trang trại (Area) → tự động có quyền truy cập tất cả ao trong trang trại đó
- Staff: giao theo từng ao (Pond) riêng lẻ → giữ nguyên behavior hiện tại
- Quan hệ Area ↔ Owner là many-to-many (1 trang trại có nhiều Owner, 1 Owner quản lý nhiều trang trại)
Current State
Đã có:
pond_usersjunction table — Staff assignment per pondUserRole.assignedResources— pond IDs trong JWT cho RBACAuthorizationPolicyService.applyReadScopeToQueryBuilder— filter theoassignedResources
Thiếu:
- Cơ chế gán Owner vào Area (trang trại)
- Area-level access control cho Owner
Design
1. Database
Bảng mới area_users — junction table (many-to-many, giống pond_users):
CREATE TABLE area_users (
area_id UUID NOT NULL REFERENCES areas(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY (area_id, user_id)
);
2. Area Entity
Thêm relation vào farm/entities/area.entity.ts:
@ManyToMany(() => User)
@JoinTable({
name: 'area_users',
joinColumn: { name: 'area_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'user_id', referencedColumnName: 'id' },
})
assignedOwners: User[];
3. JWT & Auth Token
Chiến lược: Khi tạo JWT (login/refresh), expand area assignments thành pond IDs và merge vào assignedResources. Đây là cách tiếp cận zero-change với AuthorizationPolicyService — mọi resource by-pond (cycle, device, daily-log…) tự hoạt động đúng mà không cần sửa gì thêm.
CurrentUserData — thêm field:
assignedAreaIds?: string[]; // để UI hiển thị và audit
auth.service.ts — khi tạo token:
// 1. Lấy area IDs của user
const assignedAreas = await this.areaRepository
.createQueryBuilder('area')
.innerJoin('area.assignedOwners', 'user', 'user.id = :userId', { userId: user.id })
.select(['area.id'])
.getMany();
const assignedAreaIds = assignedAreas.map(a => a.id);
// 2. Expand thành pond IDs
const pondsFromAreas = assignedAreaIds.length > 0
? await this.pondRepository.find({ where: { areaId: In(assignedAreaIds) }, select: ['id'] })
: [];
// 3. Merge vào assignedResources (dedup)
assignedResources.push(...pondsFromAreas.map(p => p.id));
// assignedResources = [...new Set(assignedResources)] — dedup đã có sẵn
// 4. Thêm vào payload
payload.assignedAreaIds = assignedAreaIds;
jwt.strategy.ts — map thêm:
assignedAreaIds: (payload.assignedAreaIds as string[]) || [],
Trade-off: Khi Admin thêm ao mới vào trang trại, Owner phải re-login để có quyền ao mới. Đây là behavior nhất quán với toàn bộ assignedResources hiện tại.
4. API Endpoints
POST /api/v1/areas/:id/assign-owner # Gán Owner vào trang trại
DELETE /api/v1/areas/:id/assign-owner/:userId # Gỡ Owner khỏi trang trại
GET /api/v1/areas/:id/owners # Danh sách Owners của trang trại
Permission: area:update:all (Admin only) cho assign/unassign; area:read:all hoặc area:read:assigned cho list owners.
Business rules:
- User được gán phải thuộc cùng organization
- Gán trùng →
400 Bad Request - Gỡ user không được gán →
404 Not Found
Response GET /areas/:id/owners:
{
"data": [
{ "id": "uuid", "fullName": "Nguyen Van A", "email": "a@example.com" }
]
}
5. DTO
File mới farm/dto/assign-area.dto.ts:
export class AssignOwnerDto {
@ApiProperty({ example: 'uuid' })
@IsUUID()
@IsNotEmpty()
userId: string;
}
6. Files thay đổi
| File | Thay đổi |
|---|---|
farm/entities/area.entity.ts |
Thêm assignedOwners ManyToMany |
auth/decorators/current-user.decorator.ts |
Thêm assignedAreaIds?: string[] |
auth/auth.service.ts |
Query area_users + expand pond IDs khi tạo token (2 nơi: login + refresh) |
auth/strategies/jwt.strategy.ts |
Map assignedAreaIds từ JWT payload |
farm/areas.service.ts |
Thêm assignOwner, unassignOwner, getOwners |
farm/controllers/areas.controller.ts |
Thêm 3 endpoints mới |
farm/dto/assign-area.dto.ts |
DTO mới |
farm/dto/index.ts |
Export DTO mới |
migrations/TIMESTAMP-AddAreaUsers.ts |
Tạo bảng area_users |
Không thay đổi: authorization-policy.service.ts, ponds.service.ts, user-role.service.ts, resource scope map.
Out of Scope
- Frontend UI cho farm assignment (spec riêng)
- Notification khi Owner được gán vào trang trại
- Tự động gán Staff khi thêm ao mới (Staff vẫn giao thủ công từng ao)