forked from wangziqi/ruoyi-vue-pro
feat(education): add class relationships
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
CREATE TABLE education_class (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT ck_education_class_status CHECK (status IN ('ACTIVE', 'ARCHIVED'))
|
||||
);
|
||||
COMMENT ON TABLE education_class IS 'Education class aggregate owned by one tenant';
|
||||
CREATE INDEX idx_education_class_tenant_status ON education_class (tenant_id, status, id) WHERE deleted = false;
|
||||
|
||||
CREATE TABLE education_class_member (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
class_id BIGINT NOT NULL REFERENCES education_class(id),
|
||||
member_user_id BIGINT NOT NULL,
|
||||
role VARCHAR(20) NOT NULL,
|
||||
joined_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT ck_education_class_member_role CHECK (role IN ('STUDENT', 'TEACHER')),
|
||||
CONSTRAINT uk_education_class_member_tenant_class_user UNIQUE (tenant_id, class_id, member_user_id)
|
||||
);
|
||||
COMMENT ON TABLE education_class_member IS 'Education-domain relationship to an existing Member user; no account credentials are stored';
|
||||
CREATE INDEX idx_education_class_member_tenant_user ON education_class_member (tenant_id, member_user_id, class_id) WHERE deleted = false;
|
||||
|
||||
CREATE TABLE education_class_invitation (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
class_id BIGINT NOT NULL REFERENCES education_class(id),
|
||||
invitee_member_user_id BIGINT NOT NULL,
|
||||
role VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
accepted_at TIMESTAMP,
|
||||
accepted_by BIGINT,
|
||||
created_by BIGINT NOT NULL,
|
||||
create_idempotency_key VARCHAR(64) NOT NULL,
|
||||
create_request_hash VARCHAR(64) NOT NULL,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT ck_education_class_invitation_role CHECK (role IN ('STUDENT', 'TEACHER')),
|
||||
CONSTRAINT ck_education_class_invitation_status CHECK (status IN ('PENDING', 'ACCEPTED', 'EXPIRED')),
|
||||
CONSTRAINT ck_education_class_invitation_acceptance CHECK (
|
||||
(status = 'ACCEPTED' AND accepted_at IS NOT NULL AND accepted_by = invitee_member_user_id)
|
||||
OR (status <> 'ACCEPTED' AND accepted_at IS NULL AND accepted_by IS NULL)
|
||||
),
|
||||
CONSTRAINT uk_education_class_invitation_create_key UNIQUE (tenant_id, created_by, create_idempotency_key)
|
||||
);
|
||||
COMMENT ON TABLE education_class_invitation IS 'Duplicate-safe invitation for an existing Member user';
|
||||
CREATE INDEX idx_education_class_invitation_invitee ON education_class_invitation (tenant_id, invitee_member_user_id, status, expires_at);
|
||||
|
||||
CREATE TABLE education_class_invitation_audit (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
invitation_id BIGINT NOT NULL REFERENCES education_class_invitation(id),
|
||||
action VARCHAR(20) NOT NULL,
|
||||
actor_id BIGINT NOT NULL,
|
||||
occurred_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
detail JSONB,
|
||||
creator VARCHAR(64) DEFAULT '',
|
||||
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '',
|
||||
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT ck_education_class_invitation_audit_action CHECK (action IN ('CREATED', 'ACCEPTED')),
|
||||
CONSTRAINT uk_education_class_invitation_audit_action UNIQUE (tenant_id, invitation_id, action)
|
||||
);
|
||||
COMMENT ON TABLE education_class_invitation_audit IS 'Append-only audit of invitation creation and acceptance';
|
||||
|
||||
CREATE OR REPLACE FUNCTION education_validate_class_relationship_tenant()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE owner_tenant BIGINT;
|
||||
BEGIN
|
||||
SELECT tenant_id INTO owner_tenant FROM education_class WHERE id = NEW.class_id AND deleted = false;
|
||||
IF owner_tenant IS NULL OR owner_tenant <> NEW.tenant_id THEN
|
||||
RAISE EXCEPTION 'education class relationship cannot cross tenants';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_education_class_member_tenant
|
||||
BEFORE INSERT OR UPDATE ON education_class_member
|
||||
FOR EACH ROW EXECUTE FUNCTION education_validate_class_relationship_tenant();
|
||||
CREATE TRIGGER trg_education_class_invitation_tenant
|
||||
BEFORE INSERT OR UPDATE ON education_class_invitation
|
||||
FOR EACH ROW EXECUTE FUNCTION education_validate_class_relationship_tenant();
|
||||
|
||||
CREATE OR REPLACE FUNCTION education_validate_invitation_audit_tenant()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE owner_tenant BIGINT;
|
||||
BEGIN
|
||||
SELECT tenant_id INTO owner_tenant FROM education_class_invitation WHERE id = NEW.invitation_id;
|
||||
IF owner_tenant IS NULL OR owner_tenant <> NEW.tenant_id THEN
|
||||
RAISE EXCEPTION 'education invitation audit cannot cross tenants';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER trg_education_class_invitation_audit_tenant
|
||||
BEFORE INSERT OR UPDATE ON education_class_invitation_audit
|
||||
FOR EACH ROW EXECUTE FUNCTION education_validate_invitation_audit_tenant();
|
||||
|
||||
CREATE OR REPLACE FUNCTION education_class_invitation_audit_append_only()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'education class invitation audit is append-only';
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER trg_education_class_invitation_audit_immutable
|
||||
BEFORE UPDATE OR DELETE ON education_class_invitation_audit
|
||||
FOR EACH ROW EXECUTE FUNCTION education_class_invitation_audit_append_only();
|
||||
Reference in New Issue
Block a user