chore: complete learning module
This commit is contained in:
@@ -18,6 +18,7 @@ import { ContactUsModule } from "./modules/contact-us/contact-us.module";
|
|||||||
import { CriticismModule } from "./modules/criticisms/criticisms.module";
|
import { CriticismModule } from "./modules/criticisms/criticisms.module";
|
||||||
import { DanakServicesModule } from "./modules/danak-services/danak-services.module";
|
import { DanakServicesModule } from "./modules/danak-services/danak-services.module";
|
||||||
import { InvoicesModule } from "./modules/invoices/invoices.module";
|
import { InvoicesModule } from "./modules/invoices/invoices.module";
|
||||||
|
import { LearningModule } from "./modules/learning/learning.module";
|
||||||
import { NotificationModule } from "./modules/notifications/notifications.module";
|
import { NotificationModule } from "./modules/notifications/notifications.module";
|
||||||
import { PaymentsModule } from "./modules/payments/payments.module";
|
import { PaymentsModule } from "./modules/payments/payments.module";
|
||||||
import { SettingModule } from "./modules/settings/settings.module";
|
import { SettingModule } from "./modules/settings/settings.module";
|
||||||
@@ -50,6 +51,7 @@ import { WalletsModule } from "./modules/wallets/wallets.module";
|
|||||||
NotificationModule,
|
NotificationModule,
|
||||||
InvoicesModule,
|
InvoicesModule,
|
||||||
SubscriptionsModule,
|
SubscriptionsModule,
|
||||||
|
LearningModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@@ -291,3 +291,18 @@ export const enum SubscriptionMessage {
|
|||||||
NOT_FOUND = "اشتراک یافت نشد",
|
NOT_FOUND = "اشتراک یافت نشد",
|
||||||
UPDATED = "اشتراک با موفقیت به روز رسانی شد",
|
UPDATED = "اشتراک با موفقیت به روز رسانی شد",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum LearningMessage {
|
||||||
|
TITLE_REQUIRED = "عنوان الزامی است",
|
||||||
|
TITLE_STRING = "عنوان باید یک رشته باشد",
|
||||||
|
DESCRIPTION_REQUIRED = "توضیحات الزامی است",
|
||||||
|
DESCRIPTION_STRING = "توضیحات باید یک رشته باشد",
|
||||||
|
COVER_URL_REQUIRED = "آدرس تصویر کاور الزامی است",
|
||||||
|
COVER_URL_INVALID = "آدرس تصویر کاور معتبر نیست",
|
||||||
|
VIDEO_URL_INVALID = "آدرس ویدیو معتبر نیست",
|
||||||
|
VIDEO_DURATION_INVALID = "مدت زمان ویدیو معتبر نیست",
|
||||||
|
NOT_FOUND = "آموزشی با این مشخصات یافت نشد",
|
||||||
|
CATEGORY_NOT_FOUND = "دسته بندی با این مشخصات یافت نشد",
|
||||||
|
CATEGORY_MUST_BE_ID = "دستهبندی باید شناسه باشد",
|
||||||
|
CATEGORY_MUST_BE_UUID = "دستهبندی باید UUID باشد",
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { LearningMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateLearningCategoryDto {
|
||||||
|
@ApiProperty({ description: "name of the learning category", maxLength: 200 })
|
||||||
|
@IsString({ message: LearningMessage.TITLE_STRING })
|
||||||
|
@IsNotEmpty({ message: LearningMessage.TITLE_REQUIRED })
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsOptional, IsString, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
import { LearningMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateLearningDto {
|
||||||
|
@ApiProperty({ description: "Title of the learning", maxLength: 200 })
|
||||||
|
@IsString({ message: LearningMessage.TITLE_STRING })
|
||||||
|
@IsNotEmpty({ message: LearningMessage.TITLE_REQUIRED })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: "Description of the learning" })
|
||||||
|
@IsString({ message: LearningMessage.DESCRIPTION_STRING })
|
||||||
|
@IsNotEmpty({ message: LearningMessage.DESCRIPTION_REQUIRED })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: "Cover image URL" })
|
||||||
|
@IsString({ message: LearningMessage.COVER_URL_INVALID })
|
||||||
|
@IsNotEmpty({ message: LearningMessage.COVER_URL_REQUIRED })
|
||||||
|
coverUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: "Video URL", required: false })
|
||||||
|
@IsString({ message: LearningMessage.VIDEO_URL_INVALID })
|
||||||
|
@IsOptional()
|
||||||
|
videoUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: "Video Duration", required: false })
|
||||||
|
@IsString({ message: LearningMessage.VIDEO_URL_INVALID })
|
||||||
|
@IsOptional()
|
||||||
|
videoDuration?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: LearningMessage.CATEGORY_MUST_BE_ID })
|
||||||
|
@IsUUID("4", { message: LearningMessage.CATEGORY_MUST_BE_UUID })
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Category ID",
|
||||||
|
example: "d290f1ee-6c54-4b01-90e6-d701748f0851",
|
||||||
|
})
|
||||||
|
categoryId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Column, Entity, OneToMany } from "typeorm";
|
||||||
|
|
||||||
|
import { Learning } from "./learning.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class LearningCategory extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 100 })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@OneToMany(() => Learning, (learning) => learning.category)
|
||||||
|
learnings: Learning[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Column, Entity, ManyToOne } from "typeorm";
|
||||||
|
|
||||||
|
import { Learning } from "./learning.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class LearningProgress extends BaseEntity {
|
||||||
|
@ManyToOne(() => User, (user) => user.learningProgress)
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@ManyToOne(() => Learning, (learning) => learning.learningProgress)
|
||||||
|
learning: Learning;
|
||||||
|
|
||||||
|
@Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
||||||
|
watchedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Column, Entity, ManyToOne, OneToMany } from "typeorm";
|
||||||
|
|
||||||
|
import { LearningCategory } from "./learning-category.entity";
|
||||||
|
import { LearningProgress } from "./learning-progress.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Learning extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 200 })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: "text" })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ type: "text" })
|
||||||
|
coverUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: "text", nullable: true })
|
||||||
|
videoUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 200, nullable: true })
|
||||||
|
videoDuration: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => LearningCategory, (category) => category.learnings, { eager: true })
|
||||||
|
category: LearningCategory;
|
||||||
|
|
||||||
|
@OneToMany(() => LearningProgress, (learningProgress) => learningProgress.learning)
|
||||||
|
learningProgress: LearningProgress[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
|
||||||
|
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateLearningCategoryDto } from "./DTO/create-learning-category.dto";
|
||||||
|
import { CreateLearningDto } from "./DTO/create-learning.dto";
|
||||||
|
import { LearningService } from "./providers/learning.service";
|
||||||
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
|
import { Roles } from "../../common/decorators/roles.decorator";
|
||||||
|
import { ParamDto } from "../../common/DTO/param.dto";
|
||||||
|
import { RoleEnum } from "../users/enums/role.enum";
|
||||||
|
|
||||||
|
@ApiTags("Learning")
|
||||||
|
@AuthGuards()
|
||||||
|
@Controller("learnings")
|
||||||
|
export class LearningController {
|
||||||
|
constructor(private readonly learningService: LearningService) {}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Create learning" })
|
||||||
|
@Roles(RoleEnum.ADMIN)
|
||||||
|
@Post()
|
||||||
|
create(@Body() createDto: CreateLearningDto) {
|
||||||
|
return this.learningService.createLearning(createDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Create learning category" })
|
||||||
|
@Roles(RoleEnum.ADMIN)
|
||||||
|
@Post("category")
|
||||||
|
createCategory(@Body() createDto: CreateLearningCategoryDto) {
|
||||||
|
return this.learningService.createLearningCategory(createDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "find all learnings" })
|
||||||
|
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
|
||||||
|
@Get()
|
||||||
|
findAllLearnings() {
|
||||||
|
return this.learningService.findAllLearnings();
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "find one learning by id" })
|
||||||
|
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
|
||||||
|
@Get(":id")
|
||||||
|
findOneLearning(@Param() paramDto: ParamDto) {
|
||||||
|
return this.learningService.findOneLearning(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "find all learning category" })
|
||||||
|
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
|
||||||
|
@Get("category")
|
||||||
|
findAllCategories() {
|
||||||
|
return this.learningService.findAllCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "find one learning category by id" })
|
||||||
|
@Roles(RoleEnum.ADMIN, RoleEnum.USER)
|
||||||
|
@Get("category/:id")
|
||||||
|
findOneCategory(@Param() paramDto: ParamDto) {
|
||||||
|
return this.learningService.findOneCategory(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//************************ */
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
|
||||||
|
import { LearningCategory } from "./entities/learning-category.entity";
|
||||||
|
import { LearningProgress } from "./entities/learning-progress.entity";
|
||||||
|
import { Learning } from "./entities/learning.entity";
|
||||||
|
import { LearningController } from "./learning.controller";
|
||||||
|
import { LearningService } from "./providers/learning.service";
|
||||||
|
import { LearningCategoryRepository } from "./repositories/learning-category.repository";
|
||||||
|
import { LearningProgressRepository } from "./repositories/learning-progress.repository";
|
||||||
|
import { LearningRepository } from "./repositories/learning.repository";
|
||||||
|
import { UsersModule } from "../users/users.module";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Learning, LearningCategory, LearningProgress]), UsersModule],
|
||||||
|
providers: [LearningService, LearningRepository, LearningCategoryRepository, LearningProgressRepository],
|
||||||
|
controllers: [LearningController],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class LearningModule {}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
import { CommonMessage, LearningMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
|
import { CreateLearningCategoryDto } from "../DTO/create-learning-category.dto";
|
||||||
|
import { CreateLearningDto } from "../DTO/create-learning.dto";
|
||||||
|
import { LearningCategoryRepository } from "../repositories/learning-category.repository";
|
||||||
|
import { LearningProgressRepository } from "../repositories/learning-progress.repository";
|
||||||
|
import { LearningRepository } from "../repositories/learning.repository";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LearningService {
|
||||||
|
constructor(
|
||||||
|
private readonly learningRepository: LearningRepository,
|
||||||
|
private readonly learningProgressRepository: LearningProgressRepository,
|
||||||
|
private readonly learningCategoryRepository: LearningCategoryRepository,
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async createLearning(createDto: CreateLearningDto) {
|
||||||
|
const { category } = await this.findOneCategory(createDto.categoryId);
|
||||||
|
|
||||||
|
const learning = this.learningRepository.create({
|
||||||
|
...createDto,
|
||||||
|
category,
|
||||||
|
});
|
||||||
|
await this.learningRepository.save(learning);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.CREATED,
|
||||||
|
learning,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async findAllLearnings() {
|
||||||
|
const learnings = await this.learningRepository.find();
|
||||||
|
|
||||||
|
return { learnings };
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async findOneLearning(id: string) {
|
||||||
|
const learning = await this.learningRepository.findOneBy({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
if (!learning) throw new BadRequestException(LearningMessage.NOT_FOUND);
|
||||||
|
|
||||||
|
return { learning };
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async createLearningCategory(createDto: CreateLearningCategoryDto) {
|
||||||
|
const category = this.learningCategoryRepository.create(createDto);
|
||||||
|
await this.learningCategoryRepository.save(category);
|
||||||
|
|
||||||
|
return { category };
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async findAllCategories() {
|
||||||
|
const categories = await this.learningCategoryRepository.find();
|
||||||
|
|
||||||
|
return { categories };
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async findOneCategory(id: string) {
|
||||||
|
const category = await this.learningCategoryRepository.findOneBy({
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
if (!category) throw new BadRequestException(LearningMessage.CATEGORY_NOT_FOUND);
|
||||||
|
|
||||||
|
return { category };
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async trackProgress(userId: string, learningId: string) {
|
||||||
|
const { user } = await this.usersService.findOneById(userId);
|
||||||
|
const { learning } = await this.findOneLearning(learningId);
|
||||||
|
|
||||||
|
const progress = this.learningProgressRepository.create({
|
||||||
|
user,
|
||||||
|
learning,
|
||||||
|
watchedAt: new Date(),
|
||||||
|
});
|
||||||
|
return this.learningProgressRepository.save(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
//******************** */
|
||||||
|
|
||||||
|
async getUserProgress(userId: string) {
|
||||||
|
return this.learningProgressRepository.find({
|
||||||
|
where: { user: { id: userId } },
|
||||||
|
relations: ["learning"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { LearningCategory } from "../entities/learning-category.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LearningCategoryRepository extends Repository<LearningCategory> {
|
||||||
|
constructor(@InjectRepository(LearningCategory) learningCategoryRepository: Repository<LearningCategory>) {
|
||||||
|
super(learningCategoryRepository.target, learningCategoryRepository.manager, learningCategoryRepository.queryRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { LearningProgress } from "../entities/learning-progress.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LearningProgressRepository extends Repository<LearningProgress> {
|
||||||
|
constructor(@InjectRepository(LearningProgress) learningProgressRepository: Repository<LearningProgress>) {
|
||||||
|
super(learningProgressRepository.target, learningProgressRepository.manager, learningProgressRepository.queryRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { Learning } from "../entities/learning.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LearningRepository extends Repository<Learning> {
|
||||||
|
constructor(@InjectRepository(Learning) learningRepository: Repository<Learning>) {
|
||||||
|
super(learningRepository.target, learningRepository.manager, learningRepository.queryRunner);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { BaseEntity } from "../../../common/entities/base.entity";
|
|||||||
import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity";
|
import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity";
|
||||||
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
||||||
import { Invoice } from "../../invoices/entities/invoice.entity";
|
import { Invoice } from "../../invoices/entities/invoice.entity";
|
||||||
|
import { LearningProgress } from "../../learning/entities/learning-progress.entity";
|
||||||
import { Notification } from "../../notifications/entities/notification.entity";
|
import { Notification } from "../../notifications/entities/notification.entity";
|
||||||
import { Payment } from "../../payments/entities/payment.entity";
|
import { Payment } from "../../payments/entities/payment.entity";
|
||||||
import { UserSetting } from "../../settings/entities/user-setting.entity";
|
import { UserSetting } from "../../settings/entities/user-setting.entity";
|
||||||
@@ -78,6 +79,9 @@ export class User extends BaseEntity {
|
|||||||
|
|
||||||
@OneToMany(() => Invoice, (invoice) => invoice.user)
|
@OneToMany(() => Invoice, (invoice) => invoice.user)
|
||||||
invoices: Invoice[];
|
invoices: Invoice[];
|
||||||
|
|
||||||
|
@OneToMany(() => LearningProgress, (learningProgress) => learningProgress.learning)
|
||||||
|
learningProgress: LearningProgress[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ManyToMany(() => DanakService, (danakService) => danakService.users)
|
// @ManyToMany(() => DanakService, (danakService) => danakService.users)
|
||||||
|
|||||||
Reference in New Issue
Block a user