feat(search): add musical_key filter and wire tags filter (G1)
This commit is contained in:
parent
8961b4ba14
commit
b042da9575
7 changed files with 60 additions and 4 deletions
|
|
@ -80,6 +80,7 @@ export function TrackSearch({
|
|||
}, [
|
||||
debouncedQuery,
|
||||
filters.genre,
|
||||
filters.musicalKey,
|
||||
filters.format,
|
||||
filters.tags,
|
||||
filters.minDuration,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' },
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export function useTrackSearchFilters(
|
|||
|
||||
const hasActiveFilters = Boolean(
|
||||
filters.genre ||
|
||||
filters.musicalKey ||
|
||||
filters.format ||
|
||||
filters.tags?.length ||
|
||||
filters.minDuration ||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue