feat(F-006): users profile, addresses and RBAC
- users module: profile + address CRUD behind use cases (users_profiles, users_addresses) - roles customer/admin on identity_users; role resolved from DB per request - shared auth contract (Authenticate, requireRole, requireOwnerOrAdmin) injected from composition root; users never imports identity - authorization runs before existence checks; address SQL scoped by user_id - @fastify/cookie registered once at app root (cross-module) - migrations 003_identity_roles + 004_users (reversible) - no new npm dependencies; tests: unit 52, integration 22 Gates: reviewer/security/qa APPROVED; verify.sh green
This commit is contained in:
22
project/src/modules/users/domain/ports.ts
Normal file
22
project/src/modules/users/domain/ports.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Ports (driven interfaces). Domain owns them; infrastructure implements them.
|
||||
* All operations are scoped by userId so ownership is enforced in the query.
|
||||
*/
|
||||
import type { Profile, ProfilePatch } from './profile.js';
|
||||
import type { Address, AddressPatch, NewAddress } from './address.js';
|
||||
|
||||
export interface ProfileRepository {
|
||||
findByUserId(userId: string): Promise<Profile | undefined>;
|
||||
/** Idempotent upsert; only provided fields change. */
|
||||
upsert(userId: string, patch: ProfilePatch): Promise<Profile>;
|
||||
list(): Promise<Profile[]>;
|
||||
}
|
||||
|
||||
export interface AddressRepository {
|
||||
listByUserId(userId: string): Promise<Address[]>;
|
||||
create(userId: string, input: NewAddress): Promise<Address>;
|
||||
/** Returns undefined when the address does not belong to userId. */
|
||||
update(userId: string, addressId: string, patch: AddressPatch): Promise<Address | undefined>;
|
||||
/** Returns false when the address does not belong to userId. */
|
||||
delete(userId: string, addressId: string): Promise<boolean>;
|
||||
}
|
||||
Reference in New Issue
Block a user