diff --git a/src/controllers/CommentReportController.ts b/src/controllers/CommentReportController.ts new file mode 100644 index 0000000..f63c09a --- /dev/null +++ b/src/controllers/CommentReportController.ts @@ -0,0 +1,69 @@ +import { Request, Response } from "express"; +import { AppDataSource } from "../config/data-source"; +import { CommentReport, CommentReportStatus } from "../entities/CommentReport"; +import { handleError } from "../utils/helpers"; +import { AppError } from "../errors/AppError"; + +export class CommentReportController { + flag = async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return handleError(req, res, AppError.authentication("Not authenticated")); + + const { commentId, reason, description } = req.body; + if (!commentId) return handleError(req, res, AppError.badRequest("commentId required")); + + const repo = AppDataSource.getRepository(CommentReport); + const existing = await repo.findOne({ where: { commentId, reporterId: userId } }); + if (existing) return handleError(req, res, AppError.conflict("Already flagged this comment")); + + const report = repo.create({ + commentId, + reporterId: userId, + reason: reason || "other", + description: description || null, + }); + await repo.save(report); + res.status(201).json({ report }); + } catch (error) { + handleError(req, res, error); + } + }; + + getQueue = async (req: Request, res: Response): Promise => { + try { + const repo = AppDataSource.getRepository(CommentReport); + const reports = await repo.find({ + where: { status: CommentReportStatus.PENDING }, + relations: ["comment", "reporter"], + order: { createdAt: "ASC" }, + }); + res.json({ reports, count: reports.length }); + } catch (error) { + handleError(req, res, error); + } + }; + + resolve = async (req: Request, res: Response): Promise => { + try { + const moderatorId = (req as any).user?.id; + const { id } = req.params; + const { action, note } = req.body; + + const repo = AppDataSource.getRepository(CommentReport); + const report = await repo.findOneBy({ id }); + if (!report) return handleError(req, res, AppError.notFound("Report not found")); + + report.status = CommentReportStatus.RESOLVED; + report.actionTaken = action || "no_action"; + report.resolvedBy = moderatorId; + report.resolvedAt = new Date(); + report.resolutionNote = note || null; + await repo.save(report); + + res.json({ report }); + } catch (error) { + handleError(req, res, error); + } + }; +} diff --git a/src/controllers/PlaylistFollowController.ts b/src/controllers/PlaylistFollowController.ts new file mode 100644 index 0000000..75938fc --- /dev/null +++ b/src/controllers/PlaylistFollowController.ts @@ -0,0 +1,68 @@ +import { Request, Response } from "express"; +import { AppDataSource } from "../config/data-source"; +import { PlaylistFollow } from "../entities/PlaylistFollow"; +import { handleError } from "../utils/helpers"; +import { AppError } from "../errors/AppError"; + +export class PlaylistFollowController { + follow = async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return handleError(req, res, AppError.authentication("Not authenticated")); + + const { playlistId } = req.body; + if (!playlistId) return handleError(req, res, AppError.badRequest("playlistId required")); + + const repo = AppDataSource.getRepository(PlaylistFollow); + const existing = await repo.findOne({ where: { userId, playlistId } }); + if (existing) return res.status(200).json({ follow: existing }); + + const follow = repo.create({ userId, playlistId }); + await repo.save(follow); + res.status(201).json({ follow }); + } catch (error) { + handleError(req, res, error); + } + }; + + unfollow = async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return handleError(req, res, AppError.authentication("Not authenticated")); + + const { playlistId } = req.params; + const repo = AppDataSource.getRepository(PlaylistFollow); + const follow = await repo.findOne({ where: { userId, playlistId } }); + if (!follow) return handleError(req, res, AppError.notFound("Not following this playlist")); + + await repo.remove(follow); + res.json({ deleted: true }); + } catch (error) { + handleError(req, res, error); + } + }; + + getFollowers = async (req: Request, res: Response): Promise => { + try { + const { playlistId } = req.params; + const repo = AppDataSource.getRepository(PlaylistFollow); + const follows = await repo.find({ where: { playlistId }, relations: ["user"] }); + res.json({ followers: follows.map((f) => f.user), count: follows.length }); + } catch (error) { + handleError(req, res, error); + } + }; + + getMyFollows = async (req: Request, res: Response): Promise => { + try { + const userId = (req as any).user?.id; + if (!userId) return handleError(req, res, AppError.authentication("Not authenticated")); + + const repo = AppDataSource.getRepository(PlaylistFollow); + const follows = await repo.find({ where: { userId }, relations: ["playlist"] }); + res.json({ playlists: follows.map((f) => f.playlist) }); + } catch (error) { + handleError(req, res, error); + } + }; +} diff --git a/src/routes/commentReportRoutes.ts b/src/routes/commentReportRoutes.ts new file mode 100644 index 0000000..0439ce3 --- /dev/null +++ b/src/routes/commentReportRoutes.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import { CommentReportController } from "../controllers/CommentReportController"; +import { requireAuth } from "../middlewares/authMiddleware"; + +const router = Router(); +const c = new CommentReportController(); + +// Any authenticated user can flag a comment +router.post("/flag", requireAuth, c.flag); + +// Moderators: view pending queue and resolve reports +router.get("/queue", requireAuth, c.getQueue); +router.post("/:id/resolve", requireAuth, c.resolve); + +export default router; diff --git a/src/routes/playlistFollowRoutes.ts b/src/routes/playlistFollowRoutes.ts new file mode 100644 index 0000000..713c7e1 --- /dev/null +++ b/src/routes/playlistFollowRoutes.ts @@ -0,0 +1,13 @@ +import { Router } from "express"; +import { PlaylistFollowController } from "../controllers/PlaylistFollowController"; +import { requireAuth } from "../middlewares/authMiddleware"; + +const router = Router(); +const c = new PlaylistFollowController(); + +router.post("/follow", requireAuth, c.follow); +router.delete("/follow/:playlistId", requireAuth, c.unfollow); +router.get("/followed", requireAuth, c.getMyFollows); +router.get("/:playlistId/followers", requireAuth, c.getFollowers); + +export default router;