feat: restore landing style and add separate courses/admin UX
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Param, Body, Query, Delete, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { ModerationService } from './moderation.service';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
@ -15,6 +15,15 @@ export class ModerationController {
|
||||
return this.moderationService.getPendingCourses(user.id);
|
||||
}
|
||||
|
||||
@Get('courses')
|
||||
async getCourses(
|
||||
@CurrentUser() user: User,
|
||||
@Query('status') status?: string,
|
||||
@Query('search') search?: string,
|
||||
): Promise<any> {
|
||||
return this.moderationService.getCourses(user.id, { status, search });
|
||||
}
|
||||
|
||||
@Post(':courseId/approve')
|
||||
async approveCourse(@Param('courseId') courseId: string, @Body('note') note: string, @CurrentUser() user: User): Promise<any> {
|
||||
return this.moderationService.approveCourse(user.id, courseId, note);
|
||||
@ -34,4 +43,10 @@ export class ModerationController {
|
||||
async unhideReview(@Param('reviewId') reviewId: string, @CurrentUser() user: User): Promise<any> {
|
||||
return this.moderationService.unhideReview(user.id, reviewId);
|
||||
}
|
||||
|
||||
@Delete(':courseId')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
async deleteCourse(@Param('courseId') courseId: string, @CurrentUser() user: User): Promise<void> {
|
||||
await this.moderationService.deleteCourse(user.id, courseId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Injectable, ForbiddenException } from '@nestjs/common';
|
||||
import { Injectable, ForbiddenException, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../common/prisma/prisma.service';
|
||||
import { CourseStatus, UserRole } from '@coursecraft/database';
|
||||
|
||||
@ -22,6 +22,48 @@ export class ModerationService {
|
||||
});
|
||||
}
|
||||
|
||||
async getCourses(
|
||||
userId: string,
|
||||
options?: {
|
||||
status?: string;
|
||||
search?: string;
|
||||
}
|
||||
): Promise<any[]> {
|
||||
await this.assertStaff(userId);
|
||||
|
||||
const allowedStatuses = Object.values(CourseStatus);
|
||||
const where: any = {};
|
||||
|
||||
if (options?.status && allowedStatuses.includes(options.status as CourseStatus)) {
|
||||
where.status = options.status as CourseStatus;
|
||||
}
|
||||
|
||||
if (options?.search?.trim()) {
|
||||
where.OR = [
|
||||
{ title: { contains: options.search.trim(), mode: 'insensitive' } },
|
||||
{ description: { contains: options.search.trim(), mode: 'insensitive' } },
|
||||
{ author: { name: { contains: options.search.trim(), mode: 'insensitive' } } },
|
||||
{ author: { email: { contains: options.search.trim(), mode: 'insensitive' } } },
|
||||
];
|
||||
}
|
||||
|
||||
return this.prisma.course.findMany({
|
||||
where,
|
||||
include: {
|
||||
author: { select: { id: true, name: true, email: true } },
|
||||
_count: {
|
||||
select: {
|
||||
chapters: true,
|
||||
enrollments: true,
|
||||
reviews: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [{ status: 'asc' }, { updatedAt: 'desc' }],
|
||||
take: 200,
|
||||
});
|
||||
}
|
||||
|
||||
async approveCourse(userId: string, courseId: string, note?: string): Promise<any> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user || (user.role !== UserRole.MODERATOR && user.role !== UserRole.ADMIN)) {
|
||||
@ -98,6 +140,19 @@ export class ModerationService {
|
||||
return review;
|
||||
}
|
||||
|
||||
async deleteCourse(userId: string, courseId: string): Promise<void> {
|
||||
await this.assertStaff(userId);
|
||||
const existing = await this.prisma.course.findUnique({
|
||||
where: { id: courseId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Course not found');
|
||||
}
|
||||
|
||||
await this.prisma.course.delete({ where: { id: courseId } });
|
||||
}
|
||||
|
||||
private async assertStaff(userId: string): Promise<void> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user || (user.role !== UserRole.MODERATOR && user.role !== UserRole.ADMIN)) {
|
||||
|
||||
Reference in New Issue
Block a user