feat: Add password reset functionality with request and reset forms
feat: Implement price list import feature with preview and apply options feat: Create price rules management page with CRUD operations feat: Develop quotes management page with itemized quotes and status tracking feat: Introduce organization registration page for new users feat: Build suppliers management page with detailed supplier information feat: Create users management page for inviting and managing roles chore: Add TypeScript configuration for improved type checking chore: Set up Vite configuration for development server and API proxy chore: Add Vite environment type definitions for better TypeScript support
This commit is contained in:
94
backend/migrations/20260521170000_public_core.sql
Normal file
94
backend/migrations/20260521170000_public_core.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
create table if not exists users (
|
||||
id uuid primary key,
|
||||
email text not null unique,
|
||||
display_name_ciphertext bytea,
|
||||
display_name_nonce bytea,
|
||||
display_name_key_id text,
|
||||
password_hash text,
|
||||
is_active boolean not null default true,
|
||||
must_change_password boolean not null default false,
|
||||
initial_password_expires_at timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
last_login_at timestamptz,
|
||||
constraint users_email_lowercase check (email = lower(email)),
|
||||
constraint users_display_name_encryption_complete check (
|
||||
(
|
||||
display_name_ciphertext is null
|
||||
and display_name_nonce is null
|
||||
and display_name_key_id is null
|
||||
)
|
||||
or (
|
||||
display_name_ciphertext is not null
|
||||
and display_name_nonce is not null
|
||||
and display_name_key_id is not null
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists organizations (
|
||||
id uuid primary key,
|
||||
display_name_ciphertext bytea,
|
||||
display_name_nonce bytea,
|
||||
display_name_key_id text,
|
||||
schema_name text unique,
|
||||
status text not null default 'pending_approval',
|
||||
registration_email text not null,
|
||||
setup_completed_at timestamptz,
|
||||
approved_by_user_id uuid references users(id),
|
||||
approved_at timestamptz,
|
||||
rejected_by_user_id uuid references users(id),
|
||||
rejected_at timestamptz,
|
||||
rejection_reason text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
constraint organizations_status_valid check (
|
||||
status in ('pending_approval', 'approved', 'active', 'rejected', 'suspended')
|
||||
),
|
||||
constraint organizations_registration_email_lowercase check (registration_email = lower(registration_email)),
|
||||
constraint organizations_schema_name_valid check (
|
||||
schema_name is null or schema_name ~ '^company_[a-z0-9_]+$'
|
||||
),
|
||||
constraint organizations_display_name_encryption_complete check (
|
||||
(
|
||||
display_name_ciphertext is null
|
||||
and display_name_nonce is null
|
||||
and display_name_key_id is null
|
||||
)
|
||||
or (
|
||||
display_name_ciphertext is not null
|
||||
and display_name_nonce is not null
|
||||
and display_name_key_id is not null
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists user_organizations (
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
organization_id uuid not null references organizations(id) on delete cascade,
|
||||
status text not null default 'pending_invitation',
|
||||
invited_by_user_id uuid references users(id),
|
||||
invited_at timestamptz,
|
||||
accepted_at timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
primary key (user_id, organization_id),
|
||||
constraint user_organizations_status_valid check (
|
||||
status in ('pending_invitation', 'active', 'disabled')
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists organization_domains (
|
||||
id uuid primary key,
|
||||
organization_id uuid not null references organizations(id) on delete cascade,
|
||||
domain text not null unique,
|
||||
is_primary boolean not null default false,
|
||||
created_at timestamptz not null default now(),
|
||||
constraint organization_domains_domain_lowercase check (domain = lower(domain))
|
||||
);
|
||||
|
||||
create index if not exists idx_user_organizations_organization_id
|
||||
on user_organizations (organization_id);
|
||||
|
||||
create index if not exists idx_organizations_status
|
||||
on organizations (status);
|
||||
68
backend/migrations/20260521171000_public_auth_sessions.sql
Normal file
68
backend/migrations/20260521171000_public_auth_sessions.sql
Normal file
@@ -0,0 +1,68 @@
|
||||
create table if not exists auth_identities (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
provider text not null,
|
||||
provider_subject text not null,
|
||||
email_at_provider text,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (provider, provider_subject)
|
||||
);
|
||||
|
||||
create table if not exists refresh_tokens (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
organization_id uuid references organizations(id) on delete cascade,
|
||||
token_hash text not null unique,
|
||||
expires_at timestamptz not null,
|
||||
revoked_at timestamptz,
|
||||
revoked_reason text,
|
||||
user_agent text,
|
||||
created_ip text,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table if not exists socket_tokens (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
organization_id uuid not null references organizations(id) on delete cascade,
|
||||
token_hash text not null unique,
|
||||
expires_at timestamptz not null,
|
||||
used_at timestamptz,
|
||||
revoked_at timestamptz,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table if not exists session_keys (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
organization_id uuid not null references organizations(id) on delete cascade,
|
||||
key_id text not null unique,
|
||||
wrapped_key bytea,
|
||||
algorithm text not null,
|
||||
created_at timestamptz not null default now(),
|
||||
expires_at timestamptz not null,
|
||||
revoked_at timestamptz
|
||||
);
|
||||
|
||||
create table if not exists idempotency_keys (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
organization_id uuid references organizations(id) on delete cascade,
|
||||
key text not null,
|
||||
request_hash text not null,
|
||||
response_status integer,
|
||||
response_body_json jsonb,
|
||||
expires_at timestamptz not null,
|
||||
created_at timestamptz not null default now(),
|
||||
unique (user_id, organization_id, key)
|
||||
);
|
||||
|
||||
create index if not exists idx_refresh_tokens_user_id
|
||||
on refresh_tokens (user_id);
|
||||
|
||||
create index if not exists idx_socket_tokens_user_organization
|
||||
on socket_tokens (user_id, organization_id);
|
||||
|
||||
create index if not exists idx_session_keys_user_organization
|
||||
on session_keys (user_id, organization_id);
|
||||
61
backend/migrations/20260521172000_public_onboarding.sql
Normal file
61
backend/migrations/20260521172000_public_onboarding.sql
Normal file
@@ -0,0 +1,61 @@
|
||||
create table if not exists organization_registration_requests (
|
||||
id uuid primary key,
|
||||
organization_name_ciphertext bytea not null,
|
||||
organization_name_nonce bytea not null,
|
||||
organization_name_key_id text not null,
|
||||
email text not null,
|
||||
status text not null default 'pending_approval',
|
||||
organization_id uuid references organizations(id),
|
||||
requested_at timestamptz not null default now(),
|
||||
decided_by_user_id uuid references users(id),
|
||||
decided_at timestamptz,
|
||||
decision_note text,
|
||||
constraint organization_registration_requests_email_lowercase check (email = lower(email)),
|
||||
constraint organization_registration_requests_status_valid check (
|
||||
status in ('pending_approval', 'approved', 'active', 'rejected', 'suspended')
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists user_invitations (
|
||||
id uuid primary key,
|
||||
organization_id uuid not null references organizations(id) on delete cascade,
|
||||
email text not null,
|
||||
invited_by_user_id uuid not null references users(id),
|
||||
status text not null default 'pending',
|
||||
expires_at timestamptz not null,
|
||||
accepted_at timestamptz,
|
||||
created_user_id uuid references users(id),
|
||||
created_at timestamptz not null default now(),
|
||||
constraint user_invitations_email_lowercase check (email = lower(email)),
|
||||
constraint user_invitations_status_valid check (
|
||||
status in ('pending', 'accepted', 'expired', 'revoked')
|
||||
)
|
||||
);
|
||||
|
||||
create table if not exists email_outbox (
|
||||
id uuid primary key,
|
||||
recipient_email text not null,
|
||||
template text not null,
|
||||
payload_ciphertext bytea not null,
|
||||
payload_nonce bytea not null,
|
||||
payload_key_id text not null,
|
||||
status text not null default 'pending',
|
||||
attempt_count integer not null default 0,
|
||||
last_error text,
|
||||
send_after timestamptz not null default now(),
|
||||
sent_at timestamptz,
|
||||
created_at timestamptz not null default now(),
|
||||
constraint email_outbox_recipient_email_lowercase check (recipient_email = lower(recipient_email)),
|
||||
constraint email_outbox_status_valid check (
|
||||
status in ('pending', 'sending', 'sent', 'failed')
|
||||
)
|
||||
);
|
||||
|
||||
create index if not exists idx_organization_registration_requests_status
|
||||
on organization_registration_requests (status, requested_at);
|
||||
|
||||
create index if not exists idx_user_invitations_organization_status
|
||||
on user_invitations (organization_id, status);
|
||||
|
||||
create index if not exists idx_email_outbox_status_send_after
|
||||
on email_outbox (status, send_after);
|
||||
@@ -0,0 +1,9 @@
|
||||
create table if not exists records (
|
||||
id uuid primary key,
|
||||
title text not null,
|
||||
updated_at timestamptz not null
|
||||
);
|
||||
|
||||
insert into records (id, title, updated_at)
|
||||
select '00000000-0000-0000-0000-000000000001'::uuid, 'Erster Datensatz', now()
|
||||
where not exists (select 1 from records);
|
||||
2
backend/migrations/20260521174000_registration_terms.sql
Normal file
2
backend/migrations/20260521174000_registration_terms.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
alter table organization_registration_requests
|
||||
add column if not exists terms_accepted_at timestamptz;
|
||||
23
backend/migrations/20260601190000_security_operations.sql
Normal file
23
backend/migrations/20260601190000_security_operations.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
alter table user_invitations
|
||||
add column if not exists token_hash text,
|
||||
add column if not exists accepted_by_user_id uuid references users(id);
|
||||
|
||||
create unique index if not exists idx_user_invitations_token_hash
|
||||
on user_invitations (token_hash)
|
||||
where token_hash is not null;
|
||||
|
||||
create table if not exists password_reset_tokens (
|
||||
id uuid primary key,
|
||||
user_id uuid not null references users(id) on delete cascade,
|
||||
token_hash text not null unique,
|
||||
expires_at timestamptz not null,
|
||||
used_at timestamptz,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_password_reset_tokens_user
|
||||
on password_reset_tokens (user_id, expires_at);
|
||||
|
||||
alter table email_outbox
|
||||
add column if not exists subject text,
|
||||
add column if not exists delivered_via text;
|
||||
Reference in New Issue
Block a user