trading-platform-database-v2/ddl/schemas/teams/tables/001_team_members.sql
rckrdmrd e520268348 Migración desde trading-platform/apps/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:32:52 -06:00

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';