Skip to content

Commit f9957cb

Browse files
Merge pull request #9 from Jamesllllllllll/fix-prod-deploy
Fix production deploy config regeneration after remote migrations
2 parents 3000f47 + aea4c83 commit f9957cb

File tree

16 files changed

+542
-84
lines changed

16 files changed

+542
-84
lines changed

.github/workflows/deploy-production.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ jobs:
5050
- name: Apply production D1 migrations
5151
run: npm run db:migrate:remote
5252

53+
- name: Regenerate production Wrangler configs
54+
run: npm run deploy:prepare
55+
5356
- name: Deploy production backend
5457
shell: bash
5558
run: |

drizzle/0002_clever_justice.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE `played_songs`
2+
ADD COLUMN `request_kind` text NOT NULL DEFAULT 'regular';

drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"when": 1773950400000,
1616
"tag": "0001_true_ares",
1717
"breakpoints": true
18+
},
19+
{
20+
"idx": 2,
21+
"version": "7",
22+
"when": 1774041600000,
23+
"tag": "0002_clever_justice",
24+
"breakpoints": true
1825
}
1926
]
2027
}

src/components/song-search-panel.tsx

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
TooltipTrigger,
5151
} from "~/components/ui/tooltip";
5252
import { pathOptions } from "~/lib/channel-options";
53+
import { formatPathLabel } from "~/lib/request-policy";
5354
import { cn, getErrorMessage } from "~/lib/utils";
5455

5556
type SearchField = "any" | "title" | "artist" | "album" | "creator";
@@ -109,6 +110,7 @@ export function SongSearchPanel(props: {
109110
title: string;
110111
eyebrow?: string;
111112
description?: string;
113+
infoNote?: string;
112114
placeholder?: string;
113115
className?: string;
114116
}) {
@@ -249,7 +251,7 @@ export function SongSearchPanel(props: {
249251

250252
const { data, error, isFetching, isLoading } = useQuery<SearchResponse>({
251253
queryKey: ["song-search", searchParams.toString()],
252-
enabled: hasSearchInput && !queryTooShort && !requiresCoreSearchTerm,
254+
enabled: !queryTooShort && !requiresCoreSearchTerm,
253255
placeholderData: keepPreviousData,
254256
queryFn: async (): Promise<SearchResponse> => {
255257
const response = await fetch(`/api/search?${searchParams.toString()}`);
@@ -271,9 +273,11 @@ export function SongSearchPanel(props: {
271273
});
272274

273275
const results =
274-
hasSearchInput && !queryTooShort && !requiresCoreSearchTerm
275-
? (data?.results ?? [])
276-
: [];
276+
!queryTooShort && !requiresCoreSearchTerm ? (data?.results ?? []) : [];
277+
const resolvedInfoNote = props.infoNote?.replace(
278+
"{count}",
279+
String(data?.total ?? 0)
280+
);
277281
const totalPages = Math.max(
278282
1,
279283
Math.ceil((data?.total ?? 0) / (data?.pageSize ?? 25))
@@ -353,7 +357,7 @@ export function SongSearchPanel(props: {
353357
}
354358

355359
function renderPagination(position: "top" | "bottom") {
356-
if (!hasSearchInput || totalPages <= 1) {
360+
if (totalPages <= 1) {
357361
return null;
358362
}
359363

@@ -441,11 +445,19 @@ export function SongSearchPanel(props: {
441445
{props.description}
442446
</p>
443447
) : null}
448+
{resolvedInfoNote ? (
449+
<div className="mt-4 rounded-[20px] border border-sky-400/30 bg-sky-500/10 px-4 py-3 text-sm text-sky-100">
450+
<p className="font-semibold uppercase tracking-[0.18em] text-sky-200">
451+
Note:
452+
</p>
453+
<p className="mt-2">{resolvedInfoNote}</p>
454+
</div>
455+
) : null}
444456
</div>
445-
{hasSearchInput && !queryTooShort && !error ? (
457+
{!queryTooShort && !error ? (
446458
<div className="rounded-[24px] border border-(--border) bg-(--panel-soft) px-4 py-3 text-right">
447459
<p className="text-lg font-semibold text-(--text)">
448-
{data?.total ?? 0} matching songs
460+
{data?.total ?? 0} songs
449461
</p>
450462
{isFetching ? (
451463
<p className="mt-1 text-xs font-medium text-(--muted)">
@@ -605,15 +617,15 @@ export function SongSearchPanel(props: {
605617
<Label>Path</Label>
606618
<MultiSelectSelect
607619
label="Path"
608-
options={pathOptions.map(
609-
(part) => part.charAt(0).toUpperCase() + part.slice(1)
620+
options={pathOptions.map((part) => formatPathLabel(part))}
621+
selectedValues={advancedFilters.parts.map((part) =>
622+
formatPathLabel(part)
610623
)}
611-
selectedValues={advancedFilters.parts.map(
612-
(part) => part.charAt(0).toUpperCase() + part.slice(1)
613-
)}
614-
onAdd={(value) => toggleAdvancedPart(value.toLowerCase())}
624+
onAdd={(value) =>
625+
toggleAdvancedPart(getPathTokenFromLabel(value))
626+
}
615627
onRemove={(value) =>
616-
toggleAdvancedPart(value.toLowerCase())
628+
toggleAdvancedPart(getPathTokenFromLabel(value))
617629
}
618630
toneByValue={getPathToneByValue}
619631
/>
@@ -651,25 +663,25 @@ export function SongSearchPanel(props: {
651663
<CardContent className="p-0">
652664
{renderPagination("top")}
653665

654-
{hasSearchInput ? (
655-
<div className="grid grid-cols-[minmax(0,2.1fr)_minmax(0,1.4fr)_minmax(0,1.2fr)_minmax(0,1fr)_72px] gap-4 border-b border-(--border) px-5 py-4 text-[11px] font-semibold uppercase tracking-[0.22em] text-(--muted)">
656-
<span>Song</span>
657-
<span>Details</span>
658-
<span>Paths / Tuning</span>
659-
<span>Stats</span>
660-
<span className="text-right">Copy</span>
661-
</div>
662-
) : null}
666+
<div className="grid grid-cols-[minmax(0,2.1fr)_minmax(0,1.4fr)_minmax(0,1.2fr)_minmax(0,1fr)_72px] gap-4 border-b border-(--border) px-5 py-4 text-[11px] font-semibold uppercase tracking-[0.22em] text-(--muted)">
667+
<span>Song</span>
668+
<span>Details</span>
669+
<span>Paths / Tuning</span>
670+
<span>Stats</span>
671+
<span className="text-right">Copy</span>
672+
</div>
663673

664674
{isLoading && results.length === 0 ? (
665675
<div className="px-5 py-8 text-sm text-(--muted)">
666-
Searching...
676+
Loading songs...
667677
</div>
668678
) : null}
669679

670680
{!isLoading && queryTooShort && !hasAdvancedFilter ? (
671-
<div className="px-5 py-8 text-sm text-(--muted)">
672-
Search terms must be at least 3 characters.
681+
<div className="grid grid-cols-[minmax(0,2.1fr)_minmax(0,1.4fr)_minmax(0,1.2fr)_minmax(0,1fr)_72px] gap-4 border-b border-(--border) px-5 py-4 text-[11px] font-semibold uppercase tracking-[0.22em] text-(--muted)">
682+
<span className="col-span-full normal-case tracking-normal text-sm font-normal text-(--muted)">
683+
Search terms must be at least 3 characters.
684+
</span>
673685
</div>
674686
) : null}
675687

@@ -686,13 +698,13 @@ export function SongSearchPanel(props: {
686698
) : null}
687699

688700
{!isLoading &&
689-
hasSearchInput &&
690701
!queryTooShort &&
691702
!requiresCoreSearchTerm &&
692703
results.length === 0 ? (
693704
<div className="px-5 py-8 text-sm text-(--muted)">
694-
No songs matched those filters yet. Try broadening the search
695-
field or clearing one of the advanced inputs.
705+
{hasSearchInput
706+
? "No songs matched those filters yet. Try broadening the search field or clearing one of the advanced inputs."
707+
: "No songs are available in the demo catalog yet."}
696708
</div>
697709
) : null}
698710

@@ -760,7 +772,7 @@ export function SongSearchPanel(props: {
760772
{song.parts?.includes("voice") ||
761773
song.parts?.includes("vocals") ? (
762774
<Badge className="border-violet-400/30 bg-violet-500/10 text-violet-300 hover:bg-violet-500/10">
763-
Voice
775+
Lyrics
764776
</Badge>
765777
) : null}
766778
</div>
@@ -775,11 +787,6 @@ export function SongSearchPanel(props: {
775787
{song.durationText ? (
776788
<p className="text-(--text)">{song.durationText}</p>
777789
) : null}
778-
{song.downloads != null ? (
779-
<p className="mt-1 text-(--muted)">
780-
{song.downloads.toLocaleString()} downloads
781-
</p>
782-
) : null}
783790
{song.year ? (
784791
<p className="mt-1 text-(--muted)">{String(song.year)}</p>
785792
) : null}
@@ -836,6 +843,10 @@ function getPathToneByValue(value: string) {
836843
}
837844
}
838845

846+
function getPathTokenFromLabel(value: string) {
847+
return value.toLowerCase() === "lyrics" ? "voice" : value.toLowerCase();
848+
}
849+
839850
function MultiSelectSelect(props: {
840851
label: string;
841852
options: string[];
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const LATEST_MIGRATION_NAME = "0001_true_ares.sql";
1+
export const LATEST_MIGRATION_NAME = "0002_clever_justice.sql";

src/lib/db/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ export const playedSongs = sqliteTable(
451451
requestedByTwitchUserId: text("requested_by_twitch_user_id"),
452452
requestedByLogin: text("requested_by_login"),
453453
requestedByDisplayName: text("requested_by_display_name"),
454+
requestKind: text("request_kind").notNull().default("regular"),
454455
requestedAt: integer("requested_at"),
455456
playedAt: integer("played_at").notNull(),
456457
createdAt: integer("created_at")

src/lib/playlist/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export interface SetCurrentInput {
4545
actorUserId: string;
4646
}
4747

48+
export interface RestorePlayedInput {
49+
channelId: string;
50+
playedSongId: string;
51+
actorUserId: string;
52+
}
53+
4854
export interface SkipItemInput {
4955
channelId: string;
5056
itemId: string;
@@ -126,6 +132,7 @@ export interface PlaylistCoordinator {
126132
addRequest(input: AddRequestInput): Promise<PlaylistMutationResult>;
127133
removeRequests(input: RemoveRequestsInput): Promise<PlaylistMutationResult>;
128134
markPlayed(input: MarkPlayedInput): Promise<PlaylistMutationResult>;
135+
restorePlayed(input: RestorePlayedInput): Promise<PlaylistMutationResult>;
129136
setCurrent(input: SetCurrentInput): Promise<PlaylistMutationResult>;
130137
skipItem(input: SkipItemInput): Promise<PlaylistMutationResult>;
131138
shuffleNext(input: ShuffleNextInput): Promise<PlaylistMutationResult>;

src/lib/request-policy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export function formatPathLabel(path: string) {
244244
return "Bass";
245245
case "voice":
246246
case "vocals":
247-
return "Voice";
247+
return "Lyrics";
248248
default:
249249
return path.trim();
250250
}

src/lib/validation.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,6 @@ export const searchInputSchema = z
4040
sortDirection: z.enum(["asc", "desc"]).default("desc"),
4141
})
4242
.superRefine((input, ctx) => {
43-
const hasAdvancedFilter = !!(
44-
input.title ||
45-
input.artist ||
46-
input.album ||
47-
input.creator ||
48-
(input.tuning && input.tuning.length > 0) ||
49-
(input.parts && input.parts.length > 0) ||
50-
(input.year && input.year.length > 0)
51-
);
5243
const hasCoreText = !!(
5344
input.query ||
5445
input.title ||
@@ -65,14 +56,6 @@ export const searchInputSchema = z
6556
});
6657
}
6758

68-
if (!input.query && !hasAdvancedFilter) {
69-
ctx.addIssue({
70-
code: z.ZodIssueCode.custom,
71-
message: "Enter a search query or at least one advanced filter.",
72-
path: ["query"],
73-
});
74-
}
75-
7659
if (
7760
!hasCoreText &&
7861
((input.tuning && input.tuning.length > 0) ||
@@ -241,6 +224,7 @@ export const songListItemSchema = z.object({
241224

242225
export const playlistMutationSchema = z.discriminatedUnion("action", [
243226
z.object({ action: z.literal("markPlayed"), itemId: z.string() }),
227+
z.object({ action: z.literal("restorePlayed"), playedSongId: z.string() }),
244228
z.object({ action: z.literal("skipItem"), itemId: z.string() }),
245229
z.object({ action: z.literal("setCurrent"), itemId: z.string() }),
246230
z.object({ action: z.literal("deleteItem"), itemId: z.string() }),

0 commit comments

Comments
 (0)