Ctrl K
ComunidadGuías…

server/src/routes/media.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import mediaItemsRepo from '../db/repositories/mediaItems';
import {
  CreateMediaItemSchema,
  UpdateMediaItemSchema,
  MediaItemFiltersSchema,
  MediaStatusSchema,
} from '../types';
import logger from '../utils/logger';

const router = Router();

// Validation middleware
function validateBody<T>(schema: z.ZodSchema<T>) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      res.status(400).json({
        success: false,
        error: 'Validation failed',
        details: result.error.issues,
      });
      return;
    }
    req.body = result.data;
    next();
  };
}

function validateQuery<T>(schema: z.ZodSchema<T>) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.query);
    if (!result.success) {
      res.status(400).json({
        success: false,
        error: 'Invalid query parameters',
        details: result.error.issues,
      });
      return;
    }
    // Express 5 makes req.query a getter, so plain assignment throws.
    Object.defineProperty(req, 'query', { value: result.data, writable: true, configurable: true });
    next();
  };
}

// GET /api/media - Get all media items with pagination and filters
router.get('/', validateQuery(MediaItemFiltersSchema), (req: Request, res: Response) => {
  try {
    const filters = req.query as unknown as z.infer<typeof MediaItemFiltersSchema>;
    const result = mediaItemsRepo.getAll(filters);
    res.json({
      success: true,
      ...result,
    });
  } catch (error) {
    logger.error('Failed to get media items:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve media items',
    });
  }
});

// GET /api/media/stats - Get media statistics
router.get('/stats', (_req: Request, res: Response) => {
  try {
    const stats = mediaItemsRepo.getStats();
    res.json({
      success: true,
      data: stats,
    });
  } catch (error) {
    logger.error('Failed to get media stats:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve statistics',
    });
  }
});

// GET /api/media/flagged - Get flagged media items
router.get('/flagged', (_req: Request, res: Response) => {
  try {
    const items = mediaItemsRepo.getFlagged();
    res.json({
      success: true,
      data: items,
      total: items.length,
    });
  } catch (error) {
    logger.error('Failed to get flagged media:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve flagged media',
    });
  }
});

// GET /api/media/protected - Get protected media items
router.get('/protected', (_req: Request, res: Response) => {
  try {
    const items = mediaItemsRepo.getProtected();
    res.json({
      success: true,
      data: items,
      total: items.length,
    });
  } catch (error) {
    logger.error('Failed to get protected media:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve protected media',
    });
  }
});

// GET /api/media/pending - Get pending deletion items
router.get('/pending', (_req: Request, res: Response) => {
  try {
    const items = mediaItemsRepo.getPendingDeletion();
    res.json({
      success: true,
      data: items,
      total: items.length,
    });
  } catch (error) {
    logger.error('Failed to get pending deletion media:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve pending media',
    });
  }
});

// GET /api/media/requesters - Distinct Overseerr/Jellyseerr requester names
router.get('/requesters', (_req: Request, res: Response) => {
  try {
    const requesters = mediaItemsRepo.getDistinctRequesters();
    res.json({
      success: true,
      data: requesters,
    });
  } catch (error) {
    logger.error('Failed to get requesters:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve requesters',
    });
  }
});

// GET /api/media/:id - Get a specific media item
router.get('/:id', (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const item = mediaItemsRepo.getById(id);
    if (!item) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      data: item,
    });
  } catch (error) {
    logger.error('Failed to get media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to retrieve media item',
    });
  }
});

// POST /api/media - Create a new media item
router.post('/', validateBody(CreateMediaItemSchema), (req: Request, res: Response) => {
  try {
    const item = mediaItemsRepo.create(req.body);
    res.status(201).json({
      success: true,
      data: item,
      message: 'Media item created successfully',
    });
  } catch (error) {
    logger.error('Failed to create media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to create media item',
    });
  }
});

// PUT /api/media/:id - Update a media item
router.put('/:id', validateBody(UpdateMediaItemSchema), (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const item = mediaItemsRepo.update(id, req.body);
    if (!item) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      data: item,
      message: 'Media item updated successfully',
    });
  } catch (error) {
    logger.error('Failed to update media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to update media item',
    });
  }
});

// DELETE /api/media/:id - Delete a media item
router.delete('/:id', (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const deleted = mediaItemsRepo.delete(id);
    if (!deleted) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      message: 'Media item deleted successfully',
    });
  } catch (error) {
    logger.error('Failed to delete media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to delete media item',
    });
  }
});

// PATCH /api/media/:id/status - Update media item status
const UpdateStatusSchema = z.object({
  status: MediaStatusSchema,
});

router.patch('/:id/status', validateBody(UpdateStatusSchema), (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const item = mediaItemsRepo.updateStatus(id, req.body.status);
    if (!item) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      data: item,
      message: `Status updated to ${req.body.status}`,
    });
  } catch (error) {
    logger.error('Failed to update media status:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to update status',
    });
  }
});

// POST /api/media/:id/protect - Protect a media item from deletion
const ProtectSchema = z.object({
  reason: z.string().min(1),
});

router.post('/:id/protect', validateBody(ProtectSchema), (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const item = mediaItemsRepo.protect(id, req.body.reason);
    if (!item) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      data: item,
      message: 'Media item protected successfully',
    });
  } catch (error) {
    logger.error('Failed to protect media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to protect media item',
    });
  }
});

// POST /api/media/:id/unprotect - Remove protection from a media item
router.post('/:id/unprotect', (req: Request, res: Response) => {
  try {
    const id = parseInt(req.params['id'] as string, 10);
    if (isNaN(id)) {
      res.status(400).json({
        success: false,
        error: 'Invalid media item ID',
      });
      return;
    }

    const item = mediaItemsRepo.unprotect(id);
    if (!item) {
      res.status(404).json({
        success: false,
        error: `Media item not found: ${id}`,
      });
      return;
    }

    res.json({
      success: true,
      data: item,
      message: 'Media item protection removed',
    });
  } catch (error) {
    logger.error('Failed to unprotect media item:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to unprotect media item',
    });
  }
});

// POST /api/media/bulk/status - Bulk update media status
const BulkStatusSchema = z.object({
  ids: z.array(z.number()),
  status: MediaStatusSchema,
});

router.post('/bulk/status', validateBody(BulkStatusSchema), (req: Request, res: Response) => {
  try {
    const { ids, status } = req.body;
    const updated: number[] = [];
    const failed: number[] = [];

    for (const id of ids) {
      const item = mediaItemsRepo.updateStatus(id, status);
      if (item) {
        updated.push(id);
      } else {
        failed.push(id);
      }
    }

    res.json({
      success: true,
      data: { updated, failed },
      message: `${updated.length} item(s) updated, ${failed.length} failed`,
    });
  } catch (error) {
    logger.error('Failed to bulk update media status:', error);
    res.status(500).json({
      success: false,
      error: 'Failed to bulk update status',
    });
  }
});

export default router;