Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/AI-USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ the part that matters most.
| 2026-10-01 | robertky | Codex (ChatGPT) | Assisted with formulating good reviews of the architecture and UI-specification PRs. The group decided to include decade filtering; Rachel implemented the corresponding schema and documentation update in #38. |
| 2026-10-10 | evenkkl | Claude | Assisted with implementing the GraphQL Yoga server with `searchAlbums` for #6, and formulated an easy to understand PR description. Guided the reset of the prototype tables in the VM database (backup taken first). |
| 2026-10-13 | evenkkl | Claude | Fixed the review feedback on #6: cursor values validated against the sort, `reviewCount` returns 0 until reviews exist. The group decided genre filters combine with AND; Claude made the filter and the genre counts match that. |
| 2026-10-09 | robertky | Codex (ChatGPT) | AI was a good sparring partner when I drafted the initial #5 PostgreSQL migration. It gave me ideas on how to structure and improve the tables, relation tables, constraints, indexes, full-text search triggers and TypeScript migration runner. |

## Notes

Expand Down
128 changes: 128 additions & 0 deletions server/drizzle/0001_user-content-and-tracks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
CREATE TABLE "app_user" (
"id" uuid PRIMARY KEY NOT NULL,
"display_name" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "crate_item" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" uuid NOT NULL,
"album_id" integer NOT NULL,
"added_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "review" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" uuid NOT NULL,
"album_id" integer NOT NULL,
"rating" smallint NOT NULL,
"comment" text DEFAULT '' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "review_rating_range" CHECK ("review"."rating" BETWEEN 1 AND 5)
);
--> statement-breakpoint
CREATE TABLE "track" (
"id" serial PRIMARY KEY NOT NULL,
"album_id" integer NOT NULL,
"title" text NOT NULL,
"track_number" integer NOT NULL,
"duration_seconds" integer
);
--> statement-breakpoint
ALTER TABLE "crate_item" ADD CONSTRAINT "crate_item_user_id_app_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."app_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "crate_item" ADD CONSTRAINT "crate_item_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "review" ADD CONSTRAINT "review_user_id_app_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."app_user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "review" ADD CONSTRAINT "review_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "track" ADD CONSTRAINT "track_album_id_album_id_fk" FOREIGN KEY ("album_id") REFERENCES "public"."album"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "crate_item_user_album_unique" ON "crate_item" USING btree ("user_id","album_id");--> statement-breakpoint
CREATE INDEX "crate_item_user_added_at_idx" ON "crate_item" USING btree ("user_id","added_at","id");--> statement-breakpoint
CREATE UNIQUE INDEX "review_user_album_unique" ON "review" USING btree ("user_id","album_id");--> statement-breakpoint
CREATE INDEX "review_album_idx" ON "review" USING btree ("album_id");--> statement-breakpoint
CREATE UNIQUE INDEX "track_album_track_number_unique" ON "track" USING btree ("album_id","track_number");--> statement-breakpoint
CREATE INDEX "track_album_track_number_idx" ON "track" USING btree ("album_id","track_number");
--> statement-breakpoint
CREATE FUNCTION set_album_search_vector()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('simple', NEW.title), 'A') ||
setweight(
to_tsvector(
'simple',
COALESCE((
SELECT string_agg(artist.name, ' ')
FROM album_artist
JOIN artist ON artist.id = album_artist.artist_id
WHERE album_artist.album_id = NEW.id
), '')
),
'B'
);
RETURN NEW;
END;
$$;
--> statement-breakpoint
CREATE TRIGGER album_search_vector_before_write
BEFORE INSERT OR UPDATE OF title ON album
FOR EACH ROW EXECUTE FUNCTION set_album_search_vector();
--> statement-breakpoint
CREATE FUNCTION refresh_album_search_vector_from_artist_link()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
target_album_id integer;
BEGIN
IF TG_OP = 'DELETE' THEN
target_album_id := OLD.album_id;
ELSE
target_album_id := NEW.album_id;
END IF;

UPDATE album
SET title = title
WHERE id = target_album_id;
RETURN NULL;
END;
$$;
--> statement-breakpoint
CREATE TRIGGER album_artist_refresh_search_vector
AFTER INSERT OR UPDATE OR DELETE ON album_artist
FOR EACH ROW EXECUTE FUNCTION refresh_album_search_vector_from_artist_link();
--> statement-breakpoint
CREATE FUNCTION refresh_album_search_vector_from_artist()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE album
SET title = title
WHERE id IN (
SELECT album_id FROM album_artist WHERE artist_id = NEW.id
);
RETURN NEW;
END;
$$;
--> statement-breakpoint
CREATE TRIGGER artist_refresh_search_vector
AFTER UPDATE OF name ON artist
FOR EACH ROW EXECUTE FUNCTION refresh_album_search_vector_from_artist();
--> statement-breakpoint
CREATE FUNCTION set_review_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW.updated_at := now();
RETURN NEW;
END;
$$;
--> statement-breakpoint
CREATE TRIGGER review_set_updated_at
BEFORE UPDATE ON review
FOR EACH ROW EXECUTE FUNCTION set_review_updated_at();
--> statement-breakpoint
UPDATE album SET title = title;
Loading