Add cash management schema and immediate variance alerts
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
-- =====================================================================
|
||||
-- 0000 Auth shim
|
||||
-- Provides `auth.users`, `auth.uid()`, `auth.role()`, `auth.jwt()` so
|
||||
-- the application migrations (which were written for Supabase) run
|
||||
-- unmodified. The backend sets `request.jwt.claim.sub` (and friends)
|
||||
-- per request from the verified JWT, then `set local role authenticated`.
|
||||
-- =====================================================================
|
||||
|
||||
create extension if not exists "pgcrypto";
|
||||
create extension if not exists "citext";
|
||||
|
||||
-- Supabase ships these roles; create them if missing (e.g. plain Postgres).
|
||||
do $$ begin
|
||||
if not exists (select 1 from pg_roles where rolname = 'anon') then
|
||||
create role anon nologin noinherit;
|
||||
end if;
|
||||
if not exists (select 1 from pg_roles where rolname = 'authenticated') then
|
||||
create role authenticated nologin noinherit;
|
||||
end if;
|
||||
if not exists (select 1 from pg_roles where rolname = 'service_role') then
|
||||
create role service_role nologin noinherit bypassrls;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
create schema if not exists auth;
|
||||
|
||||
-- Minimal `auth.users` compatible with FKs in app migrations.
|
||||
create table if not exists auth.users (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
email citext unique,
|
||||
password_hash text not null,
|
||||
full_name text,
|
||||
is_active boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
last_login_at timestamptz
|
||||
);
|
||||
|
||||
create or replace function auth.uid()
|
||||
returns uuid
|
||||
language sql
|
||||
stable
|
||||
as $$
|
||||
select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid
|
||||
$$;
|
||||
|
||||
create or replace function auth.role()
|
||||
returns text
|
||||
language sql
|
||||
stable
|
||||
as $$
|
||||
select coalesce(nullif(current_setting('request.jwt.claim.role', true), ''), 'anon')
|
||||
$$;
|
||||
|
||||
create or replace function auth.jwt()
|
||||
returns jsonb
|
||||
language sql
|
||||
stable
|
||||
as $$
|
||||
select coalesce(nullif(current_setting('request.jwt.claims', true), '')::jsonb, '{}'::jsonb)
|
||||
$$;
|
||||
|
||||
grant usage on schema auth to authenticated, anon, service_role;
|
||||
grant select on auth.users to authenticated, service_role;
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run all SQL migrations from /sql/migrations/ in lexical order.
|
||||
# On plain Postgres (no pg_cron extension), wrap any bare
|
||||
# `create extension if not exists pg_cron;` line so it doesn't abort.
|
||||
set -euo pipefail
|
||||
|
||||
TMPDIR_M=/tmp/migrations
|
||||
mkdir -p "$TMPDIR_M"
|
||||
echo ">> applying app migrations from /sql/migrations"
|
||||
for f in /sql/migrations/*.sql; do
|
||||
base="$(basename "$f")"
|
||||
# Replace the bare pg_cron extension creation with a soft variant.
|
||||
sed -E "s|^create extension if not exists pg_cron;|do \$\$ begin create extension if not exists pg_cron; exception when others then raise notice 'pg_cron unavailable, skipping schedules'; end \$\$;|i" "$f" > "$TMPDIR_M/$base"
|
||||
echo ">> $base"
|
||||
psql -v ON_ERROR_STOP=1 \
|
||||
--username "$POSTGRES_USER" \
|
||||
--dbname "$POSTGRES_DB" \
|
||||
-f "$TMPDIR_M/$base"
|
||||
done
|
||||
echo ">> migrations complete"
|
||||
@@ -0,0 +1,32 @@
|
||||
-- =====================================================================
|
||||
-- Local extension migration: simple employee payment ledger used by the
|
||||
-- Employee Payment Report UI. Backed by the API; not a Supabase migration.
|
||||
-- =====================================================================
|
||||
|
||||
create table if not exists app.employees (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
emp_id text not null unique,
|
||||
name text not null,
|
||||
email text,
|
||||
department text,
|
||||
location text,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create table if not exists app.employee_transactions (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
employee_id uuid not null references app.employees(id) on delete cascade,
|
||||
transaction_date date not null,
|
||||
collection_amount numeric(18,2) not null default 0,
|
||||
deposit_amount numeric(18,2) not null default 0,
|
||||
currency text not null check (currency in ('USD','LBP')),
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create index if not exists idx_emp_tx_emp on app.employee_transactions(employee_id, transaction_date desc);
|
||||
|
||||
-- These tables are owned by the API; RLS off, gated at the HTTP layer.
|
||||
alter table app.employees disable row level security;
|
||||
alter table app.employee_transactions disable row level security;
|
||||
grant select, insert, update, delete on app.employees to authenticated;
|
||||
grant select, insert, update, delete on app.employee_transactions to authenticated;
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# Seed (or reset) the default admin user, default shop, owner role, Till 1.
|
||||
set -euo pipefail
|
||||
|
||||
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@local.test}"
|
||||
ADMIN_PASSWORD="${ADMIN_PASSWORD:-ChangeMe123!}"
|
||||
ADMIN_NAME="${ADMIN_NAME:-Local Admin}"
|
||||
|
||||
echo ">> seeding admin user: ${ADMIN_EMAIL}"
|
||||
|
||||
# Use psql -v to safely substitute values inside the DO block via :'name' --
|
||||
# but :'name' only works at top level. So we generate plain SQL with the
|
||||
# values inlined as quoted literals (escaping single quotes).
|
||||
escape() { printf "%s" "$1" | sed "s/'/''/g"; }
|
||||
EM=$(escape "$ADMIN_EMAIL")
|
||||
PW=$(escape "$ADMIN_PASSWORD")
|
||||
NM=$(escape "$ADMIN_NAME")
|
||||
|
||||
psql -v ON_ERROR_STOP=1 \
|
||||
--username "$POSTGRES_USER" \
|
||||
--dbname "$POSTGRES_DB" <<SQL
|
||||
do \$\$
|
||||
declare
|
||||
v_user_id uuid;
|
||||
v_shop_id uuid;
|
||||
begin
|
||||
insert into auth.users(email, password_hash, full_name, is_active)
|
||||
values ('${EM}', crypt('${PW}', gen_salt('bf', 10)), '${NM}', true)
|
||||
on conflict (email) do update
|
||||
set password_hash = excluded.password_hash,
|
||||
full_name = excluded.full_name,
|
||||
is_active = true
|
||||
returning id into v_user_id;
|
||||
|
||||
insert into app.user_profiles(user_id, full_name, is_active)
|
||||
values (v_user_id, '${NM}', true)
|
||||
on conflict (user_id) do update
|
||||
set full_name = excluded.full_name,
|
||||
is_active = true;
|
||||
|
||||
insert into app.shops(name, created_by)
|
||||
values ('Default Shop', v_user_id)
|
||||
on conflict do nothing;
|
||||
|
||||
select id into v_shop_id from app.shops where name = 'Default Shop' limit 1;
|
||||
|
||||
insert into app.user_shop_assignments(user_id, shop_id, role, assigned_by)
|
||||
values (v_user_id, v_shop_id, 'owner', v_user_id)
|
||||
on conflict (user_id, shop_id) do update set role = 'owner';
|
||||
|
||||
insert into app.tills(shop_id, name)
|
||||
values (v_shop_id, 'Till 1')
|
||||
on conflict (shop_id, name) do nothing;
|
||||
end \$\$;
|
||||
SQL
|
||||
|
||||
echo ">> admin user ensured: ${ADMIN_EMAIL}"
|
||||
Reference in New Issue
Block a user