Skip to content

Commit 76029d8

Browse files
committed
Get distinct genres
1 parent 3dc56d7 commit 76029d8

4 files changed

Lines changed: 100 additions & 13 deletions

File tree

mflix/client/app/components/FilterBar/FilterBar.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,7 @@
22

33
import { useState, useCallback, useEffect } from 'react';
44
import styles from './FilterBar.module.css';
5-
import type { MovieFilterParams } from '@/lib/api';
6-
7-
const GENRES = [
8-
'Action', 'Adventure', 'Animation', 'Biography', 'Comedy', 'Crime',
9-
'Documentary', 'Drama', 'Family', 'Fantasy', 'Film-Noir', 'History',
10-
'Horror', 'Music', 'Musical', 'Mystery', 'Romance', 'Sci-Fi',
11-
'Short', 'Sport', 'Thriller', 'War', 'Western'
12-
];
5+
import { fetchGenres, type MovieFilterParams } from '@/lib/api';
136

147
const SORT_OPTIONS = [
158
{ value: 'title', label: 'Title' },
@@ -29,6 +22,19 @@ export default function FilterBar({
2922
initialFilters = {}
3023
}: FilterBarProps) {
3124
const [filters, setFilters] = useState<MovieFilterParams>(initialFilters);
25+
const [genres, setGenres] = useState<string[]>([]);
26+
const [isLoadingGenres, setIsLoadingGenres] = useState(true);
27+
28+
// Fetch genres from the API on mount
29+
useEffect(() => {
30+
async function loadGenres() {
31+
setIsLoadingGenres(true);
32+
const fetchedGenres = await fetchGenres();
33+
setGenres(fetchedGenres);
34+
setIsLoadingGenres(false);
35+
}
36+
loadGenres();
37+
}, []);
3238

3339
// Sync internal state when initialFilters changes (e.g. from URL navigation)
3440
useEffect(() => {
@@ -97,10 +103,10 @@ export default function FilterBar({
97103
className={styles.filterSelect}
98104
value={filters.genre || ''}
99105
onChange={(e) => handleFilterChange('genre', e.target.value)}
100-
disabled={isLoading}
106+
disabled={isLoading || isLoadingGenres}
101107
>
102-
<option value="">All Genres</option>
103-
{GENRES.map(genre => (
108+
<option value="">{isLoadingGenres ? 'Loading...' : 'All Genres'}</option>
109+
{genres.map(genre => (
104110
<option key={genre} value={genre}>{genre}</option>
105111
))}
106112
</select>

mflix/client/app/lib/api.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export interface MovieFilterParams {
2020
}
2121

2222
/**
23-
* Fetches movies from the backend API with pagination and filtering support.
24-
* Demonstrates using MongoDB find() with query filters.
23+
* Fetches movies from the backend API with pagination and filtering support
24+
* using MongoDB find() with query filters.
2525
*/
2626
export async function fetchMovies(
2727
limit: number = 20,
@@ -99,6 +99,34 @@ export async function fetchMovies(
9999
}
100100
}
101101

102+
/**
103+
* Fetches all unique genres from the backend API
104+
* using MongoDB's distinct() operation.
105+
*/
106+
export async function fetchGenres(): Promise<string[]> {
107+
try {
108+
const response = await fetch(`${API_BASE_URL}/api/movies/genres`, {
109+
next: { revalidate: 3600 }, // Cache genres for 1 hour since they rarely change
110+
});
111+
112+
if (!response.ok) {
113+
throw new Error(`Failed to fetch genres: ${response.status}`);
114+
}
115+
116+
const result: { success: boolean; data: string[]; message: string } = await response.json();
117+
118+
if (!result.success) {
119+
throw new Error('API returned error response');
120+
}
121+
122+
return result.data;
123+
} catch (error) {
124+
console.error('Error fetching genres:', error);
125+
// Return empty array on error - FilterBar will handle gracefully
126+
return [];
127+
}
128+
}
129+
102130
/**
103131
* Fetch a single movie by ID
104132
*/

mflix/server/js-express/src/controllers/movieController.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,34 @@ export async function getAllMovies(req: Request, res: Response): Promise<void> {
134134
res.json(createSuccessResponse(movies, `Found ${movies.length} movies`));
135135
}
136136

137+
/**
138+
* GET /api/movies/genres
139+
*
140+
* Retrieves all unique genres from the movies collection.
141+
* Demonstrates the distinct() operation.
142+
*
143+
* Returns an array of unique genre strings, sorted alphabetically.
144+
*/
145+
export async function getDistinctGenres(
146+
req: Request,
147+
res: Response
148+
): Promise<void> {
149+
const moviesCollection = getCollection("movies");
150+
151+
// Use distinct() to get all unique values from the genres array field
152+
// MongoDB automatically flattens array fields when using distinct()
153+
const genres = await moviesCollection.distinct("genres");
154+
155+
// Filter out null/empty values and sort alphabetically
156+
const validGenres = genres
157+
.filter((genre): genre is string => typeof genre === "string" && genre.length > 0)
158+
.sort((a, b) => a.localeCompare(b));
159+
160+
res.json(
161+
createSuccessResponse(validGenres, `Found ${validGenres.length} distinct genres`)
162+
);
163+
}
164+
137165
/**
138166
* GET /api/movies/:id
139167
*

mflix/server/js-express/src/routes/movies.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,31 @@ const router = express.Router();
118118
*/
119119
router.get("/", asyncHandler(movieController.getAllMovies));
120120

121+
/**
122+
* @swagger
123+
* /api/movies/genres:
124+
* get:
125+
* summary: Get all distinct genres
126+
* description: Retrieves all unique genres from the movies collection. Demonstrates the MongoDB distinct() operation.
127+
* tags: [Movies]
128+
* responses:
129+
* 200:
130+
* description: List of distinct genres
131+
* content:
132+
* application/json:
133+
* schema:
134+
* allOf:
135+
* - $ref: '#/components/schemas/SuccessResponse'
136+
* - type: object
137+
* properties:
138+
* data:
139+
* type: array
140+
* items:
141+
* type: string
142+
* example: ["Action", "Adventure", "Animation", "Comedy", "Drama"]
143+
*/
144+
router.get("/genres", asyncHandler(movieController.getDistinctGenres));
145+
121146
/**
122147
* @swagger
123148
* /api/movies/search:

0 commit comments

Comments
 (0)