223 lines
7.7 KiB
PL/PgSQL
223 lines
7.7 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Teams Schema: Team Members Table
|
|
-- Manages team/organization membership
|
|
-- ============================================================================
|
|
|
|
-- Create teams schema if not exists
|
|
CREATE SCHEMA IF NOT EXISTS teams;
|
|
|
|
-- Grant usage
|
|
GRANT USAGE ON SCHEMA teams TO trading_user;
|
|
|
|
-- ============================================================================
|
|
-- TEAM_MEMBERS TABLE
|
|
-- Tracks membership status and details for users in a tenant
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS teams.team_members (
|
|
-- Primary key
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Tenant relationship
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- User relationship
|
|
user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
|
|
|
|
-- Membership status
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('pending', 'active', 'suspended', 'removed')),
|
|
|
|
-- Member type
|
|
member_type VARCHAR(20) NOT NULL DEFAULT 'member'
|
|
CHECK (member_type IN ('owner', 'admin', 'member', 'guest')),
|
|
|
|
-- Join information
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
invited_by UUID REFERENCES users.users(id),
|
|
invitation_id UUID, -- Reference to invitation (if joined via invite)
|
|
|
|
-- Department/Team within organization
|
|
department VARCHAR(100),
|
|
job_title VARCHAR(100),
|
|
|
|
-- Member settings
|
|
settings JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
|
|
-- Notification preferences for team
|
|
notifications_enabled BOOLEAN NOT NULL DEFAULT true,
|
|
|
|
-- Audit fields
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
removed_at TIMESTAMPTZ,
|
|
removed_by UUID REFERENCES users.users(id),
|
|
removal_reason TEXT,
|
|
|
|
-- Constraints
|
|
CONSTRAINT uq_team_member UNIQUE (tenant_id, user_id)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- INDEXES
|
|
-- ============================================================================
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_tenant_id ON teams.team_members(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_user_id ON teams.team_members(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_status ON teams.team_members(status);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_member_type ON teams.team_members(member_type);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_department ON teams.team_members(tenant_id, department);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_joined_at ON teams.team_members(joined_at);
|
|
|
|
-- ============================================================================
|
|
-- ROW LEVEL SECURITY
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE teams.team_members ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Policy: Users can only see members in their tenant
|
|
CREATE POLICY team_members_tenant_isolation ON teams.team_members
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS
|
|
-- ============================================================================
|
|
|
|
-- Auto-update updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION teams.update_team_members_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_team_members_updated_at
|
|
BEFORE UPDATE ON teams.team_members
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION teams.update_team_members_timestamp();
|
|
|
|
-- ============================================================================
|
|
-- VIEW: Team members with user details
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE VIEW teams.v_team_members AS
|
|
SELECT
|
|
tm.id,
|
|
tm.tenant_id,
|
|
tm.user_id,
|
|
u.email,
|
|
u.first_name,
|
|
u.last_name,
|
|
u.display_name,
|
|
u.avatar_url,
|
|
u.status AS user_status,
|
|
tm.status AS membership_status,
|
|
tm.member_type,
|
|
tm.department,
|
|
tm.job_title,
|
|
tm.joined_at,
|
|
tm.notifications_enabled,
|
|
inviter.email AS invited_by_email,
|
|
inviter.display_name AS invited_by_name,
|
|
-- Get primary role
|
|
(
|
|
SELECT r.name
|
|
FROM rbac.user_roles ur
|
|
JOIN rbac.roles r ON ur.role_id = r.id
|
|
WHERE ur.user_id = tm.user_id
|
|
AND ur.tenant_id = tm.tenant_id
|
|
AND ur.is_primary = true
|
|
AND ur.is_active = true
|
|
LIMIT 1
|
|
) AS primary_role
|
|
FROM teams.team_members tm
|
|
JOIN users.users u ON tm.user_id = u.id
|
|
LEFT JOIN users.users inviter ON tm.invited_by = inviter.id
|
|
WHERE tm.status != 'removed';
|
|
|
|
-- ============================================================================
|
|
-- FUNCTION: Add user to team
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION teams.add_team_member(
|
|
p_tenant_id UUID,
|
|
p_user_id UUID,
|
|
p_member_type VARCHAR(20) DEFAULT 'member',
|
|
p_invited_by UUID DEFAULT NULL,
|
|
p_invitation_id UUID DEFAULT NULL,
|
|
p_department VARCHAR(100) DEFAULT NULL,
|
|
p_job_title VARCHAR(100) DEFAULT NULL
|
|
)
|
|
RETURNS UUID AS $$
|
|
DECLARE
|
|
v_member_id UUID;
|
|
BEGIN
|
|
INSERT INTO teams.team_members (
|
|
tenant_id, user_id, member_type, invited_by,
|
|
invitation_id, department, job_title, status
|
|
) VALUES (
|
|
p_tenant_id, p_user_id, p_member_type, p_invited_by,
|
|
p_invitation_id, p_department, p_job_title, 'active'
|
|
)
|
|
ON CONFLICT (tenant_id, user_id) DO UPDATE SET
|
|
status = 'active',
|
|
member_type = EXCLUDED.member_type,
|
|
department = COALESCE(EXCLUDED.department, teams.team_members.department),
|
|
job_title = COALESCE(EXCLUDED.job_title, teams.team_members.job_title),
|
|
removed_at = NULL,
|
|
removed_by = NULL,
|
|
removal_reason = NULL,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
RETURNING id INTO v_member_id;
|
|
|
|
RETURN v_member_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ============================================================================
|
|
-- FUNCTION: Remove user from team
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION teams.remove_team_member(
|
|
p_tenant_id UUID,
|
|
p_user_id UUID,
|
|
p_removed_by UUID,
|
|
p_reason TEXT DEFAULT NULL
|
|
)
|
|
RETURNS BOOLEAN AS $$
|
|
BEGIN
|
|
UPDATE teams.team_members
|
|
SET
|
|
status = 'removed',
|
|
removed_at = CURRENT_TIMESTAMP,
|
|
removed_by = p_removed_by,
|
|
removal_reason = p_reason,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE tenant_id = p_tenant_id
|
|
AND user_id = p_user_id
|
|
AND status = 'active';
|
|
|
|
RETURN FOUND;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ============================================================================
|
|
-- GRANTS
|
|
-- ============================================================================
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON teams.team_members TO trading_user;
|
|
GRANT SELECT ON teams.v_team_members TO trading_user;
|
|
GRANT EXECUTE ON FUNCTION teams.add_team_member TO trading_user;
|
|
GRANT EXECUTE ON FUNCTION teams.remove_team_member TO trading_user;
|
|
|
|
-- ============================================================================
|
|
-- COMMENTS
|
|
-- ============================================================================
|
|
|
|
COMMENT ON TABLE teams.team_members IS 'Tracks team/organization membership for users';
|
|
COMMENT ON COLUMN teams.team_members.member_type IS 'Type of membership: owner, admin, member, guest';
|
|
COMMENT ON COLUMN teams.team_members.settings IS 'JSON settings for member-specific preferences';
|
|
COMMENT ON VIEW teams.v_team_members IS 'Team members with user details and primary role';
|