Add NixOS VM configuration and new media search endpoint
Some checks failed
Test Suite / test (push) Failing after 45s

- Introduced a new NixOS VM configuration in `flake.nix` for testing purposes.
- Updated CI workflow to build the VM using the flake input for a more streamlined process.
- Added a new API endpoint `/search` in the collection API to search owned media by title, with optional filtering by media type.
- Refactored frontend components to utilize the new media search functionality, updating types and state management accordingly.
- Enhanced CSS for the map component to ensure proper rendering in various layouts.
This commit is contained in:
Danilo Reyes
2025-12-28 23:02:48 -06:00
parent 4709a05ad4
commit 1329958b4e
7 changed files with 125 additions and 20 deletions

View File

@@ -1,12 +1,12 @@
import { useState, useEffect, useRef } from 'react'
import { searchTMDB, TMDBResult } from '../utils/tmdb'
import { searchOwnedMedia, OwnedMediaResult } from '../utils/ownedMedia'
import './AutocompleteInput.css'
interface AutocompleteInputProps {
value: string
onChange: (value: string) => void
onSelect: (result: TMDBResult) => void
type: 'movie' | 'tv'
onSelect: (result: OwnedMediaResult) => void
type: 'movie' | 'show'
placeholder?: string
disabled?: boolean
}
@@ -19,7 +19,7 @@ export default function AutocompleteInput({
placeholder = 'Search...',
disabled = false,
}: AutocompleteInputProps) {
const [suggestions, setSuggestions] = useState<TMDBResult[]>([])
const [suggestions, setSuggestions] = useState<OwnedMediaResult[]>([])
const [showSuggestions, setShowSuggestions] = useState(false)
const [loading, setLoading] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
@@ -41,7 +41,7 @@ export default function AutocompleteInput({
const performSearch = async (query: string) => {
setLoading(true)
try {
const results = await searchTMDB(query, type)
const results = await searchOwnedMedia(query, type)
setSuggestions(results)
setShowSuggestions(results.length > 0)
} catch (error) {
@@ -53,7 +53,7 @@ export default function AutocompleteInput({
}
}
const handleSelect = (result: TMDBResult) => {
const handleSelect = (result: OwnedMediaResult) => {
onChange(result.title)
onSelect(result)
setShowSuggestions(false)
@@ -89,7 +89,7 @@ export default function AutocompleteInput({
<div ref={suggestionsRef} className="autocomplete-suggestions">
{suggestions.map((result) => (
<div
key={`${result.type}-${result.id}`}
key={result.id}
className="autocomplete-suggestion"
onMouseDown={(e) => {
e.preventDefault()
@@ -100,9 +100,6 @@ export default function AutocompleteInput({
{result.title}
{result.year && <span className="suggestion-year"> ({result.year})</span>}
</div>
{result.overview && (
<div className="suggestion-overview">{result.overview.substring(0, 100)}...</div>
)}
</div>
))}
</div>

View File

@@ -1,6 +1,7 @@
.map-container {
display: flex;
height: 100%;
/* Ensure maps render even if parent layout doesn't provide an explicit height */
height: calc(100vh - 72px);
width: 100%;
}

View File

@@ -4,7 +4,7 @@ import { LatLngExpression } from 'leaflet'
import 'leaflet/dist/leaflet.css'
import './Map.css'
import AutocompleteInput from './AutocompleteInput'
import { TMDBResult } from '../utils/tmdb'
import { OwnedMediaResult } from '../utils/ownedMedia'
interface WatchedItem {
id: string
@@ -132,7 +132,7 @@ export default function WatchedMap() {
}
}
const handleAutocompleteSelect = (result: TMDBResult) => {
const handleAutocompleteSelect = (result: OwnedMediaResult) => {
setNewItem({
...newItem,
title: result.title,

View File

@@ -0,0 +1,41 @@
/**
* Owned media search (backed by Movie Map DB via backend)
*/
export interface OwnedMediaResult {
id: string
title: string
year: number | null
media_type: 'movie' | 'show'
}
export interface OwnedMediaSearchResponse {
query: string
results: OwnedMediaResult[]
}
export async function searchOwnedMedia(
query: string,
mediaType: 'movie' | 'show',
limit: number = 10
): Promise<OwnedMediaResult[]> {
const q = query.trim()
if (!q) return []
const params = new URLSearchParams({
query: q,
media_type: mediaType,
limit: String(limit),
})
try {
const res = await fetch(`/api/collection/search?${params.toString()}`)
if (!res.ok) return []
const data: OwnedMediaSearchResponse = await res.json()
return data.results || []
} catch {
return []
}
}