All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
166 lines
4.4 KiB
PL/PgSQL
166 lines
4.4 KiB
PL/PgSQL
BEGIN;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'group_join_policy') THEN
|
|
CREATE TYPE group_join_policy AS ENUM (
|
|
'NOT_ACCEPTING',
|
|
'AUTO_ACCEPT',
|
|
'APPROVAL_REQUIRED'
|
|
);
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'group_join_request_status') THEN
|
|
CREATE TYPE group_join_request_status AS ENUM (
|
|
'PENDING',
|
|
'APPROVED',
|
|
'DENIED',
|
|
'CANCELED'
|
|
);
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
CREATE TABLE IF NOT EXISTS group_settings (
|
|
group_id INTEGER PRIMARY KEY REFERENCES households(id) ON DELETE CASCADE,
|
|
join_policy group_join_policy NOT NULL DEFAULT 'NOT_ACCEPTING',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
INSERT INTO group_settings (group_id, join_policy)
|
|
SELECT h.id, 'NOT_ACCEPTING'::group_join_policy
|
|
FROM households h
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM group_settings gs WHERE gs.group_id = h.id
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS group_join_requests (
|
|
id SERIAL PRIMARY KEY,
|
|
group_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
|
|
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
status group_join_request_status NOT NULL DEFAULT 'PENDING',
|
|
decided_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
decided_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_group_join_requests_pending
|
|
ON group_join_requests(group_id, user_id)
|
|
WHERE status = 'PENDING';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_group_join_requests_group
|
|
ON group_join_requests(group_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS group_invite_links (
|
|
id SERIAL PRIMARY KEY,
|
|
group_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
|
|
created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token VARCHAR(64) NOT NULL UNIQUE,
|
|
policy group_join_policy NOT NULL DEFAULT 'NOT_ACCEPTING',
|
|
single_use BOOLEAN NOT NULL DEFAULT FALSE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
revoked_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_group_invite_links_group_id
|
|
ON group_invite_links(group_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS group_audit_log (
|
|
id SERIAL PRIMARY KEY,
|
|
group_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
|
|
actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
actor_role VARCHAR(20),
|
|
event_type VARCHAR(100) NOT NULL,
|
|
request_id VARCHAR(128) NOT NULL,
|
|
ip INET,
|
|
user_agent TEXT,
|
|
success BOOLEAN NOT NULL DEFAULT TRUE,
|
|
error_code VARCHAR(100),
|
|
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_group_audit_group_created
|
|
ON group_audit_log(group_id, created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_group_audit_request_id
|
|
ON group_audit_log(request_id);
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM pg_indexes
|
|
WHERE schemaname = current_schema()
|
|
AND tablename = 'households'
|
|
AND indexdef ILIKE 'CREATE UNIQUE INDEX%'
|
|
AND indexdef ILIKE '%(invite_code)%'
|
|
) THEN
|
|
CREATE UNIQUE INDEX idx_households_invite_code_unique
|
|
ON households(invite_code);
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
ALTER TABLE household_members
|
|
DROP CONSTRAINT IF EXISTS household_members_role_check;
|
|
|
|
UPDATE household_members
|
|
SET role = 'member'
|
|
WHERE role = 'user';
|
|
|
|
WITH ranked_admins AS (
|
|
SELECT
|
|
hm.id,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY hm.household_id
|
|
ORDER BY hm.joined_at ASC, hm.id ASC
|
|
) AS admin_rank
|
|
FROM household_members hm
|
|
WHERE hm.role = 'admin'
|
|
)
|
|
UPDATE household_members hm
|
|
SET role = CASE
|
|
WHEN ra.admin_rank = 1 THEN 'owner'
|
|
ELSE 'admin'
|
|
END
|
|
FROM ranked_admins ra
|
|
WHERE hm.id = ra.id;
|
|
|
|
WITH ownerless_households AS (
|
|
SELECT h.id AS household_id
|
|
FROM households h
|
|
WHERE NOT EXISTS (
|
|
SELECT 1
|
|
FROM household_members hm
|
|
WHERE hm.household_id = h.id
|
|
AND hm.role = 'owner'
|
|
)
|
|
),
|
|
first_member AS (
|
|
SELECT DISTINCT ON (hm.household_id)
|
|
hm.id,
|
|
hm.household_id
|
|
FROM household_members hm
|
|
JOIN ownerless_households oh ON oh.household_id = hm.household_id
|
|
ORDER BY hm.household_id, hm.joined_at ASC, hm.id ASC
|
|
)
|
|
UPDATE household_members hm
|
|
SET role = 'owner'
|
|
FROM first_member fm
|
|
WHERE hm.id = fm.id;
|
|
|
|
ALTER TABLE household_members
|
|
ADD CONSTRAINT household_members_role_check
|
|
CHECK (role IN ('owner', 'admin', 'member'));
|
|
|
|
COMMIT;
|