diff --git a/supabase/migrations/20241106161733_add_get_campaign.sql b/supabase/migrations/20241106161733_add_get_campaign.sql new file mode 100644 index 0000000..ff43842 --- /dev/null +++ b/supabase/migrations/20241106161733_add_get_campaign.sql @@ -0,0 +1,171 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign(email text, artistid text, clientid text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + client_ids text[] := ARRAY[]::text[]; +BEGIN + IF $3 != '' THEN + client_ids := ARRAY[$3]; + ELSIF $2 != '' THEN + SELECT ARRAY( + SELECT "clientId"::text + FROM "campaigns" c + WHERE c."artistId" = $2::UUID + ) INTO client_ids; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $1 + ) + SELECT ARRAY( + SELECT "clientId" + FROM "campaigns" c + WHERE c."artistId" IN (SELECT artist_id FROM artist_ids) + ) INTO client_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'albums', ( + SELECT jsonb_agg(album_name ORDER BY popularity DESC) AS album_names + FROM ( + SELECT DISTINCT + album_element->>'name' AS album_name, + (album_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("savedAlbums") AS album_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedAlbums" IS NOT NULL + AND jsonb_typeof("savedAlbums") = 'array' -- Ensure it's an array + ) AS subquery + WHERE album_element->>'name' IS NOT NULL + ) AS unique_albums + ), + 'tracks', ( + SELECT jsonb_agg(track_name ORDER BY popularity DESC) AS track_names + FROM ( + SELECT DISTINCT + track_element->>'name' AS track_name, + (track_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("topTracks") AS track_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "topTracks" IS NOT NULL + AND jsonb_typeof("topTracks") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("savedTracks") AS track_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedTracks" IS NOT NULL + AND jsonb_typeof("savedTracks") = 'array' -- Ensure it's an array + ) AS track_subquery + WHERE track_element->>'name' IS NOT NULL + ) AS unique_tracks + ), + 'artists', ( + SELECT jsonb_agg(artist_name ORDER BY popularity DESC) AS artist_names + FROM ( + SELECT DISTINCT + artist_element->>'name' AS artist_name, + (artist_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("followedArtists") AS artist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "followedArtists" IS NOT NULL + AND jsonb_typeof("followedArtists") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("topArtists") AS artist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "topArtists" IS NOT NULL + AND jsonb_typeof("topArtists") = 'array' -- Ensure it's an array + ) AS artist_subquery + WHERE artist_element->>'name' IS NOT NULL + ) AS unique_artists + ), + 'audio_books', ( + SELECT jsonb_agg(audio_book_name::text) AS audio_book_names + FROM ( + SELECT DISTINCT + audio_book_element->>'name' AS audio_book_name + FROM ( + SELECT jsonb_array_elements("savedAudioBooks") AS audio_book_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedAudioBooks" IS NOT NULL + AND jsonb_typeof("savedAudioBooks") = 'array' -- Ensure it's an array + ) AS subquery + WHERE audio_book_element->>'name' IS NOT NULL + ) AS unique_audio_books + ), + 'shows', ( + SELECT jsonb_agg(show_name::text) AS show_names + FROM ( + SELECT DISTINCT + show_element->>'name' AS show_name + FROM ( + SELECT jsonb_array_elements("savedShows") AS show_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedShows" IS NOT NULL + AND jsonb_typeof("savedShows") = 'array' -- Ensure it's an array + ) AS subquery + WHERE show_element->>'name' IS NOT NULL + ) AS unique_shows + ), + 'playlist', ( + SELECT jsonb_agg(playlist_name::text) AS playlist_names + FROM ( + SELECT DISTINCT + playlist_element->>'name' AS playlist_name + FROM ( + SELECT jsonb_array_elements("playlist") AS playlist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "playlist" IS NOT NULL + AND jsonb_typeof("playlist") = 'array' -- Ensure it's an array + ) AS subquery + WHERE playlist_element->>'name' IS NOT NULL + ) AS unique_playlists + ), + 'episodes', ( + SELECT jsonb_agg(episodes_name::text) AS episodes_names + FROM ( + SELECT DISTINCT + episodes_element->>'name' AS episodes_name + FROM ( + SELECT jsonb_array_elements("episodes") AS episodes_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "episodes" IS NOT NULL + AND jsonb_typeof("episodes") = 'array' -- Ensure it's an array + ) AS subquery + WHERE episodes_element->>'name' IS NOT NULL + ) AS unique_episodes + ), + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product + FROM "fans" f + WHERE f."clientId" = ANY(client_ids) + AND f."display_name" IS NOT NULL + AND f."email" IS NOT NULL + ) AS subquery + ) + ) + ); +END;$function$ +; + + diff --git a/supabase/migrations/20241108055712_get_all_fans.sql b/supabase/migrations/20241108055712_get_all_fans.sql new file mode 100644 index 0000000..f01af34 --- /dev/null +++ b/supabase/migrations/20241108055712_get_all_fans.sql @@ -0,0 +1,169 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign(email text, artistid text, clientid text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + client_ids text[] := ARRAY[]::text[]; +BEGIN + IF $3 != '' THEN + client_ids := ARRAY[$3]; + ELSIF $2 != '' THEN + SELECT ARRAY( + SELECT "clientId"::text + FROM "campaigns" c + WHERE c."artistId" = $2::UUID + ) INTO client_ids; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $1 + ) + SELECT ARRAY( + SELECT "clientId" + FROM "campaigns" c + WHERE c."artistId" IN (SELECT artist_id FROM artist_ids) + ) INTO client_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'albums', ( + SELECT jsonb_agg(album_name ORDER BY popularity DESC) AS album_names + FROM ( + SELECT DISTINCT + album_element->>'name' AS album_name, + (album_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("savedAlbums") AS album_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedAlbums" IS NOT NULL + AND jsonb_typeof("savedAlbums") = 'array' -- Ensure it's an array + ) AS subquery + WHERE album_element->>'name' IS NOT NULL + ) AS unique_albums + ), + 'tracks', ( + SELECT jsonb_agg(track_name ORDER BY popularity DESC) AS track_names + FROM ( + SELECT DISTINCT + track_element->>'name' AS track_name, + (track_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("topTracks") AS track_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "topTracks" IS NOT NULL + AND jsonb_typeof("topTracks") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("savedTracks") AS track_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedTracks" IS NOT NULL + AND jsonb_typeof("savedTracks") = 'array' -- Ensure it's an array + ) AS track_subquery + WHERE track_element->>'name' IS NOT NULL + ) AS unique_tracks + ), + 'artists', ( + SELECT jsonb_agg(artist_name ORDER BY popularity DESC) AS artist_names + FROM ( + SELECT DISTINCT + artist_element->>'name' AS artist_name, + (artist_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("followedArtists") AS artist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "followedArtists" IS NOT NULL + AND jsonb_typeof("followedArtists") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("topArtists") AS artist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "topArtists" IS NOT NULL + AND jsonb_typeof("topArtists") = 'array' -- Ensure it's an array + ) AS artist_subquery + WHERE artist_element->>'name' IS NOT NULL + ) AS unique_artists + ), + 'audio_books', ( + SELECT jsonb_agg(audio_book_name::text) AS audio_book_names + FROM ( + SELECT DISTINCT + audio_book_element->>'name' AS audio_book_name + FROM ( + SELECT jsonb_array_elements("savedAudioBooks") AS audio_book_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedAudioBooks" IS NOT NULL + AND jsonb_typeof("savedAudioBooks") = 'array' -- Ensure it's an array + ) AS subquery + WHERE audio_book_element->>'name' IS NOT NULL + ) AS unique_audio_books + ), + 'shows', ( + SELECT jsonb_agg(show_name::text) AS show_names + FROM ( + SELECT DISTINCT + show_element->>'name' AS show_name + FROM ( + SELECT jsonb_array_elements("savedShows") AS show_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "savedShows" IS NOT NULL + AND jsonb_typeof("savedShows") = 'array' -- Ensure it's an array + ) AS subquery + WHERE show_element->>'name' IS NOT NULL + ) AS unique_shows + ), + 'playlist', ( + SELECT jsonb_agg(playlist_name::text) AS playlist_names + FROM ( + SELECT DISTINCT + playlist_element->>'name' AS playlist_name + FROM ( + SELECT jsonb_array_elements("playlist") AS playlist_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "playlist" IS NOT NULL + AND jsonb_typeof("playlist") = 'array' -- Ensure it's an array + ) AS subquery + WHERE playlist_element->>'name' IS NOT NULL + ) AS unique_playlists + ), + 'episodes', ( + SELECT jsonb_agg(episodes_name::text) AS episodes_names + FROM ( + SELECT DISTINCT + episodes_element->>'name' AS episodes_name + FROM ( + SELECT jsonb_array_elements("episodes") AS episodes_element + FROM "fans" + WHERE "clientId" = ANY(client_ids) + AND "episodes" IS NOT NULL + AND jsonb_typeof("episodes") = 'array' -- Ensure it's an array + ) AS subquery + WHERE episodes_element->>'name' IS NOT NULL + ) AS unique_episodes + ), + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product + FROM "fans" f + WHERE f."clientId" = ANY(client_ids) + ) AS subquery + ) + ) + ); +END;$function$ +; + + diff --git a/supabase/migrations/20241111092359_update_campaignId_of_luhtyler.sql b/supabase/migrations/20241111092359_update_campaignId_of_luhtyler.sql new file mode 100644 index 0000000..8917a4f --- /dev/null +++ b/supabase/migrations/20241111092359_update_campaignId_of_luhtyler.sql @@ -0,0 +1,10 @@ +create table if not exists "public"."fans" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."fans" ADD COLUMN "campaignId" TEXT; + +UPDATE "public"."fans" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + + diff --git a/supabase/migrations/20241112063734_campaign_id_foreign_keys.sql b/supabase/migrations/20241112063734_campaign_id_foreign_keys.sql new file mode 100644 index 0000000..e183f87 --- /dev/null +++ b/supabase/migrations/20241112063734_campaign_id_foreign_keys.sql @@ -0,0 +1,115 @@ +CREATE TABLE IF NOT EXISTS "public"."artists" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "name" text +); + +CREATE TABLE IF NOT EXISTS "public"."campaigns" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "clientId" text, + "artistId" uuid NOT NULL DEFAULT gen_random_uuid(), + PRIMARY KEY ("id") +); + +INSERT INTO "public"."artists" ("id", "name") +VALUES ('00000000-0000-0000-0000-000000000000', 'virtual_artist'); + +INSERT INTO "public"."campaigns" ("id", "clientId", "artistId") +VALUES ( + '00000000-0000-0000-0000-000000000000', + 'virtual_campaign', + '00000000-0000-0000-0000-000000000000' +); + +-- fans -- +create table if not exists "public"."fans" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."fans" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."fans" ADD COLUMN "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."fans" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +-- apple_login_button_clicked +create table if not exists "public"."apple_login_button_clicked" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."apple_login_button_clicked" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."apple_login_button_clicked" ADD COLUMN IF NOT EXISTS "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."apple_login_button_clicked" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +-- apple_play_button_clicked -- +create table if not exists "public"."apple_play_button_clicked" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."apple_play_button_clicked" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."apple_play_button_clicked" ADD COLUMN IF NOT EXISTS "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."apple_play_button_clicked" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +-- popup_open -- +create table if not exists "public"."popup_open" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."popup_open" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."popup_open" ADD COLUMN IF NOT EXISTS "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."popup_open" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +-- spotify_login_button_clicked +create table if not exists "public"."spotify_login_button_clicked" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."spotify_login_button_clicked" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."spotify_login_button_clicked" ADD COLUMN IF NOT EXISTS "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."spotify_login_button_clicked" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +-- spotify_play_button_clicked -- +create table if not exists "public"."spotify_play_button_clicked" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +ALTER TABLE "public"."spotify_play_button_clicked" DROP COLUMN IF EXISTS "campaignId"; + +ALTER TABLE "public"."spotify_play_button_clicked" ADD COLUMN IF NOT EXISTS "campaignId" uuid default '00000000-0000-0000-0000-000000000000'; + +UPDATE "public"."spotify_play_button_clicked" SET "campaignId" = 'b658c81f-fab7-48e2-9284-d3a85e49304a' WHERE "clientId" = 'LUHTYLER'; + +alter table "public"."apple_login_button_clicked" add constraint "apple_login_button_clicked_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES campaigns(id) ON UPDATE CASCADE not valid; + +alter table "public"."apple_login_button_clicked" validate constraint "apple_login_button_clicked_campaignId_fkey"; + +alter table "public"."apple_play_button_clicked" add constraint "apple_play_button_clicked_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES campaigns(id) ON UPDATE CASCADE not valid; + +alter table "public"."apple_play_button_clicked" validate constraint "apple_play_button_clicked_campaignId_fkey"; + +alter table "public"."fans" add constraint "fans_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES campaigns(id) ON UPDATE CASCADE not valid; + +alter table "public"."fans" validate constraint "fans_campaignId_fkey"; + +alter table "public"."spotify_login_button_clicked" add constraint "spotify_login_button_clicked_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES campaigns(id) ON UPDATE CASCADE not valid; + +alter table "public"."spotify_login_button_clicked" validate constraint "spotify_login_button_clicked_campaignId_fkey"; + +alter table "public"."spotify_play_button_clicked" add constraint "spotify_play_button_clicked_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES campaigns(id) ON UPDATE CASCADE not valid; + +alter table "public"."spotify_play_button_clicked" validate constraint "spotify_play_button_clicked_campaignId_fkey"; + + diff --git a/supabase/migrations/20241114044031_get_campaign_fans.sql b/supabase/migrations/20241114044031_get_campaign_fans.sql new file mode 100644 index 0000000..9333a04 --- /dev/null +++ b/supabase/migrations/20241114044031_get_campaign_fans.sql @@ -0,0 +1,52 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign_fans(artistid text, email text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + artist_ids UUID[]; +BEGIN + artist_ids := ARRAY[]::UUID[]; + IF $1 IS NULL OR $1 = '' THEN + SELECT ARRAY( + SELECT (jsonb_array_elements_text("artistIds"))::UUID + FROM "accounts" a + WHERE a."email" = $2 + ) INTO artist_ids; + ELSE + SELECT ARRAY(SELECT id FROM "artists" WHERE "id" = $1::UUID) INTO artist_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'campaigns', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + c.id, + c."clientId", + c."artistId", + c."timestamp", + ( + SELECT jsonb_agg(jsonb_build_object( + 'id', f.id, + 'display_name', f.display_name, + 'product', f.product, + 'country', f.country, + 'email', f.email, + 'timestamp', f.timestamp + )) + FROM "fans" f + WHERE f."campaignId" = c."id" + ) AS fans + FROM "campaigns" c + WHERE c."artistId" = ANY(artist_ids) + ) AS subquery + ) + ) + ); + +END;$function$ +; + + diff --git a/supabase/migrations/20241118185054_add_column_genres.sql b/supabase/migrations/20241118185054_add_column_genres.sql new file mode 100644 index 0000000..7e06e92 --- /dev/null +++ b/supabase/migrations/20241118185054_add_column_genres.sql @@ -0,0 +1,8 @@ +create table if not exists "public"."fans" ( + "id" uuid not null default gen_random_uuid(), + "clientId" text +); + +alter table "public"."fans" add column "genres" jsonb default '[]'::jsonb; + + diff --git a/supabase/migrations/20241118185523_update_get_campaign.sql b/supabase/migrations/20241118185523_update_get_campaign.sql new file mode 100644 index 0000000..98d4f64 --- /dev/null +++ b/supabase/migrations/20241118185523_update_get_campaign.sql @@ -0,0 +1,187 @@ +drop function if exists "public"."get_campaign"(email text, artistid text, clientid text); + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign(email text, artistid text, campaignid text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + campaign_ids UUID[] := ARRAY[]::UUID[]; +BEGIN + IF $3 != '' THEN + campaign_ids := ARRAY[$3]; + ELSIF $2 != '' THEN + SELECT ARRAY( + SELECT "id"::UUID + FROM "campaigns" c + WHERE c."artistId" = $2::UUID + ) INTO campaign_ids; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $1 + ) + SELECT ARRAY( + SELECT "id" + FROM "campaigns" c + WHERE c."artistId" IN (SELECT artist_id FROM artist_ids) + ) INTO campaign_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'genres', ( + SELECT jsonb_agg(genre_name ORDER BY popularity DESC) AS genre_names + FROM ( + SELECT DISTINCT + genre_element->>'name' AS genre_name, + (genre_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("genres") AS genre_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "genres" IS NOT NULL + AND jsonb_typeof("genres") = 'array' -- Ensure it's an array + ) AS subquery + WHERE genre_element->>'name' IS NOT NULL + ) AS unique_genres + ), + 'albums', ( + SELECT jsonb_agg(album_name ORDER BY popularity DESC) AS album_names + FROM ( + SELECT DISTINCT + album_element->>'name' AS album_name, + (album_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("savedAlbums") AS album_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAlbums" IS NOT NULL + AND jsonb_typeof("savedAlbums") = 'array' -- Ensure it's an array + ) AS subquery + WHERE album_element->>'name' IS NOT NULL + ) AS unique_albums + ), + 'tracks', ( + SELECT jsonb_agg(track_name ORDER BY popularity DESC) AS track_names + FROM ( + SELECT DISTINCT + track_element->>'name' AS track_name, + (track_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("topTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topTracks" IS NOT NULL + AND jsonb_typeof("topTracks") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("savedTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedTracks" IS NOT NULL + AND jsonb_typeof("savedTracks") = 'array' -- Ensure it's an array + ) AS track_subquery + WHERE track_element->>'name' IS NOT NULL + ) AS unique_tracks + ), + 'artists', ( + SELECT jsonb_agg(artist_name ORDER BY popularity DESC) AS artist_names + FROM ( + SELECT DISTINCT + artist_element->>'name' AS artist_name, + (artist_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("followedArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "followedArtists" IS NOT NULL + AND jsonb_typeof("followedArtists") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("topArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topArtists" IS NOT NULL + AND jsonb_typeof("topArtists") = 'array' -- Ensure it's an array + ) AS artist_subquery + WHERE artist_element->>'name' IS NOT NULL + ) AS unique_artists + ), + 'audio_books', ( + SELECT jsonb_agg(audio_book_name::text) AS audio_book_names + FROM ( + SELECT DISTINCT + audio_book_element->>'name' AS audio_book_name + FROM ( + SELECT jsonb_array_elements("savedAudioBooks") AS audio_book_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAudioBooks" IS NOT NULL + AND jsonb_typeof("savedAudioBooks") = 'array' -- Ensure it's an array + ) AS subquery + WHERE audio_book_element->>'name' IS NOT NULL + ) AS unique_audio_books + ), + 'shows', ( + SELECT jsonb_agg(show_name::text) AS show_names + FROM ( + SELECT DISTINCT + show_element->>'name' AS show_name + FROM ( + SELECT jsonb_array_elements("savedShows") AS show_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedShows" IS NOT NULL + AND jsonb_typeof("savedShows") = 'array' -- Ensure it's an array + ) AS subquery + WHERE show_element->>'name' IS NOT NULL + ) AS unique_shows + ), + 'playlist', ( + SELECT jsonb_agg(playlist_name::text) AS playlist_names + FROM ( + SELECT DISTINCT + playlist_element->>'name' AS playlist_name + FROM ( + SELECT jsonb_array_elements("playlist") AS playlist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "playlist" IS NOT NULL + AND jsonb_typeof("playlist") = 'array' -- Ensure it's an array + ) AS subquery + WHERE playlist_element->>'name' IS NOT NULL + ) AS unique_playlists + ), + 'episodes', ( + SELECT jsonb_agg(episodes_name::text) AS episodes_names + FROM ( + SELECT DISTINCT + episodes_element->>'name' AS episodes_name + FROM ( + SELECT jsonb_array_elements("episodes") AS episodes_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "episodes" IS NOT NULL + AND jsonb_typeof("episodes") = 'array' -- Ensure it's an array + ) AS subquery + WHERE episodes_element->>'name' IS NOT NULL + ) AS unique_episodes + ), + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product + FROM "fans" f + WHERE f."campaignId" = ANY(campaign_ids) + ) AS subquery + ) + ) + ); +END$function$ +; + + diff --git a/supabase/migrations/20241119002129_get_episodes_full_elements.sql b/supabase/migrations/20241119002129_get_episodes_full_elements.sql new file mode 100644 index 0000000..f218945 --- /dev/null +++ b/supabase/migrations/20241119002129_get_episodes_full_elements.sql @@ -0,0 +1,186 @@ +drop function if exists "public"."get_campaign"(email text, artistid text, clientid text); + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign(email text, artistid text, campaignid text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + campaign_ids UUID[] := ARRAY[]::UUID[]; +BEGIN + IF $3 != '' THEN + campaign_ids := ARRAY[$3]; + ELSIF $2 != '' THEN + SELECT ARRAY( + SELECT "id"::UUID + FROM "campaigns" c + WHERE c."artistId" = $2::UUID + ) INTO campaign_ids; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $1 + ) + SELECT ARRAY( + SELECT "id" + FROM "campaigns" c + WHERE c."artistId" IN (SELECT artist_id FROM artist_ids) + ) INTO campaign_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'genres', ( + SELECT jsonb_agg(genre_name ORDER BY popularity DESC) AS genre_names + FROM ( + SELECT DISTINCT + genre_element->>'name' AS genre_name, + (genre_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("genres") AS genre_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "genres" IS NOT NULL + AND jsonb_typeof("genres") = 'array' -- Ensure it's an array + ) AS subquery + WHERE genre_element->>'name' IS NOT NULL + ) AS unique_genres + ), + 'albums', ( + SELECT jsonb_agg(album_name ORDER BY popularity DESC) AS album_names + FROM ( + SELECT DISTINCT + album_element->>'name' AS album_name, + (album_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("savedAlbums") AS album_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAlbums" IS NOT NULL + AND jsonb_typeof("savedAlbums") = 'array' -- Ensure it's an array + ) AS subquery + WHERE album_element->>'name' IS NOT NULL + ) AS unique_albums + ), + 'tracks', ( + SELECT jsonb_agg(track_name ORDER BY popularity DESC) AS track_names + FROM ( + SELECT DISTINCT + track_element->>'name' AS track_name, + (track_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("topTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topTracks" IS NOT NULL + AND jsonb_typeof("topTracks") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("savedTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedTracks" IS NOT NULL + AND jsonb_typeof("savedTracks") = 'array' -- Ensure it's an array + ) AS track_subquery + WHERE track_element->>'name' IS NOT NULL + ) AS unique_tracks + ), + 'artists', ( + SELECT jsonb_agg(artist_name ORDER BY popularity DESC) AS artist_names + FROM ( + SELECT DISTINCT + artist_element->>'name' AS artist_name, + (artist_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("followedArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "followedArtists" IS NOT NULL + AND jsonb_typeof("followedArtists") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("topArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topArtists" IS NOT NULL + AND jsonb_typeof("topArtists") = 'array' -- Ensure it's an array + ) AS artist_subquery + WHERE artist_element->>'name' IS NOT NULL + ) AS unique_artists + ), + 'audio_books', ( + SELECT jsonb_agg(audio_book_name::text) AS audio_book_names + FROM ( + SELECT DISTINCT + audio_book_element->>'name' AS audio_book_name + FROM ( + SELECT jsonb_array_elements("savedAudioBooks") AS audio_book_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAudioBooks" IS NOT NULL + AND jsonb_typeof("savedAudioBooks") = 'array' -- Ensure it's an array + ) AS subquery + WHERE audio_book_element->>'name' IS NOT NULL + ) AS unique_audio_books + ), + 'shows', ( + SELECT jsonb_agg(show_name::text) AS show_names + FROM ( + SELECT DISTINCT + show_element->>'name' AS show_name + FROM ( + SELECT jsonb_array_elements("savedShows") AS show_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedShows" IS NOT NULL + AND jsonb_typeof("savedShows") = 'array' -- Ensure it's an array + ) AS subquery + WHERE show_element->>'name' IS NOT NULL + ) AS unique_shows + ), + 'playlist', ( + SELECT jsonb_agg(playlist_name::text) AS playlist_names + FROM ( + SELECT DISTINCT + playlist_element->>'name' AS playlist_name + FROM ( + SELECT jsonb_array_elements("playlist") AS playlist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "playlist" IS NOT NULL + AND jsonb_typeof("playlist") = 'array' -- Ensure it's an array + ) AS subquery + WHERE playlist_element->>'name' IS NOT NULL + ) AS unique_playlists + ), + 'episodes', ( + SELECT jsonb_agg(distinct jsonb_build_object( + 'name', episodes_element->>'name', + 'description', episodes_element->>'description' + )) AS episodes_info + FROM ( + SELECT jsonb_array_elements("episodes") AS episodes_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "episodes" IS NOT NULL + AND jsonb_typeof("episodes") = 'array' + ) AS subquery + WHERE episodes_element->>'name' IS NOT NULL + ), + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product + FROM "fans" f + WHERE f."campaignId" = ANY(campaign_ids) + ) AS subquery + ) + ) + ); +END$function$ +; + + diff --git a/supabase/migrations/20241119164720_get_fans_listening_top_songs.sql b/supabase/migrations/20241119164720_get_fans_listening_top_songs.sql new file mode 100644 index 0000000..989a12b --- /dev/null +++ b/supabase/migrations/20241119164720_get_fans_listening_top_songs.sql @@ -0,0 +1,57 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_fans_listening_top_songs(artistid text, email text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + artist_names TEXT[]; +BEGIN + IF $1 != '' THEN + SELECT ARRAY( + SELECT "name" + FROM "artists" a + WHERE a."id" = $1::UUID + ) INTO artist_names; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $2 + ) + SELECT ARRAY( + SELECT "name" + FROM "artists" a + WHERE a."id" IN (SELECT artist_id FROM artist_ids) + ) INTO artist_names; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product, + f.timestamp, + ( + SELECT jsonb_agg(rp) + FROM jsonb_array_elements(f."recentlyPlayed") AS rp + WHERE rp->>'artist' IN (SELECT unnest(artist_names)) + ) AS recently_played_filtered + FROM "fans" f + WHERE EXISTS ( + SELECT 1 + FROM jsonb_array_elements(f."recentlyPlayed") AS rp + WHERE rp->>'artist' IN (SELECT unnest(artist_names)) + ) + ) AS subquery + ) + ) + ); +END;$function$ +; + + diff --git a/supabase/migrations/20241119182129_fix_syntax.sql b/supabase/migrations/20241119182129_fix_syntax.sql new file mode 100644 index 0000000..f218945 --- /dev/null +++ b/supabase/migrations/20241119182129_fix_syntax.sql @@ -0,0 +1,186 @@ +drop function if exists "public"."get_campaign"(email text, artistid text, clientid text); + +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_campaign(email text, artistid text, campaignid text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + campaign_ids UUID[] := ARRAY[]::UUID[]; +BEGIN + IF $3 != '' THEN + campaign_ids := ARRAY[$3]; + ELSIF $2 != '' THEN + SELECT ARRAY( + SELECT "id"::UUID + FROM "campaigns" c + WHERE c."artistId" = $2::UUID + ) INTO campaign_ids; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $1 + ) + SELECT ARRAY( + SELECT "id" + FROM "campaigns" c + WHERE c."artistId" IN (SELECT artist_id FROM artist_ids) + ) INTO campaign_ids; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'genres', ( + SELECT jsonb_agg(genre_name ORDER BY popularity DESC) AS genre_names + FROM ( + SELECT DISTINCT + genre_element->>'name' AS genre_name, + (genre_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("genres") AS genre_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "genres" IS NOT NULL + AND jsonb_typeof("genres") = 'array' -- Ensure it's an array + ) AS subquery + WHERE genre_element->>'name' IS NOT NULL + ) AS unique_genres + ), + 'albums', ( + SELECT jsonb_agg(album_name ORDER BY popularity DESC) AS album_names + FROM ( + SELECT DISTINCT + album_element->>'name' AS album_name, + (album_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("savedAlbums") AS album_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAlbums" IS NOT NULL + AND jsonb_typeof("savedAlbums") = 'array' -- Ensure it's an array + ) AS subquery + WHERE album_element->>'name' IS NOT NULL + ) AS unique_albums + ), + 'tracks', ( + SELECT jsonb_agg(track_name ORDER BY popularity DESC) AS track_names + FROM ( + SELECT DISTINCT + track_element->>'name' AS track_name, + (track_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("topTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topTracks" IS NOT NULL + AND jsonb_typeof("topTracks") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("savedTracks") AS track_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedTracks" IS NOT NULL + AND jsonb_typeof("savedTracks") = 'array' -- Ensure it's an array + ) AS track_subquery + WHERE track_element->>'name' IS NOT NULL + ) AS unique_tracks + ), + 'artists', ( + SELECT jsonb_agg(artist_name ORDER BY popularity DESC) AS artist_names + FROM ( + SELECT DISTINCT + artist_element->>'name' AS artist_name, + (artist_element->>'popularity')::int AS popularity + FROM ( + SELECT jsonb_array_elements("followedArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "followedArtists" IS NOT NULL + AND jsonb_typeof("followedArtists") = 'array' -- Ensure it's an array + UNION ALL + SELECT jsonb_array_elements("topArtists") AS artist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "topArtists" IS NOT NULL + AND jsonb_typeof("topArtists") = 'array' -- Ensure it's an array + ) AS artist_subquery + WHERE artist_element->>'name' IS NOT NULL + ) AS unique_artists + ), + 'audio_books', ( + SELECT jsonb_agg(audio_book_name::text) AS audio_book_names + FROM ( + SELECT DISTINCT + audio_book_element->>'name' AS audio_book_name + FROM ( + SELECT jsonb_array_elements("savedAudioBooks") AS audio_book_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedAudioBooks" IS NOT NULL + AND jsonb_typeof("savedAudioBooks") = 'array' -- Ensure it's an array + ) AS subquery + WHERE audio_book_element->>'name' IS NOT NULL + ) AS unique_audio_books + ), + 'shows', ( + SELECT jsonb_agg(show_name::text) AS show_names + FROM ( + SELECT DISTINCT + show_element->>'name' AS show_name + FROM ( + SELECT jsonb_array_elements("savedShows") AS show_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "savedShows" IS NOT NULL + AND jsonb_typeof("savedShows") = 'array' -- Ensure it's an array + ) AS subquery + WHERE show_element->>'name' IS NOT NULL + ) AS unique_shows + ), + 'playlist', ( + SELECT jsonb_agg(playlist_name::text) AS playlist_names + FROM ( + SELECT DISTINCT + playlist_element->>'name' AS playlist_name + FROM ( + SELECT jsonb_array_elements("playlist") AS playlist_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "playlist" IS NOT NULL + AND jsonb_typeof("playlist") = 'array' -- Ensure it's an array + ) AS subquery + WHERE playlist_element->>'name' IS NOT NULL + ) AS unique_playlists + ), + 'episodes', ( + SELECT jsonb_agg(distinct jsonb_build_object( + 'name', episodes_element->>'name', + 'description', episodes_element->>'description' + )) AS episodes_info + FROM ( + SELECT jsonb_array_elements("episodes") AS episodes_element + FROM "fans" + WHERE "campaignId" = ANY(campaign_ids) + AND "episodes" IS NOT NULL + AND jsonb_typeof("episodes") = 'array' + ) AS subquery + WHERE episodes_element->>'name' IS NOT NULL + ), + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product + FROM "fans" f + WHERE f."campaignId" = ANY(campaign_ids) + ) AS subquery + ) + ) + ); +END$function$ +; + + diff --git a/supabase/migrations/20241119224720_recently_played_scalar_fix.sql b/supabase/migrations/20241119224720_recently_played_scalar_fix.sql new file mode 100644 index 0000000..c8a2f04 --- /dev/null +++ b/supabase/migrations/20241119224720_recently_played_scalar_fix.sql @@ -0,0 +1,59 @@ +set check_function_bodies = off; + +CREATE OR REPLACE FUNCTION public.get_fans_listening_top_songs(artistid text, email text) + RETURNS jsonb + LANGUAGE plpgsql +AS $function$DECLARE + artist_names TEXT[]; +BEGIN + IF $1 != '' THEN + SELECT ARRAY( + SELECT "name" + FROM "artists" a + WHERE a."id" = $1::UUID + ) INTO artist_names; + ELSE + WITH artist_ids AS ( + SELECT (jsonb_array_elements_text("artistIds"))::UUID AS artist_id + FROM "accounts" a + WHERE a."email" = $2 + ) + SELECT ARRAY( + SELECT "name" + FROM "artists" a + WHERE a."id" IN (SELECT artist_id FROM artist_ids) + ) INTO artist_names; + END IF; + + RETURN ( + SELECT jsonb_build_object( + 'fans', ( + SELECT jsonb_agg(subquery) + FROM ( + SELECT + f.display_name, + f.country, + f.email, + f.product, + f.timestamp, + ( + SELECT jsonb_agg(rp) + FROM jsonb_array_elements(f."recentlyPlayed") AS rp + WHERE rp->>'artist' IN (SELECT unnest(artist_names)) + AND jsonb_typeof(f."recentlyPlayed") = 'array' + ) AS recently_played_filtered + FROM "fans" f + WHERE EXISTS ( + SELECT 1 + FROM jsonb_array_elements(f."recentlyPlayed") AS rp + WHERE rp->>'artist' IN (SELECT unnest(artist_names)) + AND jsonb_typeof(f."recentlyPlayed") = 'array' + ) + ) AS subquery + ) + ) + ); +END;$function$ +; + + diff --git a/supabase/migrations/20241121201709_aritst_social_links_table.sql b/supabase/migrations/20241121201709_aritst_social_links_table.sql new file mode 100644 index 0000000..f6ec273 --- /dev/null +++ b/supabase/migrations/20241121201709_aritst_social_links_table.sql @@ -0,0 +1,21 @@ +create type "public"."social_type" as enum ('TIKTOK', 'YOUTUBE', 'INSTAGRAM', 'TWITTER', 'SPOTIFY', 'APPLE'); + +-- FIX: Drop the artists table if it exists (from README.md documented solution) +drop table if exists "public"."artists"; + +create table if not exists "public"."artists" ( + "id" uuid not null default gen_random_uuid(), + "name" text, + PRIMARY KEY ("id") +); +create table if not exists "public"."artist_social_links" ( + "id" uuid not null default gen_random_uuid(), + "link" text, + "type" social_type, + "artistId" uuid not null, + primary key ("id"), + CONSTRAINT "artist_social_links_artistId_fkey" + foreign key ("artistId") + references "public"."artists" ("id") + on delete cascade +); \ No newline at end of file diff --git a/supabase/migrations/20241124224638_analysis_table.sql b/supabase/migrations/20241124224638_analysis_table.sql new file mode 100644 index 0000000..72e1709 --- /dev/null +++ b/supabase/migrations/20241124224638_analysis_table.sql @@ -0,0 +1,17 @@ +create table if not exists "public"."tiktok_analysis" ( + "id" uuid not null default gen_random_uuid(), + "name" text, + "avatar" text, + "nickname" text, + "videos" jsonb, + "total_video_comments_count" integer, + "segments" jsonb, + "chat_id" uuid default gen_random_uuid() +); + + +alter table "public"."tiktok_analysis" enable row level security; + +CREATE UNIQUE INDEX tiktok_analysis_pkey ON public.tiktok_analysis USING btree (id); + +alter table "public"."tiktok_analysis" add constraint "tiktok_analysis_pkey" PRIMARY KEY using index "tiktok_analysis_pkey"; \ No newline at end of file diff --git a/supabase/migrations/20241124234638_analysis_table_update.sql b/supabase/migrations/20241124234638_analysis_table_update.sql new file mode 100644 index 0000000..b3b90f8 --- /dev/null +++ b/supabase/migrations/20241124234638_analysis_table_update.sql @@ -0,0 +1,4 @@ +ALTER TABLE "public"."tiktok_analysis" ADD COLUMN if not exists "bio" TEXT; +ALTER TABLE "public"."tiktok_analysis" ADD COLUMN if not exists "region" TEXT; +ALTER TABLE "public"."tiktok_analysis" ADD COLUMN if not exists "fans" integer; +ALTER TABLE "public"."tiktok_analysis" ADD COLUMN if not exists "following" integer; diff --git a/supabase/migrations/20241128122018_add_artistid.sql b/supabase/migrations/20241128122018_add_artistid.sql new file mode 100644 index 0000000..9646451 --- /dev/null +++ b/supabase/migrations/20241128122018_add_artistid.sql @@ -0,0 +1,12 @@ +alter table "public"."tiktok_analysis" add column "artistId" uuid default '00000000-0000-0000-0000-000000000000'::uuid; + +alter table "public"."tiktok_analysis" add constraint "tiktok_analysis_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) ON UPDATE CASCADE not valid; + +alter table "public"."tiktok_analysis" validate constraint "tiktok_analysis_artistId_fkey"; + +UPDATE "public"."tiktok_analysis" SET "artistId" = 'eb260f72-81b1-4638-8c9b-3b61f6b779b7' WHERE "name" = 'breland'; +UPDATE "public"."tiktok_analysis" SET "artistId" = 'd325c37f-558b-4015-8863-340e4d8c6ec5' WHERE "name" = 'officialluhtyler'; +UPDATE "public"."tiktok_analysis" SET "artistId" = '50baf49a-55f8-4848-8882-caedc4d67d0f' WHERE "name" = 'sweetman.eth'; + + + diff --git a/supabase/migrations/20241203140523_timestamp_analysis.sql b/supabase/migrations/20241203140523_timestamp_analysis.sql new file mode 100644 index 0000000..a415ce3 --- /dev/null +++ b/supabase/migrations/20241203140523_timestamp_analysis.sql @@ -0,0 +1,2 @@ +ALTER TABLE "public"."tiktok_analysis" DROP COLUMN IF EXISTS "timestamp"; +ALTER TABLE "public"."tiktok_analysis" ADD COLUMN IF NOT EXISTS "timestamp" TIMESTAMP DEFAULT CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/supabase/migrations/20241205153104_tiktok_reports_table.sql b/supabase/migrations/20241205153104_tiktok_reports_table.sql new file mode 100644 index 0000000..58c3fca --- /dev/null +++ b/supabase/migrations/20241205153104_tiktok_reports_table.sql @@ -0,0 +1,15 @@ +create table "public"."tiktok_reports" ( + "id" uuid not null default gen_random_uuid(), + "timestamp" timestamp with time zone not null default now(), + "summary" text, + "next_steps" text, + "report" text, + "stack_unique_id" text +); + +alter table "public"."tiktok_reports" enable row level security; + +CREATE UNIQUE INDEX tiktok_reports_pkey ON public.tiktok_reports USING btree (id); + +alter table "public"."tiktok_reports" add constraint "tiktok_reports_pkey" PRIMARY KEY using index "tiktok_reports_pkey"; + diff --git a/supabase/migrations/20241205160047_update_artists_table.sql b/supabase/migrations/20241205160047_update_artists_table.sql new file mode 100644 index 0000000..3faff1a --- /dev/null +++ b/supabase/migrations/20241205160047_update_artists_table.sql @@ -0,0 +1,7 @@ +alter table "public"."artists" add column "knowledges" jsonb default '[]'::jsonb; + +alter table "public"."artists" add column "label" text; + +alter table "public"."artists" add column "instruction" text; + + diff --git a/supabase/migrations/20241209025457_add_foreign_key.sql b/supabase/migrations/20241209025457_add_foreign_key.sql new file mode 100644 index 0000000..70afdf5 --- /dev/null +++ b/supabase/migrations/20241209025457_add_foreign_key.sql @@ -0,0 +1,19 @@ +create table if not exists "public"."accounts" ( + "id" uuid not null default gen_random_uuid(), + "email" text, + "artistIds" jsonb default '[]'::jsonb, + PRIMARY KEY ("id") +); + +create table if not exists "public"."credits_usage" ( + "id" uuid not null default gen_random_uuid(), + "account_id" uuid +); + +ALTER TABLE "public"."credits_usage" ADD COLUMN "timestamp" TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + +alter table "public"."credits_usage" add constraint "credits_usage_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON UPDATE CASCADE not valid; + +alter table "public"."credits_usage" validate constraint "credits_usage_account_id_fkey"; + + diff --git a/supabase/migrations/20241210144324_update_account_table.sql b/supabase/migrations/20241210144324_update_account_table.sql new file mode 100644 index 0000000..c7381bc --- /dev/null +++ b/supabase/migrations/20241210144324_update_account_table.sql @@ -0,0 +1,8 @@ +alter table "public"."accounts" add column "image" text; + +alter table "public"."accounts" add column "instruction" text; + +alter table "public"."accounts" add column "name" text; + +alter table "public"."accounts" add column "organization" text; + diff --git a/supabase/migrations/20241223031656_funnel_tables.sql b/supabase/migrations/20241223031656_funnel_tables.sql new file mode 100644 index 0000000..87a930e --- /dev/null +++ b/supabase/migrations/20241223031656_funnel_tables.sql @@ -0,0 +1,120 @@ +create table "public"."funnel_analytics" ( + "id" uuid not null default gen_random_uuid(), + "chat_id" uuid default gen_random_uuid(), + "timestamp" timestamp without time zone default CURRENT_TIMESTAMP, + "status" bigint default '0'::bigint +); + +alter table "public"."funnel_analytics" enable row level security; + +create table "public"."funnel_analytics_comments" ( + "id" uuid not null default gen_random_uuid(), + "analysis_id" uuid default gen_random_uuid(), + "username" text, + "timestamp" bigint default '0'::bigint, + "comment" text, + "post_url" text, + "type" social_type +); + + +alter table "public"."funnel_analytics_comments" enable row level security; + +create table "public"."funnel_analytics_profile" ( + "id" uuid not null default gen_random_uuid(), + "analysis_id" uuid default gen_random_uuid(), + "name" text, + "nickname" text, + "region" text, + "avatar" text, + "bio" text, + "followers" bigint, + "followings" bigint, + "type" social_type +); + + +alter table "public"."funnel_analytics_profile" enable row level security; + +create table "public"."funnel_reports" ( + "id" uuid not null default gen_random_uuid(), + "timestamp" timestamp with time zone not null default now(), + "summary" text, + "next_steps" text, + "report" text, + "stack_unique_id" text, + "type" social_type +); + + +alter table "public"."funnel_reports" enable row level security; + +create table "public"."spotify_analytics_albums" ( + "id" uuid not null default gen_random_uuid(), + "uri" text, + "name" text, + "popularity" bigint default '0'::bigint, + "release_date" bigint default '0'::bigint, + "created_at" timestamp with time zone not null default now(), + "analysis_id" uuid default gen_random_uuid() +); + + +alter table "public"."spotify_analytics_albums" enable row level security; + +create table "public"."spotify_analytics_tracks" ( + "id" uuid not null default gen_random_uuid(), + "uri" text, + "name" text, + "popularity" bigint default '0'::bigint, + "artist_name" text, + "created_at" timestamp with time zone not null default now(), + "analysis_id" uuid default gen_random_uuid() +); + + +alter table "public"."spotify_analytics_tracks" enable row level security; + +alter table "public"."funnel_analytics" add column if not exists "status" bigint default '0'::bigint; + +CREATE UNIQUE INDEX funnel_analysis_comments_pkey ON public.funnel_analytics_profile USING btree (id); + +CREATE UNIQUE INDEX funnel_analysis_comments_pkey1 ON public.funnel_analytics_comments USING btree (id); + +CREATE UNIQUE INDEX funnel_analysis_pkey ON public.funnel_analytics USING btree (id); + +CREATE UNIQUE INDEX funnel_reports_pkey ON public.funnel_reports USING btree (id); + +CREATE UNIQUE INDEX spotify_analytics_albums_pkey ON public.spotify_analytics_albums USING btree (id); + +CREATE UNIQUE INDEX spotify_analytics_tracks_pkey ON public.spotify_analytics_tracks USING btree (id); + +alter table "public"."funnel_analytics" add constraint "funnel_analysis_pkey" PRIMARY KEY using index "funnel_analysis_pkey"; + +alter table "public"."funnel_analytics_comments" add constraint "funnel_analysis_comments_pkey1" PRIMARY KEY using index "funnel_analysis_comments_pkey1"; + +alter table "public"."funnel_analytics_profile" add constraint "funnel_analysis_comments_pkey" PRIMARY KEY using index "funnel_analysis_comments_pkey"; + +alter table "public"."funnel_reports" add constraint "funnel_reports_pkey" PRIMARY KEY using index "funnel_reports_pkey"; + +alter table "public"."spotify_analytics_albums" add constraint "spotify_analytics_albums_pkey" PRIMARY KEY using index "spotify_analytics_albums_pkey"; + +alter table "public"."spotify_analytics_tracks" add constraint "spotify_analytics_tracks_pkey" PRIMARY KEY using index "spotify_analytics_tracks_pkey"; + +alter table "public"."funnel_analytics_comments" add constraint "funnel_analysis_comments_analysis_id_fkey1" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_comments" validate constraint "funnel_analysis_comments_analysis_id_fkey1"; + +alter table "public"."funnel_analytics_profile" add constraint "funnel_analysis_comments_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_profile" validate constraint "funnel_analysis_comments_analysis_id_fkey"; + +alter table "public"."spotify_analytics_albums" add constraint "spotify_analytics_albums_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."spotify_analytics_albums" validate constraint "spotify_analytics_albums_analysis_id_fkey"; + +alter table "public"."spotify_analytics_tracks" add constraint "spotify_analytics_tracks_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."spotify_analytics_tracks" validate constraint "spotify_analytics_tracks_analysis_id_fkey"; + + diff --git a/supabase/migrations/20241223031657_funnel_tables.sql b/supabase/migrations/20241223031657_funnel_tables.sql new file mode 100644 index 0000000..0f8ee5c --- /dev/null +++ b/supabase/migrations/20241223031657_funnel_tables.sql @@ -0,0 +1 @@ +alter table "public"."funnel_analytics" add column if not exists "status" bigint default '0'::bigint; diff --git a/supabase/migrations/20241223031659_funnel_segments.sql b/supabase/migrations/20241223031659_funnel_segments.sql new file mode 100644 index 0000000..2dee942 --- /dev/null +++ b/supabase/migrations/20241223031659_funnel_segments.sql @@ -0,0 +1,17 @@ +create table "public"."funnel_analytics_segments" ( + "id" uuid not null default gen_random_uuid(), + "analysis_id" uuid default gen_random_uuid(), + "size" bigint default '0'::bigint, + "name" text, + "icon" text, + "created_at" timestamp with time zone not null default CURRENT_TIMESTAMP +); + +CREATE UNIQUE INDEX funnel_analysis_segments_pkey ON public.funnel_analytics_segments USING btree (id); + +alter table "public"."funnel_analytics_segments" add constraint "funnel_analysis_segments_pkey" PRIMARY KEY using index "funnel_analysis_segments_pkey"; + +alter table "public"."funnel_analytics_segments" add constraint "funnel_analytics_segments_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_segments" validate constraint "funnel_analytics_segments_analysis_id_fkey"; + diff --git a/supabase/migrations/20241224075445_funnel_profile_artistId.sql b/supabase/migrations/20241224075445_funnel_profile_artistId.sql new file mode 100644 index 0000000..988e037 --- /dev/null +++ b/supabase/migrations/20241224075445_funnel_profile_artistId.sql @@ -0,0 +1,7 @@ +alter table "public"."funnel_analytics_profile" add column "artistId" uuid default '00000000-0000-0000-0000-000000000000'::uuid; + +alter table "public"."funnel_analytics_profile" add constraint "funnel_analytics_profile_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_profile" validate constraint "funnel_analytics_profile_artistId_fkey"; + + diff --git a/supabase/migrations/20241224083043_funnel_analysis_handle.sql b/supabase/migrations/20241224083043_funnel_analysis_handle.sql new file mode 100644 index 0000000..46c5efb --- /dev/null +++ b/supabase/migrations/20241224083043_funnel_analysis_handle.sql @@ -0,0 +1,3 @@ +alter table "public"."funnel_analytics" add column "handle" text; + + diff --git a/supabase/migrations/20241225031700_funnel_spotify_tables.sql b/supabase/migrations/20241225031700_funnel_spotify_tables.sql new file mode 100644 index 0000000..dc18074 --- /dev/null +++ b/supabase/migrations/20241225031700_funnel_spotify_tables.sql @@ -0,0 +1,2 @@ +alter table "public"."spotify_analytics_albums" add column "artist_name" text; +alter table "public"."spotify_analytics_albums" drop column if exists "popularity"; diff --git a/supabase/migrations/20241226043412_funnel_analytics_type.sql b/supabase/migrations/20241226043412_funnel_analytics_type.sql new file mode 100644 index 0000000..fc560ce --- /dev/null +++ b/supabase/migrations/20241226043412_funnel_analytics_type.sql @@ -0,0 +1 @@ +alter table "public"."funnel_analytics" add column "type" social_type; \ No newline at end of file diff --git a/supabase/migrations/20250115000000_create_social_fans_table.sql b/supabase/migrations/20250115000000_create_social_fans_table.sql new file mode 100644 index 0000000..f6caea5 --- /dev/null +++ b/supabase/migrations/20250115000000_create_social_fans_table.sql @@ -0,0 +1,2 @@ +-- Migration file emptied - new migration created with proper timestamp +-- This file kept to avoid missing migration errors during deployment \ No newline at end of file diff --git a/supabase/migrations/20250124130945_fans_segments_table.sql b/supabase/migrations/20250124130945_fans_segments_table.sql new file mode 100644 index 0000000..2c8be69 --- /dev/null +++ b/supabase/migrations/20250124130945_fans_segments_table.sql @@ -0,0 +1,66 @@ +create table "public"."fans_segments" ( + "id" uuid not null default gen_random_uuid(), + "created_at" timestamp with time zone not null default now(), + "artistId" uuid default gen_random_uuid(), + "handle" text, + "email" text, + "segment" text, + "avatar" text, + "bio" text, + "followerCount" bigint default '0'::bigint +); + + +alter table "public"."fans_segments" enable row level security; + +CREATE UNIQUE INDEX fans_segments_pkey ON public.fans_segments USING btree (id); + +alter table "public"."fans_segments" add constraint "fans_segments_pkey" PRIMARY KEY using index "fans_segments_pkey"; + +alter table "public"."fans_segments" add constraint "fans_segments_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) not valid; + +alter table "public"."fans_segments" validate constraint "fans_segments_artistId_fkey"; + +grant delete on table "public"."fans_segments" to "anon"; + +grant insert on table "public"."fans_segments" to "anon"; + +grant references on table "public"."fans_segments" to "anon"; + +grant select on table "public"."fans_segments" to "anon"; + +grant trigger on table "public"."fans_segments" to "anon"; + +grant truncate on table "public"."fans_segments" to "anon"; + +grant update on table "public"."fans_segments" to "anon"; + +grant delete on table "public"."fans_segments" to "authenticated"; + +grant insert on table "public"."fans_segments" to "authenticated"; + +grant references on table "public"."fans_segments" to "authenticated"; + +grant select on table "public"."fans_segments" to "authenticated"; + +grant trigger on table "public"."fans_segments" to "authenticated"; + +grant truncate on table "public"."fans_segments" to "authenticated"; + +grant update on table "public"."fans_segments" to "authenticated"; + +grant delete on table "public"."fans_segments" to "service_role"; + +grant insert on table "public"."fans_segments" to "service_role"; + +grant references on table "public"."fans_segments" to "service_role"; + +grant select on table "public"."fans_segments" to "service_role"; + +grant trigger on table "public"."fans_segments" to "service_role"; + +grant truncate on table "public"."fans_segments" to "service_role"; + +grant update on table "public"."fans_segments" to "service_role"; + + diff --git a/supabase/migrations/20250127025814_add_artistId.sql b/supabase/migrations/20250127025814_add_artistId.sql new file mode 100644 index 0000000..b5c740f --- /dev/null +++ b/supabase/migrations/20250127025814_add_artistId.sql @@ -0,0 +1,5 @@ +alter table "public"."funnel_analytics" add column "artistId" uuid default '00000000-0000-0000-0000-000000000000'::uuid; + +alter table "public"."funnel_analytics" add constraint "funnel_analytics_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) not valid; + +alter table "public"."funnel_analytics" validate constraint "funnel_analytics_artistId_fkey"; diff --git a/supabase/migrations/20250127035723_add_social_type.sql b/supabase/migrations/20250127035723_add_social_type.sql new file mode 100644 index 0000000..930108b --- /dev/null +++ b/supabase/migrations/20250127035723_add_social_type.sql @@ -0,0 +1 @@ +alter table "public"."fans_segments" add column "platform" social_type; diff --git a/supabase/migrations/20250128001254_artists_row_level_security.sql b/supabase/migrations/20250128001254_artists_row_level_security.sql new file mode 100644 index 0000000..179a76f --- /dev/null +++ b/supabase/migrations/20250128001254_artists_row_level_security.sql @@ -0,0 +1 @@ +alter table "public"."artists" enable row level security; \ No newline at end of file diff --git a/supabase/migrations/20250129000000_create_account_phone_numbers.sql b/supabase/migrations/20250129000000_create_account_phone_numbers.sql new file mode 100644 index 0000000..4d21832 --- /dev/null +++ b/supabase/migrations/20250129000000_create_account_phone_numbers.sql @@ -0,0 +1,46 @@ +DROP FUNCTION IF EXISTS public.trigger_set_timestamps(); + +CREATE OR REPLACE FUNCTION public.trigger_set_timestamps() + RETURNS trigger + LANGUAGE plpgsql + SET search_path TO '' +AS $function$ +begin + if TG_OP = 'INSERT' then + new.created_at = now(); + + new.updated_at = now(); + + else + new.updated_at = now(); + + new.created_at = old.created_at; + + end if; + + return NEW; + +end +$function$ +; + +-- Create account_phone_numbers table to establish relationship between accounts and phone numbers +CREATE TABLE IF NOT EXISTS account_phone_numbers ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + phone_number VARCHAR(20) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(account_id, phone_number) +); + +-- Create index on account_id for faster lookups +CREATE INDEX IF NOT EXISTS idx_account_phone_numbers_account_id ON account_phone_numbers(account_id); + +-- Create index on phone_number for faster lookups +CREATE INDEX IF NOT EXISTS idx_account_phone_numbers_phone_number ON account_phone_numbers(phone_number); + +-- Add trigger to update updated_at timestamp +CREATE TRIGGER set_timestamp + BEFORE UPDATE ON account_phone_numbers + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); \ No newline at end of file diff --git a/supabase/migrations/20250129000001_account_artist_ids.sql b/supabase/migrations/20250129000001_account_artist_ids.sql new file mode 100644 index 0000000..3eee539 --- /dev/null +++ b/supabase/migrations/20250129000001_account_artist_ids.sql @@ -0,0 +1,67 @@ +create table "public"."account_artist_ids" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "account_id" uuid default gen_random_uuid(), + "artist_id" uuid default gen_random_uuid() +); + + +alter table "public"."account_artist_ids" enable row level security; + +alter table "public"."artist_social_links" enable row level security; + +CREATE UNIQUE INDEX account_artist_ids_pkey ON public.account_artist_ids USING btree (id); + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_pkey" PRIMARY KEY using index "account_artist_ids_pkey"; + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) not valid; + +alter table "public"."account_artist_ids" validate constraint "account_artist_ids_account_id_fkey"; + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES artists(id) not valid; + +alter table "public"."account_artist_ids" validate constraint "account_artist_ids_artist_id_fkey"; + +grant delete on table "public"."account_artist_ids" to "anon"; + +grant insert on table "public"."account_artist_ids" to "anon"; + +grant references on table "public"."account_artist_ids" to "anon"; + +grant select on table "public"."account_artist_ids" to "anon"; + +grant trigger on table "public"."account_artist_ids" to "anon"; + +grant truncate on table "public"."account_artist_ids" to "anon"; + +grant update on table "public"."account_artist_ids" to "anon"; + +grant delete on table "public"."account_artist_ids" to "authenticated"; + +grant insert on table "public"."account_artist_ids" to "authenticated"; + +grant references on table "public"."account_artist_ids" to "authenticated"; + +grant select on table "public"."account_artist_ids" to "authenticated"; + +grant trigger on table "public"."account_artist_ids" to "authenticated"; + +grant truncate on table "public"."account_artist_ids" to "authenticated"; + +grant update on table "public"."account_artist_ids" to "authenticated"; + +grant delete on table "public"."account_artist_ids" to "service_role"; + +grant insert on table "public"."account_artist_ids" to "service_role"; + +grant references on table "public"."account_artist_ids" to "service_role"; + +grant select on table "public"."account_artist_ids" to "service_role"; + +grant trigger on table "public"."account_artist_ids" to "service_role"; + +grant truncate on table "public"."account_artist_ids" to "service_role"; + +grant update on table "public"."account_artist_ids" to "service_role"; + + diff --git a/supabase/migrations/20250129000002_fan_segment.sql b/supabase/migrations/20250129000002_fan_segment.sql new file mode 100644 index 0000000..415a7e6 --- /dev/null +++ b/supabase/migrations/20250129000002_fan_segment.sql @@ -0,0 +1,66 @@ +create table "public"."fan_segment" ( + "id" uuid not null default gen_random_uuid(), + "created_at" timestamp with time zone not null default now(), + "account_id" uuid default gen_random_uuid(), + "artist_id" uuid default gen_random_uuid(), + "segment_name" text +); + + +alter table "public"."fan_segment" enable row level security; + +CREATE UNIQUE INDEX fan_segment_pkey ON public.fan_segment USING btree (id); + +alter table "public"."fan_segment" add constraint "fan_segment_pkey" PRIMARY KEY using index "fan_segment_pkey"; + +alter table "public"."fan_segment" add constraint "fan_segment_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) not valid; + +alter table "public"."fan_segment" validate constraint "fan_segment_account_id_fkey"; + +alter table "public"."fan_segment" add constraint "fan_segment_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES artists(id) not valid; + +alter table "public"."fan_segment" validate constraint "fan_segment_artist_id_fkey"; + +grant delete on table "public"."fan_segment" to "anon"; + +grant insert on table "public"."fan_segment" to "anon"; + +grant references on table "public"."fan_segment" to "anon"; + +grant select on table "public"."fan_segment" to "anon"; + +grant trigger on table "public"."fan_segment" to "anon"; + +grant truncate on table "public"."fan_segment" to "anon"; + +grant update on table "public"."fan_segment" to "anon"; + +grant delete on table "public"."fan_segment" to "authenticated"; + +grant insert on table "public"."fan_segment" to "authenticated"; + +grant references on table "public"."fan_segment" to "authenticated"; + +grant select on table "public"."fan_segment" to "authenticated"; + +grant trigger on table "public"."fan_segment" to "authenticated"; + +grant truncate on table "public"."fan_segment" to "authenticated"; + +grant update on table "public"."fan_segment" to "authenticated"; + +grant delete on table "public"."fan_segment" to "service_role"; + +grant insert on table "public"."fan_segment" to "service_role"; + +grant references on table "public"."fan_segment" to "service_role"; + +grant select on table "public"."fan_segment" to "service_role"; + +grant trigger on table "public"."fan_segment" to "service_role"; + +grant truncate on table "public"."fan_segment" to "service_role"; + +grant update on table "public"."fan_segment" to "service_role"; + + diff --git a/supabase/migrations/20250129000003_key_relation_update.sql b/supabase/migrations/20250129000003_key_relation_update.sql new file mode 100644 index 0000000..6ce3e28 --- /dev/null +++ b/supabase/migrations/20250129000003_key_relation_update.sql @@ -0,0 +1,13 @@ +alter table "public"."account_phone_numbers" drop constraint "account_phone_numbers_account_id_fkey"; + +alter table "public"."artist_social_links" drop constraint "artist_social_links_artistId_fkey"; + +alter table "public"."account_phone_numbers" add constraint "account_phone_numbers_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) not valid; + +alter table "public"."account_phone_numbers" validate constraint "account_phone_numbers_account_id_fkey"; + +alter table "public"."artist_social_links" add constraint "artist_social_links_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) not valid; + +alter table "public"."artist_social_links" validate constraint "artist_social_links_artistId_fkey"; + + diff --git a/supabase/migrations/20250129000004_cascade_foreign_keys.sql b/supabase/migrations/20250129000004_cascade_foreign_keys.sql new file mode 100644 index 0000000..4298e4f --- /dev/null +++ b/supabase/migrations/20250129000004_cascade_foreign_keys.sql @@ -0,0 +1,43 @@ +alter table "public"."account_artist_ids" drop constraint "account_artist_ids_account_id_fkey"; + +alter table "public"."account_artist_ids" drop constraint "account_artist_ids_artist_id_fkey"; + +alter table "public"."account_phone_numbers" drop constraint "account_phone_numbers_account_id_fkey"; + +alter table "public"."artist_social_links" drop constraint "artist_social_links_artistId_fkey"; + +alter table "public"."fan_segment" drop constraint "fan_segment_account_id_fkey"; + +alter table "public"."fan_segment" drop constraint "fan_segment_artist_id_fkey"; + +alter table "public"."fans_segments" drop constraint "fans_segments_artistId_fkey"; + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_artist_ids" validate constraint "account_artist_ids_account_id_fkey"; + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."account_artist_ids" validate constraint "account_artist_ids_artist_id_fkey"; + +alter table "public"."account_phone_numbers" add constraint "account_phone_numbers_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_phone_numbers" validate constraint "account_phone_numbers_account_id_fkey"; + +alter table "public"."artist_social_links" add constraint "artist_social_links_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."artist_social_links" validate constraint "artist_social_links_artistId_fkey"; + +alter table "public"."fan_segment" add constraint "fan_segment_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."fan_segment" validate constraint "fan_segment_account_id_fkey"; + +alter table "public"."fan_segment" add constraint "fan_segment_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."fan_segment" validate constraint "fan_segment_artist_id_fkey"; + +alter table "public"."fans_segments" add constraint "fans_segments_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."fans_segments" validate constraint "fans_segments_artistId_fkey"; + + diff --git a/supabase/migrations/20250129000005_funnel_analytics_cascade_foreign_keys.sql b/supabase/migrations/20250129000005_funnel_analytics_cascade_foreign_keys.sql new file mode 100644 index 0000000..e072ad7 --- /dev/null +++ b/supabase/migrations/20250129000005_funnel_analytics_cascade_foreign_keys.sql @@ -0,0 +1,7 @@ +alter table "public"."funnel_analytics" drop constraint "funnel_analytics_artistId_fkey"; + +alter table "public"."funnel_analytics" add constraint "funnel_analytics_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES artists(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics" validate constraint "funnel_analytics_artistId_fkey"; + + diff --git a/supabase/migrations/20250129000006_account_artist_ids_table_relationship.sql b/supabase/migrations/20250129000006_account_artist_ids_table_relationship.sql new file mode 100644 index 0000000..e68b8cd --- /dev/null +++ b/supabase/migrations/20250129000006_account_artist_ids_table_relationship.sql @@ -0,0 +1,7 @@ +alter table "public"."account_artist_ids" drop constraint "account_artist_ids_artist_id_fkey"; + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_artist_ids" validate constraint "account_artist_ids_artist_id_fkey"; + + diff --git a/supabase/migrations/20250129000007_fan_segment_table_relationship.sql b/supabase/migrations/20250129000007_fan_segment_table_relationship.sql new file mode 100644 index 0000000..b902bfb --- /dev/null +++ b/supabase/migrations/20250129000007_fan_segment_table_relationship.sql @@ -0,0 +1,7 @@ +alter table "public"."fan_segment" drop constraint "fan_segment_artist_id_fkey"; + +alter table "public"."fan_segment" add constraint "fan_segment_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."fan_segment" validate constraint "fan_segment_artist_id_fkey"; + + diff --git a/supabase/migrations/20250129000008_account_info_table.sql b/supabase/migrations/20250129000008_account_info_table.sql new file mode 100644 index 0000000..85d4416 --- /dev/null +++ b/supabase/migrations/20250129000008_account_info_table.sql @@ -0,0 +1,65 @@ +create table "public"."account_info" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "image" text, + "knowledges" jsonb default '[]'::jsonb, + "label" text, + "instruction" text, + "organization" text, + "account_id" uuid default gen_random_uuid() +); + + +alter table "public"."account_info" enable row level security; + +CREATE UNIQUE INDEX account_info_pkey ON public.account_info USING btree (id); + +alter table "public"."account_info" add constraint "account_info_pkey" PRIMARY KEY using index "account_info_pkey"; + +alter table "public"."account_info" add constraint "account_info_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_info" validate constraint "account_info_account_id_fkey"; + +grant delete on table "public"."account_info" to "anon"; + +grant insert on table "public"."account_info" to "anon"; + +grant references on table "public"."account_info" to "anon"; + +grant select on table "public"."account_info" to "anon"; + +grant trigger on table "public"."account_info" to "anon"; + +grant truncate on table "public"."account_info" to "anon"; + +grant update on table "public"."account_info" to "anon"; + +grant delete on table "public"."account_info" to "authenticated"; + +grant insert on table "public"."account_info" to "authenticated"; + +grant references on table "public"."account_info" to "authenticated"; + +grant select on table "public"."account_info" to "authenticated"; + +grant trigger on table "public"."account_info" to "authenticated"; + +grant truncate on table "public"."account_info" to "authenticated"; + +grant update on table "public"."account_info" to "authenticated"; + +grant delete on table "public"."account_info" to "service_role"; + +grant insert on table "public"."account_info" to "service_role"; + +grant references on table "public"."account_info" to "service_role"; + +grant select on table "public"."account_info" to "service_role"; + +grant trigger on table "public"."account_info" to "service_role"; + +grant truncate on table "public"."account_info" to "service_role"; + +grant update on table "public"."account_info" to "service_role"; + + diff --git a/supabase/migrations/20250129000009_account_emails.sql b/supabase/migrations/20250129000009_account_emails.sql new file mode 100644 index 0000000..3dc5b0a --- /dev/null +++ b/supabase/migrations/20250129000009_account_emails.sql @@ -0,0 +1,61 @@ +create table "public"."account_emails" ( + "id" uuid not null default gen_random_uuid(), + "created_at" timestamp with time zone not null default now(), + "account_id" uuid default gen_random_uuid(), + "email" text +); + + +alter table "public"."account_emails" enable row level security; + +CREATE UNIQUE INDEX account_emails_pkey ON public.account_emails USING btree (id); + +alter table "public"."account_emails" add constraint "account_emails_pkey" PRIMARY KEY using index "account_emails_pkey"; + +alter table "public"."account_emails" add constraint "account_emails_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_emails" validate constraint "account_emails_account_id_fkey"; + +grant delete on table "public"."account_emails" to "anon"; + +grant insert on table "public"."account_emails" to "anon"; + +grant references on table "public"."account_emails" to "anon"; + +grant select on table "public"."account_emails" to "anon"; + +grant trigger on table "public"."account_emails" to "anon"; + +grant truncate on table "public"."account_emails" to "anon"; + +grant update on table "public"."account_emails" to "anon"; + +grant delete on table "public"."account_emails" to "authenticated"; + +grant insert on table "public"."account_emails" to "authenticated"; + +grant references on table "public"."account_emails" to "authenticated"; + +grant select on table "public"."account_emails" to "authenticated"; + +grant trigger on table "public"."account_emails" to "authenticated"; + +grant truncate on table "public"."account_emails" to "authenticated"; + +grant update on table "public"."account_emails" to "authenticated"; + +grant delete on table "public"."account_emails" to "service_role"; + +grant insert on table "public"."account_emails" to "service_role"; + +grant references on table "public"."account_emails" to "service_role"; + +grant select on table "public"."account_emails" to "service_role"; + +grant trigger on table "public"."account_emails" to "service_role"; + +grant truncate on table "public"."account_emails" to "service_role"; + +grant update on table "public"."account_emails" to "service_role"; + + diff --git a/supabase/migrations/20250129000010_updated_at_fields.sql b/supabase/migrations/20250129000010_updated_at_fields.sql new file mode 100644 index 0000000..d90685d --- /dev/null +++ b/supabase/migrations/20250129000010_updated_at_fields.sql @@ -0,0 +1,18 @@ +alter table "public"."account_emails" drop column "created_at"; + +alter table "public"."account_emails" add column "updated_at" timestamp with time zone not null default now(); + +alter table "public"."account_info" drop column "created_at"; + +alter table "public"."account_info" add column "updated_at" timestamp with time zone not null default now(); + + +CREATE TRIGGER set_timestamp + BEFORE UPDATE ON account_emails + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +CREATE TRIGGER set_timestamp + BEFORE UPDATE ON account_info + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); \ No newline at end of file diff --git a/supabase/migrations/20250129000011_add_nullable_account.sql b/supabase/migrations/20250129000011_add_nullable_account.sql new file mode 100644 index 0000000..25f423b --- /dev/null +++ b/supabase/migrations/20250129000011_add_nullable_account.sql @@ -0,0 +1,2 @@ +INSERT INTO "public"."accounts" ("id", "name") +VALUES ('00000000-0000-0000-0000-000000000000', 'Nullable Account'); \ No newline at end of file diff --git a/supabase/migrations/20250129000012_add_account_id_into_artist_socials.sql b/supabase/migrations/20250129000012_add_account_id_into_artist_socials.sql new file mode 100644 index 0000000..f6d655e --- /dev/null +++ b/supabase/migrations/20250129000012_add_account_id_into_artist_socials.sql @@ -0,0 +1,9 @@ +alter table "public"."artist_social_links" rename TO "account_socials"; + +ALTER TABLE "public"."account_socials" ADD COLUMN "account_id" uuid default '00000000-0000-0000-0000-000000000000'; + +alter table "public"."account_socials" add constraint "account_socials_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."account_socials" validate constraint "account_socials_account_id_fkey"; + + diff --git a/supabase/migrations/20250129000013_remove_artist_id_index_of_campaign.sql b/supabase/migrations/20250129000013_remove_artist_id_index_of_campaign.sql new file mode 100644 index 0000000..64daa2a --- /dev/null +++ b/supabase/migrations/20250129000013_remove_artist_id_index_of_campaign.sql @@ -0,0 +1,3 @@ +alter table if exists "public"."campaigns" drop constraint if exists "campaigns_artistId_fkey"; + + diff --git a/supabase/migrations/20250129000014_remove_artist_id_index_of_funnel_analytics.sql b/supabase/migrations/20250129000014_remove_artist_id_index_of_funnel_analytics.sql new file mode 100644 index 0000000..97e5c3f --- /dev/null +++ b/supabase/migrations/20250129000014_remove_artist_id_index_of_funnel_analytics.sql @@ -0,0 +1,3 @@ +alter table "public"."funnel_analytics" drop constraint "funnel_analytics_artistId_fkey"; + + diff --git a/supabase/migrations/20250129000015_rename_artistId_to_artist_id.sql b/supabase/migrations/20250129000015_rename_artistId_to_artist_id.sql new file mode 100644 index 0000000..de7daff --- /dev/null +++ b/supabase/migrations/20250129000015_rename_artistId_to_artist_id.sql @@ -0,0 +1,10 @@ +alter table "public"."account_socials" drop constraint "artist_social_links_artistId_fkey"; + +alter table "public"."account_socials" drop column "artistId"; + +ALTER TABLE if exists "public"."campaigns" RENAME COLUMN "artistId" TO "artist_id"; + +alter table if exists "public"."campaigns" enable row level security; + +ALTER TABLE if exists "public"."funnel_analytics" RENAME COLUMN "artistId" TO "artist_id"; + diff --git a/supabase/migrations/20250129000016_add_foreignkey_campaigns_funnel_analytics.sql b/supabase/migrations/20250129000016_add_foreignkey_campaigns_funnel_analytics.sql new file mode 100644 index 0000000..fc7972e --- /dev/null +++ b/supabase/migrations/20250129000016_add_foreignkey_campaigns_funnel_analytics.sql @@ -0,0 +1,9 @@ +alter table if exists "public"."campaigns" add constraint "campaigns_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table if exists "public"."campaigns" validate constraint "campaigns_artist_id_fkey"; + +alter table "public"."funnel_analytics" add constraint "funnel_analytics_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics" validate constraint "funnel_analytics_artist_id_fkey"; + + diff --git a/supabase/migrations/20250129000017_keep_only_name_of_accounts_table.sql b/supabase/migrations/20250129000017_keep_only_name_of_accounts_table.sql new file mode 100644 index 0000000..c336516 --- /dev/null +++ b/supabase/migrations/20250129000017_keep_only_name_of_accounts_table.sql @@ -0,0 +1,11 @@ +alter table "public"."accounts" drop column "artistIds"; + +alter table "public"."accounts" drop column "email"; + +alter table "public"."accounts" drop column "image"; + +alter table "public"."accounts" drop column "instruction"; + +alter table "public"."accounts" drop column "organization"; + + diff --git a/supabase/migrations/20250129000018_remove_unused_tables.sql b/supabase/migrations/20250129000018_remove_unused_tables.sql new file mode 100644 index 0000000..11f167c --- /dev/null +++ b/supabase/migrations/20250129000018_remove_unused_tables.sql @@ -0,0 +1,101 @@ +revoke delete on table "public"."fans_segments" from "anon"; + +revoke insert on table "public"."fans_segments" from "anon"; + +revoke references on table "public"."fans_segments" from "anon"; + +revoke select on table "public"."fans_segments" from "anon"; + +revoke trigger on table "public"."fans_segments" from "anon"; + +revoke truncate on table "public"."fans_segments" from "anon"; + +revoke update on table "public"."fans_segments" from "anon"; + +revoke delete on table "public"."fans_segments" from "authenticated"; + +revoke insert on table "public"."fans_segments" from "authenticated"; + +revoke references on table "public"."fans_segments" from "authenticated"; + +revoke select on table "public"."fans_segments" from "authenticated"; + +revoke trigger on table "public"."fans_segments" from "authenticated"; + +revoke truncate on table "public"."fans_segments" from "authenticated"; + +revoke update on table "public"."fans_segments" from "authenticated"; + +revoke delete on table "public"."fans_segments" from "service_role"; + +revoke insert on table "public"."fans_segments" from "service_role"; + +revoke references on table "public"."fans_segments" from "service_role"; + +revoke select on table "public"."fans_segments" from "service_role"; + +revoke trigger on table "public"."fans_segments" from "service_role"; + +revoke truncate on table "public"."fans_segments" from "service_role"; + +revoke update on table "public"."fans_segments" from "service_role"; + +revoke delete on table "public"."tiktok_analysis" from "anon"; + +revoke insert on table "public"."tiktok_analysis" from "anon"; + +revoke references on table "public"."tiktok_analysis" from "anon"; + +revoke select on table "public"."tiktok_analysis" from "anon"; + +revoke trigger on table "public"."tiktok_analysis" from "anon"; + +revoke truncate on table "public"."tiktok_analysis" from "anon"; + +revoke update on table "public"."tiktok_analysis" from "anon"; + +revoke delete on table "public"."tiktok_analysis" from "authenticated"; + +revoke insert on table "public"."tiktok_analysis" from "authenticated"; + +revoke references on table "public"."tiktok_analysis" from "authenticated"; + +revoke select on table "public"."tiktok_analysis" from "authenticated"; + +revoke trigger on table "public"."tiktok_analysis" from "authenticated"; + +revoke truncate on table "public"."tiktok_analysis" from "authenticated"; + +revoke update on table "public"."tiktok_analysis" from "authenticated"; + +revoke delete on table "public"."tiktok_analysis" from "service_role"; + +revoke insert on table "public"."tiktok_analysis" from "service_role"; + +revoke references on table "public"."tiktok_analysis" from "service_role"; + +revoke select on table "public"."tiktok_analysis" from "service_role"; + +revoke trigger on table "public"."tiktok_analysis" from "service_role"; + +revoke truncate on table "public"."tiktok_analysis" from "service_role"; + +revoke update on table "public"."tiktok_analysis" from "service_role"; + +alter table "public"."fans_segments" drop constraint "fans_segments_artistId_fkey"; + +alter table "public"."tiktok_analysis" drop constraint "tiktok_analysis_artistId_fkey"; + +alter table "public"."fans_segments" drop constraint "fans_segments_pkey"; + +alter table "public"."tiktok_analysis" drop constraint "tiktok_analysis_pkey"; + +drop index if exists "public"."fans_segments_pkey"; + +drop index if exists "public"."tiktok_analysis_pkey"; + +drop table "public"."fans_segments"; + +drop table "public"."tiktok_analysis"; + +drop table "public"."tiktok_reports"; diff --git a/supabase/migrations/20250129000019_remove_artist_foreign_key.sql b/supabase/migrations/20250129000019_remove_artist_foreign_key.sql new file mode 100644 index 0000000..e95f71d --- /dev/null +++ b/supabase/migrations/20250129000019_remove_artist_foreign_key.sql @@ -0,0 +1,5 @@ +alter table "public"."funnel_analytics_profile" drop constraint "funnel_analytics_profile_artistId_fkey"; + +alter table "public"."funnel_analytics_profile" drop column "artistId"; + +alter table "public"."funnel_analytics_profile" add column "artist_id" uuid default '00000000-0000-0000-0000-000000000000'::uuid; diff --git a/supabase/migrations/20250129000020_remove_artists_table.sql b/supabase/migrations/20250129000020_remove_artists_table.sql new file mode 100644 index 0000000..29710c3 --- /dev/null +++ b/supabase/migrations/20250129000020_remove_artists_table.sql @@ -0,0 +1,53 @@ +revoke delete on table "public"."artists" from "anon"; + +revoke insert on table "public"."artists" from "anon"; + +revoke references on table "public"."artists" from "anon"; + +revoke select on table "public"."artists" from "anon"; + +revoke trigger on table "public"."artists" from "anon"; + +revoke truncate on table "public"."artists" from "anon"; + +revoke update on table "public"."artists" from "anon"; + +revoke delete on table "public"."artists" from "authenticated"; + +revoke insert on table "public"."artists" from "authenticated"; + +revoke references on table "public"."artists" from "authenticated"; + +revoke select on table "public"."artists" from "authenticated"; + +revoke trigger on table "public"."artists" from "authenticated"; + +revoke truncate on table "public"."artists" from "authenticated"; + +revoke update on table "public"."artists" from "authenticated"; + +revoke delete on table "public"."artists" from "service_role"; + +revoke insert on table "public"."artists" from "service_role"; + +revoke references on table "public"."artists" from "service_role"; + +revoke select on table "public"."artists" from "service_role"; + +revoke trigger on table "public"."artists" from "service_role"; + +revoke truncate on table "public"."artists" from "service_role"; + +revoke update on table "public"."artists" from "service_role"; + +alter table "public"."artists" drop constraint "artists_pkey"; + +drop index if exists "public"."artists_pkey"; + +drop table "public"."artists"; + +alter table "public"."funnel_analytics_profile" add constraint "funnel_analytics_profile_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_profile" validate constraint "funnel_analytics_profile_artist_id_fkey"; + + diff --git a/supabase/migrations/20250129000855_remove_ids_of_uint8.sql b/supabase/migrations/20250129000855_remove_ids_of_uint8.sql new file mode 100644 index 0000000..fd0dc4a --- /dev/null +++ b/supabase/migrations/20250129000855_remove_ids_of_uint8.sql @@ -0,0 +1,13 @@ +alter table "public"."account_artist_ids" drop constraint "account_artist_ids_pkey"; + +alter table "public"."account_info" drop constraint "account_info_pkey"; + +drop index if exists "public"."account_artist_ids_pkey"; + +drop index if exists "public"."account_info_pkey"; + +alter table "public"."account_artist_ids" drop column "id"; + +alter table "public"."account_info" drop column "id"; + + diff --git a/supabase/migrations/20250129001251_add_ids_uuid.sql b/supabase/migrations/20250129001251_add_ids_uuid.sql new file mode 100644 index 0000000..4fe995c --- /dev/null +++ b/supabase/migrations/20250129001251_add_ids_uuid.sql @@ -0,0 +1,13 @@ +alter table "public"."account_artist_ids" add column "id" uuid not null default gen_random_uuid(); + +alter table "public"."account_info" add column "id" uuid not null default gen_random_uuid(); + +CREATE UNIQUE INDEX account_artist_ids_pkey ON public.account_artist_ids USING btree (id); + +CREATE UNIQUE INDEX account_info_pkey ON public.account_info USING btree (id); + +alter table "public"."account_artist_ids" add constraint "account_artist_ids_pkey" PRIMARY KEY using index "account_artist_ids_pkey"; + +alter table "public"."account_info" add constraint "account_info_pkey" PRIMARY KEY using index "account_info_pkey"; + + diff --git a/supabase/migrations/20250129001252_add_created_at.sql b/supabase/migrations/20250129001252_add_created_at.sql new file mode 100644 index 0000000..8aac470 --- /dev/null +++ b/supabase/migrations/20250129001252_add_created_at.sql @@ -0,0 +1,3 @@ +alter table "public"."account_info" add column "created_at" timestamp with time zone not null default now(); + +alter table "public"."account_emails" add column "created_at" timestamp with time zone not null default now(); diff --git a/supabase/migrations/20250129001253_drop_column_id.sql b/supabase/migrations/20250129001253_drop_column_id.sql new file mode 100644 index 0000000..eb2a406 --- /dev/null +++ b/supabase/migrations/20250129001253_drop_column_id.sql @@ -0,0 +1,5 @@ +alter table "public"."account_socials" drop constraint "artist_social_links_pkey"; + +drop index if exists "public"."artist_social_links_pkey"; + +alter table "public"."account_socials" drop column "id"; \ No newline at end of file diff --git a/supabase/migrations/20250129001254_add_column.sql b/supabase/migrations/20250129001254_add_column.sql new file mode 100644 index 0000000..24f49ea --- /dev/null +++ b/supabase/migrations/20250129001254_add_column.sql @@ -0,0 +1,6 @@ +alter table "public"."account_socials" add column "id" uuid not null default gen_random_uuid(); + +CREATE UNIQUE INDEX account_socials_pkey ON public.account_socials USING btree (id); + +alter table "public"."account_socials" add constraint "account_socials_pkey" PRIMARY KEY using index "account_socials_pkey"; + diff --git a/supabase/migrations/20250129145909_funnel_analytics_accounts.sql b/supabase/migrations/20250129145909_funnel_analytics_accounts.sql new file mode 100644 index 0000000..77e4e92 --- /dev/null +++ b/supabase/migrations/20250129145909_funnel_analytics_accounts.sql @@ -0,0 +1,66 @@ +create table "public"."funnel_analytics_accounts" ( + "id" bigint generated by default as identity not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone default now(), + "account_id" uuid default gen_random_uuid(), + "analysis_id" uuid default gen_random_uuid() +); + + +alter table "public"."funnel_analytics_accounts" enable row level security; + +CREATE UNIQUE INDEX account_funnel_analytics_pkey ON public.funnel_analytics_accounts USING btree (id); + +alter table "public"."funnel_analytics_accounts" add constraint "account_funnel_analytics_pkey" PRIMARY KEY using index "account_funnel_analytics_pkey"; + +alter table "public"."funnel_analytics_accounts" add constraint "account_funnel_analytics_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_accounts" validate constraint "account_funnel_analytics_account_id_fkey"; + +alter table "public"."funnel_analytics_accounts" add constraint "account_funnel_analytics_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES funnel_analytics(id) ON DELETE CASCADE not valid; + +alter table "public"."funnel_analytics_accounts" validate constraint "account_funnel_analytics_analysis_id_fkey"; + +grant delete on table "public"."funnel_analytics_accounts" to "anon"; + +grant insert on table "public"."funnel_analytics_accounts" to "anon"; + +grant references on table "public"."funnel_analytics_accounts" to "anon"; + +grant select on table "public"."funnel_analytics_accounts" to "anon"; + +grant trigger on table "public"."funnel_analytics_accounts" to "anon"; + +grant truncate on table "public"."funnel_analytics_accounts" to "anon"; + +grant update on table "public"."funnel_analytics_accounts" to "anon"; + +grant delete on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant insert on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant references on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant select on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant trigger on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant truncate on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant update on table "public"."funnel_analytics_accounts" to "authenticated"; + +grant delete on table "public"."funnel_analytics_accounts" to "service_role"; + +grant insert on table "public"."funnel_analytics_accounts" to "service_role"; + +grant references on table "public"."funnel_analytics_accounts" to "service_role"; + +grant select on table "public"."funnel_analytics_accounts" to "service_role"; + +grant trigger on table "public"."funnel_analytics_accounts" to "service_role"; + +grant truncate on table "public"."funnel_analytics_accounts" to "service_role"; + +grant update on table "public"."funnel_analytics_accounts" to "service_role"; + + diff --git a/supabase/migrations/20250129151450_funnel_analytics_profiles_refactor.sql b/supabase/migrations/20250129151450_funnel_analytics_profiles_refactor.sql new file mode 100644 index 0000000..a04c992 --- /dev/null +++ b/supabase/migrations/20250129151450_funnel_analytics_profiles_refactor.sql @@ -0,0 +1,65 @@ +revoke delete on table "public"."funnel_analytics_profile" from "anon"; + +revoke insert on table "public"."funnel_analytics_profile" from "anon"; + +revoke references on table "public"."funnel_analytics_profile" from "anon"; + +revoke select on table "public"."funnel_analytics_profile" from "anon"; + +revoke trigger on table "public"."funnel_analytics_profile" from "anon"; + +revoke truncate on table "public"."funnel_analytics_profile" from "anon"; + +revoke update on table "public"."funnel_analytics_profile" from "anon"; + +revoke delete on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke insert on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke references on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke select on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke trigger on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke truncate on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke update on table "public"."funnel_analytics_profile" from "authenticated"; + +revoke delete on table "public"."funnel_analytics_profile" from "service_role"; + +revoke insert on table "public"."funnel_analytics_profile" from "service_role"; + +revoke references on table "public"."funnel_analytics_profile" from "service_role"; + +revoke select on table "public"."funnel_analytics_profile" from "service_role"; + +revoke trigger on table "public"."funnel_analytics_profile" from "service_role"; + +revoke truncate on table "public"."funnel_analytics_profile" from "service_role"; + +revoke update on table "public"."funnel_analytics_profile" from "service_role"; + +alter table "public"."funnel_analytics_profile" drop constraint "funnel_analysis_comments_analysis_id_fkey"; + +alter table "public"."funnel_analytics_profile" drop constraint "funnel_analytics_profile_artist_id_fkey"; + +alter table "public"."funnel_analytics_profile" drop constraint "funnel_analysis_comments_pkey"; + +drop index if exists "public"."funnel_analysis_comments_pkey"; + +drop table "public"."funnel_analytics_profile"; + +alter table "public"."account_socials" add column "avatar" text; + +alter table "public"."account_socials" add column "bio" text; + +alter table "public"."account_socials" add column "followerCount" text; + +alter table "public"."account_socials" add column "followingCount" text; + +alter table "public"."account_socials" add column "region" text; + +alter table "public"."account_socials" add column "username" text; + + diff --git a/supabase/migrations/20250129151451_funnel_analytics_refactor.sql b/supabase/migrations/20250129151451_funnel_analytics_refactor.sql new file mode 100644 index 0000000..7fa2756 --- /dev/null +++ b/supabase/migrations/20250129151451_funnel_analytics_refactor.sql @@ -0,0 +1,14 @@ +ALTER TABLE "public"."funnel_analytics" DROP COLUMN IF EXISTS "timestamp"; + +alter table "public"."funnel_analytics" add column "created_at" timestamp with time zone not null default now(); + +alter table "public"."funnel_analytics" add column "updated_at" timestamp with time zone not null default now(); + +alter table "public"."funnel_analytics" DROP COLUMN IF EXISTS "chat_id"; + +alter table "public"."funnel_analytics" add column "pilot_id" uuid default gen_random_uuid(); + +CREATE TRIGGER set_timestamp + BEFORE UPDATE ON funnel_analytics + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); diff --git a/supabase/migrations/20250129194230_funnel_analytics_accounts_ids.sql b/supabase/migrations/20250129194230_funnel_analytics_accounts_ids.sql new file mode 100644 index 0000000..12e7677 --- /dev/null +++ b/supabase/migrations/20250129194230_funnel_analytics_accounts_ids.sql @@ -0,0 +1,9 @@ +alter table "public"."funnel_analytics_accounts" drop constraint "account_funnel_analytics_pkey"; + +drop index if exists "public"."account_funnel_analytics_pkey"; + +alter table "public"."account_socials" drop column "followerCount"; +alter table "public"."account_socials" drop column "followingCount"; +alter table "public"."funnel_analytics_accounts" drop column "id"; + + diff --git a/supabase/migrations/20250129194359_funnel_analytics_accounts_uuid.sql b/supabase/migrations/20250129194359_funnel_analytics_accounts_uuid.sql new file mode 100644 index 0000000..263a994 --- /dev/null +++ b/supabase/migrations/20250129194359_funnel_analytics_accounts_uuid.sql @@ -0,0 +1,8 @@ +alter table "public"."funnel_analytics_accounts" add column "id" uuid not null default gen_random_uuid(); + +CREATE UNIQUE INDEX funnel_analytics_accounts_pkey ON public.funnel_analytics_accounts USING btree (id); + +alter table "public"."funnel_analytics_accounts" add constraint "funnel_analytics_accounts_pkey" PRIMARY KEY using index "funnel_analytics_accounts_pkey"; + +alter table "public"."account_socials" add column "followerCount" bigint default '0'::bigint; +alter table "public"."account_socials" add column "followingCount" bigint default '0'::bigint; diff --git a/supabase/migrations/20250129222308_updated_at_trigger_function.sql b/supabase/migrations/20250129222308_updated_at_trigger_function.sql new file mode 100644 index 0000000..eedd338 --- /dev/null +++ b/supabase/migrations/20250129222308_updated_at_trigger_function.sql @@ -0,0 +1,55 @@ +CREATE OR REPLACE FUNCTION public.trigger_set_updated_at() + RETURNS trigger + LANGUAGE plpgsql + SET search_path TO '' +AS $function$ +begin + new.updated_at = now(); + return NEW; +end +$function$ +; + +alter table "public"."account_artist_ids" drop column "created_at"; + +alter table "public"."account_artist_ids" add column "updated_at" timestamp with time zone default now(); + +alter table "public"."account_emails" drop column "created_at"; + +alter table "public"."account_info" drop column "created_at"; + +alter table "public"."funnel_analytics" drop column "created_at"; + +DROP TRIGGER set_timestamp ON account_emails; +DROP TRIGGER set_timestamp ON account_info; +DROP TRIGGER set_timestamp ON account_phone_numbers; +DROP TRIGGER set_timestamp ON funnel_analytics; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON funnel_analytics + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_emails + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_artist_ids + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_info + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_phone_numbers + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + + + + diff --git a/supabase/migrations/20250130154735_social_posts_table.sql b/supabase/migrations/20250130154735_social_posts_table.sql new file mode 100644 index 0000000..747cf03 --- /dev/null +++ b/supabase/migrations/20250130154735_social_posts_table.sql @@ -0,0 +1,65 @@ +create table "public"."social_posts" ( + "id" uuid not null default gen_random_uuid(), + "updated_at" timestamp with time zone, + "account_social_id" uuid default gen_random_uuid(), + "post_url" text +); + + +alter table "public"."social_posts" enable row level security; + +CREATE UNIQUE INDEX social_posts_pkey ON public.social_posts USING btree (id); + +alter table "public"."social_posts" add constraint "social_posts_pkey" PRIMARY KEY using index "social_posts_pkey"; + +alter table "public"."social_posts" add constraint "social_posts_account_social_id_fkey" FOREIGN KEY (account_social_id) REFERENCES account_socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_posts" validate constraint "social_posts_account_social_id_fkey"; + +grant delete on table "public"."social_posts" to "anon"; + +grant insert on table "public"."social_posts" to "anon"; + +grant references on table "public"."social_posts" to "anon"; + +grant select on table "public"."social_posts" to "anon"; + +grant trigger on table "public"."social_posts" to "anon"; + +grant truncate on table "public"."social_posts" to "anon"; + +grant update on table "public"."social_posts" to "anon"; + +grant delete on table "public"."social_posts" to "authenticated"; + +grant insert on table "public"."social_posts" to "authenticated"; + +grant references on table "public"."social_posts" to "authenticated"; + +grant select on table "public"."social_posts" to "authenticated"; + +grant trigger on table "public"."social_posts" to "authenticated"; + +grant truncate on table "public"."social_posts" to "authenticated"; + +grant update on table "public"."social_posts" to "authenticated"; + +grant delete on table "public"."social_posts" to "service_role"; + +grant insert on table "public"."social_posts" to "service_role"; + +grant references on table "public"."social_posts" to "service_role"; + +grant select on table "public"."social_posts" to "service_role"; + +grant trigger on table "public"."social_posts" to "service_role"; + +grant truncate on table "public"."social_posts" to "service_role"; + +grant update on table "public"."social_posts" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON social_posts + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + diff --git a/supabase/migrations/20250130161836_posts_table.sql b/supabase/migrations/20250130161836_posts_table.sql new file mode 100644 index 0000000..e67fa7f --- /dev/null +++ b/supabase/migrations/20250130161836_posts_table.sql @@ -0,0 +1,72 @@ +create table "public"."posts" ( + "id" uuid not null default gen_random_uuid(), + "updated_at" timestamp with time zone not null default now(), + "post_url" text not null +); + + +alter table "public"."posts" enable row level security; + +alter table "public"."social_posts" drop column "post_url"; + +alter table "public"."social_posts" add column "post_id" uuid default gen_random_uuid(); + +CREATE UNIQUE INDEX posts_pkey ON public.posts USING btree (id); + +CREATE UNIQUE INDEX posts_post_url_key ON public.posts USING btree (post_url); + +alter table "public"."posts" add constraint "posts_pkey" PRIMARY KEY using index "posts_pkey"; + +alter table "public"."posts" add constraint "posts_post_url_key" UNIQUE using index "posts_post_url_key"; + +alter table "public"."social_posts" add constraint "social_posts_post_id_fkey" FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE not valid; + +alter table "public"."social_posts" validate constraint "social_posts_post_id_fkey"; + +grant delete on table "public"."posts" to "anon"; + +grant insert on table "public"."posts" to "anon"; + +grant references on table "public"."posts" to "anon"; + +grant select on table "public"."posts" to "anon"; + +grant trigger on table "public"."posts" to "anon"; + +grant truncate on table "public"."posts" to "anon"; + +grant update on table "public"."posts" to "anon"; + +grant delete on table "public"."posts" to "authenticated"; + +grant insert on table "public"."posts" to "authenticated"; + +grant references on table "public"."posts" to "authenticated"; + +grant select on table "public"."posts" to "authenticated"; + +grant trigger on table "public"."posts" to "authenticated"; + +grant truncate on table "public"."posts" to "authenticated"; + +grant update on table "public"."posts" to "authenticated"; + +grant delete on table "public"."posts" to "service_role"; + +grant insert on table "public"."posts" to "service_role"; + +grant references on table "public"."posts" to "service_role"; + +grant select on table "public"."posts" to "service_role"; + +grant trigger on table "public"."posts" to "service_role"; + +grant truncate on table "public"."posts" to "service_role"; + +grant update on table "public"."posts" to "service_role"; + + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON posts + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250130172638_socials_table.sql b/supabase/migrations/20250130172638_socials_table.sql new file mode 100644 index 0000000..8577fe8 --- /dev/null +++ b/supabase/migrations/20250130172638_socials_table.sql @@ -0,0 +1,73 @@ +create table "public"."socials" ( + "id" uuid not null default gen_random_uuid(), + "username" text not null, + "avatar" text, + "profile_url" text not null, + "region" text, + "bio" text, + "followerCount" bigint default '0'::bigint, + "followingCount" bigint default '0'::bigint, + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."socials" enable row level security; + +CREATE UNIQUE INDEX socials_pkey ON public.socials USING btree (id); + +CREATE UNIQUE INDEX socials_profile_url_key ON public.socials USING btree (profile_url); + +alter table "public"."socials" add constraint "socials_pkey" PRIMARY KEY using index "socials_pkey"; + +alter table "public"."socials" add constraint "socials_profile_url_key" UNIQUE using index "socials_profile_url_key"; + +alter table "public"."account_socials" add column "social_id" uuid not null default gen_random_uuid(); + +grant delete on table "public"."socials" to "anon"; + +grant insert on table "public"."socials" to "anon"; + +grant references on table "public"."socials" to "anon"; + +grant select on table "public"."socials" to "anon"; + +grant trigger on table "public"."socials" to "anon"; + +grant truncate on table "public"."socials" to "anon"; + +grant update on table "public"."socials" to "anon"; + +grant delete on table "public"."socials" to "authenticated"; + +grant insert on table "public"."socials" to "authenticated"; + +grant references on table "public"."socials" to "authenticated"; + +grant select on table "public"."socials" to "authenticated"; + +grant trigger on table "public"."socials" to "authenticated"; + +grant truncate on table "public"."socials" to "authenticated"; + +grant update on table "public"."socials" to "authenticated"; + +grant delete on table "public"."socials" to "service_role"; + +grant insert on table "public"."socials" to "service_role"; + +grant references on table "public"."socials" to "service_role"; + +grant select on table "public"."socials" to "service_role"; + +grant trigger on table "public"."socials" to "service_role"; + +grant truncate on table "public"."socials" to "service_role"; + +grant update on table "public"."socials" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON socials + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + + diff --git a/supabase/migrations/20250130184414_refactor_account_socials.sql b/supabase/migrations/20250130184414_refactor_account_socials.sql new file mode 100644 index 0000000..37a5342 --- /dev/null +++ b/supabase/migrations/20250130184414_refactor_account_socials.sql @@ -0,0 +1,21 @@ +alter table "public"."account_socials" drop column "avatar"; + +alter table "public"."account_socials" drop column "bio"; + +alter table "public"."account_socials" drop column "followerCount"; + +alter table "public"."account_socials" drop column "followingCount"; + +alter table "public"."account_socials" drop column "link"; + +alter table "public"."account_socials" drop column "region"; + +alter table "public"."account_socials" drop column "type"; + +alter table "public"."account_socials" drop column "username"; + +alter table "public"."account_socials" add constraint "account_socials_social_id_fkey" FOREIGN KEY (social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."account_socials" validate constraint "account_socials_social_id_fkey"; + + diff --git a/supabase/migrations/20250130190725_social_posts_refactoring.sql b/supabase/migrations/20250130190725_social_posts_refactoring.sql new file mode 100644 index 0000000..1a657d0 --- /dev/null +++ b/supabase/migrations/20250130190725_social_posts_refactoring.sql @@ -0,0 +1,11 @@ +alter table "public"."social_posts" drop constraint "social_posts_account_social_id_fkey"; + +alter table "public"."social_posts" drop column "account_social_id"; + +alter table "public"."social_posts" add column "social_id" uuid default gen_random_uuid(); + +alter table "public"."social_posts" add constraint "social_posts_social_id_fkey" FOREIGN KEY (social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_posts" validate constraint "social_posts_social_id_fkey"; + + diff --git a/supabase/migrations/20250130194156_post_comments_table.sql b/supabase/migrations/20250130194156_post_comments_table.sql new file mode 100644 index 0000000..72c53ff --- /dev/null +++ b/supabase/migrations/20250130194156_post_comments_table.sql @@ -0,0 +1,66 @@ +create table "public"."post_comments" ( + "id" uuid not null default gen_random_uuid(), + "post_id" uuid default gen_random_uuid(), + "social_id" uuid default gen_random_uuid(), + "comment" text, + "commented_at" timestamp with time zone not null +); + + +alter table "public"."post_comments" enable row level security; + +CREATE UNIQUE INDEX post_comments_pkey ON public.post_comments USING btree (id); + +alter table "public"."post_comments" add constraint "post_comments_pkey" PRIMARY KEY using index "post_comments_pkey"; + +alter table "public"."post_comments" add constraint "post_comments_post_id_fkey" FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE not valid; + +alter table "public"."post_comments" validate constraint "post_comments_post_id_fkey"; + +alter table "public"."post_comments" add constraint "post_comments_social_id_fkey" FOREIGN KEY (social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."post_comments" validate constraint "post_comments_social_id_fkey"; + +grant delete on table "public"."post_comments" to "anon"; + +grant insert on table "public"."post_comments" to "anon"; + +grant references on table "public"."post_comments" to "anon"; + +grant select on table "public"."post_comments" to "anon"; + +grant trigger on table "public"."post_comments" to "anon"; + +grant truncate on table "public"."post_comments" to "anon"; + +grant update on table "public"."post_comments" to "anon"; + +grant delete on table "public"."post_comments" to "authenticated"; + +grant insert on table "public"."post_comments" to "authenticated"; + +grant references on table "public"."post_comments" to "authenticated"; + +grant select on table "public"."post_comments" to "authenticated"; + +grant trigger on table "public"."post_comments" to "authenticated"; + +grant truncate on table "public"."post_comments" to "authenticated"; + +grant update on table "public"."post_comments" to "authenticated"; + +grant delete on table "public"."post_comments" to "service_role"; + +grant insert on table "public"."post_comments" to "service_role"; + +grant references on table "public"."post_comments" to "service_role"; + +grant select on table "public"."post_comments" to "service_role"; + +grant trigger on table "public"."post_comments" to "service_role"; + +grant truncate on table "public"."post_comments" to "service_role"; + +grant update on table "public"."post_comments" to "service_role"; + + diff --git a/supabase/migrations/20250130200000_create_social_fans_table.sql b/supabase/migrations/20250130200000_create_social_fans_table.sql new file mode 100644 index 0000000..cb9a75f --- /dev/null +++ b/supabase/migrations/20250130200000_create_social_fans_table.sql @@ -0,0 +1,91 @@ +create table "public"."social_fans" ( + "id" uuid not null default gen_random_uuid(), + "artist_social_id" uuid not null, + "fan_social_id" uuid not null, + "latest_engagement_id" uuid, + "latest_engagement" timestamp with time zone, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."social_fans" enable row level security; + +CREATE UNIQUE INDEX social_fans_pkey ON public.social_fans USING btree (id); + +-- Create a unique constraint to prevent duplicate artist-fan relationships +CREATE UNIQUE INDEX social_fans_artist_fan_unique ON public.social_fans USING btree (artist_social_id, fan_social_id); + +-- Index for efficient queries by artist +CREATE INDEX social_fans_artist_social_id_idx ON public.social_fans USING btree (artist_social_id); + +-- Index for efficient queries by fan +CREATE INDEX social_fans_fan_social_id_idx ON public.social_fans USING btree (fan_social_id); + +-- Index for efficient queries by latest engagement date +CREATE INDEX social_fans_latest_engagement_idx ON public.social_fans USING btree (latest_engagement); + +alter table "public"."social_fans" add constraint "social_fans_pkey" PRIMARY KEY using index "social_fans_pkey"; + +alter table "public"."social_fans" add constraint "social_fans_artist_fan_unique" UNIQUE using index "social_fans_artist_fan_unique"; + +-- Foreign key constraints +alter table "public"."social_fans" add constraint "social_fans_artist_social_id_fkey" FOREIGN KEY (artist_social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_fans" validate constraint "social_fans_artist_social_id_fkey"; + +alter table "public"."social_fans" add constraint "social_fans_fan_social_id_fkey" FOREIGN KEY (fan_social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_fans" validate constraint "social_fans_fan_social_id_fkey"; + +alter table "public"."social_fans" add constraint "social_fans_latest_engagement_id_fkey" FOREIGN KEY (latest_engagement_id) REFERENCES post_comments(id) ON DELETE SET NULL not valid; + +alter table "public"."social_fans" validate constraint "social_fans_latest_engagement_id_fkey"; + +-- Grant permissions +grant delete on table "public"."social_fans" to "anon"; + +grant insert on table "public"."social_fans" to "anon"; + +grant references on table "public"."social_fans" to "anon"; + +grant select on table "public"."social_fans" to "anon"; + +grant trigger on table "public"."social_fans" to "anon"; + +grant truncate on table "public"."social_fans" to "anon"; + +grant update on table "public"."social_fans" to "anon"; + +grant delete on table "public"."social_fans" to "authenticated"; + +grant insert on table "public"."social_fans" to "authenticated"; + +grant references on table "public"."social_fans" to "authenticated"; + +grant select on table "public"."social_fans" to "authenticated"; + +grant trigger on table "public"."social_fans" to "authenticated"; + +grant truncate on table "public"."social_fans" to "authenticated"; + +grant update on table "public"."social_fans" to "authenticated"; + +grant delete on table "public"."social_fans" to "service_role"; + +grant insert on table "public"."social_fans" to "service_role"; + +grant references on table "public"."social_fans" to "service_role"; + +grant select on table "public"."social_fans" to "service_role"; + +grant trigger on table "public"."social_fans" to "service_role"; + +grant truncate on table "public"."social_fans" to "service_role"; + +grant update on table "public"."social_fans" to "service_role"; + +-- Add updated_at trigger +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON social_fans + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250131043713_remove_funnel_comments_table.sql b/supabase/migrations/20250131043713_remove_funnel_comments_table.sql new file mode 100644 index 0000000..f53de23 --- /dev/null +++ b/supabase/migrations/20250131043713_remove_funnel_comments_table.sql @@ -0,0 +1,51 @@ +revoke delete on table "public"."funnel_analytics_comments" from "anon"; + +revoke insert on table "public"."funnel_analytics_comments" from "anon"; + +revoke references on table "public"."funnel_analytics_comments" from "anon"; + +revoke select on table "public"."funnel_analytics_comments" from "anon"; + +revoke trigger on table "public"."funnel_analytics_comments" from "anon"; + +revoke truncate on table "public"."funnel_analytics_comments" from "anon"; + +revoke update on table "public"."funnel_analytics_comments" from "anon"; + +revoke delete on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke insert on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke references on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke select on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke trigger on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke truncate on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke update on table "public"."funnel_analytics_comments" from "authenticated"; + +revoke delete on table "public"."funnel_analytics_comments" from "service_role"; + +revoke insert on table "public"."funnel_analytics_comments" from "service_role"; + +revoke references on table "public"."funnel_analytics_comments" from "service_role"; + +revoke select on table "public"."funnel_analytics_comments" from "service_role"; + +revoke trigger on table "public"."funnel_analytics_comments" from "service_role"; + +revoke truncate on table "public"."funnel_analytics_comments" from "service_role"; + +revoke update on table "public"."funnel_analytics_comments" from "service_role"; + +alter table "public"."funnel_analytics_comments" drop constraint "funnel_analysis_comments_analysis_id_fkey1"; + +alter table "public"."funnel_analytics_comments" drop constraint "funnel_analysis_comments_pkey1"; + +drop index if exists "public"."funnel_analysis_comments_pkey1"; + +drop table "public"."funnel_analytics_comments"; + + diff --git a/supabase/migrations/20250131142532_agents_tables.sql b/supabase/migrations/20250131142532_agents_tables.sql new file mode 100644 index 0000000..7faf376 --- /dev/null +++ b/supabase/migrations/20250131142532_agents_tables.sql @@ -0,0 +1,127 @@ +create table "public"."agent_status" ( + "id" uuid not null default gen_random_uuid(), + "agent_id" uuid not null default gen_random_uuid(), + "social_platform" text, + "status" bigint default '0'::bigint, + "progress" bigint default '0'::bigint, + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."agent_status" enable row level security; + +create table "public"."agents" ( + "id" uuid not null default gen_random_uuid(), + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."agents" enable row level security; + +CREATE UNIQUE INDEX agent_status_pkey ON public.agent_status USING btree (id); + +CREATE UNIQUE INDEX agents_pkey ON public.agents USING btree (id); + +alter table "public"."agent_status" add constraint "agent_status_pkey" PRIMARY KEY using index "agent_status_pkey"; + +alter table "public"."agents" add constraint "agents_pkey" PRIMARY KEY using index "agents_pkey"; + +alter table "public"."agent_status" add constraint "agent_status_agent_id_fkey" FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE not valid; + +alter table "public"."agent_status" validate constraint "agent_status_agent_id_fkey"; + +grant delete on table "public"."agent_status" to "anon"; + +grant insert on table "public"."agent_status" to "anon"; + +grant references on table "public"."agent_status" to "anon"; + +grant select on table "public"."agent_status" to "anon"; + +grant trigger on table "public"."agent_status" to "anon"; + +grant truncate on table "public"."agent_status" to "anon"; + +grant update on table "public"."agent_status" to "anon"; + +grant delete on table "public"."agent_status" to "authenticated"; + +grant insert on table "public"."agent_status" to "authenticated"; + +grant references on table "public"."agent_status" to "authenticated"; + +grant select on table "public"."agent_status" to "authenticated"; + +grant trigger on table "public"."agent_status" to "authenticated"; + +grant truncate on table "public"."agent_status" to "authenticated"; + +grant update on table "public"."agent_status" to "authenticated"; + +grant delete on table "public"."agent_status" to "service_role"; + +grant insert on table "public"."agent_status" to "service_role"; + +grant references on table "public"."agent_status" to "service_role"; + +grant select on table "public"."agent_status" to "service_role"; + +grant trigger on table "public"."agent_status" to "service_role"; + +grant truncate on table "public"."agent_status" to "service_role"; + +grant update on table "public"."agent_status" to "service_role"; + +grant delete on table "public"."agents" to "anon"; + +grant insert on table "public"."agents" to "anon"; + +grant references on table "public"."agents" to "anon"; + +grant select on table "public"."agents" to "anon"; + +grant trigger on table "public"."agents" to "anon"; + +grant truncate on table "public"."agents" to "anon"; + +grant update on table "public"."agents" to "anon"; + +grant delete on table "public"."agents" to "authenticated"; + +grant insert on table "public"."agents" to "authenticated"; + +grant references on table "public"."agents" to "authenticated"; + +grant select on table "public"."agents" to "authenticated"; + +grant trigger on table "public"."agents" to "authenticated"; + +grant truncate on table "public"."agents" to "authenticated"; + +grant update on table "public"."agents" to "authenticated"; + +grant delete on table "public"."agents" to "service_role"; + +grant insert on table "public"."agents" to "service_role"; + +grant references on table "public"."agents" to "service_role"; + +grant select on table "public"."agents" to "service_role"; + +grant trigger on table "public"."agents" to "service_role"; + +grant truncate on table "public"."agents" to "service_role"; + +grant update on table "public"."agents" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON agents + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON agent_status + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + + diff --git a/supabase/migrations/20250131143000_update_agent_status_social_reference.sql b/supabase/migrations/20250131143000_update_agent_status_social_reference.sql new file mode 100644 index 0000000..42184f4 --- /dev/null +++ b/supabase/migrations/20250131143000_update_agent_status_social_reference.sql @@ -0,0 +1,14 @@ +-- Drop the social_platform column and add social_id with foreign key reference +ALTER TABLE "public"."agent_status" + DROP COLUMN IF EXISTS "social_platform", + ADD COLUMN "social_id" uuid NOT NULL; + +-- Add foreign key constraint +ALTER TABLE "public"."agent_status" + ADD CONSTRAINT "agent_status_social_id_fkey" + FOREIGN KEY (social_id) + REFERENCES socials(id) + ON DELETE CASCADE; + +-- Create an index for the foreign key for better join performance +CREATE INDEX idx_agent_status_social_id ON public.agent_status(social_id); \ No newline at end of file diff --git a/supabase/migrations/20250201102146_spotify_tables.sql b/supabase/migrations/20250201102146_spotify_tables.sql new file mode 100644 index 0000000..46c12eb --- /dev/null +++ b/supabase/migrations/20250201102146_spotify_tables.sql @@ -0,0 +1,271 @@ +create table "public"."social_spotify_albums" ( + "id" uuid not null default gen_random_uuid(), + "social_id" uuid default gen_random_uuid(), + "album_id" uuid default gen_random_uuid(), + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."social_spotify_albums" enable row level security; + +create table "public"."social_spotify_tracks" ( + "id" uuid not null default gen_random_uuid(), + "social_id" uuid not null default gen_random_uuid(), + "track_id" uuid default gen_random_uuid(), + "updated_at" timestamp with time zone default now() +); + + +alter table "public"."social_spotify_tracks" enable row level security; + +create table "public"."spotify_albums" ( + "id" uuid not null default gen_random_uuid(), + "name" text default ''::text, + "uri" text not null, + "release_date" text, + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."spotify_albums" enable row level security; + +create table "public"."spotify_tracks" ( + "id" uuid not null default gen_random_uuid(), + "uri" text not null, + "name" text, + "pupularity" bigint default '0'::bigint, + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."spotify_tracks" enable row level security; + +CREATE UNIQUE INDEX social_spotify_albums_pkey ON public.social_spotify_albums USING btree (id); + +CREATE UNIQUE INDEX social_spotify_tracks_pkey ON public.social_spotify_tracks USING btree (id); + +CREATE UNIQUE INDEX spotify_albums_pkey ON public.spotify_albums USING btree (id); + +CREATE UNIQUE INDEX spotify_albums_uri_key ON public.spotify_albums USING btree (uri); + +CREATE UNIQUE INDEX spotify_tracks_pkey ON public.spotify_tracks USING btree (id); + +CREATE UNIQUE INDEX spotify_tracks_uri_key ON public.spotify_tracks USING btree (uri); + +alter table "public"."social_spotify_albums" add constraint "social_spotify_albums_pkey" PRIMARY KEY using index "social_spotify_albums_pkey"; + +alter table "public"."social_spotify_tracks" add constraint "social_spotify_tracks_pkey" PRIMARY KEY using index "social_spotify_tracks_pkey"; + +alter table "public"."spotify_albums" add constraint "spotify_albums_pkey" PRIMARY KEY using index "spotify_albums_pkey"; + +alter table "public"."spotify_tracks" add constraint "spotify_tracks_pkey" PRIMARY KEY using index "spotify_tracks_pkey"; + +alter table "public"."social_spotify_albums" add constraint "social_spotify_albums_album_id_fkey" FOREIGN KEY (album_id) REFERENCES spotify_albums(id) ON DELETE CASCADE not valid; + +alter table "public"."social_spotify_albums" validate constraint "social_spotify_albums_album_id_fkey"; + +alter table "public"."social_spotify_albums" add constraint "social_spotify_albums_social_id_fkey" FOREIGN KEY (social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_spotify_albums" validate constraint "social_spotify_albums_social_id_fkey"; + +alter table "public"."social_spotify_tracks" add constraint "social_spotify_tracks_social_id_fkey" FOREIGN KEY (social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."social_spotify_tracks" validate constraint "social_spotify_tracks_social_id_fkey"; + +alter table "public"."social_spotify_tracks" add constraint "social_spotify_tracks_track_id_fkey" FOREIGN KEY (track_id) REFERENCES spotify_tracks(id) ON DELETE CASCADE not valid; + +alter table "public"."social_spotify_tracks" validate constraint "social_spotify_tracks_track_id_fkey"; + +alter table "public"."spotify_albums" add constraint "spotify_albums_uri_key" UNIQUE using index "spotify_albums_uri_key"; + +alter table "public"."spotify_tracks" add constraint "spotify_tracks_uri_key" UNIQUE using index "spotify_tracks_uri_key"; + +grant delete on table "public"."social_spotify_albums" to "anon"; + +grant insert on table "public"."social_spotify_albums" to "anon"; + +grant references on table "public"."social_spotify_albums" to "anon"; + +grant select on table "public"."social_spotify_albums" to "anon"; + +grant trigger on table "public"."social_spotify_albums" to "anon"; + +grant truncate on table "public"."social_spotify_albums" to "anon"; + +grant update on table "public"."social_spotify_albums" to "anon"; + +grant delete on table "public"."social_spotify_albums" to "authenticated"; + +grant insert on table "public"."social_spotify_albums" to "authenticated"; + +grant references on table "public"."social_spotify_albums" to "authenticated"; + +grant select on table "public"."social_spotify_albums" to "authenticated"; + +grant trigger on table "public"."social_spotify_albums" to "authenticated"; + +grant truncate on table "public"."social_spotify_albums" to "authenticated"; + +grant update on table "public"."social_spotify_albums" to "authenticated"; + +grant delete on table "public"."social_spotify_albums" to "service_role"; + +grant insert on table "public"."social_spotify_albums" to "service_role"; + +grant references on table "public"."social_spotify_albums" to "service_role"; + +grant select on table "public"."social_spotify_albums" to "service_role"; + +grant trigger on table "public"."social_spotify_albums" to "service_role"; + +grant truncate on table "public"."social_spotify_albums" to "service_role"; + +grant update on table "public"."social_spotify_albums" to "service_role"; + +grant delete on table "public"."social_spotify_tracks" to "anon"; + +grant insert on table "public"."social_spotify_tracks" to "anon"; + +grant references on table "public"."social_spotify_tracks" to "anon"; + +grant select on table "public"."social_spotify_tracks" to "anon"; + +grant trigger on table "public"."social_spotify_tracks" to "anon"; + +grant truncate on table "public"."social_spotify_tracks" to "anon"; + +grant update on table "public"."social_spotify_tracks" to "anon"; + +grant delete on table "public"."social_spotify_tracks" to "authenticated"; + +grant insert on table "public"."social_spotify_tracks" to "authenticated"; + +grant references on table "public"."social_spotify_tracks" to "authenticated"; + +grant select on table "public"."social_spotify_tracks" to "authenticated"; + +grant trigger on table "public"."social_spotify_tracks" to "authenticated"; + +grant truncate on table "public"."social_spotify_tracks" to "authenticated"; + +grant update on table "public"."social_spotify_tracks" to "authenticated"; + +grant delete on table "public"."social_spotify_tracks" to "service_role"; + +grant insert on table "public"."social_spotify_tracks" to "service_role"; + +grant references on table "public"."social_spotify_tracks" to "service_role"; + +grant select on table "public"."social_spotify_tracks" to "service_role"; + +grant trigger on table "public"."social_spotify_tracks" to "service_role"; + +grant truncate on table "public"."social_spotify_tracks" to "service_role"; + +grant update on table "public"."social_spotify_tracks" to "service_role"; + +grant delete on table "public"."spotify_albums" to "anon"; + +grant insert on table "public"."spotify_albums" to "anon"; + +grant references on table "public"."spotify_albums" to "anon"; + +grant select on table "public"."spotify_albums" to "anon"; + +grant trigger on table "public"."spotify_albums" to "anon"; + +grant truncate on table "public"."spotify_albums" to "anon"; + +grant update on table "public"."spotify_albums" to "anon"; + +grant delete on table "public"."spotify_albums" to "authenticated"; + +grant insert on table "public"."spotify_albums" to "authenticated"; + +grant references on table "public"."spotify_albums" to "authenticated"; + +grant select on table "public"."spotify_albums" to "authenticated"; + +grant trigger on table "public"."spotify_albums" to "authenticated"; + +grant truncate on table "public"."spotify_albums" to "authenticated"; + +grant update on table "public"."spotify_albums" to "authenticated"; + +grant delete on table "public"."spotify_albums" to "service_role"; + +grant insert on table "public"."spotify_albums" to "service_role"; + +grant references on table "public"."spotify_albums" to "service_role"; + +grant select on table "public"."spotify_albums" to "service_role"; + +grant trigger on table "public"."spotify_albums" to "service_role"; + +grant truncate on table "public"."spotify_albums" to "service_role"; + +grant update on table "public"."spotify_albums" to "service_role"; + +grant delete on table "public"."spotify_tracks" to "anon"; + +grant insert on table "public"."spotify_tracks" to "anon"; + +grant references on table "public"."spotify_tracks" to "anon"; + +grant select on table "public"."spotify_tracks" to "anon"; + +grant trigger on table "public"."spotify_tracks" to "anon"; + +grant truncate on table "public"."spotify_tracks" to "anon"; + +grant update on table "public"."spotify_tracks" to "anon"; + +grant delete on table "public"."spotify_tracks" to "authenticated"; + +grant insert on table "public"."spotify_tracks" to "authenticated"; + +grant references on table "public"."spotify_tracks" to "authenticated"; + +grant select on table "public"."spotify_tracks" to "authenticated"; + +grant trigger on table "public"."spotify_tracks" to "authenticated"; + +grant truncate on table "public"."spotify_tracks" to "authenticated"; + +grant update on table "public"."spotify_tracks" to "authenticated"; + +grant delete on table "public"."spotify_tracks" to "service_role"; + +grant insert on table "public"."spotify_tracks" to "service_role"; + +grant references on table "public"."spotify_tracks" to "service_role"; + +grant select on table "public"."spotify_tracks" to "service_role"; + +grant trigger on table "public"."spotify_tracks" to "service_role"; + +grant truncate on table "public"."spotify_tracks" to "service_role"; + +grant update on table "public"."spotify_tracks" to "service_role"; + + + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON social_spotify_albums + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON social_spotify_tracks + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON spotify_albums + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON spotify_tracks + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250201102147_typo_fix.sql b/supabase/migrations/20250201102147_typo_fix.sql new file mode 100644 index 0000000..f5652ab --- /dev/null +++ b/supabase/migrations/20250201102147_typo_fix.sql @@ -0,0 +1,3 @@ +alter table "public"."spotify_tracks" drop column if exists "pupularity"; + +alter table "public"."spotify_tracks" ADD COLUMN IF NOT EXISTS "popularity" bigint default '0'::bigint; diff --git a/supabase/migrations/20250201102148_remove_summary_column.sql b/supabase/migrations/20250201102148_remove_summary_column.sql new file mode 100644 index 0000000..6ff02d5 --- /dev/null +++ b/supabase/migrations/20250201102148_remove_summary_column.sql @@ -0,0 +1 @@ +alter table "public"."funnel_reports" drop column if exists "summary"; diff --git a/supabase/migrations/20250204181622_artist_fan_segment_table.sql b/supabase/migrations/20250204181622_artist_fan_segment_table.sql new file mode 100644 index 0000000..fe816a7 --- /dev/null +++ b/supabase/migrations/20250204181622_artist_fan_segment_table.sql @@ -0,0 +1,120 @@ +revoke delete on table "public"."fan_segment" from "anon"; + +revoke insert on table "public"."fan_segment" from "anon"; + +revoke references on table "public"."fan_segment" from "anon"; + +revoke select on table "public"."fan_segment" from "anon"; + +revoke trigger on table "public"."fan_segment" from "anon"; + +revoke truncate on table "public"."fan_segment" from "anon"; + +revoke update on table "public"."fan_segment" from "anon"; + +revoke delete on table "public"."fan_segment" from "authenticated"; + +revoke insert on table "public"."fan_segment" from "authenticated"; + +revoke references on table "public"."fan_segment" from "authenticated"; + +revoke select on table "public"."fan_segment" from "authenticated"; + +revoke trigger on table "public"."fan_segment" from "authenticated"; + +revoke truncate on table "public"."fan_segment" from "authenticated"; + +revoke update on table "public"."fan_segment" from "authenticated"; + +revoke delete on table "public"."fan_segment" from "service_role"; + +revoke insert on table "public"."fan_segment" from "service_role"; + +revoke references on table "public"."fan_segment" from "service_role"; + +revoke select on table "public"."fan_segment" from "service_role"; + +revoke trigger on table "public"."fan_segment" from "service_role"; + +revoke truncate on table "public"."fan_segment" from "service_role"; + +revoke update on table "public"."fan_segment" from "service_role"; + +alter table "public"."fan_segment" drop constraint "fan_segment_account_id_fkey"; + +alter table "public"."fan_segment" drop constraint "fan_segment_artist_id_fkey"; + +alter table "public"."fan_segment" drop constraint "fan_segment_pkey"; + +drop index if exists "public"."fan_segment_pkey"; + +drop table "public"."fan_segment"; + +create table "public"."artist_fan_segment" ( + "updated_at" timestamp with time zone not null default now(), + "artist_social_id" uuid default gen_random_uuid(), + "fan_social_id" uuid default gen_random_uuid(), + "id" uuid not null default gen_random_uuid() +); + + +alter table "public"."artist_fan_segment" enable row level security; + +CREATE UNIQUE INDEX artist_fan_segment_pkey ON public.artist_fan_segment USING btree (id); + +alter table "public"."artist_fan_segment" add constraint "artist_fan_segment_pkey" PRIMARY KEY using index "artist_fan_segment_pkey"; + +alter table "public"."artist_fan_segment" add constraint "artist_fan_segment_artist_social_id_fkey" FOREIGN KEY (artist_social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."artist_fan_segment" validate constraint "artist_fan_segment_artist_social_id_fkey"; + +alter table "public"."artist_fan_segment" add constraint "artist_fan_segment_fan_social_id_fkey" FOREIGN KEY (fan_social_id) REFERENCES socials(id) ON DELETE CASCADE not valid; + +alter table "public"."artist_fan_segment" validate constraint "artist_fan_segment_fan_social_id_fkey"; + +grant delete on table "public"."artist_fan_segment" to "anon"; + +grant insert on table "public"."artist_fan_segment" to "anon"; + +grant references on table "public"."artist_fan_segment" to "anon"; + +grant select on table "public"."artist_fan_segment" to "anon"; + +grant trigger on table "public"."artist_fan_segment" to "anon"; + +grant truncate on table "public"."artist_fan_segment" to "anon"; + +grant update on table "public"."artist_fan_segment" to "anon"; + +grant delete on table "public"."artist_fan_segment" to "authenticated"; + +grant insert on table "public"."artist_fan_segment" to "authenticated"; + +grant references on table "public"."artist_fan_segment" to "authenticated"; + +grant select on table "public"."artist_fan_segment" to "authenticated"; + +grant trigger on table "public"."artist_fan_segment" to "authenticated"; + +grant truncate on table "public"."artist_fan_segment" to "authenticated"; + +grant update on table "public"."artist_fan_segment" to "authenticated"; + +grant delete on table "public"."artist_fan_segment" to "service_role"; + +grant insert on table "public"."artist_fan_segment" to "service_role"; + +grant references on table "public"."artist_fan_segment" to "service_role"; + +grant select on table "public"."artist_fan_segment" to "service_role"; + +grant trigger on table "public"."artist_fan_segment" to "service_role"; + +grant truncate on table "public"."artist_fan_segment" to "service_role"; + +grant update on table "public"."artist_fan_segment" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON artist_fan_segment + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250204181623_segment_name.sql b/supabase/migrations/20250204181623_segment_name.sql new file mode 100644 index 0000000..b531542 --- /dev/null +++ b/supabase/migrations/20250204181623_segment_name.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."artist_fan_segment" ADD COLUMN IF NOT EXISTS "segment_name" TEXT; diff --git a/supabase/migrations/20250206023350_segment_reports.sql b/supabase/migrations/20250206023350_segment_reports.sql new file mode 100644 index 0000000..094178a --- /dev/null +++ b/supabase/migrations/20250206023350_segment_reports.sql @@ -0,0 +1,67 @@ +create table "public"."segment_reports" ( + "id" uuid not null default gen_random_uuid(), + "next_steps" text, + "report" text, + "artist_id" uuid default '00000000-0000-0000-0000-000000000000'::uuid, + "updated_at" timestamp with time zone default now() +); + + +alter table "public"."segment_reports" enable row level security; + +CREATE UNIQUE INDEX segment_reports_pkey ON public.segment_reports USING btree (id); + +alter table "public"."segment_reports" add constraint "segment_reports_pkey" PRIMARY KEY using index "segment_reports_pkey"; + +alter table "public"."segment_reports" add constraint "segment_reports_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."segment_reports" validate constraint "segment_reports_artist_id_fkey"; + +grant delete on table "public"."segment_reports" to "anon"; + +grant insert on table "public"."segment_reports" to "anon"; + +grant references on table "public"."segment_reports" to "anon"; + +grant select on table "public"."segment_reports" to "anon"; + +grant trigger on table "public"."segment_reports" to "anon"; + +grant truncate on table "public"."segment_reports" to "anon"; + +grant update on table "public"."segment_reports" to "anon"; + +grant delete on table "public"."segment_reports" to "authenticated"; + +grant insert on table "public"."segment_reports" to "authenticated"; + +grant references on table "public"."segment_reports" to "authenticated"; + +grant select on table "public"."segment_reports" to "authenticated"; + +grant trigger on table "public"."segment_reports" to "authenticated"; + +grant truncate on table "public"."segment_reports" to "authenticated"; + +grant update on table "public"."segment_reports" to "authenticated"; + +grant delete on table "public"."segment_reports" to "service_role"; + +grant insert on table "public"."segment_reports" to "service_role"; + +grant references on table "public"."segment_reports" to "service_role"; + +grant select on table "public"."segment_reports" to "service_role"; + +grant trigger on table "public"."segment_reports" to "service_role"; + +grant truncate on table "public"."segment_reports" to "service_role"; + +grant update on table "public"."segment_reports" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON segment_reports + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + + diff --git a/supabase/migrations/20250206180226_eliza_migration_tables.sql b/supabase/migrations/20250206180226_eliza_migration_tables.sql new file mode 100644 index 0000000..c7103b6 --- /dev/null +++ b/supabase/migrations/20250206180226_eliza_migration_tables.sql @@ -0,0 +1,202 @@ +drop table if exists "public"."chat_messages"; + +drop table if exists "public"."chats"; + +create table "public"."memories" ( + "id" uuid not null default gen_random_uuid(), + "room_id" uuid default gen_random_uuid(), + "content" jsonb not null, + "artist_id" uuid default gen_random_uuid(), + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."memories" enable row level security; + +create table "public"."room_reports" ( + "id" uuid not null default gen_random_uuid(), + "room_id" uuid default gen_random_uuid(), + "report_id" uuid not null default gen_random_uuid() +); + + +alter table "public"."room_reports" enable row level security; + +create table "public"."rooms" ( + "id" uuid not null default gen_random_uuid(), + "account_id" uuid default gen_random_uuid(), + "topic" text, + "updated_at" timestamp with time zone not null default now() +); + + +alter table "public"."rooms" enable row level security; + +CREATE UNIQUE INDEX memories_pkey ON public.memories USING btree (id); + +CREATE UNIQUE INDEX room_reports_pkey ON public.room_reports USING btree (id); + +CREATE UNIQUE INDEX rooms_pkey ON public.rooms USING btree (id); + +alter table "public"."memories" add constraint "memories_pkey" PRIMARY KEY using index "memories_pkey"; + +alter table "public"."room_reports" add constraint "room_reports_pkey" PRIMARY KEY using index "room_reports_pkey"; + +alter table "public"."rooms" add constraint "rooms_pkey" PRIMARY KEY using index "rooms_pkey"; + +alter table "public"."memories" add constraint "memories_artist_id_fkey" FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."memories" validate constraint "memories_artist_id_fkey"; + +alter table "public"."memories" add constraint "memories_room_id_fkey" FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE not valid; + +alter table "public"."memories" validate constraint "memories_room_id_fkey"; + +alter table "public"."room_reports" add constraint "room_reports_report_id_fkey" FOREIGN KEY (report_id) REFERENCES segment_reports(id) ON DELETE CASCADE not valid; + +alter table "public"."room_reports" validate constraint "room_reports_report_id_fkey"; + +alter table "public"."room_reports" add constraint "room_reports_room_id_fkey" FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE not valid; + +alter table "public"."room_reports" validate constraint "room_reports_room_id_fkey"; + +grant delete on table "public"."memories" to "anon"; + +grant insert on table "public"."memories" to "anon"; + +grant references on table "public"."memories" to "anon"; + +grant select on table "public"."memories" to "anon"; + +grant trigger on table "public"."memories" to "anon"; + +grant truncate on table "public"."memories" to "anon"; + +grant update on table "public"."memories" to "anon"; + +grant delete on table "public"."memories" to "authenticated"; + +grant insert on table "public"."memories" to "authenticated"; + +grant references on table "public"."memories" to "authenticated"; + +grant select on table "public"."memories" to "authenticated"; + +grant trigger on table "public"."memories" to "authenticated"; + +grant truncate on table "public"."memories" to "authenticated"; + +grant update on table "public"."memories" to "authenticated"; + +grant delete on table "public"."memories" to "service_role"; + +grant insert on table "public"."memories" to "service_role"; + +grant references on table "public"."memories" to "service_role"; + +grant select on table "public"."memories" to "service_role"; + +grant trigger on table "public"."memories" to "service_role"; + +grant truncate on table "public"."memories" to "service_role"; + +grant update on table "public"."memories" to "service_role"; + +grant delete on table "public"."room_reports" to "anon"; + +grant insert on table "public"."room_reports" to "anon"; + +grant references on table "public"."room_reports" to "anon"; + +grant select on table "public"."room_reports" to "anon"; + +grant trigger on table "public"."room_reports" to "anon"; + +grant truncate on table "public"."room_reports" to "anon"; + +grant update on table "public"."room_reports" to "anon"; + +grant delete on table "public"."room_reports" to "authenticated"; + +grant insert on table "public"."room_reports" to "authenticated"; + +grant references on table "public"."room_reports" to "authenticated"; + +grant select on table "public"."room_reports" to "authenticated"; + +grant trigger on table "public"."room_reports" to "authenticated"; + +grant truncate on table "public"."room_reports" to "authenticated"; + +grant update on table "public"."room_reports" to "authenticated"; + +grant delete on table "public"."room_reports" to "service_role"; + +grant insert on table "public"."room_reports" to "service_role"; + +grant references on table "public"."room_reports" to "service_role"; + +grant select on table "public"."room_reports" to "service_role"; + +grant trigger on table "public"."room_reports" to "service_role"; + +grant truncate on table "public"."room_reports" to "service_role"; + +grant update on table "public"."room_reports" to "service_role"; + +grant delete on table "public"."rooms" to "anon"; + +grant insert on table "public"."rooms" to "anon"; + +grant references on table "public"."rooms" to "anon"; + +grant select on table "public"."rooms" to "anon"; + +grant trigger on table "public"."rooms" to "anon"; + +grant truncate on table "public"."rooms" to "anon"; + +grant update on table "public"."rooms" to "anon"; + +grant delete on table "public"."rooms" to "authenticated"; + +grant insert on table "public"."rooms" to "authenticated"; + +grant references on table "public"."rooms" to "authenticated"; + +grant select on table "public"."rooms" to "authenticated"; + +grant trigger on table "public"."rooms" to "authenticated"; + +grant truncate on table "public"."rooms" to "authenticated"; + +grant update on table "public"."rooms" to "authenticated"; + +grant delete on table "public"."rooms" to "service_role"; + +grant insert on table "public"."rooms" to "service_role"; + +grant references on table "public"."rooms" to "service_role"; + +grant select on table "public"."rooms" to "service_role"; + +grant trigger on table "public"."rooms" to "service_role"; + +grant truncate on table "public"."rooms" to "service_role"; + +grant update on table "public"."rooms" to "service_role"; + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON rooms + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON memories + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON room_reports + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250217173551_create_segment_tables.sql b/supabase/migrations/20250217173551_create_segment_tables.sql new file mode 100644 index 0000000..1081a1c --- /dev/null +++ b/supabase/migrations/20250217173551_create_segment_tables.sql @@ -0,0 +1,57 @@ +-- Create segments table to store segment definitions +CREATE TABLE IF NOT EXISTS segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Create artist_segments table to establish relationship between artists and segments +CREATE TABLE IF NOT EXISTS artist_segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + segment_id UUID NOT NULL REFERENCES segments(id) ON DELETE CASCADE, + artist_account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(segment_id, artist_account_id) +); + +-- Create fan_segments table to establish relationship between fans and segments +CREATE TABLE IF NOT EXISTS fan_segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + segment_id UUID NOT NULL REFERENCES segments(id) ON DELETE CASCADE, + fan_social_id UUID NOT NULL REFERENCES socials(id) ON DELETE CASCADE, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(segment_id, fan_social_id) +); + +-- Create indexes for faster lookups +CREATE INDEX IF NOT EXISTS idx_segments_name ON segments(name); +CREATE INDEX IF NOT EXISTS idx_artist_segments_segment_id ON artist_segments(segment_id); +CREATE INDEX IF NOT EXISTS idx_artist_segments_artist_account_id ON artist_segments(artist_account_id); +CREATE INDEX IF NOT EXISTS idx_fan_segments_segment_id ON fan_segments(segment_id); +CREATE INDEX IF NOT EXISTS idx_fan_segments_fan_social_id ON fan_segments(fan_social_id); + +-- Add triggers to update updated_at timestamp +CREATE TRIGGER set_timestamp_segments + BEFORE UPDATE ON segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +CREATE TRIGGER set_timestamp_artist_segments + BEFORE UPDATE ON artist_segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +CREATE TRIGGER set_timestamp_fan_segments + BEFORE UPDATE ON fan_segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +-- DOWN migration +-- Drop tables in reverse order to handle dependencies +DROP TRIGGER IF EXISTS set_timestamp_fan_segments ON fan_segments; +DROP TRIGGER IF EXISTS set_timestamp_artist_segments ON artist_segments; +DROP TRIGGER IF EXISTS set_timestamp_segments ON segments; + +DROP TABLE IF EXISTS fan_segments; +DROP TABLE IF EXISTS artist_segments; +DROP TABLE IF EXISTS segments; \ No newline at end of file diff --git a/supabase/migrations/20250217175555_create_segment_tables.sql b/supabase/migrations/20250217175555_create_segment_tables.sql new file mode 100644 index 0000000..ebf4ace --- /dev/null +++ b/supabase/migrations/20250217175555_create_segment_tables.sql @@ -0,0 +1,57 @@ +-- Create segments table to store segment definitions +CREATE TABLE IF NOT EXISTS segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Create artist_segments table to establish relationship between artists and segments +CREATE TABLE IF NOT EXISTS artist_segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + segment_id UUID NOT NULL REFERENCES segments(id) ON DELETE CASCADE, + artist_account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(segment_id, artist_account_id) +); + +-- Create fan_segments table to establish relationship between fans and segments +CREATE TABLE IF NOT EXISTS fan_segments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + segment_id UUID NOT NULL REFERENCES segments(id) ON DELETE CASCADE, + fan_social_id UUID NOT NULL REFERENCES socials(id) ON DELETE CASCADE, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(segment_id, fan_social_id) +); + +-- Create indexes for faster lookups +CREATE INDEX IF NOT EXISTS idx_segments_name ON segments(name); +CREATE INDEX IF NOT EXISTS idx_artist_segments_segment_id ON artist_segments(segment_id); +CREATE INDEX IF NOT EXISTS idx_artist_segments_artist_account_id ON artist_segments(artist_account_id); +CREATE INDEX IF NOT EXISTS idx_fan_segments_segment_id ON fan_segments(segment_id); +CREATE INDEX IF NOT EXISTS idx_fan_segments_fan_social_id ON fan_segments(fan_social_id); + +-- Add triggers to update updated_at timestamp +CREATE TRIGGER set_timestamp_segments + BEFORE UPDATE ON segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +CREATE TRIGGER set_timestamp_artist_segments + BEFORE UPDATE ON artist_segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +CREATE TRIGGER set_timestamp_fan_segments + BEFORE UPDATE ON fan_segments + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamps(); + +-- DOWN migration +-- Drop tables in reverse order to handle dependencies +-- DROP TRIGGER IF EXISTS set_timestamp_fan_segments ON fan_segments; +-- DROP TRIGGER IF EXISTS set_timestamp_artist_segments ON artist_segments; +-- DROP TRIGGER IF EXISTS set_timestamp_segments ON segments; + +-- DROP TABLE IF EXISTS fan_segments; +-- DROP TABLE IF EXISTS artist_segments; +-- DROP TABLE IF EXISTS segments; \ No newline at end of file diff --git a/supabase/migrations/20250225074220_create_segment_rooms_table.sql b/supabase/migrations/20250225074220_create_segment_rooms_table.sql new file mode 100644 index 0000000..3dc7d39 --- /dev/null +++ b/supabase/migrations/20250225074220_create_segment_rooms_table.sql @@ -0,0 +1,58 @@ +-- Create segment_rooms table to establish relationship between segments and rooms +CREATE TABLE IF NOT EXISTS "public"."segment_rooms" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "segment_id" UUID NOT NULL, + "room_id" UUID NOT NULL, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Enable row level security +ALTER TABLE "public"."segment_rooms" ENABLE ROW LEVEL SECURITY; + +-- Create primary key +CREATE UNIQUE INDEX segment_rooms_pkey ON public.segment_rooms USING btree (id); +ALTER TABLE "public"."segment_rooms" ADD CONSTRAINT "segment_rooms_pkey" PRIMARY KEY USING INDEX "segment_rooms_pkey"; + +-- Add foreign key constraints +ALTER TABLE "public"."segment_rooms" ADD CONSTRAINT "segment_rooms_segment_id_fkey" + FOREIGN KEY (segment_id) REFERENCES segments(id) ON DELETE CASCADE NOT VALID; +ALTER TABLE "public"."segment_rooms" VALIDATE CONSTRAINT "segment_rooms_segment_id_fkey"; + +ALTER TABLE "public"."segment_rooms" ADD CONSTRAINT "segment_rooms_room_id_fkey" + FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE NOT VALID; +ALTER TABLE "public"."segment_rooms" VALIDATE CONSTRAINT "segment_rooms_room_id_fkey"; + +-- Grant permissions +GRANT DELETE ON TABLE "public"."segment_rooms" TO "anon"; +GRANT INSERT ON TABLE "public"."segment_rooms" TO "anon"; +GRANT REFERENCES ON TABLE "public"."segment_rooms" TO "anon"; +GRANT SELECT ON TABLE "public"."segment_rooms" TO "anon"; +GRANT TRIGGER ON TABLE "public"."segment_rooms" TO "anon"; +GRANT TRUNCATE ON TABLE "public"."segment_rooms" TO "anon"; +GRANT UPDATE ON TABLE "public"."segment_rooms" TO "anon"; + +GRANT DELETE ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT INSERT ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT REFERENCES ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT SELECT ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT TRIGGER ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT TRUNCATE ON TABLE "public"."segment_rooms" TO "authenticated"; +GRANT UPDATE ON TABLE "public"."segment_rooms" TO "authenticated"; + +GRANT DELETE ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT INSERT ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT REFERENCES ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT SELECT ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT TRIGGER ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT TRUNCATE ON TABLE "public"."segment_rooms" TO "service_role"; +GRANT UPDATE ON TABLE "public"."segment_rooms" TO "service_role"; + +-- Add trigger for updating updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON segment_rooms + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- DOWN migration +-- DROP TRIGGER IF EXISTS set_updated_at ON segment_rooms; +-- DROP TABLE IF EXISTS "public"."segment_rooms"; \ No newline at end of file diff --git a/supabase/migrations/20250310153603_add_artist_id_to_rooms.sql b/supabase/migrations/20250310153603_add_artist_id_to_rooms.sql new file mode 100644 index 0000000..847e0e6 --- /dev/null +++ b/supabase/migrations/20250310153603_add_artist_id_to_rooms.sql @@ -0,0 +1,78 @@ +-- Migration: Add artist_id to rooms table and migrate data from memories table +-- Description: This migration adds an artist_id column to the rooms table, +-- populates it with values from the memories table, and then removes the artist_id column from memories. + +-- Add artist_id column to rooms table +ALTER TABLE "public"."rooms" ADD COLUMN "artist_id" UUID DEFAULT NULL; + +-- Create a temporary function to migrate the data +CREATE OR REPLACE FUNCTION migrate_artist_id_to_rooms() +RETURNS void AS $$ +BEGIN + -- Update rooms with artist_id from memories + -- For each room, find the artist_id from any memory in that room + UPDATE "public"."rooms" r + SET "artist_id" = ( + SELECT "artist_id" + FROM "public"."memories" m + WHERE m."room_id" = r."id" + LIMIT 1 + ) + WHERE EXISTS ( + SELECT 1 + FROM "public"."memories" m + WHERE m."room_id" = r."id" + ); +END; +$$ LANGUAGE plpgsql; + +-- Execute the migration function +SELECT migrate_artist_id_to_rooms(); + +-- Drop the temporary function +DROP FUNCTION migrate_artist_id_to_rooms(); + +-- Add foreign key constraint to artist_id in rooms table +ALTER TABLE "public"."rooms" ADD CONSTRAINT "rooms_artist_id_fkey" + FOREIGN KEY ("artist_id") REFERENCES "public"."accounts"("id") ON DELETE CASCADE NOT VALID; +ALTER TABLE "public"."rooms" VALIDATE CONSTRAINT "rooms_artist_id_fkey"; + +-- Create an index for faster lookups +CREATE INDEX IF NOT EXISTS "idx_rooms_artist_id" ON "public"."rooms" ("artist_id"); + +-- Remove artist_id from memories table +-- First drop the foreign key constraint +ALTER TABLE "public"."memories" DROP CONSTRAINT IF EXISTS "memories_artist_id_fkey"; + +-- Then drop the column +ALTER TABLE "public"."memories" DROP COLUMN "artist_id"; + +-- DOWN migration +-- In case we need to rollback +-- First recreate the artist_id column in memories +-- ALTER TABLE "public"."memories" ADD COLUMN "artist_id" UUID DEFAULT NULL; +-- ALTER TABLE "public"."memories" ADD CONSTRAINT "memories_artist_id_fkey" +-- FOREIGN KEY ("artist_id") REFERENCES "public"."accounts"("id") ON DELETE CASCADE; +-- Migrate data back from rooms to memories +-- CREATE OR REPLACE FUNCTION migrate_artist_id_to_memories() +-- RETURNS void AS $$ +-- BEGIN +-- UPDATE "public"."memories" m +-- SET "artist_id" = ( +-- SELECT "artist_id" +-- FROM "public"."rooms" r +-- WHERE r."id" = m."room_id" +-- ) +-- WHERE EXISTS ( +-- SELECT 1 +-- FROM "public"."rooms" r +-- WHERE r."id" = m."room_id" +-- ); +-- END; +-- $$ LANGUAGE plpgsql; +-- SELECT migrate_artist_id_to_memories(); +-- DROP FUNCTION migrate_artist_id_to_memories(); +-- Then remove from rooms +-- DROP INDEX IF EXISTS "public"."idx_rooms_artist_id"; +-- ALTER TABLE "public"."rooms" DROP CONSTRAINT IF EXISTS "rooms_artist_id_fkey"; +-- ALTER TABLE "public"."rooms" DROP COLUMN IF EXISTS "artist_id"; \ No newline at end of file diff --git a/supabase/migrations/20250310153604_account_wallets_table.sql b/supabase/migrations/20250310153604_account_wallets_table.sql new file mode 100644 index 0000000..6aec792 --- /dev/null +++ b/supabase/migrations/20250310153604_account_wallets_table.sql @@ -0,0 +1,20 @@ +-- Create account_wallets table +CREATE TABLE IF NOT EXISTS account_wallets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + wallet TEXT NOT NULL, + updated_at TIMESTAMPTZ DEFAULT now(), + UNIQUE(account_id, wallet) +); + +-- Create index on account_id for faster lookups +CREATE INDEX IF NOT EXISTS account_wallets_account_id_idx ON account_wallets(account_id); + +-- Create index on wallet for faster lookups +CREATE INDEX IF NOT EXISTS account_wallets_wallet_idx ON account_wallets(wallet); + +-- Create updated_at trigger +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_wallets + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250528095512_socials_profile_url_clean_trigger.sql b/supabase/migrations/20250528095512_socials_profile_url_clean_trigger.sql new file mode 100644 index 0000000..977ed84 --- /dev/null +++ b/supabase/migrations/20250528095512_socials_profile_url_clean_trigger.sql @@ -0,0 +1,24 @@ +-- Migration: Clean socials.profile_url (remove protocol, www, trailing slash, lowercase) +-- This migration creates a trigger to ensure all profile_url values in the socials table +-- have no protocol (http://, https://), no 'www' (with or without dot), no trailing '/', and are lowercase. + +-- 1. Create the cleaning function +CREATE OR REPLACE FUNCTION clean_socials_profile_url() +RETURNS TRIGGER AS $$ +BEGIN + -- Remove protocol (http://, https://) and all www (with or without dot) from the start + NEW.profile_url := regexp_replace(NEW.profile_url, '^(https?://)?(www\.?)+', ''); + -- Remove any trailing slash + NEW.profile_url := regexp_replace(NEW.profile_url, '/+$', ''); + -- Convert to lowercase + NEW.profile_url := lower(NEW.profile_url); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- 2. Create the trigger +DROP TRIGGER IF EXISTS trigger_clean_socials_profile_url ON socials; +CREATE TRIGGER trigger_clean_socials_profile_url + BEFORE INSERT OR UPDATE ON socials + FOR EACH ROW + EXECUTE FUNCTION clean_socials_profile_url(); \ No newline at end of file diff --git a/supabase/migrations/20250528103249_socials_profile_url_normalize_and_dedupe.sql b/supabase/migrations/20250528103249_socials_profile_url_normalize_and_dedupe.sql new file mode 100644 index 0000000..32d341e --- /dev/null +++ b/supabase/migrations/20250528103249_socials_profile_url_normalize_and_dedupe.sql @@ -0,0 +1,61 @@ +-- Migration: Normalize and deduplicate socials.profile_url +-- This migration normalizes all existing profile_url values in the socials table, +-- deletes duplicates, and updates references in account_socials and social_posts. + +BEGIN; + +-- 1. Create a temporary table to store duplicates info +CREATE TEMP TABLE social_duplicates AS +WITH normalized_urls AS ( + SELECT + id, + lower(regexp_replace( + regexp_replace(profile_url, '^(https?://)?(www\.?)+', ''), + '/+$', '' + )) as normalized_url + FROM socials +), +duplicate_groups AS ( + SELECT + normalized_url, + array_agg(id ORDER BY id) as id_array, -- Order by ID to keep consistent + (array_agg(id ORDER BY id))[1] as keep_id -- Keep the earliest ID + FROM normalized_urls + GROUP BY normalized_url + HAVING count(*) > 1 +) +SELECT + normalized_url, + keep_id, + duplicate_id +FROM duplicate_groups, +LATERAL unnest(id_array) AS duplicate_id +WHERE duplicate_id != keep_id; + +-- 2. Update references in account_socials +UPDATE account_socials AS a +SET social_id = d.keep_id +FROM social_duplicates AS d +WHERE a.social_id = d.duplicate_id; + +-- 3. Update references in social_posts +UPDATE social_posts AS p +SET social_id = d.keep_id +FROM social_duplicates AS d +WHERE p.social_id = d.duplicate_id; + +-- 4. Delete the duplicate social records +DELETE FROM socials +WHERE id IN (SELECT duplicate_id FROM social_duplicates); + +-- 5. Now safely update all remaining records with normalized URLs +UPDATE socials SET profile_url = + lower(regexp_replace( + regexp_replace(profile_url, '^(https?://)?(www\.?)+', ''), + '/+$', '' + )); + +-- 6. Clean up temporary table +DROP TABLE social_duplicates; + +COMMIT; \ No newline at end of file diff --git a/supabase/migrations/20250528103754_update_socials_profile_url_clean_trigger.sql b/supabase/migrations/20250528103754_update_socials_profile_url_clean_trigger.sql new file mode 100644 index 0000000..3c986a7 --- /dev/null +++ b/supabase/migrations/20250528103754_update_socials_profile_url_clean_trigger.sql @@ -0,0 +1,19 @@ +-- Migration: Update clean_socials_profile_url trigger function +-- This migration updates the trigger function to ensure all profile_url values in the socials table +-- have no protocol (http://, https://), no 'www.' at the start, no trailing '/', and are lowercase. + +-- 1. Update the cleaning function +CREATE OR REPLACE FUNCTION clean_socials_profile_url() +RETURNS TRIGGER AS $$ +BEGIN + -- Remove protocol (http://, https://) from the start + NEW.profile_url := regexp_replace(NEW.profile_url, '^https?://', ''); + -- Remove leading www. from the start + NEW.profile_url := regexp_replace(NEW.profile_url, '^www\.', ''); + -- Remove any trailing slash + NEW.profile_url := regexp_replace(NEW.profile_url, '/+$', ''); + -- Convert to lowercase + NEW.profile_url := lower(NEW.profile_url); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/supabase/migrations/20250528110305_socials_profile_url_normalize_existing.sql b/supabase/migrations/20250528110305_socials_profile_url_normalize_existing.sql new file mode 100644 index 0000000..f570e39 --- /dev/null +++ b/supabase/migrations/20250528110305_socials_profile_url_normalize_existing.sql @@ -0,0 +1,70 @@ +-- Migration: Normalize and deduplicate socials.profile_url to match trigger logic +-- This migration deduplicates and normalizes all existing profile_url values in the socials table +-- to remove protocol, leading www., trailing slashes, and lowercase. + +BEGIN; + +-- 1. Create a temporary table to store duplicates info +CREATE TEMP TABLE social_duplicates AS +WITH normalized_urls AS ( + SELECT + id, + lower( + regexp_replace( + regexp_replace( + regexp_replace(profile_url, '^https?://', ''), + '^www\.', '' + ), + '/+$', '' + ) + ) as normalized_url + FROM socials +), +duplicate_groups AS ( + SELECT + normalized_url, + array_agg(id ORDER BY id) as id_array, -- Order by ID to keep consistent + (array_agg(id ORDER BY id))[1] as keep_id -- Keep the earliest ID + FROM normalized_urls + GROUP BY normalized_url + HAVING count(*) > 1 +) +SELECT + normalized_url, + keep_id, + duplicate_id +FROM duplicate_groups, +LATERAL unnest(id_array) AS duplicate_id +WHERE duplicate_id != keep_id; + +-- 2. Update references in account_socials +UPDATE account_socials AS a +SET social_id = d.keep_id +FROM social_duplicates AS d +WHERE a.social_id = d.duplicate_id; + +-- 3. Update references in social_posts +UPDATE social_posts AS p +SET social_id = d.keep_id +FROM social_duplicates AS d +WHERE p.social_id = d.duplicate_id; + +-- 4. Delete the duplicate social records +DELETE FROM socials +WHERE id IN (SELECT duplicate_id FROM social_duplicates); + +-- 5. Now safely update all remaining records with normalized URLs +UPDATE socials SET profile_url = lower( + regexp_replace( + regexp_replace( + regexp_replace(profile_url, '^https?://', ''), + '^www\.', '' + ), + '/+$', '' + ) +); + +-- 6. Clean up temporary table +DROP TABLE social_duplicates; + +COMMIT; \ No newline at end of file diff --git a/supabase/migrations/20250601000000_update_account_wallets_table.sql b/supabase/migrations/20250601000000_update_account_wallets_table.sql new file mode 100644 index 0000000..0c829ce --- /dev/null +++ b/supabase/migrations/20250601000000_update_account_wallets_table.sql @@ -0,0 +1,3 @@ +-- Enforce global uniqueness for wallet addresses in account_wallets +ALTER TABLE account_wallets + ADD CONSTRAINT account_wallets_wallet_key UNIQUE (wallet); \ No newline at end of file diff --git a/supabase/migrations/20250601000001_youtube_tokens_consolidated.sql b/supabase/migrations/20250601000001_youtube_tokens_consolidated.sql new file mode 100644 index 0000000..f7e3aed --- /dev/null +++ b/supabase/migrations/20250601000001_youtube_tokens_consolidated.sql @@ -0,0 +1,38 @@ +-- Create YouTube tokens table for storing OAuth tokens per account +CREATE TABLE IF NOT EXISTS "public"."youtube_tokens" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "account_id" uuid NOT NULL, + "access_token" text NOT NULL, + "refresh_token" text, + "expires_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone NOT NULL DEFAULT now(), + "updated_at" timestamp with time zone NOT NULL DEFAULT now(), + PRIMARY KEY ("id"), + CONSTRAINT "youtube_tokens_account_id_fkey" + FOREIGN KEY ("account_id") + REFERENCES "public"."accounts" ("id") + ON DELETE CASCADE, + CONSTRAINT "youtube_tokens_account_id_key" + UNIQUE ("account_id") +); + +-- Create index for faster lookups by account_id +CREATE INDEX IF NOT EXISTS "youtube_tokens_account_id_idx" + ON "public"."youtube_tokens" ("account_id"); + +-- Create index for token expiry cleanup +CREATE INDEX IF NOT EXISTS "youtube_tokens_expires_at_idx" + ON "public"."youtube_tokens" ("expires_at"); + +-- Add updated_at trigger +DO $$ +BEGIN + -- Drop trigger if it exists + DROP TRIGGER IF EXISTS "youtube_tokens_updated_at_trigger" ON "public"."youtube_tokens"; + + -- Create the trigger + CREATE TRIGGER "youtube_tokens_updated_at_trigger" + BEFORE UPDATE ON "public"."youtube_tokens" + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); +END $$; \ No newline at end of file diff --git a/supabase/migrations/20250616142215_scheduled_actions_table.sql b/supabase/migrations/20250616142215_scheduled_actions_table.sql new file mode 100644 index 0000000..323a452 --- /dev/null +++ b/supabase/migrations/20250616142215_scheduled_actions_table.sql @@ -0,0 +1,19 @@ +-- Create scheduled_actions table +CREATE TABLE IF NOT EXISTS public.scheduled_actions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + prompt TEXT NOT NULL, + schedule TEXT NOT NULL, -- cron string + account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + artist_account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + enabled BOOLEAN DEFAULT TRUE, + last_run TIMESTAMPTZ, + next_run TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Optional: Indexes for efficient querying +CREATE INDEX IF NOT EXISTS idx_scheduled_actions_account_id ON public.scheduled_actions(account_id); +CREATE INDEX IF NOT EXISTS idx_scheduled_actions_artist_account_id ON public.scheduled_actions(artist_account_id); +CREATE INDEX IF NOT EXISTS idx_scheduled_actions_next_run ON public.scheduled_actions(next_run); \ No newline at end of file diff --git a/supabase/migrations/20250617000001_create_agent_templates_table.sql b/supabase/migrations/20250617000001_create_agent_templates_table.sql new file mode 100644 index 0000000..14b8c20 --- /dev/null +++ b/supabase/migrations/20250617000001_create_agent_templates_table.sql @@ -0,0 +1,20 @@ +-- Create agent_templates table for MYC-2246 P1 +-- Migrating agents.json to Supabase database + +CREATE TABLE agent_templates ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + title text NOT NULL, + description text NOT NULL, + prompt text NOT NULL, + tags text[] NOT NULL DEFAULT '{}', + updated_at timestamp with time zone DEFAULT now() +); + +-- Add updated_at trigger +CREATE TRIGGER set_updated_at_agent_templates + BEFORE UPDATE ON agent_templates + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Basic index for performance +CREATE INDEX idx_agent_templates_title ON agent_templates(title); \ No newline at end of file diff --git a/supabase/migrations/20250617000002_insert_agent_templates_data.sql b/supabase/migrations/20250617000002_insert_agent_templates_data.sql new file mode 100644 index 0000000..97fb1da --- /dev/null +++ b/supabase/migrations/20250617000002_insert_agent_templates_data.sql @@ -0,0 +1,51 @@ +-- Insert agent templates data from agents.json +-- MYC-2246 P1 - Data migration + +INSERT INTO agent_templates (title, description, prompt, tags) VALUES + ('Audience Segmentation', 'Identify and analyze key audience segments for targeted marketing and growth.', '', '{"Research"}'), + + ('Superfan Insights', 'Surface your most engaged listeners for premium engagement and monetization opportunities.', 'Analyze my artist''s fan segments and tell me who our most valuable listeners are. What demographics do they represent, how do they engage with us, and how should we target them in our next campaign? Please provide a downloadable report with actionable recommendations.', '{"Social"}'), + + ('Social Performance Audit', 'Comprehensive analysis of your social channels to inform digital strategy.', 'Give me a complete health check of my artist''s social media presence. Which posts are performing best, what are fans saying in the comments, and how does engagement vary across platforms? Could you create a diagram showing our social ecosystem with specific areas to improve?', '{"Social"}'), + + ('Collaboration Scouting', 'Identify high-potential artist collaborations to expand your roster and reach new audiences.', 'Find compatible artists we could collaborate with for mutual audience growth. Identify where our fanbases overlap and differ, then suggest creative cross-promotion campaigns that would benefit both parties and feel authentic to our respective brands.', '{"A&R"}'), + + ('Tour Strategy', 'Optimize tour routing, venues, and VIP offerings to maximize revenue and fan engagement.', 'We''re planning a tour for next quarter. Based on our streaming data, social engagement, and fan locations, where should we perform to maximize attendance and revenue? What VIP experiences could we offer, and how should we price tickets across different markets?', '{"Tour"}'), + + ('Brand Positioning', 'Strategic recommendations to refine and elevate your artist''s brand in the market.', 'My artist is ready for a brand refresh. Analyze our current perception across platforms, identify opportunities to evolve while staying authentic, and create visual mockups of what our refreshed identity could look like with implementation steps.', '{"Marketing"}'), + + ('Release Optimization', 'Leverage data to maximize the impact and reach of your next release.', 'We''re planning our next release. Analyze how our previous releases performed, what fans said about them, and current trends in our genre. Give me recommendations for the ideal release timing, messaging approach, and platform focus to maximize impact.', '{"Assistant"}'), + + ('Sentiment Analysis', 'Leverage sentiment analysis to shape PR and marketing communications.', 'Analyze the comments on my artist''s recent posts across all platforms. What are fans feeling about our latest release? Are there recurring themes or questions? Create a visual breakdown of the sentiment trends so we can adjust our messaging accordingly.', '{"PR"}'), + + ('Merchandising Strategy', 'Maximize merch revenue through data-driven product and pricing strategies.', 'Analyze what merchandise is selling best for artists in our genre and with our audience demographics. What price points, designs, and limited-edition strategies are working? Create a data-driven merchandise plan that maximizes profit while delighting fans.', '{"Merchandise"}'), + + ('Trend Analysis', 'Spot emerging trends relevant to your brand and audience.', 'What topics are trending right now that my artist could naturally join the conversation about? Look at what''s happening on Twitter, analyze if our fans are already discussing these trends, and suggest content ideas that would feel authentic for us to post. Can you create a document with the best opportunities?', '{"Research"}'), + + ('Instagram Growth Analysis', 'Evaluate follower demographics and engagement to inform content strategy.', 'Dive deep into my artist''s Instagram audience. Who are they, what other content do they engage with, and what potential brand partnerships make sense? Could you create some visual content themes that would resonate with them based on your findings?', '{"Social"}'), + + ('Competitive Benchmarking', 'Compare performance metrics against industry peers and leaders.', 'Research our top 3 competitor artists across Spotify, Twitter, and Instagram. What strategies are working for them? Where do our audiences overlap? Create a detailed report with tactics we can adapt and opportunities to differentiate ourselves.', '{"Research"}'), + + ('Content Calendar Planning', 'Develop a coordinated release and content schedule for maximum engagement.', 'Help me develop a strategic content calendar across all our platforms. Analyze what''s worked best for us in the past, when our audience is most active, and incorporate relevant trending topics. Provide a comprehensive plan we can implement over the next month.', '{"Assistant"}'), + + ('Fan Engagement Strategy', 'Best practices for high-value fan interactions and community management.', 'Review all the comments on our recent Instagram posts and help me develop a response strategy. What questions keep coming up? Which fans should we prioritize engaging with? Create a playbook we can follow for authentic and effective fan interactions.', '{"Social"}'), + + ('Market Expansion', 'Pinpoint high-potential markets for targeted expansion and investment.', 'Identify untapped audience segments that should be fans of our music but aren''t yet. Where are these potential fans having conversations online, what artists do they currently follow, and how can we reach them? Create a visual strategy map for acquiring these new listeners.', '{"Global"}'), + + ('Viral Content Innovation', 'Generate creative concepts with high viral potential based on current trends.', 'Based on my artist''s style and what''s currently going viral in our genre, suggest 5 authentic content ideas that have high viral potential. Analyze trending formats across TikTok, Instagram, and Twitter, but make sure the ideas stay true to our artistic identity. Include a strategy for each concept.', '{"Social"}'), + + ('Brand Partnership Scouting', 'Identify and evaluate potential brand partners for strategic collaborations.', 'Find the perfect brand partnership opportunities for my artist. Analyze our audience demographics, their purchasing habits, and identify brands that share our values. Rank potential partners by fit and revenue potential, with specific collaboration ideas for each.', '{"Marketing"}'), + + ('Spotify Profile Audit', 'Assess and optimize your Spotify presence for maximum discoverability.', 'Help me build a complete and consistent artist profile starting with our Spotify information. Make sure our bio, image, and socials are synchronized across platforms, and suggest playlist strategies based on what''s working for similar artists in our genre.', '{"Marketing"}'), + + ('Merchandising Optimization', 'Refine merchandising approach to maximize sales and fan satisfaction.', 'Analyze what merchandise is selling best for artists in our genre and with our audience demographics. What price points, designs, and limited-edition strategies are working? Create a data-driven merchandise plan that maximizes profit while delighting fans.', '{"Merchandise"}'), + + ('Commerce Funnel Analysis', 'Analyze and optimize the path from content to commerce for increased conversions.', 'Create a strategy that turns our social content into direct sales. Analyze which types of posts drive the most traffic to our shop, identify friction points in our current funnel, and design a seamless path from casual follower to paying customer.', '{"Merchandise"}'), + + ('Trend Participation', 'Strategic guidance for authentic and effective trend participation.', 'What current trends or challenges could my artist authentically participate in that would increase visibility? Don''t just list trends—explain how we could put our unique spin on them, which platforms we should focus on, and how to time our participation for maximum impact.', '{"PR"}'), + + ('Podcast Guest Acquisition', 'Find the right creators to feature on your podcast, build relationships, and send outreach emails that spark collaborations.', 'Find 10 creators in unexpected niches (e.g., glitch fashion, gaming modders, TikTok historians, sports crossovers) who share audience overlap with my brand. For each creator: find their email. If no contact is found, skip them and find a different creator. Draft a personalized email inviting them to be a guest on my podcast. Reference their work, explain why the collaboration makes sense, and suggest a creative topic idea for the episode. Show me all the drafted emails and get my approval before sending any outreach emails.', '{"Podcasts", "Outreach"}'), + + ('Niche Community Infiltration', 'Find overlooked communities that align with your audience, then pitch tailored collaborations to build cultural relevance.', 'Identify 5 hyper-niche communities (e.g., glitch art Discords, solarpunk Reddit threads, biohacking forums, VR modding groups) that share values with my brand. For each: find the moderator/admin contact email. If none, skip and find another. Draft a non corporate outreach email offering to guest-speak, co-host an AMA, or co-create content for their community. Show me all the drafted emails and get my approval before sending any outreach emails.', '{"Community", "Outreach"}'), + + ('Ghibli YouTube Thumbnail Generation', 'Find a strong video with low CTR, create a Ghibli-style thumbnail, and upload it.', 'Analyze my YouTube channel to identify a video with strong content but low click-through rate that needs thumbnail improvements. Create an eye-catching, brand-aligned thumbnail (ghiblified style) with clear focal points, minimal text, and high contrast colors. Show me the thumbail emage and get approval before uploading to YouTube.', '{"YouTube", "Content", "Social", "Assistant"}'); \ No newline at end of file diff --git a/supabase/migrations/20250621183810_create_error_logs_table.sql b/supabase/migrations/20250621183810_create_error_logs_table.sql new file mode 100644 index 0000000..8c615f0 --- /dev/null +++ b/supabase/migrations/20250621183810_create_error_logs_table.sql @@ -0,0 +1,79 @@ +-- Create error_logs table for logging application errors +create table "public"."error_logs" ( + "id" uuid not null default gen_random_uuid(), + "account_id" uuid, + "room_id" uuid, + "error_message" text, + "error_timestamp" timestamp with time zone, + "error_type" text, + "last_message" text, + "raw_message" text not null, + "stack_trace" text, + "telegram_message_id" bigint, + "tool_name" text, + "created_at" timestamp with time zone not null default now() +); + +alter table "public"."error_logs" enable row level security; + +CREATE UNIQUE INDEX error_logs_pkey ON public.error_logs USING btree (id); + +alter table "public"."error_logs" add constraint "error_logs_pkey" PRIMARY KEY using index "error_logs_pkey"; + +-- Add foreign key constraints +alter table "public"."error_logs" add constraint "error_logs_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE not valid; + +alter table "public"."error_logs" validate constraint "error_logs_account_id_fkey"; + +alter table "public"."error_logs" add constraint "error_logs_room_id_fkey" FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE not valid; + +alter table "public"."error_logs" validate constraint "error_logs_room_id_fkey"; + +-- Grant permissions to different roles +grant delete on table "public"."error_logs" to "anon"; + +grant insert on table "public"."error_logs" to "anon"; + +grant references on table "public"."error_logs" to "anon"; + +grant select on table "public"."error_logs" to "anon"; + +grant trigger on table "public"."error_logs" to "anon"; + +grant truncate on table "public"."error_logs" to "anon"; + +grant update on table "public"."error_logs" to "anon"; + +grant delete on table "public"."error_logs" to "authenticated"; + +grant insert on table "public"."error_logs" to "authenticated"; + +grant references on table "public"."error_logs" to "authenticated"; + +grant select on table "public"."error_logs" to "authenticated"; + +grant trigger on table "public"."error_logs" to "authenticated"; + +grant truncate on table "public"."error_logs" to "authenticated"; + +grant update on table "public"."error_logs" to "authenticated"; + +grant delete on table "public"."error_logs" to "service_role"; + +grant insert on table "public"."error_logs" to "service_role"; + +grant references on table "public"."error_logs" to "service_role"; + +grant select on table "public"."error_logs" to "service_role"; + +grant trigger on table "public"."error_logs" to "service_role"; + +grant truncate on table "public"."error_logs" to "service_role"; + +grant update on table "public"."error_logs" to "service_role"; + +-- Add updated_at trigger +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON error_logs + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); \ No newline at end of file diff --git a/supabase/migrations/20250623205635_fix_spotify_url_case_sensitivity.sql b/supabase/migrations/20250623205635_fix_spotify_url_case_sensitivity.sql new file mode 100644 index 0000000..c15c7e7 --- /dev/null +++ b/supabase/migrations/20250623205635_fix_spotify_url_case_sensitivity.sql @@ -0,0 +1,25 @@ +-- Migration: Fix Spotify URL case sensitivity in socials.profile_url +-- This migration updates the clean_socials_profile_url trigger function to preserve +-- case sensitivity for Spotify URLs while maintaining existing cleaning behavior +-- for all other social platforms. + +-- Update the cleaning function to preserve Spotify URL case sensitivity +CREATE OR REPLACE FUNCTION clean_socials_profile_url() +RETURNS TRIGGER AS $$ +BEGIN + -- Remove protocol (http://, https://) from the start + NEW.profile_url := regexp_replace(NEW.profile_url, '^https?://', ''); + -- Remove leading www. from the start + NEW.profile_url := regexp_replace(NEW.profile_url, '^www\.', ''); + -- Remove any trailing slash + NEW.profile_url := regexp_replace(NEW.profile_url, '/+$', ''); + + -- Convert to lowercase ONLY if URL does not contain 'spotify' + -- This preserves case sensitivity for Spotify artist IDs + IF NEW.profile_url NOT ILIKE '%spotify%' THEN + NEW.profile_url := lower(NEW.profile_url); + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/supabase/migrations/20250702204149_rename_youtube_tokens_account_id_to_artist_account_id.sql b/supabase/migrations/20250702204149_rename_youtube_tokens_account_id_to_artist_account_id.sql new file mode 100644 index 0000000..5d1f8f4 --- /dev/null +++ b/supabase/migrations/20250702204149_rename_youtube_tokens_account_id_to_artist_account_id.sql @@ -0,0 +1,34 @@ +-- Migration: Rename account_id to artist_account_id in youtube_tokens table +-- This migration renames the account_id column to artist_account_id while preserving +-- all foreign key constraints, indexes, and unique constraints. + +-- Step 1: Drop the existing foreign key constraint +ALTER TABLE "public"."youtube_tokens" + DROP CONSTRAINT IF EXISTS "youtube_tokens_account_id_fkey"; + +-- Step 2: Drop the existing unique constraint +ALTER TABLE "public"."youtube_tokens" + DROP CONSTRAINT IF EXISTS "youtube_tokens_account_id_key"; + +-- Step 3: Drop the existing index +DROP INDEX IF EXISTS "public"."youtube_tokens_account_id_idx"; + +-- Step 4: Rename the column +ALTER TABLE "public"."youtube_tokens" + RENAME COLUMN "account_id" TO "artist_account_id"; + +-- Step 5: Recreate the foreign key constraint with the new column name +ALTER TABLE "public"."youtube_tokens" + ADD CONSTRAINT "youtube_tokens_artist_account_id_fkey" + FOREIGN KEY ("artist_account_id") + REFERENCES "public"."accounts" ("id") + ON DELETE CASCADE; + +-- Step 6: Recreate the unique constraint with the new column name +ALTER TABLE "public"."youtube_tokens" + ADD CONSTRAINT "youtube_tokens_artist_account_id_key" + UNIQUE ("artist_account_id"); + +-- Step 7: Recreate the index with the new column name +CREATE INDEX "youtube_tokens_artist_account_id_idx" + ON "public"."youtube_tokens" ("artist_account_id"); \ No newline at end of file diff --git a/supabase/migrations/20250703000000_youtube_tokens_row_level_security.sql b/supabase/migrations/20250703000000_youtube_tokens_row_level_security.sql new file mode 100644 index 0000000..594954b --- /dev/null +++ b/supabase/migrations/20250703000000_youtube_tokens_row_level_security.sql @@ -0,0 +1,35 @@ +-- Migration: Enable Row Level Security for youtube_tokens table +-- Only allow server keys (service_role) to read/write from the table +-- Block all anon access + +-- Enable Row Level Security on the youtube_tokens table +ALTER TABLE "public"."youtube_tokens" ENABLE ROW LEVEL SECURITY; + +-- Drop any existing policies to ensure clean state +DROP POLICY IF EXISTS "youtube_tokens_service_role_policy" ON "public"."youtube_tokens"; + +-- Create policy that only allows service_role access +-- This blocks anon users and regular authenticated users +-- Only server-side operations with service_role can access this table +CREATE POLICY "youtube_tokens_service_role_policy" + ON "public"."youtube_tokens" + FOR ALL + TO service_role + USING (true) + WITH CHECK (true); + +-- Explicitly deny access to anon role +-- This is redundant but makes the intent clear +CREATE POLICY "youtube_tokens_deny_anon" + ON "public"."youtube_tokens" + FOR ALL + TO anon + USING (false); + +-- Explicitly deny access to authenticated role +-- Only service_role should have access +CREATE POLICY "youtube_tokens_deny_authenticated" + ON "public"."youtube_tokens" + FOR ALL + TO authenticated + USING (false); \ No newline at end of file diff --git a/supabase/migrations/20250710195500_social_fans_trigger.sql b/supabase/migrations/20250710195500_social_fans_trigger.sql new file mode 100644 index 0000000..b084bfe --- /dev/null +++ b/supabase/migrations/20250710195500_social_fans_trigger.sql @@ -0,0 +1,73 @@ +-- Migration: Create trigger to automatically update social_fans table when comments are added +-- This trigger will upsert records in social_fans whenever a comment is added to post_comments +-- Only updates if the new comment timestamp is newer than the existing latest_engagement + +-- Create the trigger function +CREATE OR REPLACE FUNCTION update_social_fans_on_comment() +RETURNS TRIGGER AS $$ +DECLARE + v_artist_social_id UUID; + v_existing_latest_engagement TIMESTAMPTZ; +BEGIN + -- Get the artist's social_id from the post + SELECT sp.social_id INTO v_artist_social_id + FROM social_posts sp + WHERE sp.post_id = NEW.post_id; + + -- If no artist found for this post, skip processing + IF v_artist_social_id IS NULL THEN + RETURN NEW; + END IF; + + -- Don't process if fan and artist are the same (self-comment) + IF NEW.social_id = v_artist_social_id THEN + RETURN NEW; + END IF; + + -- Check if there's an existing social_fans record + SELECT latest_engagement INTO v_existing_latest_engagement + FROM social_fans + WHERE social_fans.artist_social_id = v_artist_social_id + AND social_fans.fan_social_id = NEW.social_id; + + -- If no existing record OR new comment is newer, upsert the record + IF v_existing_latest_engagement IS NULL OR NEW.commented_at > v_existing_latest_engagement THEN + INSERT INTO social_fans ( + artist_social_id, + fan_social_id, + latest_engagement_id, + latest_engagement, + created_at, + updated_at + ) + VALUES ( + v_artist_social_id, + NEW.social_id, + NEW.id, + NEW.commented_at, + NOW(), + NOW() + ) + ON CONFLICT (artist_social_id, fan_social_id) + DO UPDATE SET + latest_engagement_id = NEW.id, + latest_engagement = NEW.commented_at, + updated_at = NOW() + WHERE social_fans.latest_engagement < NEW.commented_at OR social_fans.latest_engagement IS NULL; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create the trigger on post_comments table +DROP TRIGGER IF EXISTS trigger_update_social_fans_on_comment ON post_comments; +CREATE TRIGGER trigger_update_social_fans_on_comment + AFTER INSERT OR UPDATE ON post_comments + FOR EACH ROW + EXECUTE FUNCTION update_social_fans_on_comment(); + +-- Grant necessary permissions for the trigger function +GRANT EXECUTE ON FUNCTION update_social_fans_on_comment() TO anon; +GRANT EXECUTE ON FUNCTION update_social_fans_on_comment() TO authenticated; +GRANT EXECUTE ON FUNCTION update_social_fans_on_comment() TO service_role; \ No newline at end of file diff --git a/supabase/migrations/20250711204751_social_posts_dedupe_unique.sql b/supabase/migrations/20250711204751_social_posts_dedupe_unique.sql new file mode 100644 index 0000000..acd7401 --- /dev/null +++ b/supabase/migrations/20250711204751_social_posts_dedupe_unique.sql @@ -0,0 +1,21 @@ +-- Migration: Deduplicate and enforce uniqueness on social_posts (post_id, social_id) +-- Deletes all but one row for each (post_id, social_id) pair, then adds a unique constraint. + +BEGIN; + +-- 1. Remove duplicates, keeping the row with the lowest id for each (post_id, social_id) +WITH ranked AS ( + SELECT id, post_id, social_id, + ROW_NUMBER() OVER (PARTITION BY post_id, social_id ORDER BY updated_at ASC NULLS LAST, id ASC) AS rn + FROM social_posts +) +DELETE FROM social_posts +WHERE id IN ( + SELECT id FROM ranked WHERE rn > 1 +); + +-- 2. Add a unique constraint to prevent future duplicates +CREATE UNIQUE INDEX IF NOT EXISTS social_posts_post_id_social_id_unique + ON public.social_posts (post_id, social_id); + +COMMIT; \ No newline at end of file diff --git a/supabase/migrations/20250827104500_add_creator_and_is_private_to_agent_templates.sql b/supabase/migrations/20250827104500_add_creator_and_is_private_to_agent_templates.sql new file mode 100644 index 0000000..9e2c8fe --- /dev/null +++ b/supabase/migrations/20250827104500_add_creator_and_is_private_to_agent_templates.sql @@ -0,0 +1,8 @@ +-- Modify agent_templates to support user-created templates +-- Adds creator (FK -> accounts.id) and is_private (boolean, default false) + +ALTER TABLE agent_templates + ADD COLUMN IF NOT EXISTS creator uuid REFERENCES accounts(id) ON DELETE SET NULL, + ADD COLUMN IF NOT EXISTS is_private boolean NOT NULL DEFAULT false; + + diff --git a/supabase/migrations/20250910120000_create_files_table.sql b/supabase/migrations/20250910120000_create_files_table.sql new file mode 100644 index 0000000..96db2b3 --- /dev/null +++ b/supabase/migrations/20250910120000_create_files_table.sql @@ -0,0 +1,63 @@ +create table if not exists "public"."files" ( + "id" uuid not null default gen_random_uuid(), + "owner_account_id" uuid not null, + "artist_account_id" uuid not null, + "storage_key" text not null, + "file_name" text not null, + "mime_type" text, + "size_bytes" bigint, + "description" text, + "tags" text[] default '{}'::text[], + "shared_with_artist_account_ids" uuid[] default '{}'::uuid[], + "shared_with_account_ids" uuid[] default '{}'::uuid[], + "shared_with_all_artist_members" boolean not null default false, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."files" enable row level security; + +-- Indexes and constraints +CREATE UNIQUE INDEX files_pkey ON public.files USING btree (id); +alter table "public"."files" add constraint "files_pkey" PRIMARY KEY using index "files_pkey"; + +CREATE UNIQUE INDEX files_storage_key_key ON public.files USING btree (storage_key); +alter table "public"."files" add constraint "files_storage_key_key" UNIQUE using index "files_storage_key_key"; + +CREATE INDEX idx_files_owner_account_id ON public.files USING btree (owner_account_id); +CREATE INDEX idx_files_artist_account_id ON public.files USING btree (artist_account_id); +CREATE INDEX idx_files_created_at ON public.files USING btree (created_at); +CREATE INDEX idx_files_shared_with_account_ids ON public.files USING GIN (shared_with_account_ids); +CREATE INDEX idx_files_shared_with_artist_account_ids ON public.files USING GIN (shared_with_artist_account_ids); + +-- Foreign keys +alter table "public"."files" add constraint "files_owner_account_id_fkey" FOREIGN KEY (owner_account_id) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."files" validate constraint "files_owner_account_id_fkey"; + +alter table "public"."files" add constraint "files_artist_account_id_fkey" FOREIGN KEY (artist_account_id) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."files" validate constraint "files_artist_account_id_fkey"; + +-- Grants (consistent with existing migrations) +grant delete on table "public"."files" to "anon"; +grant insert on table "public"."files" to "anon"; +grant references on table "public"."files" to "anon"; +grant select on table "public"."files" to "anon"; +grant trigger on table "public"."files" to "anon"; +grant truncate on table "public"."files" to "anon"; +grant update on table "public"."files" to "anon"; + +grant delete on table "public"."files" to "authenticated"; +grant insert on table "public"."files" to "authenticated"; +grant references on table "public"."files" to "authenticated"; +grant select on table "public"."files" to "authenticated"; +grant trigger on table "public"."files" to "authenticated"; +grant truncate on table "public"."files" to "authenticated"; +grant update on table "public"."files" to "authenticated"; + +grant delete on table "public"."files" to "service_role"; +grant insert on table "public"."files" to "service_role"; +grant references on table "public"."files" to "service_role"; +grant select on table "public"."files" to "service_role"; +grant trigger on table "public"."files" to "service_role"; +grant truncate on table "public"."files" to "service_role"; +grant update on table "public"."files" to "service_role"; diff --git a/supabase/migrations/20250910120500_add_is_directory_to_files.sql b/supabase/migrations/20250910120500_add_is_directory_to_files.sql new file mode 100644 index 0000000..cd6c27c --- /dev/null +++ b/supabase/migrations/20250910120500_add_is_directory_to_files.sql @@ -0,0 +1,10 @@ +-- Add an explicit directory flag to files + +alter table "public"."files" + add column if not exists "is_directory" boolean not null default false; + +-- Optional: lightweight helper index for common filters (kept minimal) +create index if not exists idx_files_is_directory + on public.files using btree (is_directory); + + diff --git a/supabase/migrations/20250912170154_enforce_unique_email_in_account_emails.sql b/supabase/migrations/20250912170154_enforce_unique_email_in_account_emails.sql new file mode 100644 index 0000000..fed507e --- /dev/null +++ b/supabase/migrations/20250912170154_enforce_unique_email_in_account_emails.sql @@ -0,0 +1,31 @@ +-- Add unique constraint to email field in account_emails table +-- This migration enforces that each email can only be associated with one account + +-- First, remove any duplicate email entries if they exist +-- Keep the oldest entry for each duplicate email +WITH duplicates AS ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY email + ORDER BY updated_at ASC + ) as rn + FROM account_emails + WHERE email IS NOT NULL +) +DELETE FROM account_emails +WHERE id IN ( + SELECT id + FROM duplicates + WHERE rn > 1 +); + +-- Add unique constraint on email field +-- This will prevent duplicate emails from being inserted in the future +ALTER TABLE "public"."account_emails" +ADD CONSTRAINT "account_emails_email_unique" +UNIQUE ("email"); + +-- Add index for better performance on email lookups +CREATE INDEX IF NOT EXISTS "account_emails_email_idx" +ON "public"."account_emails" USING btree ("email"); diff --git a/supabase/migrations/20250915202437_add_memories_room_id_index.sql b/supabase/migrations/20250915202437_add_memories_room_id_index.sql new file mode 100644 index 0000000..b203657 --- /dev/null +++ b/supabase/migrations/20250915202437_add_memories_room_id_index.sql @@ -0,0 +1,20 @@ +-- Migration: Add optimized index for memories.room_id JOIN performance +-- Description: This migration adds a btree index on memories.room_id to dramatically +-- improve the performance of the rooms + memories JOIN query used in recent chats. +-- +-- Problem: Sequential scans of 17,562+ memory records for just 10 rooms +-- Solution: Direct index lookups reducing query time from 6ms+ to sub-millisecond +-- Expected Impact: 10-15 second load time reduced to <1 second + +-- Add optimized index for memories.room_id JOIN performance +CREATE INDEX IF NOT EXISTS idx_memories_room_id +ON public.memories USING btree (room_id); + +-- Optional: Composite index for cases where you need to order memories by updated_at within rooms +-- Uncomment if you need to optimize queries that filter by room_id AND order by updated_at +-- CREATE INDEX IF NOT EXISTS idx_memories_room_updated +-- ON public.memories USING btree (room_id, updated_at DESC); + +-- Add index on rooms.account_id for better filtering performance (if not already exists) +CREATE INDEX IF NOT EXISTS idx_rooms_account_id +ON public.rooms USING btree (account_id); diff --git a/supabase/migrations/20250916130000_remove_file_share_arrays.sql b/supabase/migrations/20250916130000_remove_file_share_arrays.sql new file mode 100644 index 0000000..3285918 --- /dev/null +++ b/supabase/migrations/20250916130000_remove_file_share_arrays.sql @@ -0,0 +1,13 @@ +-- Remove unused sharing arrays from public.files and related indexes + +-- Drop dependent indexes if they exist +drop index if exists public.idx_files_shared_with_account_ids; +drop index if exists public.idx_files_shared_with_artist_account_ids; + +-- Drop legacy sharing columns +alter table "public"."files" + drop column if exists "shared_with_artist_account_ids", + drop column if exists "shared_with_account_ids", + drop column if exists "shared_with_all_artist_members"; + + diff --git a/supabase/migrations/20250916130500_create_file_access_table.sql b/supabase/migrations/20250916130500_create_file_access_table.sql new file mode 100644 index 0000000..06a34fd --- /dev/null +++ b/supabase/migrations/20250916130500_create_file_access_table.sql @@ -0,0 +1,87 @@ +-- Create table to store per-file access grants + +-- Simple scope enum for access level +do $$ begin + if not exists (select 1 from pg_type where typname = 'file_access_scope') then + create type file_access_scope as enum ('read_only','admin'); + end if; +end $$; + +create table if not exists "public"."file_access" ( + "id" uuid not null default gen_random_uuid(), + "file_id" uuid not null, + "artist_account_id" uuid, + "user_id" uuid, + "organization_id" uuid, + "granted_by" uuid, + "scope" file_access_scope not null default 'read_only', + "expires_at" timestamp with time zone, + "revoked_at" timestamp with time zone, + "created_at" timestamp with time zone not null default now() +); + +alter table "public"."file_access" enable row level security; + +-- Primary key +CREATE UNIQUE INDEX file_access_pkey ON public.file_access USING btree (id); +alter table "public"."file_access" add constraint "file_access_pkey" PRIMARY KEY using index "file_access_pkey"; + +-- Foreign keys +alter table "public"."file_access" add constraint "file_access_file_id_fkey" FOREIGN KEY (file_id) REFERENCES "public"."files"(id) ON DELETE CASCADE not valid; +alter table "public"."file_access" validate constraint "file_access_file_id_fkey"; + +alter table "public"."file_access" add constraint "file_access_artist_account_id_fkey" FOREIGN KEY (artist_account_id) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."file_access" validate constraint "file_access_artist_account_id_fkey"; + +alter table "public"."file_access" add constraint "file_access_user_id_fkey" FOREIGN KEY (user_id) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."file_access" validate constraint "file_access_user_id_fkey"; + +alter table "public"."file_access" add constraint "file_access_granted_by_fkey" FOREIGN KEY (granted_by) REFERENCES "public"."accounts"(id) ON DELETE SET NULL not valid; +alter table "public"."file_access" validate constraint "file_access_granted_by_fkey"; + +-- Ensure at least one grantee is provided +alter table "public"."file_access" + add constraint "file_access_grantee_present" + check ( + artist_account_id is not null + or user_id is not null + or organization_id is not null + ); + +-- Helpful indexes +CREATE INDEX idx_file_access_file_id ON public.file_access USING btree (file_id); +CREATE INDEX idx_file_access_artist_account_id ON public.file_access USING btree (artist_account_id); +CREATE INDEX idx_file_access_user_id ON public.file_access USING btree (user_id); +CREATE INDEX idx_file_access_organization_id ON public.file_access USING btree (organization_id); + +-- Prevent duplicate grants per grantee type +CREATE UNIQUE INDEX uniq_file_access_file_artist ON public.file_access (file_id, artist_account_id) WHERE artist_account_id IS NOT NULL; +CREATE UNIQUE INDEX uniq_file_access_file_user ON public.file_access (file_id, user_id) WHERE user_id IS NOT NULL; +CREATE UNIQUE INDEX uniq_file_access_file_org ON public.file_access (file_id, organization_id) WHERE organization_id IS NOT NULL; + +-- Grants (align with project conventions) +grant delete on table "public"."file_access" to "anon"; +grant insert on table "public"."file_access" to "anon"; +grant references on table "public"."file_access" to "anon"; +grant select on table "public"."file_access" to "anon"; +grant trigger on table "public"."file_access" to "anon"; +grant truncate on table "public"."file_access" to "anon"; +grant update on table "public"."file_access" to "anon"; + +grant delete on table "public"."file_access" to "authenticated"; +grant insert on table "public"."file_access" to "authenticated"; +grant references on table "public"."file_access" to "authenticated"; +grant select on table "public"."file_access" to "authenticated"; +grant trigger on table "public"."file_access" to "authenticated"; +grant truncate on table "public"."file_access" to "authenticated"; +grant update on table "public"."file_access" to "authenticated"; + +grant delete on table "public"."file_access" to "service_role"; +grant insert on table "public"."file_access" to "service_role"; +grant references on table "public"."file_access" to "service_role"; +grant select on table "public"."file_access" to "service_role"; +grant trigger on table "public"."file_access" to "service_role"; +grant truncate on table "public"."file_access" to "service_role"; +grant update on table "public"."file_access" to "service_role"; + + diff --git a/supabase/migrations/20250917130000_drop_file_access_table.sql b/supabase/migrations/20250917130000_drop_file_access_table.sql new file mode 100644 index 0000000..7a3f44d --- /dev/null +++ b/supabase/migrations/20250917130000_drop_file_access_table.sql @@ -0,0 +1,13 @@ +-- Drop file_access table and related artifacts (indexes, RLS policies, grants) + +-- Drop table (CASCADE removes indexes, constraints, policies on the table) +drop table if exists "public"."file_access" cascade; + +-- Drop enum type used by file_access if it exists and has no remaining dependencies +do $$ begin + if exists (select 1 from pg_type where typname = 'file_access_scope') then + drop type file_access_scope; + end if; +end $$; + + diff --git a/supabase/migrations/20250917173709_add_created_at_to_agent_templates.sql b/supabase/migrations/20250917173709_add_created_at_to_agent_templates.sql new file mode 100644 index 0000000..8a351a4 --- /dev/null +++ b/supabase/migrations/20250917173709_add_created_at_to_agent_templates.sql @@ -0,0 +1,5 @@ +-- Add created_at column to agent_templates +ALTER TABLE agent_templates + ADD COLUMN IF NOT EXISTS created_at timestamp with time zone NOT NULL DEFAULT now(); + + diff --git a/supabase/migrations/20250917175346_add_agent_template_favorites.sql b/supabase/migrations/20250917175346_add_agent_template_favorites.sql new file mode 100644 index 0000000..21548a8 --- /dev/null +++ b/supabase/migrations/20250917175346_add_agent_template_favorites.sql @@ -0,0 +1,66 @@ +-- Create favorites junction table for agent templates +CREATE TABLE IF NOT EXISTS agent_template_favorites ( + template_id uuid NOT NULL REFERENCES agent_templates(id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + created_at timestamp with time zone DEFAULT now(), + PRIMARY KEY (template_id, user_id) +); + +-- Index for fast lookups by user +CREATE INDEX IF NOT EXISTS idx_agent_template_favorites_user ON agent_template_favorites(user_id); + +-- Add denormalized favorites_count to agent_templates +ALTER TABLE agent_templates + ADD COLUMN IF NOT EXISTS favorites_count integer NOT NULL DEFAULT 0; + +-- Trigger function to keep favorites_count accurate +CREATE OR REPLACE FUNCTION update_agent_template_favorites_count() +RETURNS TRIGGER AS $$ +DECLARE + affected_template_id uuid; +BEGIN + IF TG_OP = 'INSERT' THEN + affected_template_id := NEW.template_id; + ELSIF TG_OP = 'DELETE' THEN + affected_template_id := OLD.template_id; + ELSIF TG_OP = 'UPDATE' THEN + IF NEW.template_id IS DISTINCT FROM OLD.template_id THEN + UPDATE agent_templates + SET favorites_count = ( + SELECT COUNT(*) FROM agent_template_favorites WHERE template_id = OLD.template_id + ) + WHERE id = OLD.template_id; + END IF; + affected_template_id := NEW.template_id; + END IF; + + UPDATE agent_templates + SET favorites_count = ( + SELECT COUNT(*) FROM agent_template_favorites WHERE template_id = affected_template_id + ) + WHERE id = affected_template_id; + + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +-- Trigger to invoke the function after changes +DROP TRIGGER IF EXISTS trg_update_favorites_count ON agent_template_favorites; +CREATE TRIGGER trg_update_favorites_count + AFTER INSERT OR UPDATE OR DELETE ON agent_template_favorites + FOR EACH ROW + EXECUTE FUNCTION update_agent_template_favorites_count(); + +-- Backfill counts for existing data +UPDATE agent_templates SET favorites_count = 0; + +UPDATE agent_templates a +SET favorites_count = c.cnt +FROM ( + SELECT template_id, COUNT(*)::int AS cnt + FROM agent_template_favorites + GROUP BY template_id +) c +WHERE a.id = c.template_id; + + diff --git a/supabase/migrations/20250918000001_agent_templates_complete_restructure.sql b/supabase/migrations/20250918000001_agent_templates_complete_restructure.sql new file mode 100644 index 0000000..1b78958 --- /dev/null +++ b/supabase/migrations/20250918000001_agent_templates_complete_restructure.sql @@ -0,0 +1,254 @@ +-- Complete agent templates restructure with new names and descriptions +-- Implements 5-category system: Research, Plan, Create, Connect, Report + +-- Create table if it doesn't exist (for preview environments) +CREATE TABLE IF NOT EXISTS agent_templates ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + title text NOT NULL, + description text NOT NULL, + prompt text NOT NULL, + tags text[] NOT NULL DEFAULT '{}', + updated_at timestamp with time zone DEFAULT now() +); + +-- Add updated_at trigger if it doesn't exist +CREATE OR REPLACE FUNCTION trigger_set_updated_at() +RETURNS trigger +LANGUAGE plpgsql +SET search_path TO '' +AS $function$ +begin + new.updated_at = now(); + return NEW; +end +$function$; + +DROP TRIGGER IF EXISTS set_updated_at_agent_templates ON agent_templates; +CREATE TRIGGER set_updated_at_agent_templates + BEFORE UPDATE ON agent_templates + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Basic index for performance +CREATE INDEX IF NOT EXISTS idx_agent_templates_title ON agent_templates(title); + +-- Enforce unique titles to avoid duplicate inserts on re-runs +CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_templates_title ON agent_templates(title); + +-- Insert original data if table is empty +INSERT INTO agent_templates (title, description, prompt, tags) +SELECT title::text, description::text, prompt::text, tags::text[] FROM (VALUES + ('Audience Segmentation', 'Identify and analyze key audience segments for targeted marketing and growth.', '', '{"Research"}'), + ('Superfan Insights', 'Surface your most engaged listeners for premium engagement and monetization opportunities.', 'Analyze my artist''s fan segments and tell me who our most valuable listeners are. What demographics do they represent, how do they engage with us, and how should we target them in our next campaign? Please provide a downloadable report with actionable recommendations.', '{"Social"}'), + ('Social Performance Audit', 'Comprehensive analysis of your social channels to inform digital strategy.', 'Give me a complete health check of my artist''s social media presence. Which posts are performing best, what are fans saying in the comments, and how does engagement vary across platforms? Could you create a diagram showing our social ecosystem with specific areas to improve?', '{"Social"}'), + ('Collaboration Scouting', 'Identify high-potential artist collaborations to expand your roster and reach new audiences.', 'Find compatible artists we could collaborate with for mutual audience growth. Identify where our fanbases overlap and differ, then suggest creative cross-promotion campaigns that would benefit both parties and feel authentic to our respective brands.', '{"A&R"}'), + ('Tour Strategy', 'Optimize tour routing, venues, and VIP offerings to maximize revenue and fan engagement.', 'We''re planning a tour for next quarter. Based on our streaming data, social engagement, and fan locations, where should we perform to maximize attendance and revenue? What VIP experiences could we offer, and how should we price tickets across different markets?', '{"Tour"}'), + ('Brand Positioning', 'Strategic recommendations to refine and elevate your artist''s brand in the market.', 'My artist is ready for a brand refresh. Analyze our current perception across platforms, identify opportunities to evolve while staying authentic, and create visual mockups of what our refreshed identity could look like with implementation steps.', '{"Marketing"}'), + ('Release Optimization', 'Leverage data to maximize the impact and reach of your next release.', 'We''re planning our next release. Analyze how our previous releases performed, what fans said about them, and current trends in our genre. Give me recommendations for the ideal release timing, messaging approach, and platform focus to maximize impact.', '{"Assistant"}'), + ('Sentiment Analysis', 'Leverage sentiment analysis to shape PR and marketing communications.', 'Analyze the comments on my artist''s recent posts across all platforms. What are fans feeling about our latest release? Are there recurring themes or questions? Create a visual breakdown of the sentiment trends so we can adjust our messaging accordingly.', '{"PR"}'), + ('Merchandising Strategy', 'Maximize merch revenue through data-driven product and pricing strategies.', 'Analyze what merchandise is selling best for artists in our genre and with our audience demographics. What price points, designs, and limited-edition strategies are working? Create a data-driven merchandise plan that maximizes profit while delighting fans.', '{"Merchandise"}'), + ('Trend Analysis', 'Spot emerging trends relevant to your brand and audience.', 'What topics are trending right now that my artist could naturally join the conversation about? Look at what''s happening on Twitter, analyze if our fans are already discussing these trends, and suggest content ideas that would feel authentic for us to post. Can you create a document with the best opportunities?', '{"Research"}'), + ('Instagram Growth Analysis', 'Evaluate follower demographics and engagement to inform content strategy.', 'Dive deep into my artist''s Instagram audience. Who are they, what other content do they engage with, and what potential brand partnerships make sense? Could you create some visual content themes that would resonate with them based on your findings?', '{"Social"}'), + ('Competitive Benchmarking', 'Compare performance metrics against industry peers and leaders.', 'Research our top 3 competitor artists across Spotify, Twitter, and Instagram. What strategies are working for them? Where do our audiences overlap? Create a detailed report with tactics we can adapt and opportunities to differentiate ourselves.', '{"Research"}'), + ('Content Calendar Planning', 'Develop a coordinated release and content schedule for maximum engagement.', 'Help me develop a strategic content calendar across all our platforms. Analyze what''s worked best for us in the past, when our audience is most active, and incorporate relevant trending topics. Provide a comprehensive plan we can implement over the next month.', '{"Assistant"}'), + ('Fan Engagement Strategy', 'Best practices for high-value fan interactions and community management.', 'Review all the comments on our recent Instagram posts and help me develop a response strategy. What questions keep coming up? Which fans should we prioritize engaging with? Create a playbook we can follow for authentic and effective fan interactions.', '{"Social"}'), + ('Market Expansion', 'Pinpoint high-potential markets for targeted expansion and investment.', 'Identify untapped audience segments that should be fans of our music but aren''t yet. Where are these potential fans having conversations online, what artists do they currently follow, and how can we reach them? Create a visual strategy map for acquiring these new listeners.', '{"Global"}'), + ('Viral Content Innovation', 'Generate creative concepts with high viral potential based on current trends.', 'Based on my artist''s style and what''s currently going viral in our genre, suggest 5 authentic content ideas that have high viral potential. Analyze trending formats across TikTok, Instagram, and Twitter, but make sure the ideas stay true to our artistic identity. Include a strategy for each concept.', '{"Social"}'), + ('Brand Partnership Scouting', 'Identify and evaluate potential brand partners for strategic collaborations.', 'Find the perfect brand partnership opportunities for my artist. Analyze our audience demographics, their purchasing habits, and identify brands that share our values. Rank potential partners by fit and revenue potential, with specific collaboration ideas for each.', '{"Marketing"}'), + ('Spotify Profile Audit', 'Assess and optimize your Spotify presence for maximum discoverability.', 'Help me build a complete and consistent artist profile starting with our Spotify information. Make sure our bio, image, and socials are synchronized across platforms, and suggest playlist strategies based on what''s working for similar artists in our genre.', '{"Marketing"}'), + ('Merchandising Optimization', 'Refine merchandising approach to maximize sales and fan satisfaction.', 'Analyze what merchandise is selling best for artists in our genre and with our audience demographics. What price points, designs, and limited-edition strategies are working? Create a data-driven merchandise plan that maximizes profit while delighting fans.', '{"Merchandise"}'), + ('Commerce Funnel Analysis', 'Analyze and optimize the path from content to commerce for increased conversions.', 'Create a strategy that turns our social content into direct sales. Analyze which types of posts drive the most traffic to our shop, identify friction points in our current funnel, and design a seamless path from casual follower to paying customer.', '{"Merchandise"}'), + ('Trend Participation', 'Strategic guidance for authentic and effective trend participation.', 'What current trends or challenges could my artist authentically participate in that would increase visibility? Don''t just list trends—explain how we could put our unique spin on them, which platforms we should focus on, and how to time our participation for maximum impact.', '{"PR"}'), + ('Podcast Guest Acquisition', 'Find the right creators to feature on your podcast, build relationships, and send outreach emails that spark collaborations.', 'Find 10 creators in unexpected niches (e.g., glitch fashion, gaming modders, TikTok historians, sports crossovers) who share audience overlap with my brand. For each creator: find their email. If no contact is found, skip them and find a different creator. Draft a personalized email inviting them to be a guest on my podcast. Reference their work, explain why the collaboration makes sense, and suggest a creative topic idea for the episode. Show me all the drafted emails and get my approval before sending any outreach emails.', '{"Podcasts", "Outreach"}'), + ('Niche Community Infiltration', 'Find overlooked communities that align with your audience, then pitch tailored collaborations to build cultural relevance.', 'Identify 5 hyper-niche communities (e.g., glitch art Discords, solarpunk Reddit threads, biohacking forums, VR modding groups) that share values with my brand. For each: find the moderator/admin contact email. If none, skip and find another. Draft a non corporate outreach email offering to guest-speak, co-host an AMA, or co-create content for their community. Show me all the drafted emails and get my approval before sending any outreach emails.', '{"Community", "Outreach"}'), + ('Ghibli YouTube Thumbnail Generation', 'Find a strong video with low CTR, create a Ghibli-style thumbnail, and upload it.', 'Analyze my YouTube channel to identify a video with strong content but low click-through rate that needs thumbnail improvements. Create an eye-catching, brand-aligned thumbnail (ghiblified style) with clear focal points, minimal text, and high contrast colors. Show me the thumbnail image and get approval before uploading to YouTube.', '{"YouTube", "Content", "Social", "Assistant"}') +) AS new_data(title, description, prompt, tags) +WHERE NOT EXISTS (SELECT 1 FROM agent_templates LIMIT 1); + +-- Now proceed with updates and removals +-- First, remove duplicate agents +DELETE FROM agent_templates WHERE title = 'Merchandising Optimization'; + +-- Update existing agents with new names, descriptions, and tags + +-- RESEARCH CATEGORY (6 agents) +UPDATE agent_templates SET + title = 'Find Your Most Valuable Fans', + description = 'Identifies your highest-spending fans with demographics and campaign targeting for maximum monetization.', + tags = '{"Research"}' +WHERE title = 'Superfan Insights'; + +UPDATE agent_templates SET + title = 'Cross-Platform Social Audit', + description = 'Complete health check of all your social media platforms with performance analysis and visual improvement diagram.', + tags = '{"Research"}' +WHERE title = 'Social Performance Audit'; + +UPDATE agent_templates SET + title = 'Instagram Brand Partnership Finder', + description = 'Analyzes your Instagram audience to find brand partnership opportunities and creates content themes.', + tags = '{"Research"}' +WHERE title = 'Instagram Growth Analysis'; + +UPDATE agent_templates SET + title = 'Instagram Comment Response Guide', + description = 'Reviews your Instagram comments and tells you which ones to respond to with engagement priorities.', + tags = '{"Research"}' +WHERE title = 'Sentiment Analysis'; + +UPDATE agent_templates SET + title = 'Join Trending Conversations', + description = 'Finds trending topics your fans are discussing and gives you authentic content ideas to join the conversation.', + tags = '{"Research"}' +WHERE title = 'Trend Analysis'; + +UPDATE agent_templates SET + title = 'Copy Your Top 3 Competitors', + description = 'Researches your 3 biggest competitors and gives you specific tactics to copy for better performance.', + tags = '{"Research"}' +WHERE title = 'Competitive Benchmarking'; + +-- PLAN CATEGORY (8 agents) +UPDATE agent_templates SET + title = 'Tour Planning Strategy', + description = 'Creates complete tour plan with optimized routing, venue selection, VIP experiences, and pricing strategy.', + tags = '{"Plan"}' +WHERE title = 'Tour Strategy'; + +UPDATE agent_templates SET + title = 'Brand Redesign with Visual Mockups', + description = 'Analyzes your current brand perception and creates visual mockups of your refreshed identity with implementation steps.', + tags = '{"Plan"}' +WHERE title = 'Brand Positioning'; + +UPDATE agent_templates SET + title = 'Release Launch Strategy', + description = 'Analyzes your past releases and creates timing, messaging, and platform strategy for maximum impact.', + tags = '{"Plan"}' +WHERE title = 'Release Optimization'; + +UPDATE agent_templates SET + title = 'Content Calendar Creator', + description = 'Develops strategic monthly content calendar across all platforms with posting schedule and trending topics.', + tags = '{"Plan"}' +WHERE title = 'Content Calendar Planning'; + +UPDATE agent_templates SET + title = 'Find New Fans Visual Map', + description = 'Identifies untapped audience segments and creates visual map showing where to find and acquire new listeners.', + tags = '{"Plan"}' +WHERE title = 'Market Expansion'; + +UPDATE agent_templates SET + title = 'Merchandise Strategy Plan', + description = 'Analyzes successful merch in your genre and creates data-driven product plan with pricing and design recommendations.', + tags = '{"Plan"}' +WHERE title = 'Merchandising Strategy'; + +UPDATE agent_templates SET + title = 'Social Posts to Shop Sales Strategy', + description = 'Creates strategy to convert your social media posts into direct shop sales with friction analysis.', + tags = '{"Plan"}' +WHERE title = 'Commerce Funnel Analysis'; + +-- CREATE CATEGORY (4 agents) +UPDATE agent_templates SET + title = '5 Viral Content Ideas', + description = 'Analyzes current viral trends and gives you 5 authentic content ideas with high viral potential and platform strategies.', + tags = '{"Create"}' +WHERE title = 'Viral Content Innovation'; + +UPDATE agent_templates SET + title = 'Update YouTube Thumbnails', + description = 'Finds your low-performing videos and creates new eye-catching thumbnails to improve click-through rates.', + tags = '{"Create"}' +WHERE title = 'Ghibli YouTube Thumbnail Generation'; + +UPDATE agent_templates SET + title = 'Corporate Partnership Finder', + description = 'Analyzes your audience to find perfect brand partnership opportunities ranked by revenue potential.', + tags = '{"Create"}' +WHERE title = 'Brand Partnership Scouting'; + +UPDATE agent_templates SET + title = 'Spotify Profile Optimization', + description = 'Optimizes your Spotify profile with cross-platform synchronization and playlist strategy recommendations.', + tags = '{"Create"}' +WHERE title = 'Spotify Profile Audit'; + +-- CONNECT CATEGORY (4 agents) +UPDATE agent_templates SET + title = 'Artist Cross-Promotion Finder', + description = 'Finds compatible artists for mutual audience growth with fanbase overlap analysis and campaign ideas.', + tags = '{"Connect"}' +WHERE title = 'Collaboration Scouting'; + +UPDATE agent_templates SET + title = '10 Podcast Guest Email Templates', + description = 'Finds 10 niche creators with audience overlap and writes personalized podcast invitation emails ready to send.', + tags = '{"Connect"}' +WHERE title = 'Podcast Guest Acquisition'; + +UPDATE agent_templates SET + title = '5 Community Partnership Email Templates', + description = 'Identifies 5 hyper-niche communities aligned with your brand and writes collaboration emails ready to send.', + tags = '{"Connect"}' +WHERE title = 'Niche Community Infiltration'; + +UPDATE agent_templates SET + title = 'Add Your Spin to Trends', + description = 'Shows you how to authentically participate in current trends with unique angle strategies and optimal timing.', + tags = '{"Connect"}' +WHERE title = 'Trend Participation'; + +-- Remove the duplicate Fan Engagement Strategy (same prompt as Sentiment Analysis) +DELETE FROM agent_templates WHERE title = 'Fan Engagement Strategy'; + +-- INSERT NEW HIGH-VALUE AGENTS + +INSERT INTO agent_templates (title, description, prompt, tags) VALUES + +-- REPORT CATEGORY (New agents) +('YouTube Revenue Report', + 'Gets your actual YouTube revenue data and video performance metrics in an easy-to-read report.', + 'Show me my YouTube revenue data for the past month. Include total earnings, top-performing videos, and revenue trends. I want to see which videos are making the most money and how my channel is performing financially.', + '{"Report"}'), + +('Weekly Performance Dashboard', + 'Sets up automated weekly reports of your performance across all platforms delivered to your email.', + 'Set up a weekly performance dashboard that emails me every Monday with my stats from all platforms - YouTube revenue, Spotify streams, Instagram engagement, and Twitter activity. I want to see week-over-week changes and top-performing content.', + '{"Report"}'), + +('Weekly Release Reports', + 'Tracks a specific song release with automated weekly performance reports sent to your email.', + 'I need weekly tracking reports for my song release. What''s the exact song title you want me to track? I''ll monitor its performance on YouTube and Spotify and email you weekly updates with view counts, stream numbers, and engagement data.', + '{"Report"}'), + +-- RESEARCH CATEGORY (New agents) +('Top Performing Content Finder', + 'Finds your best-performing posts across all platforms to show you what content actually works.', + 'Analyze all my posts across Instagram, Twitter, and YouTube to find my top-performing content. Show me what types of posts get the most engagement, which platforms work best for me, and what patterns I should replicate.', + '{"Research"}'), + +('Fan Segment Revenue Analysis', + 'Analyzes your fan segments to show which groups are worth the most money for targeted campaigns.', + 'Create detailed fan segments for my artist and analyze which segments have the highest revenue potential. Show me demographics, spending patterns, and engagement levels so I can focus my marketing on the most valuable fans.', + '{"Research"}'), + +-- CREATE CATEGORY (New agents) +('Spotify Playlist Placement Finder', + 'Finds playlists your music should be on for maximum streams and discovery.', + 'Research playlists in my genre and style that would be perfect for my music. Find playlist curators, analyze submission requirements, and give me a strategy for getting my songs placed on high-impact playlists.', + '{"Create"}'), + +-- CONNECT CATEGORY (New agents) +('Weekly Potential Fan Finder', + 'Analyzes lookalike artists and finds their followers who would likely become your fans, delivered weekly via email.', + 'Set up weekly potential fan discovery for my artist. Find lookalike artists and niche creators in my space, analyze their followers and commenters, then email me every week with clickable handles of people who would likely become my fans so I can engage with them.', + '{"Connect"}'), + +('Comment Response Priority List', + 'Tells you exactly which comments across all platforms to respond to first for maximum engagement impact.', + 'Analyze all the comments on my recent posts across Instagram, YouTube, and Twitter. Tell me which comments I should prioritize responding to based on the commenter''s influence, engagement potential, and likelihood to become a valuable fan.', + '{"Connect"}') +ON CONFLICT (title) DO NOTHING; diff --git a/supabase/migrations/20250919000001_fix_agent_templates_complete.sql b/supabase/migrations/20250919000001_fix_agent_templates_complete.sql new file mode 100644 index 0000000..f9e086a --- /dev/null +++ b/supabase/migrations/20250919000001_fix_agent_templates_complete.sql @@ -0,0 +1,156 @@ +-- Add new clean agent templates with 5-category system +-- You can manually delete old/duplicate agents from the UI after deployment + +INSERT INTO agent_templates (title, description, prompt, tags, creator, is_private, created_at) VALUES + +-- RESEARCH CATEGORY (8 agents) +('Find Your Most Valuable Fans', + 'Identifies your highest-spending fans with demographics and campaign targeting for maximum monetization.', + 'Analyze my artist''s fan segments and tell me who our most valuable listeners are. What demographics do they represent, how do they engage with us, and how should we target them in our next campaign? Please provide a downloadable report with actionable recommendations.', + ARRAY['Research'], NULL, false, now()), + +('Cross-Platform Social Audit', + 'Complete health check of all your social media platforms with performance analysis and visual improvement diagram.', + 'Give me a complete health check of my artist''s social media presence. Which posts are performing best, what are fans saying in the comments, and how does engagement vary across platforms? Could you create a diagram showing our social ecosystem with specific areas to improve?', + ARRAY['Research'], NULL, false, now()), + +('Instagram Brand Partnership Finder', + 'Analyzes your Instagram audience to find brand partnership opportunities and creates content themes.', + 'Dive deep into my artist''s Instagram audience. Who are they, what other content do they engage with, and what potential brand partnerships make sense? Could you create some visual content themes that would resonate with them based on your findings?', + ARRAY['Research'], NULL, false, now()), + +('Instagram Comment Response Guide', + 'Reviews your Instagram comments and tells you which ones to respond to with engagement priorities.', + 'Analyze the comments on my artist''s recent posts across all platforms. What are fans feeling about our latest release? Are there recurring themes or questions? Create a visual breakdown of the sentiment trends so we can adjust our messaging accordingly.', + ARRAY['Research'], NULL, false, now()), + +('Join Trending Conversations', + 'Finds trending topics your fans are discussing and gives you authentic content ideas to join the conversation.', + 'What topics are trending right now that my artist could naturally join the conversation about? Look at what''s happening on Twitter, analyze if our fans are already discussing these trends, and suggest content ideas that would feel authentic for us to post. Can you create a document with the best opportunities?', + ARRAY['Research'], NULL, false, now()), + +('Copy Your Top 3 Competitors', + 'Researches your 3 biggest competitors and gives you specific tactics to copy for better performance.', + 'Research our top 3 competitor artists across Spotify, Twitter, and Instagram. What strategies are working for them? Where do our audiences overlap? Create a detailed report with tactics we can adapt and opportunities to differentiate ourselves.', + ARRAY['Research'], NULL, false, now()), + +('Top Performing Content Finder', + 'Finds your best-performing posts across all platforms to show you what content actually works.', + 'Analyze all my posts across Instagram, Twitter, and YouTube to find my top-performing content. Show me what types of posts get the most engagement, which platforms work best for me, and what patterns I should replicate.', + ARRAY['Research'], NULL, false, now()), + +('Fan Segment Revenue Analysis', + 'Analyzes your fan segments to show which groups are worth the most money for targeted campaigns.', + 'Create detailed fan segments for my artist and analyze which segments have the highest revenue potential. Show me demographics, spending patterns, and engagement levels so I can focus my marketing on the most valuable fans.', + ARRAY['Research'], NULL, false, now()), + +-- PLAN CATEGORY (7 agents) +('Tour Planning Strategy', + 'Creates complete tour plan with optimized routing, venue selection, VIP experiences, and pricing strategy.', + 'We''re planning a tour for next quarter. Based on our streaming data, social engagement, and fan locations, where should we perform to maximize attendance and revenue? What VIP experiences could we offer, and how should we price tickets across different markets?', + ARRAY['Plan'], NULL, false, now()), + +('Brand Redesign with Visual Mockups', + 'Analyzes your current brand perception and creates visual mockups of your refreshed identity with implementation steps.', + 'My artist is ready for a brand refresh. Analyze our current perception across platforms, identify opportunities to evolve while staying authentic, and create visual mockups of what our refreshed identity could look like with implementation steps.', + ARRAY['Plan'], NULL, false, now()), + +('Release Launch Strategy', + 'Analyzes your past releases and creates timing, messaging, and platform strategy for maximum impact.', + 'We''re planning our next release. Analyze how our previous releases performed, what fans said about them, and current trends in our genre. Give me recommendations for the ideal release timing, messaging approach, and platform focus to maximize impact.', + ARRAY['Plan'], NULL, false, now()), + +('Content Calendar Creator', + 'Develops strategic monthly content calendar across all platforms with posting schedule and trending topics.', + 'Help me develop a strategic content calendar across all our platforms. Analyze what''s worked best for us in the past, when our audience is most active, and incorporate relevant trending topics. Provide a comprehensive plan we can implement over the next month.', + ARRAY['Plan'], NULL, false, now()), + +('Find New Fans Visual Map', + 'Identifies untapped audience segments and creates visual map showing where to find and acquire new listeners.', + 'Identify untapped audience segments that should be fans of our music but aren''t yet. Where are these potential fans having conversations online, what artists do they currently follow, and how can we reach them? Create a visual strategy map for acquiring these new listeners.', + ARRAY['Plan'], NULL, false, now()), + +('Merchandise Strategy Plan', + 'Analyzes successful merch in your genre and creates data-driven product plan with pricing and design recommendations.', + 'Analyze what merchandise is selling best for artists in our genre and with our audience demographics. What price points, designs, and limited-edition strategies are working? Create a data-driven merchandise plan that maximizes profit while delighting fans.', + ARRAY['Plan'], NULL, false, now()), + +('Social Posts to Shop Sales Strategy', + 'Creates strategy to convert your social media posts into direct shop sales with friction analysis.', + 'Create a strategy that turns our social content into direct sales. Analyze which types of posts drive the most traffic to our shop, identify friction points in our current funnel, and design a seamless path from casual follower to paying customer.', + ARRAY['Plan'], NULL, false, now()), + +-- CREATE CATEGORY (5 agents) +('5 Viral Content Ideas', + 'Analyzes current viral trends and gives you 5 authentic content ideas with high viral potential and platform strategies.', + 'Based on my artist''s style and what''s currently going viral in our genre, suggest 5 authentic content ideas that have high viral potential. Analyze trending formats across TikTok, Instagram, and Twitter, but make sure the ideas stay true to our artistic identity. Include a strategy for each concept.', + ARRAY['Create'], NULL, false, now()), + +('Update YouTube Thumbnails', + 'Finds your low-performing videos and creates new eye-catching thumbnails to improve click-through rates.', + 'Analyze my YouTube channel to identify a video with strong content but low click-through rate that needs thumbnail improvements. Create an eye-catching, brand-aligned thumbnail (ghiblified style) with clear focal points, minimal text, and high contrast colors. Show me the thumbnail image and get approval before uploading to YouTube.', + ARRAY['Create'], NULL, false, now()), + +('Corporate Partnership Finder', + 'Analyzes your audience to find perfect brand partnership opportunities ranked by revenue potential.', + 'Find the perfect brand partnership opportunities for my artist. Analyze our audience demographics, their purchasing habits, and identify brands that share our values. Rank potential partners by fit and revenue potential, with specific collaboration ideas for each.', + ARRAY['Create'], NULL, false, now()), + +('Spotify Profile Optimization', + 'Optimizes your Spotify profile with cross-platform synchronization and playlist strategy recommendations.', + 'Help me build a complete and consistent artist profile starting with our Spotify information. Make sure our bio, image, and socials are synchronized across platforms, and suggest playlist strategies based on what''s working for similar artists in our genre.', + ARRAY['Create'], NULL, false, now()), + +('Spotify Playlist Placement Finder', + 'Finds playlists your music should be on for maximum streams and discovery.', + 'Research playlists in my genre and style that would be perfect for my music. Find playlist curators, analyze submission requirements, and give me a strategy for getting my songs placed on high-impact playlists.', + ARRAY['Create'], NULL, false, now()), + +-- CONNECT CATEGORY (6 agents) +('Artist Cross-Promotion Finder', + 'Finds compatible artists for mutual audience growth with fanbase overlap analysis and campaign ideas.', + 'Find compatible artists we could collaborate with for mutual audience growth. Identify where our fanbases overlap and differ, then suggest creative cross-promotion campaigns that would benefit both parties and feel authentic to our respective brands.', + ARRAY['Connect'], NULL, false, now()), + +('10 Podcast Guest Email Templates', + 'Finds 10 niche creators with audience overlap and writes personalized podcast invitation emails ready to send.', + 'Find 10 creators in unexpected niches (e.g., glitch fashion, gaming modders, TikTok historians, sports crossovers) who share audience overlap with my brand. For each creator: find their email. If no contact is found, skip them and find a different creator. Draft a personalized email inviting them to be a guest on my podcast. Reference their work, explain why the collaboration makes sense, and suggest a creative topic idea for the episode. Show me all the drafted emails and get my approval before sending any outreach emails.', + ARRAY['Connect'], NULL, false, now()), + +('5 Community Partnership Email Templates', + 'Identifies 5 hyper-niche communities aligned with your brand and writes collaboration emails ready to send.', + 'Identify 5 hyper-niche communities (e.g., glitch art Discords, solarpunk Reddit threads, biohacking forums, VR modding groups) that share values with my brand. For each: find the moderator/admin contact email. If none, skip and find another. Draft a non corporate outreach email offering to guest-speak, co-host an AMA, or co-create content for their community. Show me all the drafted emails and get my approval before sending any outreach emails.', + ARRAY['Connect'], NULL, false, now()), + +('Add Your Spin to Trends', + 'Shows you how to authentically participate in current trends with unique angle strategies and optimal timing.', + 'What current trends or challenges could my artist authentically participate in that would increase visibility? Don''t just list trends—explain how we could put our unique spin on them, which platforms we should focus on, and how to time our participation for maximum impact.', + ARRAY['Connect'], NULL, false, now()), + +('Weekly Potential Fan Finder', + 'Analyzes lookalike artists and finds their followers who would likely become your fans, delivered weekly via email.', + 'Set up weekly potential fan discovery for my artist. Find lookalike artists and niche creators in my space, analyze their followers and commenters, then email me every week with clickable handles of people who would likely become my fans so I can engage with them.', + ARRAY['Connect'], NULL, false, now()), + +('Comment Response Priority List', + 'Tells you exactly which comments across all platforms to respond to first for maximum engagement impact.', + 'Analyze all the comments on my recent posts across Instagram, YouTube, and Twitter. Tell me which comments I should prioritize responding to based on the commenter''s influence, engagement potential, and likelihood to become a valuable fan.', + ARRAY['Connect'], NULL, false, now()), + +-- REPORT CATEGORY (3 agents) +('YouTube Revenue Report', + 'Gets your actual YouTube revenue data and video performance metrics in an easy-to-read report.', + 'Show me my YouTube revenue data for the past month. Include total earnings, top-performing videos, and revenue trends. I want to see which videos are making the most money and how my channel is performing financially.', + ARRAY['Report'], NULL, false, now()), + +('Weekly Performance Dashboard', + 'Sets up automated weekly reports of your performance across all platforms delivered to your email.', + 'Set up a weekly performance dashboard that emails me every Monday with my stats from all platforms - YouTube revenue, Spotify streams, Instagram engagement, and Twitter activity. I want to see week-over-week changes and top-performing content.', + ARRAY['Report'], NULL, false, now()), + +('Weekly Release Reports', + 'Tracks a specific song release with automated weekly performance reports sent to your email.', + 'I need weekly tracking reports for my song release. What''s the exact song title you want me to track? I''ll monitor its performance on YouTube and Spotify and email you weekly updates with view counts, stream numbers, and engagement data.', + ARRAY['Report'], NULL, false, now()) + +ON CONFLICT (title) DO NOTHING; diff --git a/supabase/migrations/20250919141957_create_agent_template_shares.sql b/supabase/migrations/20250919141957_create_agent_template_shares.sql new file mode 100644 index 0000000..3c6be5b --- /dev/null +++ b/supabase/migrations/20250919141957_create_agent_template_shares.sql @@ -0,0 +1,20 @@ +-- Create agent template shares table +-- Allows users to share agent templates with other users + +CREATE TABLE agent_template_shares ( + template_id uuid NOT NULL REFERENCES agent_templates(id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + created_at timestamp with time zone DEFAULT now(), + PRIMARY KEY (template_id, user_id) +); + +-- Indexes for performance +CREATE INDEX idx_agent_template_shares_user_id ON agent_template_shares(user_id); +CREATE INDEX idx_agent_template_shares_template_id ON agent_template_shares(template_id); + +-- Enable row level security +ALTER TABLE agent_template_shares ENABLE ROW LEVEL SECURITY; + +-- Grant permissions +GRANT SELECT, INSERT, DELETE ON agent_template_shares TO authenticated; +GRANT SELECT, INSERT, DELETE ON agent_template_shares TO anon; diff --git a/supabase/migrations/20250924003512_add_pinned_column_to_account_artist_ids.sql b/supabase/migrations/20250924003512_add_pinned_column_to_account_artist_ids.sql new file mode 100644 index 0000000..70429d0 --- /dev/null +++ b/supabase/migrations/20250924003512_add_pinned_column_to_account_artist_ids.sql @@ -0,0 +1,9 @@ +-- Add pinned column to account_artist_ids table +ALTER TABLE "public"."account_artist_ids" +ADD COLUMN "pinned" boolean DEFAULT false NOT NULL; + +-- Add index for efficient queries on pinned artists +CREATE INDEX account_artist_ids_account_pinned_idx ON account_artist_ids(account_id, pinned); + +-- Add comment for documentation +COMMENT ON COLUMN "public"."account_artist_ids"."pinned" IS 'Indicates whether an artist is pinned by the user'; diff --git a/supabase/migrations/20251005210048_create_songs_table.sql b/supabase/migrations/20251005210048_create_songs_table.sql new file mode 100644 index 0000000..f742cdb --- /dev/null +++ b/supabase/migrations/20251005210048_create_songs_table.sql @@ -0,0 +1,39 @@ +create table "public"."songs" ( + "isrc" text not null, + "name" text not null, + "album" text not null, + "lyrics" text not null, + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."songs" enable row level security; + +-- Primary key constraint on isrc +CREATE UNIQUE INDEX songs_pkey ON public.songs USING btree (isrc); +alter table "public"."songs" add constraint "songs_pkey" PRIMARY KEY using index "songs_pkey"; + +-- Indexes for efficient queries +CREATE INDEX idx_songs_name ON public.songs USING btree (name); +CREATE INDEX idx_songs_album ON public.songs USING btree (album); + +-- Grant permissions to service_role only +grant delete on table "public"."songs" to "service_role"; +grant insert on table "public"."songs" to "service_role"; +grant references on table "public"."songs" to "service_role"; +grant select on table "public"."songs" to "service_role"; +grant trigger on table "public"."songs" to "service_role"; +grant truncate on table "public"."songs" to "service_role"; +grant update on table "public"."songs" to "service_role"; + +-- Add trigger to automatically update updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON songs + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Add comments for documentation +COMMENT ON TABLE "public"."songs" IS 'Table storing song information with ISRC as unique identifier'; +COMMENT ON COLUMN "public"."songs"."isrc" IS 'International Standard Recording Code - unique identifier for the recording'; +COMMENT ON COLUMN "public"."songs"."name" IS 'Name/title of the song'; +COMMENT ON COLUMN "public"."songs"."album" IS 'Album name where the song appears'; +COMMENT ON COLUMN "public"."songs"."lyrics" IS 'Full lyrics text of the song'; diff --git a/supabase/migrations/20251005212508_create_catalogs_table.sql b/supabase/migrations/20251005212508_create_catalogs_table.sql new file mode 100644 index 0000000..b0f83be --- /dev/null +++ b/supabase/migrations/20251005212508_create_catalogs_table.sql @@ -0,0 +1,36 @@ +create table "public"."catalogs" ( + "id" uuid not null default gen_random_uuid(), + "name" text not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."catalogs" enable row level security; + +-- Primary key constraint on id +CREATE UNIQUE INDEX catalogs_pkey ON public.catalogs USING btree (id); +alter table "public"."catalogs" add constraint "catalogs_pkey" PRIMARY KEY using index "catalogs_pkey"; + +-- Indexes for efficient queries +CREATE INDEX idx_catalogs_name ON public.catalogs USING btree (name); +CREATE INDEX idx_catalogs_created_at ON public.catalogs USING btree (created_at); + +-- Grant permissions to service_role only +grant delete on table "public"."catalogs" to "service_role"; +grant insert on table "public"."catalogs" to "service_role"; +grant references on table "public"."catalogs" to "service_role"; +grant select on table "public"."catalogs" to "service_role"; +grant trigger on table "public"."catalogs" to "service_role"; +grant truncate on table "public"."catalogs" to "service_role"; +grant update on table "public"."catalogs" to "service_role"; + +-- Add trigger to automatically update updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON catalogs + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Add comments for documentation +COMMENT ON TABLE "public"."catalogs" IS 'Table storing catalog information'; +COMMENT ON COLUMN "public"."catalogs"."id" IS 'Unique identifier for the catalog'; +COMMENT ON COLUMN "public"."catalogs"."name" IS 'Name of the catalog'; diff --git a/supabase/migrations/20251005214038_create_catalog_songs_table.sql b/supabase/migrations/20251005214038_create_catalog_songs_table.sql new file mode 100644 index 0000000..4dc43b2 --- /dev/null +++ b/supabase/migrations/20251005214038_create_catalog_songs_table.sql @@ -0,0 +1,50 @@ +create table "public"."catalog_songs" ( + "id" uuid not null default gen_random_uuid(), + "catalog" uuid not null, + "song" text not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."catalog_songs" enable row level security; + +-- Primary key constraint on id +CREATE UNIQUE INDEX catalog_songs_pkey ON public.catalog_songs USING btree (id); +alter table "public"."catalog_songs" add constraint "catalog_songs_pkey" PRIMARY KEY using index "catalog_songs_pkey"; + +-- Unique constraint to ensure one song can only be in a catalog once +CREATE UNIQUE INDEX catalog_songs_catalog_song_unique ON public.catalog_songs USING btree (catalog, song); +alter table "public"."catalog_songs" add constraint "catalog_songs_catalog_song_unique" UNIQUE using index "catalog_songs_catalog_song_unique"; + +-- Indexes for efficient queries +CREATE INDEX idx_catalog_songs_catalog ON public.catalog_songs USING btree (catalog); +CREATE INDEX idx_catalog_songs_song ON public.catalog_songs USING btree (song); +CREATE INDEX idx_catalog_songs_created_at ON public.catalog_songs USING btree (created_at); + +-- Foreign key constraints +alter table "public"."catalog_songs" add constraint "catalog_songs_catalog_fkey" FOREIGN KEY (catalog) REFERENCES "public"."catalogs"(id) ON DELETE CASCADE not valid; +alter table "public"."catalog_songs" validate constraint "catalog_songs_catalog_fkey"; + +alter table "public"."catalog_songs" add constraint "catalog_songs_song_fkey" FOREIGN KEY (song) REFERENCES "public"."songs"(isrc) ON DELETE CASCADE not valid; +alter table "public"."catalog_songs" validate constraint "catalog_songs_song_fkey"; + +-- Grant permissions to service_role only +grant delete on table "public"."catalog_songs" to "service_role"; +grant insert on table "public"."catalog_songs" to "service_role"; +grant references on table "public"."catalog_songs" to "service_role"; +grant select on table "public"."catalog_songs" to "service_role"; +grant trigger on table "public"."catalog_songs" to "service_role"; +grant truncate on table "public"."catalog_songs" to "service_role"; +grant update on table "public"."catalog_songs" to "service_role"; + +-- Add trigger to automatically update updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON catalog_songs + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Add comments for documentation +COMMENT ON TABLE "public"."catalog_songs" IS 'Join table linking catalogs to songs'; +COMMENT ON COLUMN "public"."catalog_songs"."id" IS 'Unique identifier for the catalog-song relationship'; +COMMENT ON COLUMN "public"."catalog_songs"."catalog" IS 'Foreign key reference to catalogs table'; +COMMENT ON COLUMN "public"."catalog_songs"."song" IS 'Foreign key reference to songs table (ISRC)'; diff --git a/supabase/migrations/20251005214926_create_account_catalogs_table.sql b/supabase/migrations/20251005214926_create_account_catalogs_table.sql new file mode 100644 index 0000000..22ba6b2 --- /dev/null +++ b/supabase/migrations/20251005214926_create_account_catalogs_table.sql @@ -0,0 +1,50 @@ +create table "public"."account_catalogs" ( + "id" uuid not null default gen_random_uuid(), + "account" uuid not null, + "catalog" uuid not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."account_catalogs" enable row level security; + +-- Primary key constraint on id +CREATE UNIQUE INDEX account_catalogs_pkey ON public.account_catalogs USING btree (id); +alter table "public"."account_catalogs" add constraint "account_catalogs_pkey" PRIMARY KEY using index "account_catalogs_pkey"; + +-- Unique constraint to ensure one catalog can only be associated with an account once +CREATE UNIQUE INDEX account_catalogs_account_catalog_unique ON public.account_catalogs USING btree (account, catalog); +alter table "public"."account_catalogs" add constraint "account_catalogs_account_catalog_unique" UNIQUE using index "account_catalogs_account_catalog_unique"; + +-- Indexes for efficient queries +CREATE INDEX idx_account_catalogs_account ON public.account_catalogs USING btree (account); +CREATE INDEX idx_account_catalogs_catalog ON public.account_catalogs USING btree (catalog); +CREATE INDEX idx_account_catalogs_created_at ON public.account_catalogs USING btree (created_at); + +-- Foreign key constraints +alter table "public"."account_catalogs" add constraint "account_catalogs_account_fkey" FOREIGN KEY (account) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."account_catalogs" validate constraint "account_catalogs_account_fkey"; + +alter table "public"."account_catalogs" add constraint "account_catalogs_catalog_fkey" FOREIGN KEY (catalog) REFERENCES "public"."catalogs"(id) ON DELETE CASCADE not valid; +alter table "public"."account_catalogs" validate constraint "account_catalogs_catalog_fkey"; + +-- Grant permissions to service_role only +grant delete on table "public"."account_catalogs" to "service_role"; +grant insert on table "public"."account_catalogs" to "service_role"; +grant references on table "public"."account_catalogs" to "service_role"; +grant select on table "public"."account_catalogs" to "service_role"; +grant trigger on table "public"."account_catalogs" to "service_role"; +grant truncate on table "public"."account_catalogs" to "service_role"; +grant update on table "public"."account_catalogs" to "service_role"; + +-- Add trigger to automatically update updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_catalogs + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Add comments for documentation +COMMENT ON TABLE "public"."account_catalogs" IS 'Join table linking accounts to catalogs'; +COMMENT ON COLUMN "public"."account_catalogs"."id" IS 'Unique identifier for the account-catalog relationship'; +COMMENT ON COLUMN "public"."account_catalogs"."account" IS 'Foreign key reference to accounts table'; +COMMENT ON COLUMN "public"."account_catalogs"."catalog" IS 'Foreign key reference to catalogs table'; diff --git a/supabase/migrations/20251005220838_create_song_artists_table.sql b/supabase/migrations/20251005220838_create_song_artists_table.sql new file mode 100644 index 0000000..df70524 --- /dev/null +++ b/supabase/migrations/20251005220838_create_song_artists_table.sql @@ -0,0 +1,50 @@ +create table "public"."song_artists" ( + "id" uuid not null default gen_random_uuid(), + "song" text not null, + "artist" uuid not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone not null default now() +); + +alter table "public"."song_artists" enable row level security; + +-- Primary key constraint on id +CREATE UNIQUE INDEX song_artists_pkey ON public.song_artists USING btree (id); +alter table "public"."song_artists" add constraint "song_artists_pkey" PRIMARY KEY using index "song_artists_pkey"; + +-- Unique constraint to ensure one artist can only be associated with a song once +CREATE UNIQUE INDEX song_artists_song_artist_unique ON public.song_artists USING btree (song, artist); +alter table "public"."song_artists" add constraint "song_artists_song_artist_unique" UNIQUE using index "song_artists_song_artist_unique"; + +-- Indexes for efficient queries +CREATE INDEX idx_song_artists_song ON public.song_artists USING btree (song); +CREATE INDEX idx_song_artists_artist ON public.song_artists USING btree (artist); +CREATE INDEX idx_song_artists_created_at ON public.song_artists USING btree (created_at); + +-- Foreign key constraints +alter table "public"."song_artists" add constraint "song_artists_song_fkey" FOREIGN KEY (song) REFERENCES "public"."songs"(isrc) ON DELETE CASCADE not valid; +alter table "public"."song_artists" validate constraint "song_artists_song_fkey"; + +alter table "public"."song_artists" add constraint "song_artists_artist_fkey" FOREIGN KEY (artist) REFERENCES "public"."accounts"(id) ON DELETE CASCADE not valid; +alter table "public"."song_artists" validate constraint "song_artists_artist_fkey"; + +-- Grant permissions to service_role only +grant delete on table "public"."song_artists" to "service_role"; +grant insert on table "public"."song_artists" to "service_role"; +grant references on table "public"."song_artists" to "service_role"; +grant select on table "public"."song_artists" to "service_role"; +grant trigger on table "public"."song_artists" to "service_role"; +grant truncate on table "public"."song_artists" to "service_role"; +grant update on table "public"."song_artists" to "service_role"; + +-- Add trigger to automatically update updated_at timestamp +CREATE TRIGGER set_updated_at + BEFORE UPDATE ON song_artists + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + +-- Add comments for documentation +COMMENT ON TABLE "public"."song_artists" IS 'Join table linking songs to artists'; +COMMENT ON COLUMN "public"."song_artists"."id" IS 'Unique identifier for the song-artist relationship'; +COMMENT ON COLUMN "public"."song_artists"."song" IS 'Foreign key reference to songs table (ISRC)'; +COMMENT ON COLUMN "public"."song_artists"."artist" IS 'Foreign key reference to accounts table (artist account)'; diff --git a/supabase/migrations/20251006120528_update_songs_table_nullable_columns.sql b/supabase/migrations/20251006120528_update_songs_table_nullable_columns.sql new file mode 100644 index 0000000..42df223 --- /dev/null +++ b/supabase/migrations/20251006120528_update_songs_table_nullable_columns.sql @@ -0,0 +1,14 @@ +-- Update songs table to make all columns nullable except ISRC +ALTER TABLE "public"."songs" +ALTER COLUMN "name" DROP NOT NULL; + +ALTER TABLE "public"."songs" +ALTER COLUMN "album" DROP NOT NULL; + +ALTER TABLE "public"."songs" +ALTER COLUMN "lyrics" DROP NOT NULL; + +-- Add comment for documentation +COMMENT ON COLUMN "public"."songs"."name" IS 'Name/title of the song (nullable)'; +COMMENT ON COLUMN "public"."songs"."album" IS 'Album name where the song appears (nullable)'; +COMMENT ON COLUMN "public"."songs"."lyrics" IS 'Full lyrics text of the song (nullable)'; diff --git a/supabase/migrations/20251006194226_add_timestamp_trigger_to_accounts.sql b/supabase/migrations/20251006194226_add_timestamp_trigger_to_accounts.sql new file mode 100644 index 0000000..464c4e8 --- /dev/null +++ b/supabase/migrations/20251006194226_add_timestamp_trigger_to_accounts.sql @@ -0,0 +1,22 @@ +-- Create trigger function to auto-update timestamp field +CREATE OR REPLACE FUNCTION public.trigger_set_timestamp() + RETURNS trigger + LANGUAGE plpgsql + SET search_path TO '' +AS $function$ +begin + new.timestamp = extract(epoch from now()); + return NEW; +end +$function$ +; + +-- Add trigger to automatically update timestamp field on accounts table +CREATE TRIGGER set_timestamp + BEFORE UPDATE ON accounts + FOR EACH ROW + EXECUTE FUNCTION trigger_set_timestamp(); + +-- Add comment for documentation +COMMENT ON FUNCTION public.trigger_set_timestamp() IS 'Trigger function to auto-update timestamp field with current Unix timestamp'; +COMMENT ON TRIGGER set_timestamp ON public.accounts IS 'Auto-updates timestamp field to current moment on row update'; diff --git a/supabase/migrations/20251006194801_add_timestamp_column_to_accounts.sql b/supabase/migrations/20251006194801_add_timestamp_column_to_accounts.sql new file mode 100644 index 0000000..946f88a --- /dev/null +++ b/supabase/migrations/20251006194801_add_timestamp_column_to_accounts.sql @@ -0,0 +1,6 @@ +-- Add timestamp column to accounts table (if not exists) +ALTER TABLE "public"."accounts" +ADD COLUMN IF NOT EXISTS "timestamp" bigint DEFAULT extract(epoch from now()); + +-- Add comment for documentation +COMMENT ON COLUMN "public"."accounts"."timestamp" IS 'Unix timestamp of when the account was last updated'; diff --git a/supabase/migrations/20251016000000_rename_lyrics_to_notes_in_songs_table.sql b/supabase/migrations/20251016000000_rename_lyrics_to_notes_in_songs_table.sql new file mode 100644 index 0000000..6d931c9 --- /dev/null +++ b/supabase/migrations/20251016000000_rename_lyrics_to_notes_in_songs_table.sql @@ -0,0 +1,7 @@ +-- Rename lyrics column to notes in songs table +ALTER TABLE "public"."songs" +RENAME COLUMN "lyrics" TO "notes"; + +-- Update column comment for documentation +COMMENT ON COLUMN "public"."songs"."notes" IS 'Notes or lyrics text for the song (nullable)'; + diff --git a/supabase/migrations/20251104000000_add_trigger_schedule_id_to_scheduled_actions.sql b/supabase/migrations/20251104000000_add_trigger_schedule_id_to_scheduled_actions.sql new file mode 100644 index 0000000..b5268c1 --- /dev/null +++ b/supabase/migrations/20251104000000_add_trigger_schedule_id_to_scheduled_actions.sql @@ -0,0 +1,26 @@ +-- Migration: Add trigger_schedule_id to scheduled_actions +-- Purpose: Store Trigger.dev schedule id for external schedule lookup + +-- Reference (current schema excerpt): +-- Table: public.scheduled_actions +-- Columns (existing): +-- account_id uuid NOT NULL, +-- artist_account_id uuid NOT NULL, +-- created_at timestamptz NULL, +-- enabled boolean NULL, +-- id uuid PRIMARY KEY, +-- last_run timestamptz NULL, +-- next_run timestamptz NULL, +-- prompt text NOT NULL, +-- schedule text NOT NULL, +-- title text NOT NULL, +-- updated_at timestamptz NULL + +-- Change: add optional text column trigger_schedule_id with default NULL +ALTER TABLE public.scheduled_actions +ADD COLUMN IF NOT EXISTS trigger_schedule_id text DEFAULT NULL; + +COMMENT ON COLUMN public.scheduled_actions.trigger_schedule_id IS +'Trigger.dev schedule id for looking up the external scheduled task'; + + diff --git a/supabase/migrations/20251120000000_add_onboarding_to_account_info.sql b/supabase/migrations/20251120000000_add_onboarding_to_account_info.sql new file mode 100644 index 0000000..98495ef --- /dev/null +++ b/supabase/migrations/20251120000000_add_onboarding_to_account_info.sql @@ -0,0 +1,8 @@ +-- Add onboarding columns to account_info +ALTER TABLE "public"."account_info" +ADD COLUMN IF NOT EXISTS "job_title" text, +ADD COLUMN IF NOT EXISTS "role_type" text, +ADD COLUMN IF NOT EXISTS "company_name" text, +ADD COLUMN IF NOT EXISTS "onboarding_data" jsonb, +ADD COLUMN IF NOT EXISTS "onboarding_status" jsonb; + diff --git a/supabase/migrations/20251121_normalize_threads_urls.sql b/supabase/migrations/20251121_normalize_threads_urls.sql new file mode 100644 index 0000000..4bb8442 --- /dev/null +++ b/supabase/migrations/20251121_normalize_threads_urls.sql @@ -0,0 +1,36 @@ +-- Migration: Normalize Threads URLs +-- Description: Automatically convert threads.com to threads.net for all social URLs + +-- Create function to normalize Threads URLs +CREATE OR REPLACE FUNCTION normalize_threads_url() +RETURNS TRIGGER AS $$ +BEGIN + -- Only process if the URL contains threads.com + IF NEW.profile_url LIKE '%threads.com%' THEN + NEW.profile_url = REPLACE(NEW.profile_url, 'threads.com', 'threads.net'); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create trigger for INSERT operations +CREATE TRIGGER normalize_threads_url_insert + BEFORE INSERT ON socials + FOR EACH ROW + EXECUTE FUNCTION normalize_threads_url(); + +-- Create trigger for UPDATE operations +CREATE TRIGGER normalize_threads_url_update + BEFORE UPDATE OF profile_url ON socials + FOR EACH ROW + WHEN (NEW.profile_url IS DISTINCT FROM OLD.profile_url) + EXECUTE FUNCTION normalize_threads_url(); + +-- Update any existing threads.com URLs (though we just deleted the only one) +UPDATE socials +SET profile_url = REPLACE(profile_url, 'threads.com', 'threads.net') +WHERE profile_url LIKE '%threads.com%'; + +-- Add a comment to document this behavior +COMMENT ON FUNCTION normalize_threads_url() IS +'Automatically converts threads.com URLs to threads.net for scraper compatibility'; diff --git a/supabase/migrations/20251203000000_add_account_type_column.sql b/supabase/migrations/20251203000000_add_account_type_column.sql new file mode 100644 index 0000000..39c8b28 --- /dev/null +++ b/supabase/migrations/20251203000000_add_account_type_column.sql @@ -0,0 +1,12 @@ +-- Add account_type column to accounts table +-- Types: 'customer', 'artist', 'workspace', (future: 'organization', 'campaign') +-- No default value - must be explicitly set when creating accounts + +ALTER TABLE "public"."accounts" ADD COLUMN "account_type" TEXT; + +-- Add index for faster filtering by account type +CREATE INDEX idx_accounts_account_type ON "public"."accounts" ("account_type"); + +-- Optional: Add comment for documentation +COMMENT ON COLUMN "public"."accounts"."account_type" IS 'Type of account: customer, artist, workspace, organization, campaign'; + diff --git a/supabase/migrations/20251204000000_create_account_organization_ids.sql b/supabase/migrations/20251204000000_create_account_organization_ids.sql new file mode 100644 index 0000000..a7a2360 --- /dev/null +++ b/supabase/migrations/20251204000000_create_account_organization_ids.sql @@ -0,0 +1,60 @@ +-- Create account_organization_ids table +-- Links user/customer accounts to organization accounts +-- Similar structure to account_artist_ids (without pinned column) +-- Made idempotent: safe to run even if table already exists + +CREATE TABLE IF NOT EXISTS "public"."account_organization_ids" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "account_id" uuid, + "organization_id" uuid, + "updated_at" timestamp with time zone DEFAULT now() +); + +-- Enable row level security (idempotent by default) +ALTER TABLE "public"."account_organization_ids" ENABLE ROW LEVEL SECURITY; + +-- Primary key (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_organization_ids_pkey') THEN + CREATE UNIQUE INDEX account_organization_ids_pkey ON public.account_organization_ids USING btree (id); + ALTER TABLE "public"."account_organization_ids" ADD CONSTRAINT "account_organization_ids_pkey" PRIMARY KEY USING INDEX "account_organization_ids_pkey"; + END IF; +END $$; + +-- Foreign keys with cascade delete (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_organization_ids_account_id_fkey') THEN + ALTER TABLE "public"."account_organization_ids" ADD CONSTRAINT "account_organization_ids_account_id_fkey" + FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."account_organization_ids" VALIDATE CONSTRAINT "account_organization_ids_account_id_fkey"; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_organization_ids_organization_id_fkey') THEN + ALTER TABLE "public"."account_organization_ids" ADD CONSTRAINT "account_organization_ids_organization_id_fkey" + FOREIGN KEY (organization_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."account_organization_ids" VALIDATE CONSTRAINT "account_organization_ids_organization_id_fkey"; + END IF; +END $$; + +-- Updated_at trigger (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'set_updated_at' AND tgrelid = 'account_organization_ids'::regclass) THEN + CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_organization_ids + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + END IF; +END $$; + +-- Indexes for faster lookups (IF NOT EXISTS) +CREATE INDEX IF NOT EXISTS idx_account_organization_ids_account_id ON "public"."account_organization_ids" ("account_id"); +CREATE INDEX IF NOT EXISTS idx_account_organization_ids_organization_id ON "public"."account_organization_ids" ("organization_id"); + +-- Comment for documentation (idempotent by default) +COMMENT ON TABLE "public"."account_organization_ids" IS 'Links customer accounts to organization accounts. When either account is deleted, the link is automatically removed.'; diff --git a/supabase/migrations/20251204000001_create_artist_organization_ids.sql b/supabase/migrations/20251204000001_create_artist_organization_ids.sql new file mode 100644 index 0000000..7a3674f --- /dev/null +++ b/supabase/migrations/20251204000001_create_artist_organization_ids.sql @@ -0,0 +1,64 @@ +-- Create artist_organization_ids table +-- Links artists to organizations (many-to-many: an artist can belong to multiple orgs) +-- Made idempotent: safe to run even if table already exists + +CREATE TABLE IF NOT EXISTS "public"."artist_organization_ids" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "artist_id" uuid NOT NULL, + "organization_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now(), + "updated_at" timestamp with time zone DEFAULT now() +); + +-- Enable row level security (idempotent by default) +ALTER TABLE "public"."artist_organization_ids" ENABLE ROW LEVEL SECURITY; + +-- Primary key (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'artist_organization_ids_pkey') THEN + CREATE UNIQUE INDEX artist_organization_ids_pkey ON public.artist_organization_ids USING btree (id); + ALTER TABLE "public"."artist_organization_ids" ADD CONSTRAINT "artist_organization_ids_pkey" PRIMARY KEY USING INDEX "artist_organization_ids_pkey"; + END IF; +END $$; + +-- Foreign key to accounts table (artist accounts) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'artist_organization_ids_artist_id_fkey') THEN + ALTER TABLE "public"."artist_organization_ids" ADD CONSTRAINT "artist_organization_ids_artist_id_fkey" + FOREIGN KEY (artist_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."artist_organization_ids" VALIDATE CONSTRAINT "artist_organization_ids_artist_id_fkey"; + END IF; +END $$; + +-- Foreign key to accounts table (organization accounts) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'artist_organization_ids_organization_id_fkey') THEN + ALTER TABLE "public"."artist_organization_ids" ADD CONSTRAINT "artist_organization_ids_organization_id_fkey" + FOREIGN KEY (organization_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."artist_organization_ids" VALIDATE CONSTRAINT "artist_organization_ids_organization_id_fkey"; + END IF; +END $$; + +-- Unique constraint to prevent duplicate artist-org links (IF NOT EXISTS) +CREATE UNIQUE INDEX IF NOT EXISTS artist_organization_ids_unique ON "public"."artist_organization_ids" ("artist_id", "organization_id"); + +-- Indexes for faster lookups (IF NOT EXISTS) +CREATE INDEX IF NOT EXISTS idx_artist_organization_ids_artist_id ON "public"."artist_organization_ids" ("artist_id"); +CREATE INDEX IF NOT EXISTS idx_artist_organization_ids_organization_id ON "public"."artist_organization_ids" ("organization_id"); + +-- Updated_at trigger (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'set_updated_at' AND tgrelid = 'artist_organization_ids'::regclass) THEN + CREATE TRIGGER set_updated_at + BEFORE UPDATE ON artist_organization_ids + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + END IF; +END $$; + +-- Comment for documentation (idempotent by default) +COMMENT ON TABLE "public"."artist_organization_ids" IS 'Links artist accounts to organizations. An artist can belong to multiple orgs. When either is deleted, the link is automatically removed.'; diff --git a/supabase/migrations/20251204000002_create_organization_domains.sql b/supabase/migrations/20251204000002_create_organization_domains.sql new file mode 100644 index 0000000..e0e5114 --- /dev/null +++ b/supabase/migrations/20251204000002_create_organization_domains.sql @@ -0,0 +1,61 @@ +-- Create organization_domains table +-- Maps email domains to organizations for auto-assignment on login +-- One org can have multiple domains (e.g., Rostrum Pacific has 6) +-- Made idempotent: safe to run even if table already exists + +CREATE TABLE IF NOT EXISTS "public"."organization_domains" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "domain" text NOT NULL, + "organization_id" uuid NOT NULL, + "created_at" timestamp with time zone DEFAULT now() +); + +-- Enable row level security (idempotent by default) +ALTER TABLE "public"."organization_domains" ENABLE ROW LEVEL SECURITY; + +-- Primary key (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'organization_domains_pkey') THEN + CREATE UNIQUE INDEX organization_domains_pkey ON public.organization_domains USING btree (id); + ALTER TABLE "public"."organization_domains" ADD CONSTRAINT "organization_domains_pkey" PRIMARY KEY USING INDEX "organization_domains_pkey"; + END IF; +END $$; + +-- Foreign key to accounts table (organization accounts) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'organization_domains_organization_id_fkey') THEN + ALTER TABLE "public"."organization_domains" ADD CONSTRAINT "organization_domains_organization_id_fkey" + FOREIGN KEY (organization_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."organization_domains" VALIDATE CONSTRAINT "organization_domains_organization_id_fkey"; + END IF; +END $$; + +-- Unique constraint - each domain can only belong to one org (IF NOT EXISTS) +CREATE UNIQUE INDEX IF NOT EXISTS organization_domains_domain_unique ON "public"."organization_domains" ("domain"); + +-- Index for fast domain lookups (IF NOT EXISTS) +CREATE INDEX IF NOT EXISTS idx_organization_domains_domain ON "public"."organization_domains" ("domain"); +CREATE INDEX IF NOT EXISTS idx_organization_domains_organization_id ON "public"."organization_domains" ("organization_id"); + +-- Comment for documentation (idempotent by default) +COMMENT ON TABLE "public"."organization_domains" IS 'Maps email domains to organizations. Used for auto-assigning users to orgs on login. Each domain can only belong to one org.'; + +-- NOTE: Seed data is NOT included in this migration because it references +-- the Rostrum Pacific org which only exists in production. +-- +-- After deploying to production, run this SQL manually: +-- +-- INSERT INTO "public"."organization_domains" (domain, organization_id) VALUES +-- ('recoupable.com', 'cebcc866-34c3-451c-8cd7-f63309acff0a'), +-- ('rostrum.com', 'cebcc866-34c3-451c-8cd7-f63309acff0a'), +-- ('rostrumrecords.com', 'cebcc866-34c3-451c-8cd7-f63309acff0a'), +-- ('spaceheatermusic.io', 'cebcc866-34c3-451c-8cd7-f63309acff0a'), +-- ('fatbeats.com', 'cebcc866-34c3-451c-8cd7-f63309acff0a'), +-- ('cantorarecords.net', 'cebcc866-34c3-451c-8cd7-f63309acff0a'); +-- +-- When onboarding new enterprise customers: +-- 1. Create their organization account (account_type = 'organization') +-- 2. Add their domains to this table +-- 3. Also add 'recoupable.com' to their org so Recoup team can access it diff --git a/supabase/migrations/20251204000003_create_account_workspace_ids.sql b/supabase/migrations/20251204000003_create_account_workspace_ids.sql new file mode 100644 index 0000000..73396f5 --- /dev/null +++ b/supabase/migrations/20251204000003_create_account_workspace_ids.sql @@ -0,0 +1,61 @@ +-- Create account_workspace_ids table +-- Links owner accounts to workspace accounts +-- Similar structure to account_artist_ids (without pinned column) +-- Used to identify which accounts are workspaces (vs artists) +-- Made idempotent: safe to run even if table already exists + +CREATE TABLE IF NOT EXISTS "public"."account_workspace_ids" ( + "id" uuid NOT NULL DEFAULT gen_random_uuid(), + "account_id" uuid, + "workspace_id" uuid, + "updated_at" timestamp with time zone DEFAULT now() +); + +-- Enable row level security (idempotent by default) +ALTER TABLE "public"."account_workspace_ids" ENABLE ROW LEVEL SECURITY; + +-- Primary key (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_workspace_ids_pkey') THEN + CREATE UNIQUE INDEX account_workspace_ids_pkey ON public.account_workspace_ids USING btree (id); + ALTER TABLE "public"."account_workspace_ids" ADD CONSTRAINT "account_workspace_ids_pkey" PRIMARY KEY USING INDEX "account_workspace_ids_pkey"; + END IF; +END $$; + +-- Foreign keys with cascade delete +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_workspace_ids_account_id_fkey') THEN + ALTER TABLE "public"."account_workspace_ids" ADD CONSTRAINT "account_workspace_ids_account_id_fkey" + FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."account_workspace_ids" VALIDATE CONSTRAINT "account_workspace_ids_account_id_fkey"; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'account_workspace_ids_workspace_id_fkey') THEN + ALTER TABLE "public"."account_workspace_ids" ADD CONSTRAINT "account_workspace_ids_workspace_id_fkey" + FOREIGN KEY (workspace_id) REFERENCES accounts(id) ON DELETE CASCADE NOT VALID; + ALTER TABLE "public"."account_workspace_ids" VALIDATE CONSTRAINT "account_workspace_ids_workspace_id_fkey"; + END IF; +END $$; + +-- Updated_at trigger (only if not exists) +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'set_updated_at' AND tgrelid = 'account_workspace_ids'::regclass) THEN + CREATE TRIGGER set_updated_at + BEFORE UPDATE ON account_workspace_ids + FOR EACH ROW + EXECUTE FUNCTION trigger_set_updated_at(); + END IF; +END $$; + +-- Indexes for faster lookups (IF NOT EXISTS) +CREATE INDEX IF NOT EXISTS idx_account_workspace_ids_account_id ON "public"."account_workspace_ids" ("account_id"); +CREATE INDEX IF NOT EXISTS idx_account_workspace_ids_workspace_id ON "public"."account_workspace_ids" ("workspace_id"); + +-- Comment for documentation (idempotent by default) +COMMENT ON TABLE "public"."account_workspace_ids" IS 'Links owner accounts to workspace accounts. When either account is deleted, the link is automatically removed. Used to identify workspaces (vs artists) without relying on account_type column.'; diff --git a/supabase/migrations/20251204000004_drop_account_type_column.sql b/supabase/migrations/20251204000004_drop_account_type_column.sql new file mode 100644 index 0000000..9bd8e6c --- /dev/null +++ b/supabase/migrations/20251204000004_drop_account_type_column.sql @@ -0,0 +1,13 @@ +-- Drop account_type column from accounts table +-- We now use join tables to determine account type: +-- - account_artist_ids: links owner accounts to their artists +-- - account_workspace_ids: links owner accounts to their workspaces +-- - account_organization_ids: links accounts to their organizations +-- - artist_organization_ids: links artists to organizations + +-- Drop the index first +DROP INDEX IF EXISTS idx_accounts_account_type; + +-- Drop the column +ALTER TABLE "public"."accounts" DROP COLUMN IF EXISTS "account_type"; + diff --git a/supabase/migrations/20251204000005_add_unique_constraints.sql b/supabase/migrations/20251204000005_add_unique_constraints.sql new file mode 100644 index 0000000..04195f6 --- /dev/null +++ b/supabase/migrations/20251204000005_add_unique_constraints.sql @@ -0,0 +1,25 @@ +-- Add unique constraints to prevent duplicate links +-- These tables were created in earlier migrations, so we need a separate migration +-- First removes any duplicate rows, then creates unique indexes + +-- Remove duplicate account_organization_ids (keep the first one by id) +DELETE FROM "public"."account_organization_ids" a +USING "public"."account_organization_ids" b +WHERE a.id > b.id + AND a.account_id = b.account_id + AND a.organization_id = b.organization_id; + +-- Unique constraint on account_organization_ids +CREATE UNIQUE INDEX IF NOT EXISTS account_organization_ids_unique + ON "public"."account_organization_ids" ("account_id", "organization_id"); + +-- Remove duplicate account_workspace_ids (keep the first one by id) +DELETE FROM "public"."account_workspace_ids" a +USING "public"."account_workspace_ids" b +WHERE a.id > b.id + AND a.account_id = b.account_id + AND a.workspace_id = b.workspace_id; + +-- Unique constraint on account_workspace_ids +CREATE UNIQUE INDEX IF NOT EXISTS account_workspace_ids_unique + ON "public"."account_workspace_ids" ("account_id", "workspace_id"); diff --git a/supabase/migrations/20251205000000_add_model_to_scheduled_actions.sql b/supabase/migrations/20251205000000_add_model_to_scheduled_actions.sql new file mode 100644 index 0000000..8de1ac7 --- /dev/null +++ b/supabase/migrations/20251205000000_add_model_to_scheduled_actions.sql @@ -0,0 +1,9 @@ +-- Add model column to scheduled_actions table +-- Allows tasks to specify which AI model to use for execution + +ALTER TABLE public.scheduled_actions +ADD COLUMN IF NOT EXISTS model TEXT DEFAULT NULL; + +COMMENT ON COLUMN public.scheduled_actions.model IS +'Model ID for task execution (e.g., openai/gpt-5-mini). NULL = default model.'; + diff --git a/supabase/migrations/20251206000000_create_account_api_keys.sql b/supabase/migrations/20251206000000_create_account_api_keys.sql new file mode 100644 index 0000000..9a66710 --- /dev/null +++ b/supabase/migrations/20251206000000_create_account_api_keys.sql @@ -0,0 +1,26 @@ +-- Create account_api_keys table +-- Stores API keys associated with accounts + +CREATE TABLE public.account_api_keys ( + id uuid NOT NULL DEFAULT gen_random_uuid(), + name text NOT NULL, + account uuid NULL, + key_hash text NULL, + created_at timestamp with time zone NOT NULL DEFAULT now(), + last_used timestamp without time zone NULL, + CONSTRAINT account_api_keys_pkey PRIMARY KEY (id), + CONSTRAINT account_api_keys_account_fkey FOREIGN KEY (account) + REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE +) TABLESPACE pg_default; + +-- Enable Row Level Security +ALTER TABLE public.account_api_keys ENABLE ROW LEVEL SECURITY; + +-- Create index on account for faster lookups +CREATE INDEX IF NOT EXISTS account_api_keys_account_idx + ON public.account_api_keys(account); + +-- Grant permissions +GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.account_api_keys TO authenticated; +GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.account_api_keys TO service_role; +