first commit
This commit is contained in:
222
src/routes/tmdb.routes.ts
Normal file
222
src/routes/tmdb.routes.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { authMiddleware } from '../middleware/auth.middleware.js';
|
||||
import { scrapeRateLimiter } from '../middleware/rateLimit.middleware.js';
|
||||
import { TmdbService } from '../services/tmdb.service.js';
|
||||
import type {
|
||||
ApiResponse,
|
||||
TmdbSearchResponse,
|
||||
} from '../types/index.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* Validation schema for TMDB search
|
||||
*/
|
||||
const tmdbSearchSchema = z.object({
|
||||
query: z.string().trim().min(1, 'Query must be at least 1 character').max(200, 'Query must be at most 200 characters'),
|
||||
year: z.coerce.number().int().min(1900).max(new Date().getFullYear() + 10).optional(),
|
||||
type: z.enum(['movie', 'tv', 'multi']).optional(),
|
||||
seasonYear: z.coerce.number().int().min(1900).max(new Date().getFullYear() + 10).optional(),
|
||||
seasonNumber: z.coerce.number().int().min(1).max(100).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/tmdb/search
|
||||
* Search for movies and TV shows using TMDB API
|
||||
*
|
||||
* Request body: { query: string, year?: number, type?: 'movie' | 'tv' | 'multi' }
|
||||
* Headers: X-API-Key: <api_key>
|
||||
*
|
||||
* Response: { success: boolean, data?: TmdbSearchResponse, error?: ApiError }
|
||||
*/
|
||||
router.post(
|
||||
'/search',
|
||||
authMiddleware,
|
||||
scrapeRateLimiter,
|
||||
async (
|
||||
req: Request,
|
||||
res: Response<ApiResponse<TmdbSearchResponse>>
|
||||
) => {
|
||||
// Validate request body
|
||||
const result = tmdbSearchSchema.safeParse(req.body);
|
||||
|
||||
if (!result.success) {
|
||||
const errors = result.error.issues.map((issue) => ({
|
||||
field: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
}));
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid request parameters',
|
||||
details: { errors },
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const { query, year, type, seasonYear, seasonNumber } = result.data;
|
||||
|
||||
try {
|
||||
const searchResult = await TmdbService.search({
|
||||
query,
|
||||
year,
|
||||
type: type || 'multi',
|
||||
seasonYear,
|
||||
seasonNumber,
|
||||
});
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: true,
|
||||
data: searchResult,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TMDB_ERROR',
|
||||
message:
|
||||
error instanceof Error ? error.message : 'Failed to search TMDB',
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/tmdb/search/movie
|
||||
* Search for movies only
|
||||
*/
|
||||
router.post(
|
||||
'/search/movie',
|
||||
authMiddleware,
|
||||
scrapeRateLimiter,
|
||||
async (
|
||||
req: Request,
|
||||
res: Response<ApiResponse<TmdbSearchResponse>>
|
||||
) => {
|
||||
const movieSearchSchema = z.object({
|
||||
query: z.string().trim().min(1).max(200),
|
||||
year: z.coerce.number().int().min(1900).max(new Date().getFullYear() + 10).optional(),
|
||||
});
|
||||
|
||||
const result = movieSearchSchema.safeParse(req.body);
|
||||
|
||||
if (!result.success) {
|
||||
const errors = result.error.issues.map((issue) => ({
|
||||
field: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
}));
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid request parameters',
|
||||
details: { errors },
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const { query, year } = result.data;
|
||||
|
||||
try {
|
||||
const searchResult = await TmdbService.searchMovies(query, year);
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: true,
|
||||
data: searchResult,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TMDB_ERROR',
|
||||
message:
|
||||
error instanceof Error ? error.message : 'Failed to search movies',
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* POST /api/tmdb/search/tv
|
||||
* Search for TV shows only
|
||||
*/
|
||||
router.post(
|
||||
'/search/tv',
|
||||
authMiddleware,
|
||||
scrapeRateLimiter,
|
||||
async (
|
||||
req: Request,
|
||||
res: Response<ApiResponse<TmdbSearchResponse>>
|
||||
) => {
|
||||
const tvSearchSchema = z.object({
|
||||
query: z.string().trim().min(1).max(200),
|
||||
year: z.coerce.number().int().min(1900).max(new Date().getFullYear() + 10).optional(),
|
||||
seasonYear: z.coerce.number().int().min(1900).max(new Date().getFullYear() + 10).optional(),
|
||||
seasonNumber: z.coerce.number().int().min(1).max(100).optional(),
|
||||
});
|
||||
|
||||
const result = tvSearchSchema.safeParse(req.body);
|
||||
|
||||
if (!result.success) {
|
||||
const errors = result.error.issues.map((issue) => ({
|
||||
field: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
}));
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid request parameters',
|
||||
details: { errors },
|
||||
},
|
||||
};
|
||||
res.status(400).json(response);
|
||||
return;
|
||||
}
|
||||
|
||||
const { query, year, seasonYear, seasonNumber } = result.data;
|
||||
|
||||
try {
|
||||
const searchResult = await TmdbService.searchTv(query, year, seasonNumber, seasonYear);
|
||||
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: true,
|
||||
data: searchResult,
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
const response: ApiResponse<TmdbSearchResponse> = {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TMDB_ERROR',
|
||||
message:
|
||||
error instanceof Error ? error.message : 'Failed to search TV shows',
|
||||
},
|
||||
};
|
||||
|
||||
res.status(500).json(response);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user