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:
19
project/migrations/003_identity_roles.js
Normal file
19
project/migrations/003_identity_roles.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Identity schema evolution: roles for RBAC (introduced by F-006).
|
||||
* Role is authorization truth and belongs with the account row.
|
||||
* Shipped as its own migration to keep ownership explicit.
|
||||
*/
|
||||
|
||||
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
||||
export const up = (pgm) => {
|
||||
pgm.sql(`
|
||||
ALTER TABLE identity_users
|
||||
ADD COLUMN role text NOT NULL DEFAULT 'customer'
|
||||
CHECK (role IN ('customer', 'admin'))
|
||||
`);
|
||||
};
|
||||
|
||||
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
||||
export const down = (pgm) => {
|
||||
pgm.sql('ALTER TABLE identity_users DROP COLUMN role');
|
||||
};
|
||||
40
project/migrations/004_users.js
Normal file
40
project/migrations/004_users.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Users module tables. Module-owned naming: users_<table>.
|
||||
* FKs to identity_users are schema-level integrity only; runtime queries in
|
||||
* the users module touch users_* tables exclusively.
|
||||
*/
|
||||
|
||||
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
||||
export const up = (pgm) => {
|
||||
pgm.sql(`
|
||||
CREATE TABLE users_profiles (
|
||||
user_id uuid PRIMARY KEY REFERENCES identity_users(id) ON DELETE CASCADE,
|
||||
display_name text,
|
||||
phone text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`);
|
||||
pgm.sql(`
|
||||
CREATE TABLE users_addresses (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES identity_users(id) ON DELETE CASCADE,
|
||||
label text,
|
||||
recipient_name text NOT NULL,
|
||||
street text NOT NULL,
|
||||
city text NOT NULL,
|
||||
postal_code text NOT NULL,
|
||||
country text NOT NULL,
|
||||
is_default boolean NOT NULL DEFAULT false,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
)
|
||||
`);
|
||||
pgm.sql('CREATE INDEX users_addresses_user_id_idx ON users_addresses (user_id)');
|
||||
};
|
||||
|
||||
/** @param {import('node-pg-migrate').MigrationBuilder} pgm */
|
||||
export const down = (pgm) => {
|
||||
pgm.sql('DROP TABLE IF EXISTS users_addresses');
|
||||
pgm.sql('DROP TABLE IF EXISTS users_profiles');
|
||||
};
|
||||
Reference in New Issue
Block a user