feat(search): add musical_key filter and wire tags filter (G1)

This commit is contained in:
senke 2026-02-20 16:50:30 +01:00
parent 8961b4ba14
commit b042da9575
7 changed files with 60 additions and 4 deletions

View file

@ -80,6 +80,7 @@ export function TrackSearch({
}, [
debouncedQuery,
filters.genre,
filters.musicalKey,
filters.format,
filters.tags,
filters.minDuration,

View file

@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button';
import { X } from 'lucide-react';
import {
GENRES,
MUSICAL_KEYS,
FORMATS,
SORT_OPTIONS,
SORT_ORDER_OPTIONS,
@ -44,6 +45,21 @@ export function TrackSearchFiltersBasic({
className={selectClass}
/>
</div>
<div className="flex-1 min-w-48">
<Label htmlFor="musicalKey" className={labelClass}>
Tonalité
</Label>
<Select
options={MUSICAL_KEYS}
value={filters.musicalKey || ''}
onChange={(value) => {
const musicalKey = Array.isArray(value) ? value[0] : value;
updateFilter('musicalKey', musicalKey || undefined);
}}
placeholder="Toutes les tonalités"
className={selectClass}
/>
</div>
<div className="flex-1 min-w-48">
<Label htmlFor="format" className={labelClass}>
Format

View file

@ -23,6 +23,24 @@ export const GENRES: SelectOption[] = [
{ value: 'Metal', label: 'Metal' },
];
export const MUSICAL_KEYS: SelectOption[] = [
{ value: '', label: 'Toutes les tonalités' },
{ value: 'C', label: 'C' },
{ value: 'Cm', label: 'Cm' },
{ value: 'D', label: 'D' },
{ value: 'Dm', label: 'Dm' },
{ value: 'E', label: 'E' },
{ value: 'Em', label: 'Em' },
{ value: 'F', label: 'F' },
{ value: 'Fm', label: 'Fm' },
{ value: 'G', label: 'G' },
{ value: 'Gm', label: 'Gm' },
{ value: 'A', label: 'A' },
{ value: 'Am', label: 'Am' },
{ value: 'B', label: 'B' },
{ value: 'Bm', label: 'Bm' },
];
export const FORMATS: SelectOption[] = [
{ value: '', label: 'Tous les formats' },
{ value: 'MP3', label: 'MP3' },

View file

@ -37,6 +37,7 @@ export function useTrackSearchFilters(
const hasActiveFilters = Boolean(
filters.genre ||
filters.musicalKey ||
filters.format ||
filters.tags?.length ||
filters.minDuration ||

View file

@ -29,6 +29,7 @@ export interface TrackSearchParams {
query?: string;
tags?: string[];
tagMode?: 'AND' | 'OR';
musicalKey?: string;
minDuration?: number;
maxDuration?: number;
minBPM?: number;
@ -86,6 +87,10 @@ function buildSearchQuery(params: TrackSearchParams): URLSearchParams {
queryParams.append('tag_mode', params.tagMode);
}
if (params.musicalKey) {
queryParams.append('musical_key', params.musicalKey);
}
if (params.minDuration !== undefined) {
queryParams.append('min_duration', params.minDuration.toString());
}

View file

@ -1613,6 +1613,11 @@ func (h *TrackHandler) SearchTracks(c *gin.Context) {
params.Format = &format
}
// Parser musical_key
if musicalKey := c.Query("musical_key"); musicalKey != "" {
params.MusicalKey = &musicalKey
}
// Parser min_date
if minDate := c.Query("min_date"); minDate != "" {
params.MinDate = &minDate

View file

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/lib/pq"
"veza-backend-api/internal/database"
"veza-backend-api/internal/models"
@ -17,6 +18,7 @@ type TrackSearchParams struct {
Query string
Tags []string
TagMode string // "AND" or "OR"
MusicalKey *string
MinDuration *int // seconds
MaxDuration *int // seconds
MinBPM *int
@ -59,11 +61,19 @@ func (s *TrackSearchService) SearchTracks(ctx context.Context, params TrackSearc
)
}
// Tag search - Note: Tags field not in current model, skipping for now
// This can be implemented when tags are added to the Track model
// Tag search (tracks.tags pq.StringArray, migration 085)
if len(params.Tags) > 0 {
// Tags functionality would go here when Tags field is added
// For now, we'll skip tag filtering
tagArray := pq.Array(params.Tags)
if params.TagMode == "AND" {
query = query.Where("tags @> ?", tagArray)
} else {
query = query.Where("tags && ?", tagArray)
}
}
// Musical key filter (case-insensitive)
if params.MusicalKey != nil && *params.MusicalKey != "" {
query = query.Where("LOWER(musical_key) = ?", strings.ToLower(strings.TrimSpace(*params.MusicalKey)))
}
// Duration filter (supports combined min/max)