Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
393 changes: 248 additions & 145 deletions client/app/components/SearchMovieModal/SearchMovieModal.tsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/app/components/SearchMovieModal/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default } from './SearchMovieModal';
export type { SearchParams } from './SearchMovieModal';
export type { SearchParams, SearchType } from './SearchMovieModal';
233 changes: 156 additions & 77 deletions client/app/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export async function deleteMovie(id: string): Promise<{ success: boolean; error
*/
export async function createMovie(movieData: Omit<Movie, '_id'>): Promise<{ success: boolean; error?: string; movieId?: string }> {
try {
const response = await fetch(`${API_BASE_URL}/api/movies/`, {
const response = await fetch(`${API_BASE_URL}/api/movies`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -276,7 +276,7 @@ export async function deleteMoviesBatch(movieIds: string[]): Promise<{ success:
}
};

const response = await fetch(`${API_BASE_URL}/api/movies/`, {
const response = await fetch(`${API_BASE_URL}/api/movies`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -326,7 +326,7 @@ export async function updateMoviesBatch(movieIds: string[], updateData: Partial<
}
};

const response = await fetch(`${API_BASE_URL}/api/movies/`, {
const response = await fetch(`${API_BASE_URL}/api/movies`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -364,80 +364,6 @@ export async function updateMoviesBatch(movieIds: string[], updateData: Partial<
}
}

/**
* Search movies using MongoDB Search across multiple fields with pagination support
*/
export async function searchMovies(searchParams: {
plot?: string;
fullplot?: string;
directors?: string;
writers?: string;
cast?: string;
limit?: number;
skip?: number;
search_operator?: 'must' | 'should' | 'mustNot' | 'filter';
}): Promise<{ success: boolean; error?: string; movies?: Movie[]; hasNextPage?: boolean; hasPrevPage?: boolean; totalCount?: number }> {
try {
// Build query parameters
const limit = searchParams.limit || 20;
const skip = searchParams.skip || 0;

const queryParams = new URLSearchParams();

if (searchParams.plot) queryParams.append('plot', searchParams.plot);
if (searchParams.fullplot) queryParams.append('fullplot', searchParams.fullplot);
if (searchParams.directors) queryParams.append('directors', searchParams.directors);
if (searchParams.writers) queryParams.append('writers', searchParams.writers);
if (searchParams.cast) queryParams.append('cast', searchParams.cast);
queryParams.append('limit', limit.toString());
queryParams.append('skip', skip.toString());
if (searchParams.search_operator) queryParams.append('search_operator', searchParams.search_operator);

const response = await fetch(`${API_BASE_URL}/api/movies/search?${queryParams}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const result = await response.json();

if (!response.ok) {
return {
success: false,
error: result.message || result.error?.message || `Failed to search movies: ${response.status}`
};
}

if (!result.success) {
return {
success: false,
error: result.message || result.error?.message || 'API returned error response'
};
}

const responseData = result.data || {};
const movies = responseData.movies || [];
const totalCount = responseData.totalCount || 0;
const hasNextPage = skip + limit < totalCount;
const hasPrevPage = skip > 0;

return {
success: true,
movies,
hasNextPage,
hasPrevPage,
totalCount
};
} catch (error) {
console.error('Error searching movies:', error);
return {
success: false,
error: 'Network error occurred while searching movies'
};
}
}

/**
* Aggregation API Functions for Aggregations
*/
Expand Down Expand Up @@ -655,3 +581,156 @@ export async function fetchDirectorStats(limit: number = 20): Promise<{ success:
};
}
}

/**
* Search movies using MongoDB Search across multiple fields with pagination support
*/
export async function searchMovies(searchParams: {
plot?: string;
fullplot?: string;
directors?: string;
writers?: string;
cast?: string;
limit?: number;
skip?: number;
search_operator?: 'must' | 'should' | 'mustNot' | 'filter';
}): Promise<{ success: boolean; error?: string; movies?: Movie[]; hasNextPage?: boolean; hasPrevPage?: boolean; totalCount?: number }> {
try {
// Build query parameters
const limit = searchParams.limit || 20;
const skip = searchParams.skip || 0;

const queryParams = new URLSearchParams();

if (searchParams.plot) queryParams.append('plot', searchParams.plot);
if (searchParams.fullplot) queryParams.append('fullplot', searchParams.fullplot);
if (searchParams.directors) queryParams.append('directors', searchParams.directors);
if (searchParams.writers) queryParams.append('writers', searchParams.writers);
if (searchParams.cast) queryParams.append('cast', searchParams.cast);
queryParams.append('limit', limit.toString());
queryParams.append('skip', skip.toString());
if (searchParams.search_operator) queryParams.append('search_operator', searchParams.search_operator);

const response = await fetch(`${API_BASE_URL}/api/movies/search?${queryParams}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const result = await response.json();

if (!response.ok) {
return {
success: false,
error: result.message || result.error?.message || `Failed to search movies: ${response.status}`
};
}

if (!result.success) {
return {
success: false,
error: result.message || result.error?.message || 'API returned error response'
};
}

const responseData = result.data || {};
const movies = responseData.movies || [];
const totalCount = responseData.totalCount || 0;
const hasNextPage = skip + limit < totalCount;
const hasPrevPage = skip > 0;

return {
success: true,
movies,
hasNextPage,
hasPrevPage,
totalCount
};
} catch (error) {
console.error('Error searching movies:', error);
return {
success: false,
error: 'Network error occurred while searching movies'
};
}
}

/**
* Search movies using MongoDB Vector Search to find movies with similar plots
*/
export async function vectorSearchMovies(searchParams: {
q: string;
limit?: number;
}): Promise<{ success: boolean; error?: string; movies?: Movie[]; results?: any[] }> {
try {
const limit = searchParams.limit || 10;

const queryParams = new URLSearchParams();
queryParams.append('q', searchParams.q);
queryParams.append('limit', limit.toString());

const response = await fetch(`${API_BASE_URL}/api/movies/vector-search?${queryParams}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});

const result = await response.json();

if (!response.ok) {
return {
success: false,
error: result.error || `Failed to perform vector search: ${response.status}`
};
}

if (!result.success) {
return {
success: false,
error: result.error || 'API returned error response'
};
}

// Transform VectorSearchResult objects to Movie objects for backend compatibility
const movies: Movie[] = (result.data || []).map((item: any) => {
// Convert VectorSearchResult to Movie format
return {
_id: item._id || item.id, // Handle both _id (Python) and id (Java) field names
title: item.title || '',
plot: item.plot || '',
poster: item.poster,
year: item.year,
genres: item.genres || [],
directors: item.directors || [],
cast: item.cast || [],
// Add default values for fields not included in VectorSearchResult
fullplot: undefined,
released: undefined,
runtime: undefined,
writers: [],
countries: [],
languages: [],
rated: undefined,
awards: undefined,
imdb: undefined,
tomatoes: undefined,
metacritic: undefined,
type: undefined
} as Movie;
});

return {
success: true,
movies,
results: result.data || []
};
} catch (error) {
console.error('Error performing vector search:', error);
return {
success: false,
error: 'Network error occurred while performing vector search'
};
}
}
11 changes: 11 additions & 0 deletions client/app/movies/movies.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@
text-align: center;
}

.searchInfo {
margin: 2rem 0;
padding: 1rem;
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
text-align: center;
font-size: 0.9rem;
color: #666;
}

.selectAllButton {
padding: 0.5rem 1rem;
background: #6c757d;
Expand Down
Loading