chore: Projektbasis und PostgreSQL-Migrationen einrichten
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
commit
10af6159b4
14 files changed
+2214
No files matched your search
@@ -0,0 +1,97 @@
|
||||
CREATE TABLE pickup_contacts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
child_id uuid NOT NULL REFERENCES children(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
relationship text NOT NULL,
|
||||
phone text NOT NULL DEFAULT '',
|
||||
authorized boolean NOT NULL DEFAULT true,
|
||||
notes text NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE INDEX pickup_contacts_child ON pickup_contacts(child_id);
|
||||
|
||||
CREATE TABLE alone_rules (
|
||||
child_id uuid NOT NULL REFERENCES children(id) ON DELETE CASCADE,
|
||||
weekday integer NOT NULL CHECK (weekday BETWEEN 1 AND 7),
|
||||
earliest time NOT NULL,
|
||||
PRIMARY KEY(child_id, weekday)
|
||||
);
|
||||
|
||||
ALTER TABLE attendance DROP CONSTRAINT attendance_pickup_kind_check;
|
||||
ALTER TABLE attendance ADD CONSTRAINT attendance_pickup_kind_check
|
||||
CHECK (pickup_kind IN ('planned', 'parents', 'alone'));
|
||||
ALTER TABLE attendance ADD CONSTRAINT attendance_chronology
|
||||
CHECK (picked_up_at IS NULL OR picked_up_at >= arrived_at);
|
||||
|
||||
CREATE SEQUENCE attendance_revision;
|
||||
ALTER TABLE attendance ADD COLUMN version bigint NOT NULL DEFAULT nextval('attendance_revision');
|
||||
CREATE FUNCTION bump_attendance_revision() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
NEW.version := nextval('attendance_revision');
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
CREATE TRIGGER attendance_revision_trigger BEFORE UPDATE ON attendance
|
||||
FOR EACH ROW EXECUTE FUNCTION bump_attendance_revision();
|
||||
|
||||
CREATE TABLE audit_log (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
child_id uuid NOT NULL,
|
||||
entity text NOT NULL,
|
||||
entity_id text,
|
||||
day date,
|
||||
operation text NOT NULL CHECK (operation IN ('INSERT', 'UPDATE', 'DELETE')),
|
||||
actor_id uuid,
|
||||
actor_name text NOT NULL,
|
||||
reason text NOT NULL,
|
||||
before_data jsonb,
|
||||
after_data jsonb,
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp()
|
||||
);
|
||||
CREATE INDEX audit_log_child ON audit_log(child_id, id DESC);
|
||||
|
||||
CREATE FUNCTION record_child_audit() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
old_data jsonb;
|
||||
new_data jsonb;
|
||||
row_data jsonb;
|
||||
actor uuid;
|
||||
actor_label text;
|
||||
BEGIN
|
||||
IF TG_OP <> 'INSERT' THEN old_data := to_jsonb(OLD); END IF;
|
||||
IF TG_OP <> 'DELETE' THEN new_data := to_jsonb(NEW); END IF;
|
||||
IF TG_OP = 'UPDATE' AND old_data - 'version' = new_data - 'version' THEN RETURN NEW; END IF;
|
||||
row_data := COALESCE(new_data, old_data);
|
||||
actor := NULLIF(current_setting('kibu.actor_id', true), '')::uuid;
|
||||
SELECT name || ' (' || email || ')' INTO actor_label FROM staff WHERE id = actor;
|
||||
INSERT INTO audit_log(child_id, entity, entity_id, day, operation, actor_id, actor_name,
|
||||
reason, before_data, after_data)
|
||||
VALUES (
|
||||
CASE WHEN TG_TABLE_NAME = 'children' THEN (row_data->>'id')::uuid ELSE (row_data->>'child_id')::uuid END,
|
||||
TG_TABLE_NAME, row_data->>'id', (row_data->>'day')::date, TG_OP, actor,
|
||||
COALESCE(actor_label, 'Datenbank / Einrichtung'),
|
||||
COALESCE(NULLIF(current_setting('kibu.reason', true), ''), 'Direkte Datenbankänderung'),
|
||||
old_data, new_data
|
||||
);
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER children_audit AFTER INSERT OR UPDATE OR DELETE ON children FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
CREATE TRIGGER weekly_audit AFTER INSERT OR UPDATE OR DELETE ON weekly_schedules FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
CREATE TRIGGER override_audit AFTER INSERT OR UPDATE OR DELETE ON day_overrides FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
CREATE TRIGGER attendance_audit AFTER INSERT OR UPDATE OR DELETE ON attendance FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
CREATE TRIGGER contacts_audit AFTER INSERT OR UPDATE OR DELETE ON pickup_contacts FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
CREATE TRIGGER alone_audit AFTER INSERT OR UPDATE OR DELETE ON alone_rules FOR EACH ROW EXECUTE FUNCTION record_child_audit();
|
||||
|
||||
CREATE FUNCTION protect_audit_log() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'Das Audit-Log ist unveraenderlich.';
|
||||
END;
|
||||
$$;
|
||||
CREATE TRIGGER audit_immutable BEFORE UPDATE OR DELETE OR TRUNCATE ON audit_log
|
||||
FOR EACH STATEMENT EXECUTE FUNCTION protect_audit_log();
|
||||
|
||||
INSERT INTO audit_log(child_id, entity, day, operation, actor_name, reason, after_data)
|
||||
SELECT child_id, 'attendance', day, 'INSERT', 'Datenbank / Einrichtung',
|
||||
'Bestandsübernahme bei Einführung des Audit-Logs; ursprünglicher Änderungsverlauf nicht verfügbar.',
|
||||
to_jsonb(a) FROM attendance a;
|
||||
@@ -0,0 +1,57 @@
|
||||
CREATE TABLE pickup_slots (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
starts_at time NOT NULL,
|
||||
ends_at time NOT NULL,
|
||||
active boolean NOT NULL DEFAULT true,
|
||||
CHECK (starts_at < ends_at),
|
||||
UNIQUE(starts_at, ends_at)
|
||||
);
|
||||
INSERT INTO pickup_slots(starts_at, ends_at) VALUES
|
||||
('14:00', '14:15'), ('15:00', '15:15'), ('16:00', '16:15'), ('17:00', '17:15');
|
||||
ALTER TABLE weekly_schedules ADD COLUMN pickup_end time;
|
||||
ALTER TABLE weekly_schedules ADD COLUMN pickup_slot_id uuid REFERENCES pickup_slots(id);
|
||||
ALTER TABLE weekly_schedules ADD CONSTRAINT weekly_pickup_window CHECK(pickup_end IS NULL OR pickup_end > pickup);
|
||||
ALTER TABLE day_overrides ADD COLUMN pickup_end time;
|
||||
ALTER TABLE day_overrides ADD COLUMN pickup_slot_id uuid REFERENCES pickup_slots(id);
|
||||
ALTER TABLE day_overrides ADD CONSTRAINT override_pickup_window CHECK(pickup_end IS NULL OR (attending AND pickup_end > pickup));
|
||||
UPDATE weekly_schedules w SET pickup_slot_id = s.id, pickup_end = s.ends_at FROM pickup_slots s WHERE w.pickup = s.starts_at;
|
||||
UPDATE day_overrides o SET pickup_slot_id = s.id, pickup_end = s.ends_at FROM pickup_slots s WHERE o.pickup = s.starts_at;
|
||||
|
||||
CREATE TABLE admission_rounds (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
code_hash text NOT NULL UNIQUE,
|
||||
expires_on date NOT NULL,
|
||||
status text NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'closed', 'drawn')),
|
||||
created_by uuid NOT NULL REFERENCES staff(id),
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
drawn_by uuid REFERENCES staff(id),
|
||||
drawn_at timestamptz,
|
||||
places integer CHECK(places > 0),
|
||||
CHECK ((status = 'drawn' AND drawn_at IS NOT NULL AND drawn_by IS NOT NULL AND places IS NOT NULL)
|
||||
OR (status <> 'drawn' AND drawn_at IS NULL AND drawn_by IS NULL AND places IS NULL))
|
||||
);
|
||||
CREATE TABLE applications (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
round_id uuid NOT NULL REFERENCES admission_rounds(id),
|
||||
first_name text NOT NULL,
|
||||
last_name text NOT NULL,
|
||||
class_name text NOT NULL,
|
||||
parent_name text NOT NULL,
|
||||
email text NOT NULL,
|
||||
notes text NOT NULL DEFAULT '',
|
||||
status text NOT NULL DEFAULT 'waiting' CHECK(status IN ('waiting', 'selected', 'waitlisted', 'accepted')),
|
||||
child_id uuid UNIQUE REFERENCES children(id),
|
||||
submitted_at timestamptz NOT NULL DEFAULT now(),
|
||||
accepted_at timestamptz,
|
||||
accepted_by uuid REFERENCES staff(id),
|
||||
CHECK ((status = 'accepted' AND child_id IS NOT NULL AND accepted_at IS NOT NULL AND accepted_by IS NOT NULL)
|
||||
OR (status <> 'accepted' AND child_id IS NULL AND accepted_at IS NULL AND accepted_by IS NULL))
|
||||
);
|
||||
CREATE UNIQUE INDEX application_unique_child ON applications(round_id, lower(first_name), lower(last_name), lower(email));
|
||||
CREATE INDEX applications_round ON applications(round_id, submitted_at, id);
|
||||
CREATE TABLE application_limits (
|
||||
key_hash text PRIMARY KEY,
|
||||
attempts integer NOT NULL,
|
||||
window_start timestamptz NOT NULL
|
||||
);
|
||||
Reference in new issue
Block a user