1+ /*
2+ This migration adds team slugs and profile pictures to support user-friendly URLs and team branding.
3+
4+ It performs the following steps:
5+
6+ 1. Adds two new columns to the teams table:
7+ - slug: A URL-friendly version of the team name (e.g. "acme-inc")
8+ - profile_picture_url: URL to the team's profile picture
9+
10+ 2. Creates a slug generation function that:
11+ - Takes a team name and converts it to a URL-friendly format
12+ - Removes special characters, accents, and spaces
13+ - Handles email addresses by only using the part before @
14+ - Converts to lowercase and replaces spaces with hyphens
15+
16+ 3. Installs the unaccent PostgreSQL extension for proper accent handling
17+
18+ 4. Generates initial slugs for all existing teams:
19+ - Uses the team name as base for the slug
20+ - If multiple teams would have the same slug, appends part of the team ID
21+ to ensure uniqueness
22+
23+ 5. Sets up automatic slug generation for new teams:
24+ - Creates a trigger that runs before team insertion
25+ - Generates a unique slug using random suffixes if needed
26+ - Only generates a slug if one isn't explicitly provided
27+
28+ 6. Enforces slug uniqueness with a database constraint
29+ */
30+
31+ ALTER TABLE teams
32+ ADD COLUMN slug TEXT ,
33+ ADD COLUMN profile_picture_url TEXT ;
34+
35+ CREATE OR REPLACE FUNCTION generate_team_slug (name TEXT )
36+ RETURNS TEXT AS $$
37+ DECLARE
38+ base_name TEXT ;
39+ BEGIN
40+ base_name := SPLIT_PART(name, ' @' , 1 );
41+
42+ RETURN LOWER (
43+ REGEXP_REPLACE(
44+ REGEXP_REPLACE(
45+ UNACCENT(TRIM (base_name)),
46+ ' [^a-zA-Z0-9\s -]' ,
47+ ' ' ,
48+ ' g'
49+ ),
50+ ' \s +' ,
51+ ' -' ,
52+ ' g'
53+ )
54+ );
55+ END;
56+ $$ LANGUAGE plpgsql;
57+
58+ CREATE EXTENSION IF NOT EXISTS unaccent;
59+
60+ WITH numbered_teams AS (
61+ SELECT
62+ id,
63+ name,
64+ generate_team_slug(name) as base_slug,
65+ ROW_NUMBER() OVER (PARTITION BY generate_team_slug(name) ORDER BY created_at) as slug_count
66+ FROM teams
67+ WHERE slug IS NULL
68+ )
69+ UPDATE teams
70+ SET slug =
71+ CASE
72+ WHEN t .slug_count = 1 THEN t .base_slug
73+ ELSE t .base_slug || ' -' || SUBSTRING (teams .id ::text , 1 , 4 )
74+ END
75+ FROM numbered_teams t
76+ WHERE teams .id = t .id ;
77+
78+ CREATE OR REPLACE FUNCTION generate_team_slug_trigger ()
79+ RETURNS TRIGGER AS $$
80+ DECLARE
81+ base_slug TEXT ;
82+ test_slug TEXT ;
83+ suffix TEXT ;
84+ BEGIN
85+ IF NEW .slug IS NULL THEN
86+ base_slug := generate_team_slug(NEW .name );
87+ test_slug := base_slug;
88+
89+ WHILE EXISTS (SELECT 1 FROM teams WHERE slug = test_slug) LOOP
90+ suffix := SUBSTRING (gen_random_uuid()::text , 1 , 4 );
91+ test_slug := base_slug || ' -' || suffix;
92+ END LOOP;
93+
94+ NEW .slug := test_slug;
95+ END IF;
96+ RETURN NEW;
97+ END;
98+ $$ LANGUAGE plpgsql;
99+
100+ CREATE TRIGGER team_slug_trigger
101+ BEFORE INSERT ON teams
102+ FOR EACH ROW
103+ EXECUTE FUNCTION generate_team_slug_trigger();
104+
105+ ALTER TABLE teams
106+ ADD CONSTRAINT teams_slug_unique UNIQUE (slug);
107+
108+ ALTER TABLE teams
109+ ALTER COLUMN slug SET NOT NULL ;
0 commit comments