Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
196 lines
6.5 KiB
PL/PgSQL
196 lines
6.5 KiB
PL/PgSQL
-- Portal roles, league masters, team extras, support issues
|
|
|
|
CREATE TYPE portal_role AS ENUM ('league_master', 'manager');
|
|
|
|
ALTER TABLE profiles
|
|
ADD COLUMN IF NOT EXISTS portal_role portal_role NOT NULL DEFAULT 'manager';
|
|
|
|
ALTER TABLE teams
|
|
ADD COLUMN IF NOT EXISTS nickname TEXT,
|
|
ADD COLUMN IF NOT EXISTS icon TEXT NOT NULL DEFAULT 'shield';
|
|
|
|
CREATE TABLE IF NOT EXISTS league_masters (
|
|
league_id UUID NOT NULL REFERENCES leagues(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
assigned_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (league_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS support_issues (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
league_id UUID NOT NULL REFERENCES leagues(id) ON DELETE CASCADE,
|
|
competition_id UUID REFERENCES competitions(id) ON DELETE SET NULL,
|
|
submitted_by UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
subject TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'in_progress', 'resolved')),
|
|
master_reply TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TRIGGER support_issues_updated_at
|
|
BEFORE UPDATE ON support_issues
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
-- Helpers
|
|
CREATE OR REPLACE FUNCTION is_global_league_master()
|
|
RETURNS BOOLEAN AS $$
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM profiles WHERE id = auth.uid() AND portal_role = 'league_master'
|
|
);
|
|
$$ LANGUAGE sql SECURITY DEFINER STABLE;
|
|
|
|
CREATE OR REPLACE FUNCTION is_league_master(p_league_id UUID)
|
|
RETURNS BOOLEAN AS $$
|
|
SELECT is_global_league_master()
|
|
OR EXISTS (SELECT 1 FROM leagues WHERE id = p_league_id AND created_by = auth.uid())
|
|
OR EXISTS (
|
|
SELECT 1 FROM league_masters WHERE league_id = p_league_id AND user_id = auth.uid()
|
|
);
|
|
$$ LANGUAGE sql SECURITY DEFINER STABLE;
|
|
|
|
-- Extend organizer check to include assigned masters
|
|
CREATE OR REPLACE FUNCTION is_league_organizer(p_league_id UUID)
|
|
RETURNS BOOLEAN AS $$
|
|
SELECT is_league_master(p_league_id);
|
|
$$ LANGUAGE sql SECURITY DEFINER STABLE;
|
|
|
|
-- Profile trigger: portal role from signup metadata
|
|
CREATE OR REPLACE FUNCTION handle_new_user()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
v_role portal_role;
|
|
BEGIN
|
|
BEGIN
|
|
v_role := COALESCE(
|
|
(NEW.raw_user_meta_data->>'portal_role')::portal_role,
|
|
'manager'::portal_role
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
v_role := 'manager';
|
|
END;
|
|
|
|
INSERT INTO public.profiles (id, display_name, portal_role)
|
|
VALUES (
|
|
NEW.id,
|
|
COALESCE(NEW.raw_user_meta_data->>'display_name', split_part(NEW.email, '@', 1)),
|
|
v_role
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
display_name = COALESCE(EXCLUDED.display_name, profiles.display_name),
|
|
portal_role = COALESCE(EXCLUDED.portal_role, profiles.portal_role);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- RLS new tables
|
|
ALTER TABLE league_masters ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE support_issues ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY league_masters_select ON league_masters FOR SELECT TO authenticated
|
|
USING (is_league_master(league_id) OR user_id = auth.uid());
|
|
|
|
CREATE POLICY league_masters_insert ON league_masters FOR INSERT TO authenticated
|
|
WITH CHECK (is_league_master(league_id));
|
|
|
|
CREATE POLICY league_masters_delete ON league_masters FOR DELETE TO authenticated
|
|
USING (is_league_master(league_id));
|
|
|
|
CREATE POLICY support_issues_select ON support_issues FOR SELECT TO authenticated
|
|
USING (
|
|
submitted_by = auth.uid()
|
|
OR is_league_master(league_id)
|
|
);
|
|
|
|
CREATE POLICY support_issues_insert ON support_issues FOR INSERT TO authenticated
|
|
WITH CHECK (submitted_by = auth.uid());
|
|
|
|
CREATE POLICY support_issues_update ON support_issues FOR UPDATE TO authenticated
|
|
USING (is_league_master(league_id));
|
|
|
|
-- League delete (masters only)
|
|
CREATE POLICY leagues_delete ON leagues FOR DELETE TO authenticated
|
|
USING (is_league_master(id));
|
|
|
|
-- Team delete in draft competitions
|
|
CREATE POLICY teams_delete ON teams FOR DELETE TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM competitions c
|
|
WHERE c.id = competition_id
|
|
AND c.status = 'draft'
|
|
AND is_league_master(c.league_id)
|
|
)
|
|
);
|
|
|
|
-- Recreate standings view with nickname/icon (must DROP first: REPLACE cannot reorder columns)
|
|
DROP VIEW IF EXISTS competition_standings;
|
|
CREATE VIEW competition_standings AS
|
|
SELECT
|
|
t.competition_id,
|
|
t.id AS team_id,
|
|
t.name AS team_name,
|
|
t.nickname AS team_nickname,
|
|
t.icon AS team_icon,
|
|
t.logo_path,
|
|
COUNT(m.id) FILTER (WHERE m.status = 'completed') AS played,
|
|
COUNT(m.id) FILTER (
|
|
WHERE m.status = 'completed'
|
|
AND (
|
|
(m.home_team_id = t.id AND m.home_score > m.away_score)
|
|
OR (m.away_team_id = t.id AND m.away_score > m.home_score)
|
|
)
|
|
) AS won,
|
|
COUNT(m.id) FILTER (
|
|
WHERE m.status = 'completed' AND m.home_score = m.away_score
|
|
) AS drawn,
|
|
COUNT(m.id) FILTER (
|
|
WHERE m.status = 'completed'
|
|
AND (
|
|
(m.home_team_id = t.id AND m.home_score < m.away_score)
|
|
OR (m.away_team_id = t.id AND m.away_score < m.home_score)
|
|
)
|
|
) AS lost,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN m.status = 'completed' AND m.home_team_id = t.id THEN m.home_score
|
|
WHEN m.status = 'completed' AND m.away_team_id = t.id THEN m.away_score
|
|
ELSE 0
|
|
END
|
|
), 0)::INT AS goals_for,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN m.status = 'completed' AND m.home_team_id = t.id THEN m.away_score
|
|
WHEN m.status = 'completed' AND m.away_team_id = t.id THEN m.home_score
|
|
ELSE 0
|
|
END
|
|
), 0)::INT AS goals_against,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN m.status = 'completed' AND m.home_team_id = t.id THEN m.home_score - m.away_score
|
|
WHEN m.status = 'completed' AND m.away_team_id = t.id THEN m.away_score - m.home_score
|
|
ELSE 0
|
|
END
|
|
), 0)::INT AS goal_difference,
|
|
COALESCE(SUM(
|
|
CASE
|
|
WHEN m.status <> 'completed' THEN 0
|
|
WHEN m.home_score = m.away_score THEN 1
|
|
WHEN m.home_team_id = t.id AND m.home_score > m.away_score THEN 3
|
|
WHEN m.away_team_id = t.id AND m.away_score > m.home_score THEN 3
|
|
ELSE 0
|
|
END
|
|
), 0)::INT AS points
|
|
FROM teams t
|
|
LEFT JOIN matches m ON m.competition_id = t.competition_id
|
|
AND (m.home_team_id = t.id OR m.away_team_id = t.id)
|
|
GROUP BY t.competition_id, t.id, t.name, t.nickname, t.icon, t.logo_path;
|
|
|
|
CREATE OR REPLACE FUNCTION get_user_id_by_email(p_email TEXT)
|
|
RETURNS UUID AS $$
|
|
SELECT id FROM auth.users WHERE email = lower(trim(p_email)) LIMIT 1;
|
|
$$ LANGUAGE sql SECURITY DEFINER STABLE;
|