feat(store): add Area admission and audit outbox [T-010]
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
-- Bell owns Area and capture-policy truth. Sense records only the highest
|
||||
-- projection version it has observed and the version used for admission.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bell.areas (
|
||||
tenant_id text NOT NULL,
|
||||
site_id text NOT NULL,
|
||||
id text NOT NULL,
|
||||
name text NOT NULL,
|
||||
capture_policy text NOT NULL DEFAULT 'video_allowed',
|
||||
version bigint NOT NULL DEFAULT 1,
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz,
|
||||
PRIMARY KEY (tenant_id, id),
|
||||
CONSTRAINT bell_areas_site_fk FOREIGN KEY (tenant_id, site_id)
|
||||
REFERENCES bell.sites(tenant_id, id),
|
||||
CONSTRAINT bell_areas_capture_policy CHECK (
|
||||
capture_policy IN ('video_allowed', 'non_imaging_only')
|
||||
),
|
||||
CONSTRAINT bell_areas_version_positive CHECK (version >= 1),
|
||||
CONSTRAINT bell_areas_identity_not_blank CHECK (
|
||||
btrim(tenant_id) <> '' AND btrim(site_id) <> ''
|
||||
AND btrim(id) <> '' AND btrim(name) <> ''
|
||||
)
|
||||
);
|
||||
ALTER TABLE bell.areas OWNER TO bell_app;
|
||||
|
||||
CREATE OR REPLACE FUNCTION bell.bump_area_version()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY INVOKER
|
||||
SET search_path = pg_catalog, bell
|
||||
AS $function$
|
||||
BEGIN
|
||||
IF NEW.tenant_id IS DISTINCT FROM OLD.tenant_id
|
||||
OR NEW.site_id IS DISTINCT FROM OLD.site_id
|
||||
OR NEW.id IS DISTINCT FROM OLD.id THEN
|
||||
RAISE EXCEPTION 'Bell Area identity and Site are immutable';
|
||||
END IF;
|
||||
NEW.version := OLD.version + 1;
|
||||
NEW.updated_at := clock_timestamp();
|
||||
RETURN NEW;
|
||||
END
|
||||
$function$;
|
||||
ALTER FUNCTION bell.bump_area_version() OWNER TO bell_app;
|
||||
|
||||
DROP TRIGGER IF EXISTS bell_areas_bump_version ON bell.areas;
|
||||
CREATE TRIGGER bell_areas_bump_version
|
||||
BEFORE UPDATE ON bell.areas
|
||||
FOR EACH ROW EXECUTE FUNCTION bell.bump_area_version();
|
||||
|
||||
CREATE OR REPLACE VIEW bell.area_policy_v1 (
|
||||
tenant_id,
|
||||
site_id,
|
||||
area_id,
|
||||
capture_policy,
|
||||
source_version,
|
||||
source_updated_at
|
||||
) AS
|
||||
SELECT
|
||||
area.tenant_id,
|
||||
area.site_id,
|
||||
area.id,
|
||||
area.capture_policy,
|
||||
area.version,
|
||||
area.updated_at
|
||||
FROM bell.areas AS area
|
||||
JOIN bell.sites AS site
|
||||
ON site.tenant_id = area.tenant_id AND site.id = area.site_id
|
||||
WHERE area.deleted_at IS NULL AND site.deleted_at IS NULL;
|
||||
ALTER VIEW bell.area_policy_v1 OWNER TO bell_app;
|
||||
COMMENT ON VIEW bell.area_policy_v1 IS
|
||||
'v1 read-only Area capture-policy projection owned by Bell and consumed by Sense';
|
||||
|
||||
ALTER TABLE sense.devices ADD COLUMN IF NOT EXISTS area_id text;
|
||||
ALTER TABLE sense.devices ADD COLUMN IF NOT EXISTS area_policy_source_version bigint;
|
||||
|
||||
DO $constraints$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'sense.devices'::regclass
|
||||
AND conname = 'sense_devices_area_not_blank'
|
||||
) THEN
|
||||
ALTER TABLE sense.devices ADD CONSTRAINT sense_devices_area_not_blank
|
||||
CHECK (area_id IS NULL OR btrim(area_id) <> '');
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'sense.devices'::regclass
|
||||
AND conname = 'sense_devices_area_version_positive'
|
||||
) THEN
|
||||
ALTER TABLE sense.devices ADD CONSTRAINT sense_devices_area_version_positive
|
||||
CHECK (area_policy_source_version IS NULL OR area_policy_source_version >= 1);
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'sense.devices'::regclass
|
||||
AND conname = 'sense_devices_tenant_site_id_unique'
|
||||
) THEN
|
||||
ALTER TABLE sense.devices ADD CONSTRAINT sense_devices_tenant_site_id_unique
|
||||
UNIQUE (tenant_id, site_id, id);
|
||||
END IF;
|
||||
END
|
||||
$constraints$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sense.area_policy_projection_state (
|
||||
tenant_id text NOT NULL,
|
||||
site_id text NOT NULL,
|
||||
area_id text NOT NULL,
|
||||
source_version bigint NOT NULL,
|
||||
synced_at timestamptz NOT NULL,
|
||||
PRIMARY KEY (tenant_id, site_id, area_id),
|
||||
CONSTRAINT sense_area_projection_identity_not_blank CHECK (
|
||||
btrim(tenant_id) <> '' AND btrim(site_id) <> '' AND btrim(area_id) <> ''
|
||||
),
|
||||
CONSTRAINT sense_area_projection_version_positive CHECK (source_version >= 1)
|
||||
);
|
||||
ALTER TABLE sense.area_policy_projection_state OWNER TO sense_app;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS sense_devices_area_idx
|
||||
ON sense.devices(tenant_id, site_id, area_id);
|
||||
|
||||
INSERT INTO bell.schema_migrations(version) VALUES (2)
|
||||
ON CONFLICT (version) DO NOTHING;
|
||||
INSERT INTO sense.schema_migrations(version) VALUES (2)
|
||||
ON CONFLICT (version) DO NOTHING;
|
||||
Reference in New Issue
Block a user