update:add ci cd files ==> do not edit those file

This commit is contained in:
mahyargdz
2024-10-10 16:58:55 +03:30
parent dc6c7c63ea
commit ed5e2023f4
179 changed files with 15757 additions and 2277 deletions
+12 -3
View File
@@ -1,4 +1,4 @@
PORT=6060
PORT=3000
SECRET=fDS9j269DSfd823FhdfG65
ACCESS_TOKEN_SECERT=PNd98dsDS089ds3SDh7C5hg265SFC42bgHxTg34x
ACCESS_TOKEN_SECERT_ADMIN=G89dsJsd90DFdsf96F8se3sgL7JB29265SFC42bgHxTg34x
@@ -12,7 +12,16 @@ EMAIL_ADDRESS=
SMS_USER=
SMS_PASS=
SMS_NUMBER=
URLIMAGE=https://api-delearn.iran.liara.run
MONGO_CONNECTION_HOST=127.0.0.1 mongodb://127.0.0.1:27017/delearn
URLIMAGE=https://delearn-api.run.danakcorp.com
MONGO_CONNECTION_HOST=127.0.0.1
MONGO_CONNECTION_PORT=27017
MONGO_CONNECTION_NAME=delearn
MONGO_CONNECTION_OPTION=authSource=admin
STORAGE_ACCESS_KEY=d4eo96t2mklfe228
STORAGE_SECRET_KEY=95e78068-ff60-41c0-82c3-71a0de0a3e2f
STORAGE_ENDPOINT=https://storage.iran.liara.space
STORAGE_BUCKET=dlearn
STORAGE_REGION=default
MONGO_CONNECTION_DANAK=mongodb://root:ahh62bv6o7jndf32@srv-captain--danak-mongo-db:27017/delearn?authSource=admin
NODE_ENV=development
+28
View File
@@ -0,0 +1,28 @@
PORT=3000
SECRET=fDS9j269DSfd823FhdfG65
ACCESS_TOKEN_SECERT=PNd98dsDS089ds3SDh7C5hg265SFC42bgHxTg34x
ACCESS_TOKEN_SECERT_ADMIN=G89dsJsd90DFdsf96F8se3sgL7JB29265SFC42bgHxTg34x
CONNECTION_STRING=
MERCHANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
EMAIL_HOST=
EMAIL_PORT=
EMAIL_USER=
EMAIL_PASS=
EMAIL_ADDRESS=
SMS_USER=
SMS_PASS=
SMS_NUMBER=
URLIMAGE=https://delearn-api.run.danakcorp.com
MONGO_CONNECTION_HOST=danak-db-public.danakcorp.com
MONGO_CONNECTION_PORT=45698
MONGO_CONNECTION_NAME=delearn
MONGO_CONNECTION_USER=root
MONGO_CONNECTION_PASS=ahh62bv6o7jndf32
MONGO_CONNECTION_OPTION=authSource=admin
STORAGE_ACCESS_KEY=d4eo96t2mklfe228
STORAGE_SECRET_KEY=95e78068-ff60-41c0-82c3-71a0de0a3e2f
STORAGE_ENDPOINT=https://storage.iran.liara.space
STORAGE_BUCKET=dlearn
STORAGE_REGION=default
MONGO_CONNECTION_DANAK=mongodb://root:ahh62bv6o7jndf32@srv-captain--danak-mongo-db:27017/delearn?authSource=admin
NODE_ENV=development
+48
View File
@@ -0,0 +1,48 @@
name: deploy to danak
on:
push:
branches:
- main
jobs:
build_and_deploy:
runs-on: ubuntu-latest
env:
DANAK_SERVER: "https://captain.run.danakcorp.com"
APP_TOKEN: fb6db122b78d5362d5b9c9297172d3145fa616420fca464b556008da9f86b3f1
CAPROVER_APP_NAME: dlearn-api
GITHUB_TOKEN: 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
steps:
- name: Check out repositorys
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: zmihamid
password: ${{ env.GITHUB_TOKEN }}
- name: Preset Image Name
run: echo "IMAGE_URL=$(echo ghcr.io/zmihamid/${{ github.event.repository.name }}:$(echo ${{ github.sha }} | cut -c1-7) | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
- name: Build and push Docker Image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ env.IMAGE_URL }}
- name: Install CapRover CLI
run: npm install -g caprover
- name: deploy to server
run: |
caprover deploy -a $CAPROVER_APP_NAME -u $DANAK_SERVER --appToken $APP_TOKEN -i "$IMAGE_URL"
+37
View File
@@ -0,0 +1,37 @@
FROM node:20-alpine AS base
RUN apk add --no-cache tzdata
# Set the timezone to Asia/Tehran
RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone
FROM base AS deps
WORKDIR /temp-deps
COPY package*.json ./
RUN yarn install
FROM base AS builder
WORKDIR /build
COPY . ./
COPY --from=deps /temp-deps/node_modules ./node_modules
# Check if a build script exists and run the build if it does
RUN if [ -f package.json ] && grep -q '"build":' package.json; then yarn build; fi
RUN yarn install --production --frozen-lockfile
FROM base AS runner
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build ./
RUN chown -R appuser:appgroup /app
USER appuser
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npm", "start"]
+16 -5
View File
@@ -8,8 +8,8 @@
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start": "node dist/main",
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
@@ -21,9 +21,15 @@
},
"author": "daniel.3380",
"dependencies": {
"@aws-sdk/client-s3": "^3.574.0",
"@ffprobe-installer/ffprobe": "^2.1.2",
"@liara/cli": "^6.0.3",
"@nestjs/axios": "^2.0.0",
"@nestjs/common": "^10.0.5",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.0.5",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mongoose": "^10.0.6",
"@nestjs/platform-express": "^10.0.5",
"@nestjs/platform-socket.io": "^10.0.5",
@@ -38,20 +44,24 @@
"@types/multer": "^1.4.7",
"@types/request": "^2.48.8",
"@types/request-ip": "^0.0.37",
"aws-sdk": "^2.1618.0",
"axios": "^1.3.6",
"base64-async": "^2.1.3",
"bluebird": "^3.7.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"cross-env": "^7.0.3",
"crypto-js": "^4.1.1",
"dotenv": "^16.4.5",
"ejs": "^3.1.8",
"express-session": "^1.17.2",
"fs": "^0.0.1-security",
"get-video-duration": "^4.1.0",
"jalali-moment": "^3.3.11",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.2",
"md5": "^2.3.0",
"moment": "^2.29.4",
"mongoose": "^8.3.2",
"mysql": "^2.18.1",
"mysql2": "^3.2.3",
@@ -62,19 +72,20 @@
"random-number": "^0.0.9",
"reflect-metadata": "^0.1.13",
"request": "^2.88.2",
"request-ip": "^2.1.3",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"sequelize": "^6.32.1",
"sequelize-typescript": "^2.1.5",
"shelljs": "^0.8.5",
"soap": "^1.0.0",
"soft-delete-plugin-mongoose": "^1.0.17",
"swagger-ui-express": "^4.6.3",
"ts-loader": "^9.4.2",
"typescript": "^5.1.6"
},
"devDependencies": {
"@nestjs/cli": "^8.2.5",
"@nestjs/cli": "^10.3.2",
"@nestjs/schematics": "^8.0.11",
"@types/bluebird": "^3.5.36",
"@types/express": "^4.17.13",
+228
View File
@@ -0,0 +1,228 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class-validator';
import blogStatuses, { ItemStatus } from '../common/eNums/itemStatus.enum';
import { PostDto, postObjDto } from './blogPost.dto';
import { CategoryDataAccess } from '../dataAccess/blogCategory.dataAccess';
import itemStatuses from '../common/eNums/itemStatus.enum';
const categoryDataAccess = new CategoryDataAccess();
async function findParent(id) {
const parent = await categoryDataAccess.findById(id);
return parent.title;
}
export async function blogCatObjDto(category) {
const active = Object.keys(blogStatuses).find(
(key) => blogStatuses[key].code === category.active,
);
let Posts = [];
if (category.Posts && category.Posts.length !== 0) {
Posts = category.Posts.map((post) => postObjDto(post));
}
const parent =
category.parentId !== null ? await findParent(category.parentId) : 'root';
return {
id: category.id,
parentId: category.parentId,
parent: parent,
title: category.title,
description: category.description,
statusText: itemStatuses[category.status],
status: category.status,
createdAt: category.createdAt,
Posts,
};
}
export async function blogCatArrObjDto(category) {
console.log(category);
const parent =
category.parentId !== null ? await findParent(category.parentId) : 'root';
return {
id: category.id,
parentId: category.parentId,
parent: parent,
parents: category.parents,
title: category.title,
description: category.description,
status: category.status,
statusText: itemStatuses[category.status],
createdAt: category.createdAt,
};
}
export function blogCatObjInfoDto(category) {
return {
title: category.title,
statusText: itemStatuses[category.status],
};
}
export function childBlogCatObj(category) {
return {
id: category.id,
title: category.title,
};
}
export function getAllBlogCategoriesObj(category) {
let BlogCategories = [];
if (category.BlogCategories && category.BlogCategories !== 0) {
BlogCategories = category.BlogCategories.map((category) =>
childBlogCatObj(category),
);
}
return {
id: category.id,
title: category.title,
BlogCategories,
};
}
export class BlogCatInfoDto {
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
statusText: string;
}
export class ChildBlogCatDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
}
export class GetAllBlogCategoriesDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: ChildBlogCatDto, isArray: true })
BlogCategories: ChildBlogCatDto[];
}
export class BlogCatDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
parent: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
statusText: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
@ApiProperty({ type: PostDto, isArray: true })
Posts: PostDto[];
}
export class BlogCatListDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
parent: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
statusText: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class CreateBlogCatDto {
@ApiProperty({ type: String })
parentId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
}
export class UpdateBlogCatDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: String })
parentId: string;
@IsNotEmpty()
@IsOptional()
@ApiProperty({ type: String })
title: string;
@IsOptional()
@ApiProperty({ type: String })
description: string;
}
export class StatusBlogCatDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
}
export class getParentListBlogCatDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
parentId: string;
}
+426
View File
@@ -0,0 +1,426 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsNotEmpty,
IsNumberString,
IsOptional,
} from 'class-validator';
import blogStatuses, { ItemStatus } from '../common/eNums/itemStatus.enum';
import { postCommentObj } from './blogPostComment.dto';
import { BlogCatInfoDto, blogCatObjInfoDto } from './blogCategory.dto';
import { UserMidDto, userMidObj } from './user.dto';
export function postObjDto(post) {
const blogCat = blogCatObjInfoDto(post.blogCat);
return {
id: post.id,
adminId: post.adminId,
blogCat,
title: post.title,
slug: post.slug,
visitCount: post.visitCount,
statusText: blogStatuses[post.status],
status: post.status,
createdAt: post.createdAt,
};
}
export function viewPostObjDto(post) {
const blogCat = blogCatObjInfoDto(post.blogCat);
return {
id: post.id,
blogCat,
mediaUrl: post.mediaUrl,
title: post.title,
slug: post.slug,
description: post.description,
visitCount: post.visitCount,
createdAt: post.createdAt,
};
}
export function postInfoObjDto(post) {
// let PostMedias = [];
// if (post.PostMedias && post.PostMedias.length !== 0) {
// PostMedias = post.PostMedias.map((postMedia) => postMediaObj(postMedia));
// }
let PostComments = [];
if (post.PostComments && post.PostComments.length !== 0) {
PostComments = post.PostComments.map((postComment) =>
postCommentObj(postComment),
);
}
const admin = userMidObj(post.admin);
const blogCat = blogCatObjInfoDto(post.blogCat);
// let media = null;
// if (post.Media) {
// media = mediaObj(post.Media);
// }
return {
id: post.id,
admin,
blogCat,
mediaUrl: post.mediaUrl,
// media,
description: post.description,
title: post.title,
slug: post.slug,
text: post.text,
writer: post.writer,
mediaAlt: post.mediaAlt,
metaTitle: post.metaTitle,
metaDescription: post.metaDescription,
metaKeywords: post.metaKeywords,
visitCount: post.visitCount,
sort: post.sort,
isInLanding: post.isInLanding,
statusText: blogStatuses[post.status],
status: post.status,
createdAt: post.createdAt,
PostComments,
};
}
export function postTitleObjDto(post) {
return {
id: post.id,
title: post.title,
slug: post.slug,
};
}
export class PostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: () => UserMidDto })
admin: UserMidDto;
@ApiProperty({ type: () => BlogCatInfoDto })
blogCat: BlogCatInfoDto;
// @IsNotEmpty()
// @ApiProperty({ type: Number })
// adminId: number;
// @ApiProperty({ type: AdminInfoDto })
// admin: AdminInfoDto;
@IsNotEmpty()
@ApiProperty({ type: String })
mediaUrl: string;
// @ApiProperty({ type: MediaDto })
// media: MediaDto;
@IsNotEmpty()
@ApiProperty({ type: String })
description: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
slug: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@ApiProperty({ type: String })
writer: string;
@ApiProperty({ type: String })
mediaAlt: string;
@ApiProperty({ type: String })
metaTitle: string;
@ApiProperty({ type: String })
metaDescription: string;
@ApiProperty({ type: String })
metaKeywords: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
visitCount: number;
@IsNotEmpty()
@ApiProperty({ type: String })
statusText: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
@ApiProperty({ type: Number })
sort: number;
@IsNotEmpty()
@ApiProperty({ type: Boolean })
isInLanding: boolean;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
// @ApiProperty({ isArray: true, type: PostMediaDto })
// PostMedias: PostMediaDto[];
// @ApiProperty({ isArray: true, type: PostCommentDto })
// PostComments: PostCommentDto[];
}
export class ListPostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: () => BlogCatInfoDto })
blogCat: BlogCatInfoDto;
@IsNotEmpty()
@ApiProperty({ type: String })
adminId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
slug: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
visitCount: number;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
statusText: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class ViewListPostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: () => BlogCatInfoDto })
blogCat: BlogCatInfoDto;
@IsNotEmpty()
@ApiProperty({ type: String })
mediaUrl: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
slug: string;
@ApiProperty({ type: String })
description: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
visitCount: number;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class FilterListPostDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
blogCatId: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
title: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
slug: string;
@IsOptional()
@ApiProperty({ type: Number, required: false })
visitCount: number;
@IsOptional()
@ApiProperty({ type: Boolean, required: false })
isInLanding: boolean;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
}
export class CreatePostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'دسته بندی اجباری می باشد' })
blogCatId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'مدیا اجباری می باشد' })
mediaUrl: string;
@IsNotEmpty()
@IsNotEmpty({ message: 'عنوان اجباری می باشد' })
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'توضیحات کوتاه اجباری می باشد' })
description: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'متن اجباری می باشد' })
text: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'توضیحات عکس اجباری می باشد' })
mediaAlt: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'عنوان متا اجباری می باشد' })
metaTitle: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'توضیحات متا اجباری می باشد' })
metaDescription: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'کلمات کلیدی اجباری می باشد' })
metaKeywords: string;
@IsNotEmpty()
@ApiProperty({ type: Boolean })
@IsNotEmpty({ message: 'توضیحات متا اجباری می باشد' })
isInLanding: boolean;
// @Optional()
// @ApiProperty({ isArray: true, type: CreatePostMediaDto })
// postMedias: CreatePostMediaDto[];
}
export class UpdatePostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
blogCat: string;
@IsNotEmpty()
@ApiProperty({ type: String })
mediaUrl: string;
@IsNotEmpty()
@ApiProperty({ type: String })
description: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@ApiProperty({ type: String })
writer: string;
@ApiProperty({ type: String })
mediaAlt: string;
@ApiProperty({ type: String })
metaTitle: string;
@ApiProperty({ type: String })
metaDescription: string;
@ApiProperty({ type: String })
metaKeywords: string;
@IsNotEmpty()
@ApiProperty({ type: Boolean })
isInLanding: boolean;
// @Optional()
// @ApiProperty({ isArray: true, type: CreatePostMediaDto })
// postMedias: CreatePostMediaDto[];
}
export class PostStatusDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
}
export class ChangeSortPostDto {
@IsNotEmpty()
@ApiProperty({ type: String })
fromId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
toId: string;
}
export class getPostInputDto {
@IsOptional()
@IsNumberString()
@ApiProperty({ type: String, required: false })
blogCatId: string;
@IsOptional()
@ApiProperty({ type: Number, required: false })
page: number;
@IsOptional()
@ApiProperty({ type: Number, required: false })
pageSize: number;
}
export class SearchParameter {
@IsOptional()
@ApiProperty({ type: String, required: false })
title: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
page: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
pageSize: string;
}
+183
View File
@@ -0,0 +1,183 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsMongoId, IsNotEmpty, IsOptional } from 'class-validator';
import { ItemStatus } from '../common/eNums/itemStatus.enum';
import { postTitleObjDto } from './blogPost.dto';
export function postCommentObj(postComment) {
return {
id: postComment.id,
postId: postComment.postId,
parentId: postComment.parentId,
userName: postComment.userName,
text: postComment.text,
status: postComment.status,
createdAt: postComment.createdAt,
};
}
export function postCommentInfoObj(postComment) {
let replies = [];
if (postComment.Replies && postComment.Replies.length !== 0) {
replies = postComment.Replies.map((postComment) =>
postCommentObj(postComment),
);
}
const post = postTitleObjDto(postComment.blogPost);
return {
id: postComment.id,
postId: postComment.postId,
post: post,
parentId: postComment.parentId,
userName: postComment.userName,
text: postComment.text,
status: postComment.status,
createdAt: postComment.createdAt,
replies,
};
}
export function postCommentArrObjDto(postComment) {
const post = postTitleObjDto(postComment.Post);
return {
id: postComment.id,
postId: postComment.postId,
post: post.title,
parentId: postComment.parentId,
userName: postComment.userName,
status: postComment.status,
text: postComment.text,
createdAt: postComment.createdAt,
};
}
export class ListPostCommentDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
postId: string;
@ApiProperty({ type: String })
post: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class FilterListPostCommentDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
postId: string;
@IsEnum(ItemStatus)
@IsOptional()
@ApiProperty({ type: String, required: false, enum: ItemStatus })
status: string;
@IsOptional()
@ApiProperty({ type: Date, required: false })
fromDate: Date;
@IsOptional()
@ApiProperty({ type: Date, required: false })
toDate: Date;
}
export class PostCommentDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
postId: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class PostCommentInfoDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
postId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
parentId: string;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
@ApiProperty({ isArray: true, type: PostCommentDto })
replies: PostCommentDto[];
}
export class CreatePostCommentDto {
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
}
export class StatusPostCommentDto {
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: String })
id: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
}
+17
View File
@@ -0,0 +1,17 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsMongoId, IsNotEmpty, IsOptional } from "class-validator";
export class CreateCommentDto {
@ApiProperty({type:String})
@IsNotEmpty({message:"متن کامنت نمیتواند خالی باشد"})
text: string;
@ApiProperty({type:String})
@IsOptional()
@IsMongoId({message:"شناسه والد نامعتبر است"})
parent: string;
course: string;
createdBy: string;
}
+177
View File
@@ -0,0 +1,177 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { IsMongoId, IsNotEmpty, IsOptional } from 'class-validator';
export class CreateCourseDto {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'نام دوره نباید خالی باشد.' })
name: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'توضیحات دوره نباید خالی باشد.' })
description: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'آدرس دوره نباید خالی باشد.' })
slug: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'مدت دوره نباید خالی باشد.' })
duration: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'عکس دوره نباید خالی باشد.' })
image: string;
@ApiProperty({ type: [String] })
@IsNotEmpty({ message: 'دسته‌بندی‌های دوره نباید خالی باشد.' })
categories: string[];
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'قیمت دوره نباید خالی باشد.' })
price: number;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'ایدی مدرس نباید خالی باشد.' })
@IsMongoId({ message: 'ایدی مدرس نامعتبر است.' })
teacher: string;
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'درصد تکمیل دوره نباید خالی باشد.' })
completionPercentage: number;
@ApiProperty({ enum: ['مبتدی', 'متوسط', 'پیشرفته'], enumName: 'CourseLevel' })
@IsNotEmpty({ message: 'سطح دوره نباید خالی باشد.' })
level: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'زبان دوره نباید خالی باشد.' })
language: string;
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'تعداد ویدیوهای دوره نباید خالی باشد.' })
countOfVideos: number;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'حجم فایل دوره نباید خالی باشد.' })
totalFileSize: string;
}
export class UpdateCourseDto {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'ایدی دوره نباید خالی باشد.' })
@IsMongoId({ message: 'ایدی دوره نامعتبر است.' })
id: string;
@ApiProperty({ type: String })
@IsOptional()
name: string;
@ApiProperty({ type: String })
@IsOptional()
description: string;
@ApiProperty({ type: String })
@IsOptional()
slug: string;
@ApiProperty({ type: String })
@IsOptional()
duration: string;
@ApiProperty({ type: String })
@IsOptional()
image: string;
@ApiProperty({ type: Number })
@IsOptional()
price: number;
@ApiProperty({ type: String })
@IsOptional()
teacher: string;
@ApiProperty({ type: [String] })
@IsOptional()
categories: string[];
@ApiProperty({ type: Number })
@IsOptional()
completionPercentage: number;
@ApiProperty({
enum: ['Beginner', 'Intermediate', 'Advanced'],
enumName: 'CourseLevel',
})
@IsOptional()
level: string;
@ApiProperty({ type: String })
@IsOptional()
language: string;
@ApiProperty({ type: Number })
@IsOptional()
countOfVideos: number;
@ApiProperty({ type: String })
@IsOptional()
totalFileSize: string;
}
export class PaginationCourseDto {
@IsOptional()
@ApiProperty({ type: Number, required: false })
page: number;
@IsOptional()
@ApiProperty({ type: Number, required: false })
pageSize: number;
}
export class FilterCategoryPaginationCourseDto {
@ApiProperty({ type: String, required: false })
@IsOptional()
@IsMongoId({ message: 'ایدی دسته‌بندی نامعتبر است.' })
categoryId: string;
@IsOptional()
@ApiProperty({ type: Number, required: false })
page: number;
@IsOptional()
@ApiProperty({ type: Number, required: false })
pageSize: number;
@IsOptional()
@ApiProperty({ type: String, required: false })
price: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
name: string;
}
export function MidCourseDtoObj(item) {
return {
id: item.id,
name: item.name,
image: item.image,
price: item.price,
};
}
export class MidCourseDto {
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'نام دوره نباید خالی باشد.' })
name: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'عکس دوره نباید خالی باشد.' })
image: string;
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'قیمت دوره نباید خالی باشد.' })
price: number;
}
+9
View File
@@ -0,0 +1,9 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty } from "class-validator";
export class CreateCourseCategoryDto {
@ApiProperty({type: String})
@IsNotEmpty({message: 'نام دسته بندی را وارد کنید'})
name: string;
}
+56
View File
@@ -0,0 +1,56 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { ItemStatusCourseHeadline } from '../common/eNums/itemStatusCourseHeadline.enum';
export class CreateCourseHeadlineDTO {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'عنوان دوره نباید خالی باشد.' })
headline: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'توضیحات دوره نباید خالی باشد.' })
description: string;
episodes: EpisodeDTO[];
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'ایدی دوره نباید خالی باشد.' })
course: string; // Assuming course is referenced by its ID
@ApiProperty({ type: String })
@IsOptional()
videoUrl: string;
@IsEnum(ItemStatusCourseHeadline)
@ApiProperty({ type: String, enum: ItemStatusCourseHeadline })
@IsNotEmpty({ message: 'وضعیت دوره نباید خالی باشد.' })
status: string;
}
export class UpdateCourseHeadlineDTO {
@ApiProperty({ type: String })
@IsOptional()
headline: string;
@ApiProperty({ type: String })
@IsOptional()
description: string;
episodes: EpisodeDTO[];
@ApiProperty({ type: String })
@IsOptional()
course: string; // Assuming course is referenced by its ID
@ApiProperty({ type: String })
@IsOptional()
videoUrl: string;
@IsEnum(ItemStatusCourseHeadline)
@ApiProperty({ type: String, enum: ItemStatusCourseHeadline })
@IsOptional()
status: string;
}
export class EpisodeDTO {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'عنوان دوره نباید خالی باشد.' })
title: string;
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'مدت دوره نباید خالی باشد.' })
duration: number;
}
+194
View File
@@ -0,0 +1,194 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsMongoId, IsNotEmpty, IsOptional } from 'class-validator';
import { ItemStatus } from '../common/eNums/itemStatus.enum';
export function courseHeadlineCommentObj(item) {
return {
id: item.id,
courseHeadlineId: item.courseHeadlineId,
parentId: item.parentId,
userName: item.userName,
text: item.text,
createdAt: item.createdAt,
};
}
export function courseHeadlineCommentInfoObj(courseHeadlineComment) {
let replies = [];
if (
courseHeadlineComment.Comment &&
courseHeadlineComment.Comment.length !== 0
) {
replies = courseHeadlineComment.Comment.map((courseHeadlineComment) =>
courseHeadlineCommentObj(courseHeadlineComment),
);
}
return {
id: courseHeadlineComment.id,
courseHeadlineId: courseHeadlineComment.courseHeadlineId,
parentId: courseHeadlineComment.parentId,
userName: courseHeadlineComment.userName,
text: courseHeadlineComment.text,
createdAt: courseHeadlineComment.createdAt,
replies,
};
}
export function courseHeadlineCommentArrObjDto(courseHeadlineComment) {
return {
id: courseHeadlineComment.id,
courseHeadlineId: courseHeadlineComment.courseHeadlineId,
parentId: courseHeadlineComment.parentId,
userName: courseHeadlineComment.userName,
text: courseHeadlineComment.text,
createdAt: courseHeadlineComment.createdAt,
};
}
export class ListPostCommentDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
@IsMongoId({ message: 'آیدی سرفصل نامعتبر است.' })
courseHeadlineId: string;
@ApiProperty({ type: String })
courseHeadline: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
active: number;
@IsNotEmpty()
@ApiProperty({ type: String })
activeText: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class FilterListPostCommentDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
@IsMongoId({ message: 'آیدی سرفصل نامعتبر است.' })
courseHeadlineId: string;
@IsEnum(ItemStatus)
@IsOptional()
@ApiProperty({ type: String, required: false, enum: ItemStatus })
status: string;
@IsOptional()
@ApiProperty({ type: Date, required: false })
fromDate: Date;
@IsOptional()
@ApiProperty({ type: Date, required: false })
toDate: Date;
}
export class PostCommentDto {
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@IsMongoId({ message: 'آیدی سرفصل نامعتبر است.' })
@ApiProperty({ type: Number })
courseHeadlineId: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
active: number;
@IsNotEmpty()
@ApiProperty({ type: String })
activeText: string;
@IsNotEmpty()
@ApiProperty({ type: String })
createdAt: string;
}
export class PostCommentInfoDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
courseHeadlineId: number;
@IsNotEmpty()
@ApiProperty({ type: Number })
parentId: number;
@IsNotEmpty()
@ApiProperty({ type: String })
userName: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
@IsNotEmpty()
@ApiProperty({ type: Number })
active: number;
@IsNotEmpty()
@ApiProperty({ type: String })
activeText: string;
@ApiProperty({ isArray: true, type: PostCommentDto })
replies: PostCommentDto[];
}
export class CreateCourseHeadlineCommentDto {
@IsNotEmpty()
@IsMongoId({ message: 'آیدی سرفصل نامعتبر است.' })
@ApiProperty({ type: String })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
text: string;
}
export class StatusCommentDto {
@IsNotEmpty()
@ApiProperty({ type: String })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
active: string;
}
+212
View File
@@ -0,0 +1,212 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsMongoId, IsEnum, IsOptional } from 'class-validator';
import discountStatus from '../common/eNums/discountStatus.enum';
import discountTypes, {
DiscountTypeEnum,
} from '../common/eNums/discountType.enum';
import discountUser from '../common/eNums/discountUser.enum';
import itemStatuses, { ItemStatus } from '../common/eNums/itemStatus.enum';
import { UserMidDto, userMidObj } from './user.dto';
// OBJ *******************
export function detailsAdminDiscountObj(discount) {
const adminObj = userMidObj(discount.user);
return {
id: discount.id,
admin: adminObj,
startDate: discount.startDate,
endDate: discount.endDate,
code: discount.code,
price: discount.price,
percent: discount.percent,
allUsers: discount.allUsers,
status: discount.status,
discountType: discount.discountType,
createdAt: discount.createdAt,
updatedAt: discount.updatedAt,
};
}
export function listDiscountObj(discount) {
const adminObj = userMidObj(discount.user);
return {
id: discount.id,
admin: adminObj,
startDate: discount.startDate,
endDate: discount.endDate,
code: discount.code,
discountType: discount.discountType,
status: discount.status,
statusText: itemStatuses[discount.status],
createdAt: discount.createdAt,
};
}
// CLASS *******************
export class CreateDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: Date })
startDate: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
endDate: Date;
@IsNotEmpty()
@ApiProperty({ type: String })
code: string;
@IsEnum(DiscountTypeEnum)
@ApiProperty({ type: String, required: false })
discountType: string;
@ApiProperty({ required: false, type: String })
price: string;
@ApiProperty({ required: false, type: Number })
percent: number;
@IsNotEmpty()
@ApiProperty({ type: Boolean })
allUsers: boolean;
}
export class UpdateDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: Date })
startDate: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
endDate: Date;
@IsNotEmpty()
@ApiProperty({ type: String })
code: string;
@IsEnum(DiscountTypeEnum)
@ApiProperty({ type: String, required: false })
discountType: string;
@ApiProperty({ required: false, type: String })
price: string;
@ApiProperty({ required: false, type: Number })
percent: number;
@ApiProperty({ type: Boolean })
allUsers: boolean;
}
export class UpdatedStatusDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
}
export class ListDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: UserMidDto })
admin: UserMidDto;
@IsNotEmpty()
@ApiProperty({ type: Date })
startDate: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
endDate: Date;
@IsNotEmpty()
@ApiProperty({ type: String })
code: string;
@IsNotEmpty()
@ApiProperty({ type: String })
discountType: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String })
status: string;
@IsNotEmpty()
@ApiProperty({ type: Date })
createdAt: Date;
}
export class DetailsAdminDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: UserMidDto })
admin: UserMidDto;
@IsNotEmpty()
@ApiProperty({ type: Date })
startDate: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
endDate: Date;
@IsNotEmpty()
@ApiProperty({ type: String })
code: string;
@ApiProperty({ required: false, type: String })
price: string;
@ApiProperty({ required: false, type: Number })
percent: number;
@IsNotEmpty()
@ApiProperty({ type: Boolean })
allUsers: boolean;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
discountType: string;
@IsNotEmpty()
@ApiProperty({ type: Date })
updatedAt: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
createdAt: Date;
}
export class IdDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
}
+51
View File
@@ -0,0 +1,51 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
import { UserMidDto, userMidObj } from './user.dto';
import {
DetailsAdminDiscountDto,
detailsAdminDiscountObj,
} from './discount.dto';
import { ListUserDiscountDto, listUserDiscountObj } from './userDiscount.dto';
// OBJ *******************
export function listDiscountHistoryObj(result) {
const user = result.user ? userMidObj(result.user) : null;
const discount = result.discount
? detailsAdminDiscountObj(result.discount)
: null;
const userDiscount = result.userDiscount
? listUserDiscountObj(result.userDiscount)
: null;
return {
id: discount.id,
user,
discount,
userDiscount,
createdAt: discount.createdAt,
};
}
// CLASS *******************
export class ListDiscountHistoryDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: UserMidDto })
user: UserMidDto;
@ApiProperty({ type: ListUserDiscountDto })
userDiscount: ListUserDiscountDto;
@IsNotEmpty()
@ApiProperty({ type: DetailsAdminDiscountDto })
discount: DetailsAdminDiscountDto;
@IsNotEmpty()
@ApiProperty({ type: Date })
createdAt: Date;
}
+137
View File
@@ -0,0 +1,137 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsInt,
IsMongoId,
IsNotEmpty,
IsOptional,
} from 'class-validator';
import { ItemStatus } from '../common/eNums/itemStatus.enum';
// OBJ *******************
export function faqObj(faq) {
let faqCatTitle = null;
if (faq.FaqCategory) {
faqCatTitle = faq.FaqCategory.title;
}
return {
id: faq.id,
status: faq.status,
faqCat: faq.faqCat,
faqCatTitle,
question: faq.question,
answer: faq.answer,
createdAt: faq.createdAt,
updatedAt: faq.updatedAt,
};
}
// CLASS *******************
export class FaqDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
faqCat: string;
@ApiProperty({ type: String })
faqCatTitle: string;
@IsNotEmpty()
@ApiProperty({ type: String })
question: string;
@IsNotEmpty()
@ApiProperty({ type: String })
answer: string;
@ApiProperty({ type: Date })
createdAt: Date;
@ApiProperty({ type: Date })
updatedAt: Date;
}
export class CreateFaqDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
faqCat: string;
@IsNotEmpty()
@ApiProperty({ type: String })
question: string;
@ApiProperty({ type: String })
answer: string;
}
export class UpdateFaqDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
faqCat: string;
@IsNotEmpty()
@ApiProperty({ type: String })
question: string;
@ApiProperty({ type: String })
answer: string;
}
export class UpdateStatusFaqDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
status: string;
}
export class FilterFaqDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
@IsMongoId({ message: 'آیدی دسته بندی نامعتبر است.' })
faqCat: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
}
export class ParamsPositionDto {
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: String })
fId: string;
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: String })
tId: string;
}
+81
View File
@@ -0,0 +1,81 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsMongoId, IsNotEmpty, IsOptional } from 'class-validator';
import { FaqDto, faqObj } from './faq.dto';
import { ItemStatus } from '../common/eNums/itemStatus.enum';
// OBJ *******************
export function faqCategoryObj(faqCategory) {
let Faqs = [];
if (faqCategory.Faqs && faqCategory.Faqs.length !== 0) {
Faqs = faqCategory.Faqs.map((faq) => faqObj(faq));
}
return {
id: faqCategory.id,
title: faqCategory.title,
status: faqCategory.status,
description: faqCategory.description,
Faqs,
};
}
// CLASS *******************
export class FaqCategoryDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsNotEmpty()
@ApiProperty({ type: String })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
@ApiProperty({ isArray: true, type: FaqDto })
Faqs: FaqDto[];
}
export class CreateFaqCategoryDto {
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
}
export class UpdateFaqCategoryDto {
@IsNotEmpty()
@ApiProperty({ type: String })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
@IsNotEmpty()
@ApiProperty({ type: String })
title: string;
@ApiProperty({ type: String })
description: string;
}
export class FilterFaqCategoryDto {
@IsOptional()
@ApiProperty({ type: String, required: false })
@IsMongoId({ message: 'آیدی نامعتبر است.' })
id: string;
@IsOptional()
@IsEnum(ItemStatus)
@ApiProperty({ type: String, required: false })
status: string;
}
+12
View File
@@ -0,0 +1,12 @@
import { IsNumber, IsOptional } from "class-validator";
export class PaginationDto {
@IsNumber()
@IsOptional()
page: number;
@IsNumber()
@IsOptional()
pageSize: number;
}
+11
View File
@@ -0,0 +1,11 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsNumber, Max, Min } from "class-validator";
export class CreateRateDto {
@ApiProperty({ type: Number })
@IsNotEmpty({message:"امتیاز نمیتواند خالی باشد"})
@IsNumber()
@Min(0,{message:"امتیاز باید بیشتر از 0 باشد"})
@Max(5,{message:"امتیاز باید کمتر از 5 باشد"})
rating: number;
}
+52
View File
@@ -0,0 +1,52 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional } from 'class-validator';
export class CreateDto {
@IsOptional()
@ApiProperty({ type: String })
about: string;
@IsOptional()
@ApiProperty({ type: Number })
student: number;
@IsOptional()
@ApiProperty({ type: Number })
hours: number;
@IsOptional()
@ApiProperty({ type: Number })
title: number;
@IsOptional()
@ApiProperty({ type: Number })
expertise: number;
@IsOptional()
@ApiProperty({ type: String })
nameCompany: string;
@IsOptional()
@ApiProperty({ type: String })
mission: string;
@IsOptional()
@ApiProperty({ type: String })
start: string;
@IsOptional()
@ApiProperty({ type: String })
goals: string;
@IsOptional()
@ApiProperty({ type: String })
rate: string;
@IsOptional()
@ApiProperty({ type: String })
titleText: string;
@IsOptional()
@ApiProperty({ type: String })
header: string;
}
+19
View File
@@ -0,0 +1,19 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty } from "class-validator";
export class CreateTicketDto {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'موضوع تیکت اجباری میباشد' })
subject: string;
}
export class CreateTicketMessageDto {
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'متن تیکت اجباری میباشد' })
content: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'آیدی تیکت اجباری میباشد' })
ticketId: string;
}
+17
View File
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class UploaderDTO {
@ApiProperty({ type: 'string', format: 'binary' })
file: string;
@ApiProperty({ type: 'string' })
@IsNotEmpty({ message: 'نام فولدر الزامی است' })
directoryName: string;
}
export class GetFileDTO {
@ApiProperty({ type: 'string' })
@IsNotEmpty({ message: 'نام فایل الزامی است' })
fileName: string;
}
+217
View File
@@ -0,0 +1,217 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsEnum,
IsMobilePhone,
IsMongoId,
IsNotEmpty,
IsNumberString,
IsOptional,
} from 'class-validator';
import itemStatuses, { ItemStatus } from '../common/eNums/itemStatus.enum';
import role, { Role } from '../common/eNums/role.enum';
import { MidCourseDto, MidCourseDtoObj } from './course.dto';
export function userMidObj(user) {
return {
id: user.id,
phone: user.phone,
token: user.jwtToken,
email: user.email,
userName: user.userName,
name: user.name,
avatarMedia: user.avatarMedia,
roles: user.roles,
rolesText: role[user.roles],
status: user.status,
statusText: itemStatuses[user.status],
};
}
export function userObj(user) {
let course = [];
if (user.courseIds && user.courseIds !== 0) {
course = user.courseIds.map((item) => MidCourseDtoObj(item));
}
return {
id: user.id,
phone: user.phone,
avatarMedia: user.avatarMedia,
name: user.name,
email: user.email,
userName: user.userName,
lastName: user.lastName,
cityId: user.cityId,
tel: user.tel,
address: user.address,
description: user.description,
gender: user.gender,
roles: user.roles,
rolesText: role[user.roles],
course,
status: user.status,
createdAt: user.createdAt,
};
}
export class LoginDto {
@IsNumberString()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'شماره موبایل اجباری می باشد' })
phone: any;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'نوع تماس اجباری می باشد' })
type: string;
}
export class LoginStepTwoDto {
@IsNumberString()
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'شماره موبایل اجباری می باشد' })
phone: string;
@IsNotEmpty({ message: 'کد تایید اجباری می باشد' })
@ApiProperty({ type: Number })
code: number;
}
export class LoginRes {
@ApiProperty({ type: String })
message: string;
}
export class UserMidDto {
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: String })
phone: string;
@ApiProperty({ type: String })
token: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
@ApiProperty({ type: String })
statusText: string;
}
export class UserDto {
@ApiProperty({ type: String })
id: string;
@ApiProperty({ type: String })
phone: string;
@ApiProperty({ type: String })
avatarMedia: string;
@ApiProperty({ type: String })
name: string;
@ApiProperty({ type: String })
email: string;
@ApiProperty({ type: String })
userName: string;
@ApiProperty({ type: String })
lastName: string;
@ApiProperty({ type: String })
cityId: string;
@ApiProperty({ type: String })
tel: string;
@ApiProperty({ type: String })
address: string;
@ApiProperty({ type: String })
description: string;
@ApiProperty({ type: String })
gender: string;
@IsEnum(Role)
@ApiProperty({ type: String, enum: Role })
roles: string;
@ApiProperty({ type: String })
rolesText: string;
@ApiProperty({ isArray: true, type: MidCourseDto })
course: MidCourseDto[];
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
}
export class QueryUserList {
@IsOptional()
@ApiProperty({ type: String, required: false })
page: string;
@IsOptional()
@ApiProperty({ type: String, required: false })
pageSize: string;
}
export class UserStatus {
@ApiProperty({ type: String })
userId: string;
@IsEnum(ItemStatus)
@ApiProperty({ type: String, enum: ItemStatus })
status: string;
}
export class UserUpdateDto {
@IsOptional()
@ApiProperty({ type: String })
avatarMedia: string;
@IsOptional()
@ApiProperty({ type: String })
name: string;
@IsOptional()
@ApiProperty({ type: String })
lastName: string;
@IsOptional()
@ApiProperty({ type: String })
userName: string;
@IsOptional()
@ApiProperty({ type: String })
email: string;
}
export class RegisterCourseDto {
@ApiProperty({ type: String })
@IsMongoId({ message: 'شناسه دوره اجباری می باشد' })
@IsNotEmpty({ message: 'شناسه دوره اجباری می باشد' })
courseId: string;
@IsOptional()
@ApiProperty({ type: String })
code: string;
}
export class AddBalanceDto {
@ApiProperty({ type: Number })
@IsNotEmpty({ message: 'مبلغ اجباری می باشد' })
amount: number;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'شماره پرداخت اجباری می باشد' })
paymentNumber: string;
@ApiProperty({ type: String })
@IsNotEmpty({ message: 'دلیل اجباری می باشد' })
reason: string;
}
+58
View File
@@ -0,0 +1,58 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsMongoId, IsNotEmpty } from 'class-validator';
import {
DetailsAdminDiscountDto,
detailsAdminDiscountObj,
} from './discount.dto';
import { UserMidDto, userMidObj } from './user.dto';
// OBJ *******************
export function listUserDiscountObj(userDiscount) {
const user = userMidObj(userDiscount.user);
const discount = detailsAdminDiscountObj(userDiscount.discount);
return {
id: userDiscount.id,
user,
discount,
updatedAt: userDiscount.updatedAt,
createdAt: userDiscount.createdAt,
};
}
// CLASS *******************
export class ListUserDiscountDto {
@IsNotEmpty()
@ApiProperty({ type: Number })
id: number;
@IsNotEmpty()
@ApiProperty({ type: UserMidDto })
user: UserMidDto;
@IsNotEmpty()
@ApiProperty({ type: DetailsAdminDiscountDto })
discount: DetailsAdminDiscountDto;
@IsNotEmpty()
@ApiProperty({ type: Date })
createdAt: Date;
@IsNotEmpty()
@ApiProperty({ type: Date })
updatedAt: Date;
}
export class CreateUserDiscountDto {
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: String })
user: string;
@IsNotEmpty()
@IsMongoId({ message: 'آیدی نامعتبر است.' })
@ApiProperty({ type: String })
discount: string;
}
+36 -4
View File
@@ -1,20 +1,52 @@
import { AdminModule } from './application/admin/admin.module';
import { CommonModule } from './application/common/common.module';
import { TicketModule } from './application/ticket/ticket.module';
import { UserModule } from './application/user/user.module';
import { AuthModule } from './auth/auth.module';
import { Module } from '@nestjs/common';
import { ThrottlerModule } from '@nestjs/throttler';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { UploaderService } from './application/common/uploader/uploader.service';
import { CourseModule } from './application/course/course.module';
import { UploaderModule } from './application/common/uploader/uploader.module';
import { CourseHeadlineModule } from './application/courseHeadline/courseHeadline.module';
import { ConfigModule } from '@nestjs/config';
import { CourseCategoryModule } from './application/courseCategory/courseCategory.module';
@Module({
imports: [
AdminModule,
// EventEmitterModule.forRoot({
// wildcard: false,
// delimiter: '.',
// newListener: false,
// removeListener: false,
// maxListeners: 10,
// verboseMemoryLeak: false,
// ignoreErrors: false,
// }),
EventEmitterModule.forRoot(),
CommonModule,
AuthModule,
UserModule,
TicketModule,
CourseModule,
UploaderModule,
CourseHeadlineModule,
CourseCategoryModule,
// ServeStaticModule.forRoot({
// rootPat h: join(__dirname, '..', 'public'),
// }),
// ConfigModule.forRoot({
// isGlobal: true,
// }),
ConfigModule.forRoot({
isGlobal: true,
}),
ThrottlerModule.forRoot({
ttl: 60,
limit: 10,
}),
],
controllers: [],
providers: [],
providers: [UploaderService],
exports: [UploaderService],
})
export class AppModule {}
@@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { AdminService } from './admin.service';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('admin')
@Controller('admin')
export class AdminController {
constructor(private readonly adminService: AdminService) {}
}
+14
View File
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { AdminService } from './admin.service';
import { AdminController } from './admin.controller';
import { UserModule } from './user/user.module';
import { BlogModule } from './blog/blog.module';
import { FaqModule } from './faq/faq.module';
import { DiscountModule } from './discount/discount.module';
import { SettingsModule } from './settings/settings.module';
@Module({
imports: [UserModule, SettingsModule, BlogModule, FaqModule, DiscountModule],
controllers: [AdminController],
providers: [AdminService],
})
export class AdminModule {}
+4
View File
@@ -0,0 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AdminService {}
@@ -0,0 +1,400 @@
import {
Body,
Controller,
Post,
HttpStatus,
HttpCode,
Get,
Put,
Query,
Patch,
Param,
UseGuards,
} from '@nestjs/common';
import {
ApiTags,
ApiOkResponse,
ApiBody,
ApiOperation,
ApiBearerAuth,
} from '@nestjs/swagger';
import {
BlogCatDto,
CreateBlogCatDto,
UpdateBlogCatDto,
StatusBlogCatDto,
BlogCatListDto,
getParentListBlogCatDto,
} from '../../..//DTO/blogCategory.dto';
import {
PostDto,
CreatePostDto,
UpdatePostDto,
PostStatusDto,
ListPostDto,
FilterListPostDto,
ChangeSortPostDto,
} from '../../../DTO/blogPost.dto';
// import { DeletePostMediaDto } from '../../../DTO/postMedia.dto';
import {
StatusPostCommentDto,
ListPostCommentDto,
PostCommentDto,
CreatePostCommentDto,
FilterListPostCommentDto,
PostCommentInfoDto,
} from '../../../DTO/blogPostComment.dto';
import { BlogService } from './blog.service';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import { AuthGuard } from '../../../auth/auth.guard';
import { CurrentUser } from '../../../decorators/currentUser.decorator';
@ApiTags('admin blog')
@Controller('admins/blog')
export class BlogController {
constructor(private readonly blogService: BlogService) {}
// create new post **************************************************************************
@ApiOperation({ summary: ' ساخت پست برای بلاگ ' })
@ApiOkResponse({
description: 'PostDto info',
type: [PostDto],
})
@ApiBody({
type: CreatePostDto,
description: 'create new post',
})
@Post('post')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async createPost(
@CurrentUser() user,
@Body() createPostDto: CreatePostDto,
): Promise<boolean> {
try {
await this.blogService.createPost(createPostDto, user);
return true;
} catch (err) {
throw err;
}
}
// update post ******************************************************************************
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: ' بروز رسانی پست ' })
@ApiOkResponse({
description: 'update post',
type: Boolean,
})
@ApiBody({
type: UpdatePostDto,
description: 'update post DTO',
})
@Put('post')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async updatePost(
@CurrentUser() user,
@Body() updatePostDto: UpdatePostDto,
): Promise<any> {
try {
await this.blogService.updatePost(updatePostDto, user);
return { status: true };
} catch (err) {
throw err;
}
}
// change status of post ********************************************************************
@ApiOperation({ summary: 'تغییر وضعیت پست بلاگ' })
@ApiOkResponse({
description: 'api status',
type: Boolean,
})
@ApiBody({
type: PostStatusDto,
description: 'change Status of post',
})
@Patch('post')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeStatusPost(@Body() postStatusDto: PostStatusDto): Promise<any> {
try {
await this.blogService.changeStatusPost(postStatusDto);
return { status: true };
} catch (err) {
throw err;
}
}
// Filter / list post ***********************************************************************
@ApiOperation({ summary: ' فیلتر و لیست پست های بلاگ' })
@HttpCode(HttpStatus.OK)
@ApiOkResponse({
description: 'posts list',
type: [ListPostDto],
})
@Get('post')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async ListPosts(
@Query() filterListPostDto: FilterListPostDto,
): Promise<ListPostDto[]> {
const blogService = await this.blogService.listPosts(filterListPostDto);
return blogService;
}
// get detail post by id *****************************************************************************
@ApiOperation({ summary: 'جزییات کامل پست بلاگ' })
@ApiOkResponse({
description: 'post detail info',
type: PostDto,
})
@Get('post/:postId')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async postDetail(
@Param('postId')
postId: string,
): Promise<PostDto> {
try {
const postDetail = await this.blogService.postDetail(postId);
return postDetail;
} catch (err) {
throw err;
}
}
// change sort post ************************************************************************
@ApiOperation({ summary: 'تغییر ترتیب پست' })
@HttpCode(HttpStatus.OK)
@ApiOkResponse({
description: 'change sort post',
type: [ListPostDto],
})
@ApiBody({
type: ChangeSortPostDto,
description: 'change sort post',
})
@Put('post/sort')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeSortPost(
@Body() changeSortPostDto: ChangeSortPostDto,
): Promise<boolean> {
try {
await this.blogService.changeSortPost(changeSortPostDto);
return true;
} catch (err) {
throw err;
}
}
// create new blogCat ***********************************************************************
@ApiOperation({ summary: ' ساخت دسته بندی بلاگ ' })
@ApiOkResponse({
description: 'blog Category Dto info',
type: [BlogCatDto],
})
@ApiBody({
type: CreateBlogCatDto,
description: 'create new blog category',
})
@Post('category')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async createCategory(
@Body() createBlogCatDto: CreateBlogCatDto,
): Promise<BlogCatDto> {
try {
const category = await this.blogService.createCategory(createBlogCatDto);
return category;
} catch (err) {
throw err;
}
}
// list blogCat ***********************************************************************
@ApiOperation({ summary: ' لیست دسته بندی های بلاگ ' })
@HttpCode(HttpStatus.OK)
@ApiOkResponse({
description: 'blog category list',
type: [BlogCatListDto],
})
@Get('category')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async blogCatList(
@Query() getParentForListDto: getParentListBlogCatDto,
): Promise<BlogCatListDto[]> {
try {
const categories = await this.blogService.blogCatList(
getParentForListDto,
);
return categories;
} catch (err) {
throw err;
}
}
// update blogCat **********************************************************************
@ApiOperation({ summary: ' بروز رسانی دسته بندی بلاگ ' })
@HttpCode(HttpStatus.OK)
@ApiOkResponse({
description: 'update blog category',
type: Boolean,
})
@ApiBody({
type: UpdateBlogCatDto,
description: 'update blog category',
})
@Put('category')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async updateBlogCategory(
@Body() updateBlogCategory: UpdateBlogCatDto,
): Promise<any> {
try {
await this.blogService.updateBlogCategory(updateBlogCategory);
return { status: true };
} catch (err) {
throw err;
}
}
//change status of blog Category **********************************************************************
@ApiOperation({ summary: ' وضعیت دسته بندی بلاگ ' })
@ApiOkResponse({
description: 'api status',
type: Boolean,
})
@ApiBody({
type: StatusBlogCatDto,
description: 'change Status of blog category',
})
@Patch('category')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeStatusCategory(
@Body() statusBlogCatDto: StatusBlogCatDto,
): Promise<any> {
try {
await this.blogService.changeStatusCategory(statusBlogCatDto);
return { status: true };
} catch (err) {
throw err;
}
}
// Filter/ list post comment ********************************************************************
@ApiOperation({ summary: ' لیست پست ها به همراه فیلتر ' })
@ApiOkResponse({
description: 'postComments List',
type: [ListPostCommentDto],
})
@HttpCode(HttpStatus.OK)
@Get('postComment')
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async listPostComment(
@Query() filterListPostCommentDto: FilterListPostCommentDto,
): Promise<ListPostCommentDto[]> {
const list = await this.blogService.listPostComments(
filterListPostCommentDto,
);
return list;
}
//get post comment detail by id ****************************************************************
@ApiOperation({ summary: 'detail PostComment' })
@ApiOkResponse({
description: 'post comment detail info',
type: PostCommentInfoDto,
})
@HttpCode(HttpStatus.OK)
@Get('postComment/:id')
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async postCommentDetail(
@Param('id')
id: string,
): Promise<PostCommentInfoDto> {
try {
const postCommentDetail = await this.blogService.postCommentDetail(id);
return postCommentDetail;
} catch (err) {
throw err;
}
}
// changeStatus postComment *********************************************************************
@ApiOperation({ summary: 'change status PostComment' })
@ApiOkResponse({
description: 'api status',
type: Boolean,
})
@ApiBody({
type: StatusPostCommentDto,
description: 'change status of postComment',
})
@Patch('postComment')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeStatusPostComment(
@Body() statusPostCommentDto: StatusPostCommentDto,
): Promise<any> {
try {
await this.blogService.changeStatusPostComment(statusPostCommentDto);
return { status: true };
} catch (err) {
throw err;
}
}
// reply post comment *****************************************************
@ApiOperation({ summary: 'reply PostComment' })
@ApiOkResponse({
description: 'PostComment Dto',
type: [PostCommentDto],
})
@ApiBody({
type: CreatePostCommentDto,
description: 'reply post comment',
})
@Post('postComment/reply/:id')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async replyPostComment(
@CurrentUser() user,
@Body() createPostCommentDto: CreatePostCommentDto,
@Param('id')
id: string,
): Promise<PostCommentDto> {
try {
const result = await this.blogService.replyPostComment(
createPostCommentDto,
id,
user,
);
return result;
} catch (err) {
throw err;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
import { BlogPostDataAccess } from '../../../dataAccess/blogPost.dataAccess';
import { CategoryDataAccess } from '../../../dataAccess/blogCategory.dataAccess';
import { BlogPostCommentDataAccess } from '../../../dataAccess/blogPostComment.dataAccess';
import { Tools } from '../../../common/helpers/tools.helpers';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
controllers: [BlogController],
providers: [
BlogService,
CategoryDataAccess,
BlogPostDataAccess,
BlogPostCommentDataAccess,
Tools,
UserDataAccess,
],
})
export class BlogModule {}
+476
View File
@@ -0,0 +1,476 @@
/* eslint-disable object-curly-newline */
/* eslint-disable prefer-const */
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import bluebirdPromise = require('bluebird');
import { CategoryDataAccess } from '../../../dataAccess/blogCategory.dataAccess';
import { BlogPostDataAccess } from '../../../dataAccess/blogPost.dataAccess';
import { BlogPostCommentDataAccess } from '../../../dataAccess/blogPostComment.dataAccess';
import { Tools } from '../../../common/helpers/tools.helpers';
import { blogCatArrObjDto, blogCatObjDto } from '../../../DTO/blogCategory.dto';
import {
postObjDto,
postInfoObjDto,
ListPostDto,
} from '../../../DTO/blogPost.dto';
import { ItemStatus } from '../../../common/eNums/itemStatus.enum';
import {
postCommentArrObjDto,
postCommentObj,
postCommentInfoObj,
PostCommentInfoDto,
PostCommentDto,
} from '../../../DTO/blogPostComment.dto';
@Injectable()
export class BlogService {
constructor(
private readonly categoryDataAccess: CategoryDataAccess,
private readonly blogPostDataAccess: BlogPostDataAccess,
private readonly postCommentDataAccess: BlogPostCommentDataAccess,
private readonly tools: Tools,
) {}
// create post ******************************************************************
async createPost(createPostDto, admin) {
const {
blogCatId,
mediaUrl,
title,
description,
text,
mediaAlt,
metaTitle,
metaDescription,
metaKeywords,
isInLanding,
} = createPostDto;
const blogCat = await this.categoryDataAccess.findById(blogCatId);
if (!blogCat) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'دسته بندی یافت نشد',
},
HttpStatus.NOT_FOUND,
);
}
// if (blogCat.parents.length !== 0) {
// throw new HttpException(
// {
// status: HttpStatus.NOT_ACCEPTABLE,
// error: 'دسته بندی ریشه نیست و قابل ثبت نمی باشد',
// },
// HttpStatus.NOT_ACCEPTABLE,
// );
// }
const slug = this.tools.convertToSlug(title);
const checkPost = await this.blogPostDataAccess.checkDuplicate(slug);
if (checkPost) {
throw new HttpException(
{
status: HttpStatus.CONFLICT,
error: 'این آدرس قبلا رزرو شده است',
},
HttpStatus.CONFLICT,
);
}
// const media = await this.mediaDataAccess.findById(mediaUrl);
// if (!media) {
// throw new HttpException(
// {
// status: HttpStatus.NOT_FOUND,
// error: 'مدیا یافت نشد',
// },
// HttpStatus.NOT_FOUND,
// );
// }
const length = await this.blogPostDataAccess.findAllLength();
const post = await this.blogPostDataAccess.createBlogPostRequest(
admin.id,
blogCatId,
mediaUrl,
title,
description,
slug,
text,
length,
mediaAlt,
metaTitle,
metaDescription,
metaKeywords,
isInLanding,
);
await this.blogPostDataAccess.updateSortBlogPost(post._id);
blogCat.Posts.push(post._id ? post._id : post.id);
await blogCat.save();
return postObjDto(await this.blogPostDataAccess.findById(post._id));
}
// update post *********************************************************************
async updatePost(updatePostDto, admin) {
const {
id,
mediaUrl,
title,
description,
text,
writerId,
mediaAlt,
metaTitle,
metaDescription,
metaKeywords,
isInLanding,
} = updatePostDto;
let { blogCat } = updatePostDto;
let blogCatOne;
const post = await this.blogPostDataAccess.findById(id);
if (!post) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'POST_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
if (blogCat) {
blogCatOne = await this.categoryDataAccess.findById(blogCat);
if (!blogCatOne) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'BLOG_CATEGORY_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
// if (blogCatOne.parents.length !== 0) {
// throw new HttpException(
// {
// status: HttpStatus.NOT_ACCEPTABLE,
// error: 'BLOG_CATEGORY_NOT_ACCEPTABLE',
// },
// HttpStatus.NOT_ACCEPTABLE,
// );
// }
} else {
blogCat = post.blogCat;
}
const checkTitle = await this.blogPostDataAccess.checkTitle(title);
if (checkTitle && title !== post.title) {
throw new HttpException(
{
status: HttpStatus.CONFLICT,
error: 'TITLE_CONFLICT',
},
HttpStatus.CONFLICT,
);
}
if (post.blogCat._id.toString() !== blogCat) {
blogCatOne.Posts.push(post._id);
await blogCatOne.save();
await this.categoryDataAccess.pullCate(post.blogCat._id, post._id);
}
await this.blogPostDataAccess.updateBlogPost(
id,
admin.id,
blogCat,
mediaUrl,
title,
description,
text,
writerId,
mediaAlt,
metaTitle,
metaDescription,
metaKeywords,
isInLanding,
);
}
// post detail ***********************************************************************
async postDetail(postId) {
const postDetail = await this.blogPostDataAccess.findByIdDetails(postId);
if (postDetail) {
postDetail.Comment = [];
return postInfoObjDto(postDetail);
} else {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'POST_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
}
// change status post ********************************************************************
async changeStatusPost(postStatusDto) {
const { id, status } = postStatusDto;
const post = await this.blogPostDataAccess.findById(id);
if (!post) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'پست مورد نظر وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
}
await this.blogPostDataAccess.changeStatusBlogPost(id, status);
}
// list posts ***************************************************************************
async listPosts(filterListPostDto): Promise<ListPostDto[]> {
const list = await this.blogPostDataAccess.filterListAndFilter(
filterListPostDto,
);
return list.map((item) => postObjDto(item));
}
//change Sort post *************************************************************************
async changeSortPost(changeSortPostDto) {
const { fromId, toId } = changeSortPostDto;
const from = await this.blogPostDataAccess.findById(fromId);
if (!from) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'پست مبدا مورد نظر وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
}
const to = await this.blogPostDataAccess.findById(toId);
if (!to) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'پست مقصد مورد نظر وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
}
await this.blogPostDataAccess.changeSortBlogPost(from, to);
return true;
}
// create category *************************************************************************
async createCategory(createBlogCatDto) {
let { parentId } = createBlogCatDto;
let parent;
const { title, description } = createBlogCatDto;
if (parentId && parentId !== null) {
parent = await this.categoryDataAccess.findById(parentId);
if (!parent) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'منوی والد پیدا نشد',
},
HttpStatus.NOT_FOUND,
);
}
if (parent.parentId !== null) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'فقط برای منو های اصلی می توانید منو اضافه کنید',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
} else {
parentId = null;
}
const checkTitle = await this.categoryDataAccess.checkDuplicate(title);
if (checkTitle) {
throw new HttpException(
{
status: HttpStatus.CONFLICT,
error: 'TITLE_CONFLICT',
},
HttpStatus.CONFLICT,
);
}
const category = await this.categoryDataAccess.createCategory(
parentId,
title,
description,
);
if (parentId && parentId !== null) {
parent.parents.push(category._id);
await parent.save();
}
return blogCatObjDto(category);
}
// list category ****************************************************************************
async blogCatList(getParentForListDto) {
const { parentId } = getParentForListDto;
const categories = await this.categoryDataAccess.findAll(parentId);
const categoryList = await bluebirdPromise.map(
categories,
async (category) => {
return blogCatArrObjDto(category);
},
);
return categoryList;
}
// update category *************************************************************************
async updateBlogCategory(updateBlogCategory): Promise<boolean> {
let { parentId } = updateBlogCategory;
const { id, title, description } = updateBlogCategory;
let parent;
const category = await this.categoryDataAccess.findById(id);
if (!category) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'CATEGORY_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
if (parentId && parentId !== null && parentId !== id) {
parent = await this.categoryDataAccess.findById(parentId);
if (!parent) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'منوی اصلی پیدا نشد',
},
HttpStatus.NOT_FOUND,
);
}
if (parent.parentId !== null) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'فقط برای منو های اصلی می توانید منو اضافه کنید',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
// if (parent.Posts.length !== 0) {
// throw new HttpException(
// {
// status: HttpStatus.NOT_ACCEPTABLE,
// error: 'قابل ثبت نمی باشد منوی مورد نظر دارای پست می باشد',
// },
// HttpStatus.NOT_ACCEPTABLE,
// );
// }
} else {
parentId = null;
}
const checkTitle = await this.categoryDataAccess.checkDuplicate(title);
if (checkTitle && title !== category.title) {
throw new HttpException(
{
status: HttpStatus.CONFLICT,
error: 'این عنوان وجود دارد ',
},
HttpStatus.CONFLICT,
);
}
if (
parentId &&
parentId !== null &&
category.parentId._id.toString() !== parentId
) {
parent.parents.push(category._id);
await parent.save();
await this.categoryDataAccess.pullCateParents(
category.parentId._id,
category._id,
);
}
await this.categoryDataAccess.updateBlogCategory(
id,
parentId,
title,
description,
);
return true;
}
// change status category *************************************************************************
async changeStatusCategory(statusBlogCatDto): Promise<boolean> {
const { id, status } = statusBlogCatDto;
const category = await this.categoryDataAccess.findById(id);
if (!category) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'CATEGORY_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.categoryDataAccess.changeStatusCategory(id, status);
return true;
}
//list post comment *****************************************************************************
async listPostComments(filterListPostCommentDto) {
const list = await this.postCommentDataAccess.listAndFilter(
filterListPostCommentDto,
);
return list.map((item) => postCommentArrObjDto(item));
}
// change status post comment ********************************************************************
async changeStatusPostComment(statusPostCommentDto): Promise<boolean> {
const { id, status } = statusPostCommentDto;
const postComment = await this.postCommentDataAccess.findById(id);
if (!postComment) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'POST_COMMENT_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.postCommentDataAccess.changeStatus(id, status);
return true;
}
// get detail post comment by id *****************************************************************
async postCommentDetail(id: string): Promise<PostCommentInfoDto> {
const postComment = await this.postCommentDataAccess.findById(id);
if (!postComment) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'POST_COMMENT-NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
} else {
return postCommentInfoObj(postComment);
}
}
// reply post Comment ****************************************************************************
async replyPostComment(
createPostCommentDto,
id,
admin,
): Promise<PostCommentDto> {
const postComment = await this.postCommentDataAccess.findById(id);
if (!postComment || postComment.status !== ItemStatus.Active) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'COMMENT_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
} else {
const { text } = createPostCommentDto;
const replyPostComment = await this.postCommentDataAccess.replyComment(
postComment.blogPost.id,
id,
admin.id,
text,
);
postComment.Replies.push(replyPostComment.id);
postComment.save();
return postCommentObj(replyPostComment);
}
}
}
@@ -0,0 +1,161 @@
// NEST
import {
Body,
Controller,
Post,
HttpStatus,
Get,
Res,
Param,
Put,
Patch,
HttpCode,
UseGuards,
} from '@nestjs/common';
import { Response } from 'express';
// SWAGGER
import {
ApiTags,
ApiOkResponse,
ApiBody,
ApiOperation,
ApiBearerAuth,
} from '@nestjs/swagger';
import { DiscountService } from './discount.service';
// DTO
import {
CreateDiscountDto,
UpdateDiscountDto,
UpdatedStatusDiscountDto,
ListDiscountDto,
DetailsAdminDiscountDto,
IdDiscountDto,
} from '../../../DTO/discount.dto';
import { AuthGuard } from '../../../auth/auth.guard';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import { CurrentUser } from '../../../decorators/currentUser.decorator';
@ApiTags('admin discounts')
@Controller('admins/discount')
export class DiscountController {
constructor(private readonly discountService: DiscountService) {}
// CREATE DISCOUNT ****************************
@Post('')
@ApiOperation({ summary: 'create discount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiBody({
description: 'create discount',
type: CreateDiscountDto,
})
@ApiOkResponse({
description: 'response created discount',
type: Boolean,
})
async createDiscount(
@Body() created: CreateDiscountDto,
@CurrentUser() user,
) {
try {
await this.discountService.createDiscount(created, user.id);
return true;
} catch (err) {
throw err;
}
}
// UPDATE DISCOUNT ****************************
@Put('')
@ApiOperation({ summary: 'updated discount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiBody({
description: 'updated discount',
type: UpdateDiscountDto,
})
@ApiOkResponse({
description: 'response updated discount',
type: Boolean,
})
async updateDiscount(
@Body() updated: UpdateDiscountDto,
@CurrentUser() user,
) {
try {
await this.discountService.updatedDiscount(updated, user.id);
return true;
} catch (err) {
throw err;
}
}
// UPDATE STATUS DISCOUNT ****************************
@Patch('/status')
@ApiOperation({ summary: 'updated discount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiBody({
description: 'updated status discount',
type: UpdatedStatusDiscountDto,
})
@ApiOkResponse({
description: 'response updated status discount',
type: Boolean,
})
async updateStatusDiscount(@Body() updatedStatus: UpdatedStatusDiscountDto) {
try {
await this.discountService.updatedStatusDiscount(updatedStatus);
return true;
} catch (err) {
throw err;
}
}
// LIST DISCOUNT ****************************
@Get('')
@ApiOperation({ summary: 'list discount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'response list discount',
type: [ListDiscountDto],
})
async listDiscount(@Res() res: Response) {
try {
const discounts = await this.discountService.listDiscount();
res.json(discounts);
return;
} catch (err) {
throw err;
}
}
// DETAILS DISCOUNT ****************************
@Get('/:id')
@ApiOperation({ summary: 'details discount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'response details discount',
type: DetailsAdminDiscountDto,
})
async detailsDiscount(
@Param()
id: IdDiscountDto,
) {
try {
const discounts = await this.discountService.detailsDiscount(id);
return discounts;
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { DiscountController } from './discount.controller';
import { DiscountService } from './discount.service';
import { DiscountDataAccess } from '../../../dataAccess/discount.dataAccess';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
import { DiscountHistoryModule } from './discountHistory/discountHistory.module';
import { UserDiscountModule } from './userDiscount/userDiscount.module';
@Module({
imports: [UserDiscountModule, DiscountHistoryModule],
controllers: [DiscountController],
providers: [DiscountService, DiscountDataAccess, UserDataAccess],
})
export class DiscountModule {}
@@ -0,0 +1,137 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DiscountDataAccess } from '../../../dataAccess/discount.dataAccess';
import {
DetailsAdminDiscountDto,
IdDiscountDto,
ListDiscountDto,
detailsAdminDiscountObj,
listDiscountObj,
} from '../../../DTO/discount.dto';
@Injectable()
export class DiscountService {
constructor(private readonly discountDataAccess: DiscountDataAccess) {}
// CREATE DISCOUNT
async createDiscount(createDiscountDto, adminId): Promise<boolean> {
const { startDate, endDate, code, discountType, price, percent, allUsers } =
createDiscountDto;
// validate startDate longer
if (startDate > endDate) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' تاریخ پایان نباید از تاریخ شروع کم تر باشد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
await this.discountDataAccess.create(
startDate,
endDate,
code,
discountType,
price,
percent,
allUsers,
adminId,
);
return true;
}
// UPDATED DISCOUNT
async updatedDiscount(updatedDiscountDto, adminId): Promise<boolean> {
const {
id,
startDate,
endDate,
code,
discountType,
price,
percent,
allUsers,
} = updatedDiscountDto;
// validate startDate longer
if (startDate > endDate) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' تاریخ پایان نباید از تاریخ شروع کم تر باشد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
// validate exist discount
const discount = await this.discountDataAccess.findOne(id);
if (!discount) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: ' تخفیف وجود ندارد ',
},
HttpStatus.NOT_FOUND,
);
}
await this.discountDataAccess.update(
id,
adminId,
startDate,
endDate,
code,
discountType,
price,
percent,
allUsers,
);
return true;
}
// UPDATED STATUS DISCOUNT
async updatedStatusDiscount(updatedStatusDiscountDto): Promise<boolean> {
const { id, status } = updatedStatusDiscountDto;
// validate exist discount
const discount = await this.discountDataAccess.findOne(id);
if (!discount) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: ' تخفیف موجود نمی باشد ',
},
HttpStatus.NOT_FOUND,
);
}
// update status discount
await this.discountDataAccess.updateStatus(id, status);
return true;
}
// LIST DISCOUNT
async listDiscount(): Promise<{
discount: ListDiscountDto[];
count: number;
}> {
// list discount
const discounts = await this.discountDataAccess.findAll();
const count = await this.discountDataAccess.count();
return {
discount: discounts.map((discount) => listDiscountObj(discount)),
count: count,
};
}
// DETAILS DISCOUNT
async detailsDiscount(
idDiscountDto: IdDiscountDto,
): Promise<DetailsAdminDiscountDto> {
const { id } = idDiscountDto;
// details discount
const discounts = await this.discountDataAccess.findOne(id);
if (!discounts) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'تخفیف مورد نظر موجود نمی باشد',
},
HttpStatus.NOT_FOUND,
);
}
return detailsAdminDiscountObj(discounts);
}
}
@@ -0,0 +1,51 @@
// NEST
import {
Controller,
HttpStatus,
Get,
HttpCode,
UseGuards,
} from '@nestjs/common';
// SWAGGER
import {
ApiTags,
ApiOkResponse,
ApiOperation,
ApiBearerAuth,
} from '@nestjs/swagger';
import { DiscountHistoryService } from './discountHistory.service';
// DTO
import { ListDiscountHistoryDto } from '../../../../DTO/discountHistory.dto';
import { AuthGuard } from '../../../../auth/auth.guard';
import { Roles } from '../../../../decorators/role.decorators';
import { Role } from '../../../../common/eNums/role.enum';
@ApiTags('admin discountHistory')
@Controller('admins/discountHistory')
export class DiscountHistoryController {
constructor(
private readonly discountHistoryService: DiscountHistoryService,
) {}
// LIST DISCOUNT ****************************
@Get('')
@ApiOperation({ summary: 'list discountHistory' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'response list discountHistory',
type: [ListDiscountHistoryDto],
})
async listDiscountHistory(): Promise<{
discount: ListDiscountHistoryDto[];
count: number;
}> {
try {
return await this.discountHistoryService.listDiscountHistory();
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { DiscountHistoryController } from './discountHistory.controller';
import { DiscountHistoryService } from './discountHistory.service';
import { DiscountHistoryDataAccess } from '../../../../dataAccess/discountHistory.dataAccess';
import { UserDataAccess } from '../../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [DiscountHistoryController],
providers: [
DiscountHistoryService,
DiscountHistoryDataAccess,
UserDataAccess,
],
})
export class DiscountHistoryModule {}
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { DiscountHistoryDataAccess } from '../../../../dataAccess/discountHistory.dataAccess';
import {
ListDiscountHistoryDto,
listDiscountHistoryObj,
} from '../../../../DTO/discountHistory.dto';
@Injectable()
export class DiscountHistoryService {
constructor(
private readonly discountHistoryDataAccess: DiscountHistoryDataAccess,
) {}
// LIST DISCOUNT
async listDiscountHistory(): Promise<{
discount: ListDiscountHistoryDto[];
count: number;
}> {
// list discountHistory
const result = await this.discountHistoryDataAccess.findAll();
const count = await this.discountHistoryDataAccess.count();
return {
discount: result.map((discountHistory) =>
listDiscountHistoryObj(discountHistory),
),
count: count,
};
}
}
@@ -0,0 +1,79 @@
// NEST
import {
Body,
Controller,
Post,
HttpStatus,
Get,
HttpCode,
UseGuards,
} from '@nestjs/common';
// SWAGGER
import {
ApiTags,
ApiOkResponse,
ApiBody,
ApiOperation,
ApiBearerAuth,
} from '@nestjs/swagger';
import { UserDiscountService } from './userDiscount.service';
// DTO
import { AuthGuard } from '../../../../auth/auth.guard';
import { Roles } from '../../../../decorators/role.decorators';
import { Role } from '../../../../common/eNums/role.enum';
import {
CreateUserDiscountDto,
ListUserDiscountDto,
} from '../../../../DTO/userDiscount.dto';
@ApiTags('admin UserDiscounts')
@Controller('admins/UserDiscount')
export class UserDiscountController {
constructor(private readonly userDiscountService: UserDiscountService) {}
// CREATE DISCOUNT ****************************
@Post('')
@ApiOperation({ summary: 'create userUserDiscount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiBody({
description: 'create userUserDiscount',
type: CreateUserDiscountDto,
})
@ApiOkResponse({
description: 'response created userUserDiscount',
type: Boolean,
})
async createUserDiscount(
@Body() created: CreateUserDiscountDto,
): Promise<boolean> {
try {
await this.userDiscountService.createUserDiscount(created);
return true;
} catch (err) {
throw err;
}
}
// LIST DISCOUNT ****************************
@Get('')
@ApiOperation({ summary: 'list userUserDiscount' })
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'response list userUserDiscount',
type: [ListUserDiscountDto],
})
async listUserDiscount(): Promise<ListUserDiscountDto[]> {
try {
return await this.userDiscountService.listUserDiscount();
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { UserDiscountController } from './userDiscount.controller';
import { UserDiscountService } from './userDiscount.service';
import { UserDiscountDataAccess } from '../../../../dataAccess/userDiscount.dataAccess';
import { UserDataAccess } from '../../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [UserDiscountController],
providers: [UserDiscountService, UserDiscountDataAccess, UserDataAccess],
})
export class UserDiscountModule {}
@@ -0,0 +1,36 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
// DATAACCESS
import { UserDiscountDataAccess } from '../../../../dataAccess/userDiscount.dataAccess';
// ENUM
// DTO
import {
CreateUserDiscountDto,
ListUserDiscountDto,
listUserDiscountObj,
} from '../../../../DTO/userDiscount.dto';
// APPLICATION
@Injectable()
export class UserDiscountService {
constructor(
private readonly userUserDiscountDataAccess: UserDiscountDataAccess,
) {}
// CREATE DISCOUNT
async createUserDiscount(
createUserDiscountDto: CreateUserDiscountDto,
): Promise<boolean> {
const { user, discount } = createUserDiscountDto;
// validate startDate longer
await this.userUserDiscountDataAccess.create(user, discount);
return true;
}
// LIST DISCOUNT
async listUserDiscount(): Promise<ListUserDiscountDto[]> {
// list userUserDiscount
const userUserDiscounts = await this.userUserDiscountDataAccess.findAll();
return userUserDiscounts.map((userUserDiscount) =>
listUserDiscountObj(userUserDiscount),
);
}
}
+248
View File
@@ -0,0 +1,248 @@
import {
Body,
Controller,
Post,
HttpStatus,
HttpCode,
Get,
Put,
Query,
Delete,
Param,
Patch,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '../../../auth/auth.guard';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import {
ApiTags,
ApiOkResponse,
ApiInternalServerErrorResponse,
ApiOperation,
ApiBearerAuth,
} from '@nestjs/swagger';
import {
CreateFaqDto,
FaqDto,
FilterFaqDto,
ParamsPositionDto,
UpdateFaqDto,
UpdateStatusFaqDto,
} from '../../../DTO/faq.dto';
import {
CreateFaqCategoryDto,
FaqCategoryDto,
FilterFaqCategoryDto,
UpdateFaqCategoryDto,
} from '../../../DTO/faqCategory.dto';
import { FaqService } from './faq.service';
@ApiTags('admin faqs')
@Controller('admins/faq')
export class FaqController {
constructor(private readonly faqService: FaqService) {}
// faqCategory list
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'faqs list',
type: [FaqDto],
})
@HttpCode(HttpStatus.OK)
@Get('/get')
async faqList(@Query() filterFaqDto: FilterFaqDto): Promise<FaqDto[]> {
const faqList = await this.faqService.faqList(filterFaqDto);
return faqList;
}
// create new faqCategory ************************************
@ApiOkResponse({
description: 'create new faqCategory',
type: FaqCategoryDto,
})
@ApiInternalServerErrorResponse({
description: 'create new faqCategory failed',
})
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Post('category')
@HttpCode(HttpStatus.OK)
async createFaqCategory(
@Body() createFaqCategoryDto: CreateFaqCategoryDto,
): Promise<FaqCategoryDto> {
try {
const faqCategory = await this.faqService.createCategory(
createFaqCategoryDto,
);
return faqCategory;
} catch (err) {
throw err;
}
}
@ApiOkResponse({
description: 'faq list',
type: [FaqCategoryDto],
})
@HttpCode(HttpStatus.OK)
@Get('category')
async faqCategoryList(
@Query() filterFaqCategoryDto: FilterFaqCategoryDto,
): Promise<FaqCategoryDto[]> {
const faqCategoryList = await this.faqService.faqCategoryList(
filterFaqCategoryDto,
);
return faqCategoryList;
}
// update faqCategory ***********************************
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'category detail',
type: FaqCategoryDto,
})
@HttpCode(HttpStatus.OK)
@Put('category')
async updateFaqCategory(
@Body() updateFaqCategoryDto: UpdateFaqCategoryDto,
): Promise<FaqCategoryDto> {
try {
const faqCategory = await this.faqService.updateFaqCategory(
updateFaqCategoryDto,
);
return faqCategory;
} catch (err) {
throw err;
}
}
// delete faqCategory ***********************************
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'categories list',
type: [FaqCategoryDto],
})
@HttpCode(HttpStatus.OK)
@Delete('category/:id')
async deleteFaqCategory(
@Param('id')
id: string,
): Promise<FaqCategoryDto[]> {
try {
const faqCategoryList = await this.faqService.deleteFaqCategory(id);
return faqCategoryList;
} catch (err) {
throw err;
}
}
// create new faq ************************************
@ApiOkResponse({
description: 'create new faq',
type: FaqDto,
})
@ApiInternalServerErrorResponse({
description: 'create new faq failed',
})
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Post('')
@HttpCode(HttpStatus.OK)
async createFaq(@Body() createFaqDto: CreateFaqDto): Promise<FaqDto> {
try {
const faq = await this.faqService.createFaq(createFaqDto);
return faq;
} catch (err) {
throw err;
}
}
// update faqCategory ***********************************
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'faq detail',
type: FaqDto,
})
@HttpCode(HttpStatus.OK)
@Put('')
async updateFaq(@Body() updateFaqDto: UpdateFaqDto): Promise<FaqDto> {
try {
const faq = await this.faqService.updateFaq(updateFaqDto);
return faq;
} catch (err) {
throw err;
}
}
// update Status ***********************************
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'faq detail',
type: FaqDto,
})
@HttpCode(HttpStatus.OK)
@Patch('status')
async updateStatusFaq(
@Body() updateStatusFaqDto: UpdateStatusFaqDto,
): Promise<FaqDto> {
try {
const faq = await this.faqService.updateStatusFaq(updateStatusFaqDto);
return faq;
} catch (err) {
throw err;
}
}
// delete faq ***********************************
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@ApiOkResponse({
description: 'faqs list',
type: [FaqDto],
})
@HttpCode(HttpStatus.OK)
@Delete('/:id')
async deleteFaq(
@Param('id')
id: string,
): Promise<FaqDto[]> {
try {
const faqList = await this.faqService.deleteFaq(id);
return faqList;
} catch (err) {
throw err;
}
}
// update faqs position *********************************************************************************
@ApiOperation({ summary: 'update faqs position' })
@ApiOkResponse({
description: 'boolean',
type: Boolean,
})
@ApiInternalServerErrorResponse({
description: 'menu not found',
})
@Patch(':fId/:tId')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changePosition(
@Param()
paramsPositionDto: ParamsPositionDto,
): Promise<boolean> {
try {
const status = await this.faqService.changePosition(paramsPositionDto);
return status;
} catch (err) {
throw err;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { FaqController } from './faq.controller';
import { FaqService } from './faq.service';
import { FaqDataAccess } from '../../../dataAccess/faq.dataAccess';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
import { Tools } from '../../../common/helpers/tools.helpers';
import { ConvertDate } from '../../../common/helpers/convertDate.helper';
@Module({
imports: [],
controllers: [FaqController],
providers: [FaqService, UserDataAccess, FaqDataAccess, Tools, ConvertDate],
})
export class FaqModule {}
+206
View File
@@ -0,0 +1,206 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { faqCategoryObj } from '../../../DTO/faqCategory.dto';
import { faqObj, ParamsPositionDto } from '../../../DTO/faq.dto';
import { Types } from 'mongoose';
import { FaqDataAccess } from '../../../dataAccess/faq.dataAccess';
@Injectable()
export class FaqService {
constructor(private readonly faqDataAccess: FaqDataAccess) {}
// create new category ************************************
async createCategory(createFaqCategoryDto) {
const { title, description } = createFaqCategoryDto;
const newFaqCategory = await this.faqDataAccess.createFaqCategory(
title,
description,
);
return faqCategoryObj(newFaqCategory);
}
// faqCategoryList list
async faqCategoryList(filterFaqCategoryDto) {
const list = await this.faqDataAccess.findAllFaqCategory(
filterFaqCategoryDto,
);
return list.map((item) => faqCategoryObj(item));
}
// update FaqCategory ************************************
async updateFaqCategory(updateFaqCategoryDto) {
const { id, status, title, description } = updateFaqCategoryDto;
const faqCategory = await this.faqDataAccess.findCatById(id);
if (!faqCategory) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'FaqCategory_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.faqDataAccess.updateFaqCategory(id, status, title, description);
return faqCategoryObj(await this.faqDataAccess.findCatById(id));
}
// create new faq ************************************
async createFaq(createFaqDto) {
const { faqCat, question, answer, language } = createFaqDto;
const faqCategory = await this.faqDataAccess.findCatById(faqCat);
if (!faqCategory) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'FaqCategory_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
const newFaq = await this.faqDataAccess.createFaq(
faqCat,
question,
answer,
language,
);
faqCategory.faqs.push(newFaq);
faqCategory.save();
const maxFaq = await this.faqDataAccess.findAll();
this.faqDataAccess.updatePosition(newFaq.id, maxFaq.length);
return faqObj(await this.faqDataAccess.findFaqById(newFaq._id));
}
// faqList list
async faqList(filterFaqDto) {
const list = await this.faqDataAccess.findAllFaq(filterFaqDto);
return list.map((item) => faqObj(item));
}
// update Faq ************************************
async updateFaq(updateFaqDto) {
// eslint-disable-next-line object-curly-newline
const { id, status, faqCat, question, answer, language } = updateFaqDto;
const faqCategory = await this.faqDataAccess.findCatById(faqCat);
if (!faqCategory) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'FaqCategory_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
const faq = await this.faqDataAccess.findFaqById(id);
if (!faq) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'Faq_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
if (faq.faqCat._id.toString() !== faqCat) {
const parents = await this.faqDataAccess.findCatById(faq.faqCat);
parents.faqs = faqCategory.faqs.filter((sub) => sub._id !== id);
parents.save();
faqCategory.faqs.push(faq);
faqCategory.save();
}
await this.faqDataAccess.updateFaq(
id,
status,
faqCat,
question,
answer,
language,
);
return faqObj(await this.faqDataAccess.findFaqById(id));
}
// delete FaqCategory ************************************
async deleteFaqCategory(id) {
if (!Types.ObjectId.isValid(id)) {
throw new HttpException(
{
status: HttpStatus.FAILED_DEPENDENCY,
error: ' رشته وارد شده برای شناسه معتبر نیست',
},
HttpStatus.FAILED_DEPENDENCY,
);
}
await this.faqDataAccess.deleteFaqCat(id);
const filter: any = {};
const list = await this.faqDataAccess.findAllFaqCategory(filter);
return list.map((item) => faqCategoryObj(item));
}
// update Status
async updateStatusFaq(updateFaqDto) {
const { id, status } = updateFaqDto;
if (!Types.ObjectId.isValid(id)) {
throw new HttpException(
{
status: HttpStatus.FAILED_DEPENDENCY,
error: ' رشته وارد شده برای شناسه معتبر نیست',
},
HttpStatus.FAILED_DEPENDENCY,
);
}
const faq = await this.faqDataAccess.findFaqById(id);
if (!faq) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'Faq_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.faqDataAccess.updateStatusFaq(id, status);
return faqObj(await this.faqDataAccess.findFaqById(id));
}
// delete Faq ************************************
async deleteFaq(id) {
if (!Types.ObjectId.isValid(id)) {
throw new HttpException(
{
status: HttpStatus.FAILED_DEPENDENCY,
error: ' رشته وارد شده برای شناسه معتبر نیست',
},
HttpStatus.FAILED_DEPENDENCY,
);
}
const filter = { faqCat: null };
const category = await this.faqDataAccess.findFaqById(id);
if (category) {
filter.faqCat = category.faqCat;
}
await this.faqDataAccess.deleteFaq(id);
const list = await this.faqDataAccess.findAllFaq(filter);
return list.map((item) => faqObj(item));
}
async changePosition(paramsPositionDto: ParamsPositionDto) {
try {
const { fId, tId } = paramsPositionDto;
const fFaq = await this.faqDataAccess.findFaqById(fId);
const tFaq = await this.faqDataAccess.findFaqById(tId);
if (!fFaq || !tFaq) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'پرسش وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
}
await this.faqDataAccess.updatePosition(fFaq.id, tFaq.position);
await this.faqDataAccess.updatePosition(tFaq.id, fFaq.position);
return true;
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,101 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { SettingsService } from './settings.service';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import { AuthGuard } from '../../../auth/auth.guard';
import { CreateDto } from '../../../DTO/settings.dto';
@ApiTags('admin settings')
@Controller('admin/settings')
export class SettingController {
constructor(private readonly settingsService: SettingsService) {}
// get ************************************
@ApiOperation({
summary: ' تنظیمات سایت',
})
@ApiOkResponse({
description: ' Create Dto ',
type: CreateDto,
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Get()
async list(): Promise<CreateDto> {
try {
const result = await this.settingsService.find();
return result;
} catch (err) {
throw err;
}
}
// create ************************************
@ApiOperation({
summary: ' افزودن تنظیمات',
})
@ApiOkResponse({
description: ' Boolean',
type: Boolean,
})
@ApiBody({
type: CreateDto,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Post('create')
async create(@Body() createDto: CreateDto): Promise<boolean> {
try {
const result = await this.settingsService.create(createDto);
return result;
} catch (err) {
throw err;
}
}
// update ************************************
@ApiOperation({
summary: ' تغییر تنظیمات',
})
@ApiOkResponse({
description: ' Boolean',
type: Boolean,
})
@ApiBody({
type: CreateDto,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Patch('edit')
async edit(@Body() createDto: CreateDto): Promise<boolean> {
try {
const result = await this.settingsService.edit(createDto);
return result;
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { SettingsDataAccess } from '../../../dataAccess/settings.dataAccess';
import { SettingsService } from './settings.service';
import { SettingController } from './settings.controller';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [SettingController],
providers: [SettingsService, SettingsDataAccess, UserDataAccess],
})
export class SettingsModule {}
@@ -0,0 +1,112 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { SettingsDataAccess } from '../../../dataAccess/settings.dataAccess';
import { CreateDto } from '../../../DTO/settings.dto';
import { Settings } from '../../../models/schemas/settings.schema';
@Injectable()
export class SettingsService {
constructor(private readonly settingDataAccess: SettingsDataAccess) {}
async find(): Promise<Settings> {
const result = await this.settingDataAccess.findOne();
if (result.length === 0) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' تنظیمات وجود ندارد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
return result[0];
}
async create(createDto: CreateDto): Promise<boolean> {
const {
about,
student,
hours,
title,
expertise,
nameCompany,
mission,
start,
goals,
rate,
titleText,
header,
} = createDto;
const result = await this.settingDataAccess.findOne();
if (result.length === 0) {
await this.settingDataAccess.create(
about,
student,
hours,
title,
expertise,
nameCompany,
mission,
start,
goals,
rate,
titleText,
header,
);
} else {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' قابل ساخت دوباره نیست شما می توانید تغییر دهید ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
return true;
}
async edit(createDto: CreateDto): Promise<boolean> {
const {
about,
student,
hours,
title,
expertise,
nameCompany,
mission,
start,
goals,
rate,
titleText,
header,
} = createDto;
const result = await this.settingDataAccess.findOne();
if (result.length === 0) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' تنظیمات وجود ندارد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
await this.settingDataAccess.update(
result[0]._id,
about,
student,
hours,
title,
expertise,
nameCompany,
mission,
start,
goals,
rate,
titleText,
header,
);
return true;
}
}
@@ -0,0 +1,78 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Patch,
Query,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { UserService } from './user.service';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import { AuthGuard } from '../../../auth/auth.guard';
import { QueryUserList, UserMidDto, UserStatus } from '../../../DTO/user.dto';
import { CurrentUser } from '../../../decorators/currentUser.decorator';
@ApiTags('admin user')
@Controller('admin/user')
export class UserController {
constructor(private readonly userService: UserService) {}
// list user ************************************
@ApiOperation({
summary: ' لیست کاربران ',
})
@ApiOkResponse({
description: ' لیست کاربران به صورت آرایه زیر ',
type: [UserMidDto],
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Get('list')
async listUser(
@Query() queryUserList: QueryUserList,
): Promise<{ user: UserMidDto[]; count: number }> {
try {
const result = await this.userService.list(queryUserList);
return result;
} catch (err) {
throw err;
}
}
// update user ************************************
@ApiOperation({
summary: ' تغییر وضعیت کاربر',
})
@ApiOkResponse({
description: ' پاسخ تغییر وضعیت ',
type: Boolean,
})
@ApiBody({
type: UserStatus,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
@Patch('status')
async statusUser(@Body() userStatus: UserStatus): Promise<boolean> {
try {
const result = await this.userService.status(userStatus);
return result;
} catch (err) {
throw err;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [],
controllers: [UserController],
providers: [UserService, UserDataAccess],
})
export class UserModule {}
@@ -0,0 +1,38 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import {
QueryUserList,
UserMidDto,
userMidObj,
UserStatus,
} from '../../../DTO/user.dto';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Injectable()
export class UserService {
constructor(private readonly userDataAccess: UserDataAccess) {}
async list(
queryUserList: QueryUserList,
): Promise<{ user: UserMidDto[]; count: number }> {
const { page, pageSize } = queryUserList;
const result = await this.userDataAccess.list(page, pageSize);
const count = await this.userDataAccess.count();
return { user: result.map((item) => userMidObj(item)), count: count };
}
// user status
async status(userStatus: UserStatus): Promise<boolean> {
const { userId, status } = userStatus;
const user = await this.userDataAccess.findOne(userId);
if (!user) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' کاربر وجود ندارد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
await this.userDataAccess.status(userId, status);
return true;
}
}
@@ -0,0 +1,154 @@
import { IpAddress } from '../../../decorators/ipAddress.decorator';
import {
Controller,
HttpStatus,
HttpCode,
Get,
Param,
Post,
Body,
Query,
Res,
UseGuards,
} from '@nestjs/common';
import { Response } from 'express';
import {
ApiTags,
ApiOkResponse,
ApiOperation,
ApiBody,
ApiBearerAuth,
} from '@nestjs/swagger';
import {
PostDto,
getPostInputDto,
ViewListPostDto,
SearchParameter,
} from '../../../DTO/blogPost.dto';
import { BlogService } from './blog.service';
import {
CreatePostCommentDto,
PostCommentDto,
} from '../../../DTO/blogPostComment.dto';
import { AuthGuard } from '../../../auth/auth.guard';
import { Roles } from '../../../decorators/role.decorators';
import { Role } from '../../../common/eNums/role.enum';
import { CurrentUser } from '../../../decorators/currentUser.decorator';
import { CheckGuard } from './blog.guard';
@ApiTags('common blog')
@Controller('common/blog')
export class BlogController {
constructor(private readonly blogService: BlogService) {}
// blog cat ***********************************************************
@ApiOperation({ summary: 'فیلتر و لیست پست های بلاگ' })
@ApiOkResponse({
description: 'list Posts',
type: [ViewListPostDto],
})
@Get('postList')
@HttpCode(HttpStatus.OK)
async listPost(@Query() query: getPostInputDto): Promise<ViewListPostDto[]> {
const list = await this.blogService.listPost(query);
return list;
}
// post **********************************************************************
@ApiOperation({ summary: 'جزییات پست بلاگ' })
@ApiOkResponse({
description: 'Post info',
type: [PostDto],
})
@Get('post/:slug')
@HttpCode(HttpStatus.OK)
@UseGuards(CheckGuard)
@ApiBearerAuth()
async Post(
@CurrentUser() user,
@Param('slug')
slug: string,
@IpAddress() ip: string,
@Res() res: Response,
): Promise<PostDto> {
try {
const post = await this.blogService.post(slug, user, ip);
res.json(post);
return;
} catch (err) {
throw err;
}
}
// create post comment **********************************************************
@ApiOperation({ summary: ' ساخت کامنت ' })
@ApiOkResponse({
description: 'post comment Dto',
type: [PostCommentDto],
})
@ApiBody({
type: CreatePostCommentDto,
description: 'create postComment Dto',
})
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Post('postComment/:id')
@HttpCode(HttpStatus.OK)
async createPostComment(
@CurrentUser() user,
@Body() createPostCommentDto: CreatePostCommentDto,
@Param('id')
id: string,
): Promise<boolean> {
try {
await this.blogService.createPostComment(user, createPostCommentDto, id);
return true;
} catch (err) {
throw err;
}
}
// reply post comment *********************************************************
@ApiOperation({ summary: ' پاسخ دادن کامنت یک پست ' })
@ApiOkResponse({
description: 'PostComment Dto',
type: [PostCommentDto],
})
@ApiBody({
type: CreatePostCommentDto,
description: 'reply post comment',
})
// @Post('postComment/reply/:id')
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Post('postComment/:id')
@HttpCode(HttpStatus.OK)
async replyPostComment(
@CurrentUser() user,
@Body() createPostCommentDto: CreatePostCommentDto,
@Param('id')
id: string,
): Promise<boolean> {
try {
await this.blogService.replyPostComment(user, createPostCommentDto, id);
return true;
} catch (err) {
throw err;
}
}
// search blog ******************************************************************
@ApiOperation({ summary: ' جست و جو در بلاگ ' })
@ApiOkResponse({
description: 'search result',
type: [ViewListPostDto],
})
@Get('search')
@HttpCode(HttpStatus.OK)
async search(@Query() query: SearchParameter): Promise<ViewListPostDto[]> {
try {
const result = await this.blogService.search(query);
return result;
} catch (err) {
throw err;
}
}
}
+33
View File
@@ -0,0 +1,33 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { Request } from 'express';
@Injectable()
export class CheckGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<any> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
return true;
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: jwtConstants.secret,
});
request['user'] = payload;
return payload;
} catch (err) {
console.log(err);
return true;
}
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
@@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
import { BlogPostDataAccess } from '../../../dataAccess/blogPost.dataAccess';
import { CategoryDataAccess } from '../../../dataAccess/blogCategory.dataAccess';
import { BlogPostCommentDataAccess } from '../../../dataAccess/blogPostComment.dataAccess';
import { PostVisitDataAccess } from '../../../dataAccess/postVisit.dataAccess';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [BlogController],
providers: [
BlogService,
CategoryDataAccess,
BlogPostDataAccess,
BlogPostCommentDataAccess,
PostVisitDataAccess,
UserDataAccess,
],
})
export class BlogModule {}
+144
View File
@@ -0,0 +1,144 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { getAllBlogCategoriesObj } from '../../../DTO/blogCategory.dto';
import { CategoryDataAccess } from '../../../dataAccess/blogCategory.dataAccess';
import { ItemStatus } from '../../../common/eNums/itemStatus.enum';
import { BlogPostCommentDataAccess } from '../../../dataAccess/blogPostComment.dataAccess';
import { BlogPostDataAccess } from '../../../dataAccess/blogPost.dataAccess';
import { PostVisitDataAccess } from '../../../dataAccess/postVisit.dataAccess';
import {
postInfoObjDto,
viewPostObjDto,
ViewListPostDto,
} from '../../../DTO/blogPost.dto';
import commentStatuses from '../../../common/eNums/itemStatus.enum';
@Injectable()
export class BlogService {
constructor(
private readonly categoryDataAccess: CategoryDataAccess,
private readonly blogPostCommentDataAccess: BlogPostCommentDataAccess,
private readonly blogPostDataAccess: BlogPostDataAccess,
private readonly postVisitDataAccess: PostVisitDataAccess,
) {}
// blogCategory ******************************************************
async listPost(query) {
const listPost = await this.blogPostDataAccess.listBlogPosts(query);
return listPost.map((item) => viewPostObjDto(item));
}
// post *****************************************************************
async post(slug, user, ip) {
const post = await this.blogPostDataAccess.findBlogPostBySlug(slug);
if (!post) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'POST_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
} else {
if (!user) {
user = { id: null };
}
const visit = await this.postVisitDataAccess.findById(
user.id,
post._id,
ip,
);
if (!visit) {
if (user) {
await this.blogPostDataAccess.updateVisitCount(
post._id,
post.visitCount + 1,
);
await this.postVisitDataAccess.create(user.id, post._id, ip);
} else {
user = { id: null };
await this.blogPostDataAccess.updateVisitCount(
post._id,
post.visitCount + 1,
);
await this.postVisitDataAccess.create(user.id, post._id, ip);
}
}
// await this.blogPostDataAccess.updateVisitCount(post.id, post.visitCount + 1);
if (post.Comment.length !== 0) {
post.Comment = post.Comment.filter(
(x) => x.status === commentStatuses.Active,
);
} else {
post.Comment = [];
}
return postInfoObjDto(post);
}
}
// create postComment ***************************************************
async createPostComment(userInf, createPostCommentDto, id): Promise<boolean> {
const { text } = createPostCommentDto;
const post = await this.blogPostDataAccess.findById(id);
if (!post || post.status !== ItemStatus.Active) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'پست وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
} else {
const comment =
await this.blogPostCommentDataAccess.createBlogPostComment(
id,
userInf.id,
text,
);
post.Comment.push(comment._id ? comment._id : comment.id);
post.save();
return true;
}
}
// blogCat structure ******************************************************
async getAllBlogCategories() {
const list = await this.categoryDataAccess.getAll();
for (let i = 0; i < list.length; i++) {
list[i].parents = list[i].parents.filter(
(x) => x.status === ItemStatus.Active,
);
}
return list.map((item) => getAllBlogCategoriesObj(item));
}
// replay post comment *******************************************************
async replyPostComment(userInf, createPostCommentDto, id): Promise<boolean> {
const postComment = await this.blogPostCommentDataAccess.findById(id);
if (!postComment || postComment.status !== ItemStatus.Active) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'کامنت وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
} else {
const { text } = createPostCommentDto;
const replyComment = await this.blogPostCommentDataAccess.replyComment(
postComment.blogPost._id,
id,
userInf.id,
text,
);
postComment.Replies.push(replyComment.id);
postComment.save();
return true;
}
}
// search blog *************************************************************
async search(query): Promise<ViewListPostDto[]> {
const { title, page, pageSize } = query;
const resultPosts = await this.blogPostDataAccess.search(
title,
page,
pageSize,
);
return resultPosts.map((item) => viewPostObjDto(item));
}
}
+3
View File
@@ -0,0 +1,3 @@
export const jwtConstants = {
secret: '8FWQDF%$ERDFGH#$FDSJ&*#@R1!34Vh',
};
@@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { CommonService } from './common.service';
@ApiTags('commons')
@Controller('commons')
export class CommonController {
constructor(private readonly commonService: CommonService) {}
}
+16
View File
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { Tools } from '../../common/helpers/tools.helpers';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { CommonController } from './common.controller';
import { CommonService } from './common.service';
import { BlogModule } from './blog/blog.module';
import { FaqModule } from './faq/faq.module';
import { SettingsModule } from './settings/settings.module';
@Module({
imports: [BlogModule, FaqModule, SettingsModule],
controllers: [CommonController],
providers: [CommonService, Tools, UserDataAccess],
})
export class CommonModule {}
+6
View File
@@ -0,0 +1,6 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class CommonService {
constructor() {}
}
@@ -0,0 +1,27 @@
import { Controller, HttpStatus, HttpCode, Get, Query } from '@nestjs/common';
import { ApiTags, ApiOkResponse } from '@nestjs/swagger';
import {
FaqCategoryDto,
FilterFaqCategoryDto,
} from '../../../DTO/faqCategory.dto';
import { FaqService } from './faq.service';
@ApiTags('common faqs')
@Controller('common/faq')
export class FaqController {
constructor(private readonly faqService: FaqService) {}
// faqCategory list
@ApiOkResponse({
description: 'faqs list',
type: [FaqCategoryDto],
})
@HttpCode(HttpStatus.OK)
@Get('faq')
async faqList(
@Query() filterFaqCategoryDto: FilterFaqCategoryDto,
): Promise<FaqCategoryDto[]> {
const faqList = await this.faqService.faqList(filterFaqCategoryDto);
return faqList;
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { FaqController } from './faq.controller';
import { FaqService } from './faq.service';
import { FaqDataAccess } from '../../../dataAccess/faq.dataAccess';
@Module({
imports: [],
controllers: [FaqController],
providers: [FaqService, FaqDataAccess],
})
export class FaqModule {}
+22
View File
@@ -0,0 +1,22 @@
import { Injectable } from '@nestjs/common';
import {
FaqCategoryDto,
faqCategoryObj,
FilterFaqCategoryDto,
} from '../../../DTO/faqCategory.dto';
import { FaqDataAccess } from '../../../dataAccess/faq.dataAccess';
@Injectable()
export class FaqService {
constructor(private readonly faqDataAccess: FaqDataAccess) {}
async faqList(
filterFaqCategoryDto: FilterFaqCategoryDto,
): Promise<FaqCategoryDto[]> {
const list = await this.faqDataAccess.findAllFaqCategory(
filterFaqCategoryDto,
);
return list.map((item) => faqCategoryObj(item));
}
}
@@ -0,0 +1,28 @@
import { Controller, Get, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { SettingsService } from './settings.service';
import { CreateDto } from '../../../DTO/settings.dto';
@ApiTags('common settings')
@Controller('common/settings')
export class SettingController {
constructor(private readonly settingsService: SettingsService) {}
// get ************************************
@ApiOperation({
summary: ' تنظیمات سایت',
})
@ApiOkResponse({
description: ' Create Dto ',
type: CreateDto,
})
@HttpCode(HttpStatus.OK)
@Get()
async list(): Promise<CreateDto> {
try {
const result = await this.settingsService.find();
return result;
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { SettingsDataAccess } from '../../../dataAccess/settings.dataAccess';
import { SettingsService } from './settings.service';
import { SettingController } from './settings.controller';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [SettingController],
providers: [SettingsService, SettingsDataAccess, UserDataAccess],
})
export class SettingsModule {}
@@ -0,0 +1,23 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { SettingsDataAccess } from '../../../dataAccess/settings.dataAccess';
import { Settings } from '../../../models/schemas/settings.schema';
@Injectable()
export class SettingsService {
constructor(private readonly settingDataAccess: SettingsDataAccess) {}
async find(): Promise<Settings> {
const result = await this.settingDataAccess.findOne();
if (result.length === 0) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' تنظیمات وجود ندارد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
return result[0];
}
}
@@ -0,0 +1,81 @@
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Post,
Query,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
ApiBearerAuth,
ApiBody,
ApiConsumes,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { GetFileDTO, UploaderDTO } from '../../../DTO/uploader.dto';
import { UploaderService } from './uploader.service';
import { AuthGuard } from '../../../auth/auth.guard';
import { Role } from '../../../common/eNums/role.enum';
import { Roles } from '../../../decorators/role.decorators';
@ApiTags('Uploader')
@Controller('uploader')
export class UploaderController {
constructor(private readonly uploaderService: UploaderService) {}
@ApiOperation({
summary: ' آپلود فایل ',
})
@ApiBody({
type: UploaderDTO,
description: 'Uploader DTO',
})
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Post()
uploadFile(@UploadedFile() file, @Body() uploaderDto: UploaderDTO) {
try {
if (file) {
return this.uploaderService.upload(file, uploaderDto.directoryName);
}
} catch (err) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'BAD_REQUEST',
},
HttpStatus.BAD_REQUEST,
);
}
}
@ApiOperation({
summary: ' چک کردن وجود داشتن مدیا ',
})
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Get('file')
getFile(@Query() getFileDTO: GetFileDTO) {
try {
return this.uploaderService.getFileUrl(getFileDTO.fileName);
} catch (err) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'BAD_REQUEST',
},
HttpStatus.BAD_REQUEST,
);
}
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { UploaderService } from './uploader.service';
import { UploaderController } from './uploader.controller';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
imports: [],
controllers: [UploaderController],
providers: [UploaderService, UserDataAccess],
exports: [UploaderService],
})
export class UploaderModule {}
@@ -0,0 +1,52 @@
import { Injectable } from '@nestjs/common';
import { S3 } from 'aws-sdk';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class UploaderService {
constructor() {}
client: any = new S3({
accessKeyId: process.env.STORAGE_ACCESS_KEY,
secretAccessKey: process.env.STORAGE_SECRET_KEY,
endpoint: process.env.STORAGE_ENDPOINT,
region: process.env.STORAGE_REGION,
});
async upload(file, directoryName) {
const params = {
Body: file.buffer,
Bucket: process.env.STORAGE_BUCKET,
Key: directoryName + '/' + uuidv4() + file.originalname,
};
const uploadFile = await this.client.upload(params).promise();
return {
url: uploadFile.Location,
key: uploadFile.key,
};
}
async getFileUrl(fileName) {
const params = {
Bucket: process.env.STORAGE_BUCKET,
Key: fileName,
};
try {
// Check if the file exists in storage
const data = await this.client.headObject(params).promise();
// If the file exists, return the file
return {
result: true,
url: `${process.env.STORAGE_ENDPOINT}/${params.Bucket}/${params.Key}`,
data: data,
};
} catch (error) {
// If the file does not exist, return an error
return {
result: false,
error: `File ${params.Key} does not exist in storage`,
};
}
}
}
+211
View File
@@ -0,0 +1,211 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
Put,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { CourseService } from './course.service';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiQuery,
ApiTags,
} from '@nestjs/swagger';
import {
CreateCourseDto,
FilterCategoryPaginationCourseDto,
UpdateCourseDto,
} from '../../DTO/course.dto';
import { AuthGuard } from '../../auth/auth.guard';
import { UploaderService } from '../common/uploader/uploader.service';
import { Role } from '../../common/eNums/role.enum';
import { Roles } from '../../decorators/role.decorators';
import { CreateCommentDto } from '../../DTO/comment.dto';
import { CurrentUser } from '../../decorators/currentUser.decorator';
import { CreateRateDto } from '../../DTO/rate.dto';
import { StatusCommentDto } from 'src/DTO/courseHeadlineComment.dto';
@ApiTags('course')
@Controller('course')
export class CourseController {
constructor(
private courseService: CourseService,
private uploaderService: UploaderService,
) {}
@ApiOperation({
summary: 'ایجاد دوره',
})
@ApiBody({
type: CreateCourseDto,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Post()
@HttpCode(HttpStatus.CREATED)
async createCourse(@Body() createCourseDto: CreateCourseDto) {
return this.courseService.createCourse(createCourseDto);
}
// profile user ************************************
@ApiOperation({
summary: ' دریافت دوره های من',
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Get('/course-user')
async getProfile(@CurrentUser() user) {
try {
const result = await this.courseService.getCourseUser(user._id);
return result;
} catch (err) {
throw err;
}
}
@ApiOperation({
summary: 'دریافت دوره با آیدی',
})
@ApiQuery({
name: 'userId',
type: String,
required: false,
description: 'آیدی کاربر',
})
@Get(':id')
@HttpCode(HttpStatus.OK)
async getCourse(@Param('id') id: string, @Query('userId') userId?: string) {
return this.courseService.getCourse(id, userId);
}
@ApiOperation({
summary: 'دریافت همه دوره ها',
})
@Get('')
@HttpCode(HttpStatus.OK)
async getAllCourses(
@Query()
filterCategoryPaginationCourseDto: FilterCategoryPaginationCourseDto,
) {
return this.courseService.getAllCourses(filterCategoryPaginationCourseDto);
}
@ApiOperation({
summary: 'بروزرسانی دوره',
})
@ApiBody({
type: UpdateCourseDto,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Put('')
@HttpCode(HttpStatus.OK)
async updateCourse(@Body() updateCourseDto: UpdateCourseDto) {
return this.courseService.updateCourse(updateCourseDto);
}
// changeStatus postComment *********************************************************************
@ApiOperation({ summary: 'change status' })
@ApiOkResponse({
description: 'api status',
type: Boolean,
})
@ApiBody({
type: StatusCommentDto,
description: 'change status ',
})
@Patch('status')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeStatus(@Body() statusCommentDto: StatusCommentDto): Promise<any> {
try {
await this.courseService.changeStatus(statusCommentDto);
return { status: true };
} catch (err) {
throw err;
}
}
@ApiOperation({
summary: 'دریافت دوره ها بر اساس فیلتر دسته بندی',
})
@Get('category/:id')
async findCoursesByCategory(@Param('id') id: string) {
return this.courseService.findCoursesByCategory(id);
}
@ApiOperation({
summary: 'ایجاد کامنت',
})
@ApiBody({
type: CreateCommentDto,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin, Role.User, Role.Reader, Role.Writer)
@Post(':id/comment')
async createComment(
@Param('id') id: string,
@CurrentUser() user,
@Body() createCommentDto: CreateCommentDto,
) {
return this.courseService.createComment(createCommentDto, id, user.id);
}
@ApiOperation({
summary: 'ایجاد امتیاز',
})
@ApiBody({
type: CreateRateDto,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin, Role.User, Role.Reader, Role.Writer)
@Post(':id/rate')
async createRate(
@Param('id') id: string,
@CurrentUser() user,
@Body() createRateDto: CreateRateDto,
) {
return this.courseService.addRating(createRateDto, id, user.id);
}
@ApiOperation({
summary: 'دریافت امتیاز',
})
@Get(':id/rate')
async getRating(@Param('id') id: string) {
return this.courseService.getRating(id);
}
@ApiOperation({
summary: 'حذف دوره',
})
@Delete(':id')
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
async deleteCourse(@Param('id') id: string, @CurrentUser() user) {
return this.courseService.deleteCourse(id, user.id);
}
}
+28
View File
@@ -0,0 +1,28 @@
import { Module } from '@nestjs/common';
import { CourseController } from './course.controller';
import { CourseService } from './course.service';
import { UserModule } from '../user/user.module';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { CourseDataAccess } from '../../dataAccess/course.dataAccess';
import { UploaderService } from '../common/uploader/uploader.service';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
import { CourseCategoryDataAccess } from '../../dataAccess/courseCategory.dataAccess';
import { CommentDataAccess } from '../../dataAccess/comment.dataAccess';
@Module({
imports: [
UserModule,
],
controllers: [CourseController],
providers: [
CourseService,
CourseDataAccess,
UserDataAccess,
CourseHeadlineDataAccess,
UploaderService,
CourseCategoryDataAccess,
CommentDataAccess
],
exports: [CourseService]
})
export class CourseModule { }
+200
View File
@@ -0,0 +1,200 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CourseDataAccess } from '../../dataAccess/course.dataAccess';
import {
CreateCourseDto,
FilterCategoryPaginationCourseDto,
UpdateCourseDto,
} from '../../DTO/course.dto';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { Course } from '../../models/schemas/course.schema';
import { CourseCategoryDataAccess } from '../../dataAccess/courseCategory.dataAccess';
import { CreateCommentDto } from '../../DTO/comment.dto';
import { CommentDataAccess } from '../../dataAccess/comment.dataAccess';
import { Comment } from '../../models/schemas/comment.schema';
import { Rate } from '../../models/schemas/rate.schema';
import { CreateRateDto } from '../../DTO/rate.dto';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
@Injectable()
export class CourseService {
constructor(
private courseDataAccess: CourseDataAccess,
private userDataAccess: UserDataAccess,
private courseCategoryDataAccess: CourseCategoryDataAccess,
private commentDataAccess: CommentDataAccess,
private courseHeadlineDataAccess: CourseHeadlineDataAccess,
) {}
async createCourse(
createCourseDto: CreateCourseDto,
): Promise<{ message: string; course: Course }> {
try {
const teacher = await this.userDataAccess.findOne(
createCourseDto.teacher,
);
if (!teacher) {
throw new HttpException('استاد یافت نشد', HttpStatus.NOT_FOUND);
}
// if (createCourseDto.image) {
// const fileUrl = await this.uploaderService.getFileUrl(createCourseDto.image);
// if (fileUrl.result === true) {
// createCourseDto.image = fileUrl.url;
// } else {
// throw new HttpException('فایل یافت نشد', HttpStatus.NOT_FOUND);
// }
// }
createCourseDto.teacher = teacher._id.toString();
if (createCourseDto.categories) {
const courseCategory =
await this.courseCategoryDataAccess.findCourseCategoryByIds(
createCourseDto.categories,
);
if (!courseCategory) {
throw new HttpException(
'دسته بندی دوره یافت نشد',
HttpStatus.NOT_FOUND,
);
}
createCourseDto.categories = courseCategory.map((category) =>
category._id ? category._id : category.id,
);
}
const course = await this.courseDataAccess.createCourse(createCourseDto);
return { message: 'دوره با موفقیت ثبت شد', course };
} catch (error) {
if (error.code === 11000 && error.keyPattern && error.keyPattern.slug) {
throw new HttpException(
'دوره با این slug قبلا ثبت شده است',
HttpStatus.CONFLICT,
);
} else {
console.log(error);
throw new HttpException('خطای سرور', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
async getCourse(
id: string,
userId?: string,
): Promise<{
course: Course;
comments: Comment[];
completionPercentage: number;
}> {
const course = await this.courseDataAccess.getCourse(id);
if (!course) {
throw new HttpException('دوره یافت نشد', HttpStatus.NOT_FOUND);
}
const comments = await this.commentDataAccess.getCommentsByCourseId(id);
if (userId) {
const user = await this.userDataAccess.findOne(userId);
const courseHeadlines =
await this.courseHeadlineDataAccess.getCourseHeadlines(id);
const totalHeadlines = courseHeadlines.length;
let completionPercentage = 0;
if (user.courseHeadlineProgress) {
console.log(user.courseHeadlineProgress);
console.log(courseHeadlines);
const completedHeadlines = user.courseHeadlineProgress.filter(
(progress) =>
courseHeadlines.some(
(headline) => headline._id.toString() === progress.toString(),
),
).length;
console.log({ completedHeadlines });
completionPercentage = (completedHeadlines / totalHeadlines) * 100;
}
return { course, comments, completionPercentage };
}
return { course, comments, completionPercentage: 0 };
}
async getAllCourses(
filterCategoryPaginationCourseDto: FilterCategoryPaginationCourseDto,
): Promise<{ courses: Course[]; count: number }> {
return this.courseDataAccess.getCourses(filterCategoryPaginationCourseDto);
}
async updateCourse(updateCourseDto: UpdateCourseDto): Promise<Course> {
if (updateCourseDto.teacher) {
const teacher = await this.userDataAccess.findOne(
updateCourseDto.teacher,
);
if (!teacher) {
throw new HttpException('استاد یافت نشد', HttpStatus.NOT_FOUND);
}
updateCourseDto.teacher = teacher._id ? teacher._id : teacher.id;
}
return this.courseDataAccess.updateCourse(updateCourseDto);
}
async findCoursesByCategory(
categoryId: string,
): Promise<{ courses: Course[]; count: number }> {
return this.courseDataAccess.findCoursesByCategory(categoryId);
}
async createComment(
createCommentDto: CreateCommentDto,
courseId: string,
userId: string,
): Promise<Comment> {
return this.commentDataAccess.createComment(
createCommentDto,
courseId,
userId,
);
}
async addRating(
createRateDto: CreateRateDto,
courseId,
userId,
): Promise<Rate> {
return this.courseDataAccess.addRating(
courseId,
userId,
createRateDto.rating,
);
}
async getRating(courseId: string): Promise<Rate[]> {
return this.courseDataAccess.getRating(courseId);
}
async getCourseUser(userId: string) {
return this.courseDataAccess.getCourseUser(userId);
}
async deleteCourse(courseId: string, userId: string) {
const course = await this.courseDataAccess.getCourse(courseId);
// if (course.teacher.toString() !== userId) {
if (!course) {
throw new HttpException('دوره وجود ندارد', HttpStatus.FORBIDDEN);
}
return this.courseDataAccess.deleteCourse(courseId);
}
async changeStatus(statusPostCommentDto): Promise<boolean> {
const { id, status } = statusPostCommentDto;
const result = await this.courseDataAccess.findByIdCom(id);
if (!result) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'COMMENT_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.courseDataAccess.changeStatus(id, status);
return true;
}
}
@@ -0,0 +1,66 @@
import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
import { CourseCategoryService } from './courseCategory.service';
import { CourseCategory } from '../../models/schemas/courseCategory.schema';
import { CreateCourseCategoryDto } from '../../DTO/courseCategory.dto';
import { AuthGuard } from '../../auth/auth.guard';
import { Roles } from '../../decorators/role.decorators';
import { Role } from '../../common/eNums/role.enum';
import { HttpStatus } from 'aws-sdk/clients/lambda';
@ApiTags('courseCategory')
@Controller('courseCategory')
export class CourseCategoryController {
constructor(private readonly courseCategoryService: CourseCategoryService) {}
@ApiOperation({
summary: 'ایجاد دسته بندی دوره',
})
@ApiBody({
type: CreateCourseCategoryDto,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Post()
async createCourseCategory(
@Body() createCourseCategoryDto: CreateCourseCategoryDto,
): Promise<CourseCategory> {
return this.courseCategoryService.createCourseCategory(
createCourseCategoryDto,
);
}
@ApiOperation({
summary: 'دریافت همه دسته بندی ها',
})
@Get()
async getAllCourseCategories(): Promise<CourseCategory[]> {
return this.courseCategoryService.getAllCourseCategories();
}
@ApiOperation({
summary: 'دریافت دسته بندی دوره با آیدی',
})
@Get(':id')
async getCourseCategoryById(
@Param('id') id: string,
): Promise<CourseCategory> {
return this.courseCategoryService.getCourseCategoryById(id);
}
@ApiOperation({
summary: 'حذف دسته بندی دوره با آیدی',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Delete(':id')
async deleteCourseCategoryById(@Param('id') id: string): Promise<{
status: HttpStatus;
message: string;
}> {
return this.courseCategoryService.deleteCourseCategoryById(id);
}
}
@@ -0,0 +1,18 @@
import { Module } from "@nestjs/common";
import { CourseCategoryController } from "./courseCategory.controller";
import { CourseCategoryService } from "./courseCategory.service";
import { CourseCategoryDataAccess } from "../../dataAccess/courseCategory.dataAccess";
import { UserDataAccess } from "../../dataAccess/user.dataAccess";
@Module({
imports: [],
controllers: [CourseCategoryController],
providers: [
CourseCategoryService,
CourseCategoryDataAccess,
UserDataAccess
],
exports: [CourseCategoryService]
})
export class CourseCategoryModule {}
@@ -0,0 +1,41 @@
import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { CreateCourseCategoryDto } from "../../DTO/courseCategory.dto";
import { CourseCategoryDataAccess } from "../../dataAccess/courseCategory.dataAccess";
import { CourseCategory } from "../../models/schemas/courseCategory.schema";
@Injectable()
export class CourseCategoryService {
constructor(private courseCategoryDataAccess: CourseCategoryDataAccess) {}
async createCourseCategory(courseCategoryDto: CreateCourseCategoryDto): Promise<CourseCategory> {
return this.courseCategoryDataAccess.createCourseCategory(courseCategoryDto);
}
async getAllCourseCategories(): Promise<CourseCategory[]> {
return this.courseCategoryDataAccess.findAllCourseCategories();
}
async getCourseCategoryById(id: string): Promise<CourseCategory> {
return this.courseCategoryDataAccess.findCourseCategoryById(id);
}
async deleteCourseCategoryById(id: string) {
const courses = await this.courseCategoryDataAccess.findCoursesByCategoryId(id);
console.log(courses)
if (courses.length > 0) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'این دسته بندی نمیتواند حذف شود',
},
HttpStatus.BAD_REQUEST,
);
}
await this.courseCategoryDataAccess.deleteCourseCategoryById(id);
return {
status: HttpStatus.OK,
message: 'دسته بندی با موفقیت حذف شد',
};
}
}
@@ -0,0 +1,125 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { CourseHeadlineService } from './courseHeadline.service';
import {
CreateCourseHeadlineDTO,
EpisodeDTO,
UpdateCourseHeadlineDTO,
} from '../../DTO/courseHeadline.dto';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import CourseHeadline from '../../models/courseHeadline.model';
import { AuthGuard } from '../../auth/auth.guard';
import { Roles } from '../../decorators/role.decorators';
import { Role } from '../../common/eNums/role.enum';
import { CurrentUser } from '../../decorators/currentUser.decorator';
@ApiTags('courseHeadline')
@Controller('courseHeadline')
export class CourseHeadlineController {
constructor(private readonly courseHeadlineService: CourseHeadlineService) {}
@ApiOperation({
summary: 'ایجاد سرفصل دوره',
})
@ApiBody({
type: CreateCourseHeadlineDTO,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Post()
createCourseHeadline(
@Body() createCourseHeadlineDto: CreateCourseHeadlineDTO,
) {
return this.courseHeadlineService.createCourseHeadline(
createCourseHeadlineDto,
);
}
@ApiOperation({
summary: 'دریافت سرفصل دوره با ایدی دوره',
})
@ApiOkResponse({
type: CourseHeadline,
})
@Get()
getCourseHeadlines(@Query('courseId') courseId: string) {
return this.courseHeadlineService.getCourseHeadlines(courseId);
}
@ApiOperation({
summary: 'ویرایش سرفصل دوره',
})
@ApiBody({
type: UpdateCourseHeadlineDTO,
description: 'get Body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Put(':id')
async updateCourseHeadline(
@Param('id') id: string,
@Body() updateCourseHeadlineDto: UpdateCourseHeadlineDTO,
) {
return this.courseHeadlineService.updateCourseHeadline(
id,
updateCourseHeadlineDto,
);
}
@ApiOperation({
summary: 'ثبت پایان سرفصل دوره',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.User)
@Post('complete/:id')
async completeCourseHeadline(@Param('id') id: string, @CurrentUser() user) {
return this.courseHeadlineService.completeCourseHeadline(id, user.id);
}
@ApiOperation({
summary: 'حذف سرفصل دوره',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Delete(':id')
async deleteCourseHeadline(@Param('id') id: string) {
return this.courseHeadlineService.deleteCourseHeadline(id);
}
@ApiOperation({
summary: 'حذف سرفصل دوره از دوره',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Post('removeHeadlineFromCourse/:courseId/:headlineId')
async removeHeadlineFromCourse(
@Param('courseId') courseId: string,
@Param('headlineId') headlineId: string,
) {
return this.courseHeadlineService.removeHeadlineFromCourse(
courseId,
headlineId,
);
}
}
@@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { CourseHeadlineController } from './courseHeadline.controller';
import { CourseHeadlineService } from './courseHeadline.service';
import { CourseDataAccess } from '../../dataAccess/course.dataAccess';
import { CourseModule } from '../course/course.module';
import { CourseHeadlineCommentModule } from './courseHeadlineComment/courseHeadlineComment.module';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { Tools } from '../../common/helpers/tools.helpers';
import { UploaderService } from '../common/uploader/uploader.service';
@Module({
imports: [CourseModule, CourseHeadlineCommentModule],
controllers: [CourseHeadlineController],
providers: [
CourseHeadlineService,
CourseHeadlineDataAccess,
CourseDataAccess,
UserDataAccess,
Tools,
UploaderService,
],
exports: [CourseHeadlineService],
})
export class CourseHeadlineModule {}
@@ -0,0 +1,159 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CourseDataAccess } from '../../dataAccess/course.dataAccess';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
import {
CreateCourseHeadlineDTO,
EpisodeDTO,
UpdateCourseHeadlineDTO,
} from '../../DTO/courseHeadline.dto';
import { Tools } from '../../common/helpers/tools.helpers';
import { UploaderService } from '../common/uploader/uploader.service';
import { CourseHeadline } from '../../models/schemas/courseHeadline.schema';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import mongoose from 'mongoose';
@Injectable()
export class CourseHeadlineService {
constructor(
private courseHeadlineDataAccess: CourseHeadlineDataAccess,
private courseDataAccess: CourseDataAccess,
private tools: Tools,
private uploaderService: UploaderService,
private userDataAccess: UserDataAccess,
) {}
async createCourseHeadline(
createCourseHeadlineDto: CreateCourseHeadlineDTO,
): Promise<CourseHeadline> {
const course = await this.courseDataAccess.getOneCourse(
createCourseHeadlineDto.course,
);
if (!course) {
throw new Error('Course not found');
}
const files = await this.uploaderService.getFileUrl(
createCourseHeadlineDto.videoUrl,
);
console.log(files);
if (files.result == false) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' لینک ویدیو معتبر نمی باشد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
const time = await this.tools.timeVideo(files.url);
const result = await this.courseHeadlineDataAccess.createCourseHeadline(
createCourseHeadlineDto,
time,
);
course.courseHeadlines.push(result._id ? result._id : result.id);
await course.save();
return result;
}
async getCourseHeadlines(courseId: string): Promise<CourseHeadline[]> {
return await this.courseHeadlineDataAccess.getCourseHeadlines(courseId);
}
async addEpisodeToCourseHeadline(
courseHeadlineId: string,
episodeDto: EpisodeDTO,
): Promise<CourseHeadline> {
return await this.courseHeadlineDataAccess.addEpisode(
courseHeadlineId,
episodeDto,
);
}
async removeEpisodeFromCourseHeadline(
courseHeadlineId: string,
episodeId: string,
): Promise<CourseHeadline> {
return await this.courseHeadlineDataAccess.removeEpisode(
courseHeadlineId,
episodeId,
);
}
async updateCourseHeadline(
id: string,
updateCourseHeadlineDto: UpdateCourseHeadlineDTO,
): Promise<CourseHeadline> {
return await this.courseHeadlineDataAccess.updateCourseHeadline(
id,
updateCourseHeadlineDto,
);
}
async deleteCourseHeadline(
id: string,
): Promise<{ success: boolean; message: string }> {
const courseHeadline = await this.courseHeadlineDataAccess.getHeadline(id);
const removeHeadlineFromCourse = await this.removeHeadlineFromCourse(
courseHeadline.course.toString(),
id,
);
if (removeHeadlineFromCourse.success) {
await this.courseHeadlineDataAccess.deleteCourseHeadline(id);
return { success: true, message: 'سرفصل با موفقیت حذف شد' };
}
return { success: false, message: 'حذف سرفصل با مشکل موجه شد' };
}
async completeCourseHeadline(
id: string,
userId: string,
): Promise<{ success: boolean; message: string }> {
const courseHeadline = await this.courseHeadlineDataAccess.getHeadline(id);
if (!courseHeadline) {
throw new HttpException('سرفصل یافت نشد', HttpStatus.NOT_FOUND);
}
const user = await this.userDataAccess.findOne(userId);
if (
user.courseHeadlineProgress.includes(
courseHeadline._id as mongoose.Types.ObjectId,
)
) {
throw new HttpException('سرفصل فبلا ثبت شده است', HttpStatus.BAD_REQUEST);
}
user.courseHeadlineProgress.push(
courseHeadline._id as mongoose.Types.ObjectId,
);
await user.save();
return {
success: true,
message: 'سرفصل با موفقیت ثبت شد',
};
}
async removeHeadlineFromCourse(courseId: string, headlineId: string) {
const course = await this.courseDataAccess.getOneCourse(courseId);
if (!course) {
throw new HttpException('کورس یافت نشد', HttpStatus.NOT_FOUND);
}
const headline = await this.courseHeadlineDataAccess.getHeadline(
headlineId,
);
if (!headline) {
throw new HttpException('سرفصل یافت نشد', HttpStatus.NOT_FOUND);
}
if (
!course.courseHeadlines.some(
(headlineId) => headlineId._id.toString() === headline._id.toString(),
)
) {
throw new HttpException(
'سرفصل در این دوره یافت نشد',
HttpStatus.NOT_FOUND,
);
}
await this.courseDataAccess.removeHeadlineFromCourse(courseId, headlineId);
return {
success: true,
message: 'سرفصل با موفقیت از دوره حذف شد',
};
}
}
@@ -0,0 +1,116 @@
import {
Body,
Controller,
HttpCode,
HttpStatus,
Patch,
Post,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { AuthGuard } from '../../../auth/auth.guard';
import { Role } from '../../../common/eNums/role.enum';
import { CurrentUser } from '../../../decorators/currentUser.decorator';
import { Roles } from '../../../decorators/role.decorators';
import { CourseHeadlineCommentService } from './courseHeadlineComment.service';
import {
CreateCourseHeadlineCommentDto,
StatusCommentDto,
} from '../../../DTO/courseHeadlineComment.dto';
@ApiTags('courseHeadlineComment')
@Controller('courseHeadlineComment')
export class CourseHeadlineCommentController {
constructor(
private readonly courseHeadlineCommentService: CourseHeadlineCommentService,
) {}
// create post comment **********************************************************
@ApiOperation({ summary: ' ساخت کامنت ' })
@ApiOkResponse({
description: 'post comment Dto',
type: Boolean,
})
@ApiBody({
type: CreateCourseHeadlineCommentDto,
description: 'create comment Dto',
})
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Post('create')
@HttpCode(HttpStatus.OK)
async createPostComment(
@CurrentUser() user,
@Body() createCourseHeadlineCommentDto: CreateCourseHeadlineCommentDto,
): Promise<boolean> {
try {
await this.courseHeadlineCommentService.create(
user,
createCourseHeadlineCommentDto,
);
return true;
} catch (err) {
throw err;
}
}
// reply post comment *********************************************************
@ApiOperation({ summary: ' پاسخ دادن کامنت یک پست ' })
@ApiOkResponse({
description: 'PostComment Dto',
type: Boolean,
})
@ApiBody({
type: CreateCourseHeadlineCommentDto,
description: 'reply post comment',
})
@UseGuards(AuthGuard)
@Roles(Role.Admin, Role.User)
@ApiBearerAuth()
@Post('reply')
@HttpCode(HttpStatus.OK)
async replyPostComment(
@CurrentUser() user,
@Body() createCourseHeadlineCommentDto: CreateCourseHeadlineCommentDto,
): Promise<boolean> {
try {
await this.courseHeadlineCommentService.reply(
user,
createCourseHeadlineCommentDto,
);
return true;
} catch (err) {
throw err;
}
}
// changeStatus postComment *********************************************************************
@ApiOperation({ summary: 'change status' })
@ApiOkResponse({
description: 'api status',
type: Boolean,
})
@ApiBody({
type: StatusCommentDto,
description: 'change status ',
})
@Patch('status')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.Admin)
@ApiBearerAuth()
async changeStatus(@Body() statusCommentDto: StatusCommentDto): Promise<any> {
try {
await this.courseHeadlineCommentService.changeStatus(statusCommentDto);
return { status: true };
} catch (err) {
throw err;
}
}
}
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { CourseHeadlineCommentController } from './courseHeadlineComment.controller';
import { CourseHeadlineCommentService } from './courseHeadlineComment.service';
import { CourseHeadlineDataAccess } from '../../../dataAccess/courseHeadline.dataAccess';
import { CourseHeadlineCommentDataAccess } from '../../../dataAccess/courseHeadlineComment.dataAccess';
import { UserDataAccess } from '../../../dataAccess/user.dataAccess';
@Module({
controllers: [CourseHeadlineCommentController],
providers: [
CourseHeadlineCommentService,
CourseHeadlineCommentDataAccess,
CourseHeadlineDataAccess,
UserDataAccess,
],
})
export class CourseHeadlineCommentModule {}
@@ -0,0 +1,93 @@
import mongoose from 'mongoose';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CourseHeadlineDataAccess } from '../../../dataAccess/courseHeadline.dataAccess';
import { CourseHeadlineCommentDataAccess } from '../../../dataAccess/courseHeadlineComment.dataAccess';
import { ItemStatusCourseHeadline } from '../../../common/eNums/itemStatusCourseHeadline.enum';
import { CreateCourseHeadlineCommentDto } from '../../../DTO/courseHeadlineComment.dto';
import { ItemStatus } from '../../../common/eNums/itemStatus.enum';
@Injectable()
export class CourseHeadlineCommentService {
constructor(
private courseHeadlineDataAccess: CourseHeadlineDataAccess,
private courseHeadlineCommentDataAccess: CourseHeadlineCommentDataAccess,
) {}
// create ***************************************************
async create(
userInf,
createCourseHeadlineCommentDto: CreateCourseHeadlineCommentDto,
): Promise<boolean> {
const { text, id } = createCourseHeadlineCommentDto;
const courseHeadline = await this.courseHeadlineDataAccess.getHeadline(id);
if (
!courseHeadline ||
courseHeadline.status !== ItemStatusCourseHeadline.Completed
) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'سرفصل دوره وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
} else {
const comment = await this.courseHeadlineCommentDataAccess.create(
id,
userInf.id,
text,
);
courseHeadline.Comment.push(comment._id ? comment._id : comment.id);
courseHeadline.save();
return true;
}
}
// replay *******************************************************
async reply(
userInf,
createCourseHeadlineCommentDto: CreateCourseHeadlineCommentDto,
): Promise<boolean> {
const { text, id } = createCourseHeadlineCommentDto;
const courseHeadlineComment =
await this.courseHeadlineCommentDataAccess.findById(id);
if (
!courseHeadlineComment ||
courseHeadlineComment.status !== ItemStatus.Active
) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'کامنت وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
} else {
const replyComment =
await this.courseHeadlineCommentDataAccess.replyComment(
courseHeadlineComment.courseHeadline._id,
id,
userInf.id,
text,
);
courseHeadlineComment.Replies.push(replyComment.id);
courseHeadlineComment.save();
return true;
}
}
async changeStatus(statusPostCommentDto): Promise<boolean> {
const { id, status } = statusPostCommentDto;
const result = await this.courseHeadlineCommentDataAccess.findById(id);
if (!result) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'COMMENT_NOT_FOUND',
},
HttpStatus.NOT_FOUND,
);
}
await this.courseHeadlineCommentDataAccess.changeStatus(id, status);
return true;
}
}
+123
View File
@@ -0,0 +1,123 @@
import {
Body,
Controller,
DefaultValuePipe,
Get,
HttpCode,
HttpStatus,
Param,
ParseIntPipe,
Patch,
Post,
Query,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { TicketService } from './ticket.service';
import {
ApiBearerAuth,
ApiBody,
ApiOperation,
ApiQuery,
} from '@nestjs/swagger';
import { CreateTicketDto, CreateTicketMessageDto } from '../../DTO/ticket.dto';
import { CurrentUser } from '../../decorators/currentUser.decorator';
import { AuthGuard } from '../../auth/auth.guard';
import { ApiTags } from '@nestjs/swagger';
import { UploaderService } from '../common/uploader/uploader.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { Roles } from '../../decorators/role.decorators';
import { Role } from '../../common/eNums/role.enum';
@ApiTags('ticket')
@Controller('ticket')
export class TicketController {
constructor(
private ticketService: TicketService,
private uploaderService: UploaderService,
) {}
@ApiOperation({
summary: 'ایجاد تیکت',
})
@ApiBody({
type: CreateTicketDto,
description: 'get body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.User, Role.Admin)
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() createTicketDto: CreateTicketDto, @CurrentUser() user) {
return this.ticketService.create(createTicketDto, user.id);
}
@ApiOperation({
summary: 'لیست تیکت ها',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@ApiQuery({ name: 'page', required: false, type: Number })
@ApiQuery({ name: 'pageSize', required: false, type: Number })
@Roles(Role.User, Role.Admin, Role.Reader, Role.Writer)
@Get()
@HttpCode(HttpStatus.OK)
async findAll(
@CurrentUser() user,
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1,
@Query('pageSize', new DefaultValuePipe(10), ParseIntPipe) pageSize = 10,
) {
return this.ticketService.findAll({ page, pageSize }, user.id);
}
@ApiOperation({
summary: 'دریافت تیکت با آیدی',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.User, Role.Admin, Role.Reader, Role.Writer)
@Get(':id')
@HttpCode(HttpStatus.OK)
async findOne(@Param('id') id: string) {
return this.ticketService.findOne(id);
}
@ApiOperation({
summary: 'ایجاد پیام تیکت',
})
@ApiBody({
type: CreateTicketMessageDto,
description: 'get body',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.User, Role.Admin, Role.Reader, Role.Writer)
@Post('message')
@HttpCode(HttpStatus.CREATED)
async createMessage(
@Body() createTicketMessageDto: CreateTicketMessageDto,
@CurrentUser() user,
) {
return this.ticketService.createMessage(createTicketMessageDto, user.id);
}
@ApiOperation({
summary: 'بستن تیکت',
})
@UseGuards(AuthGuard)
@ApiBearerAuth()
@Roles(Role.Admin)
@Patch('close/:id')
@HttpCode(HttpStatus.OK)
async closeTicket(@Param('id') id: string) {
return this.ticketService.closeTicket(id);
}
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
console.log('file', file);
return this.uploaderService.upload(file, 'ticket');
}
}
+24
View File
@@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { TicketController } from './ticket.controller';
import { TicketService } from './ticket.service';
import { UserModule } from '../user/user.module';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { TicketDataAccess } from '../../dataAccess/ticket.dataAccess';
import { TicketMessageDataAccess } from '../../dataAccess/ticketMessage.dataAccess';
import { UploaderService } from '../common/uploader/uploader.service';
@Module({
imports: [
UserModule,
],
controllers: [TicketController],
providers: [
TicketService,
TicketDataAccess,
TicketMessageDataAccess,
UserDataAccess,
UploaderService
],
exports: [TicketService]
})
export class TicketModule { }
+133
View File
@@ -0,0 +1,133 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateTicketDto, CreateTicketMessageDto } from '../../DTO/ticket.dto';
import { TicketDataAccess } from '../../dataAccess/ticket.dataAccess';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { Ticket } from '../../models/schemas/ticket.schema';
import { TicketMessage } from '../../models/schemas/ticketMessage.schema';
import { TicketMessageDataAccess } from '../../dataAccess/ticketMessage.dataAccess';
import { PaginationDto } from '../../DTO/pagination.dto';
import { Role } from '../../common/eNums/role.enum';
@Injectable()
export class TicketService {
constructor(
private readonly ticketDataAccess: TicketDataAccess,
private readonly ticketMessageDataAccess: TicketMessageDataAccess,
private readonly userDataAccess: UserDataAccess,
) {}
async create(
createTicketDto: CreateTicketDto,
userId: string,
): Promise<{ message: string }> {
const user = await this.userDataAccess.findOne(userId);
if (!user) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'چنین کاربری یافت نشد',
},
HttpStatus.NOT_FOUND,
);
}
const result = await this.ticketDataAccess.create(
createTicketDto.subject,
user._id.toString(),
);
if (!result) {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'تیکت با مشکل هایی ثبت نشد',
},
HttpStatus.BAD_REQUEST,
);
}
return { message: 'تیکت با موفقیت ثبت شد' };
}
async findOne(id: string): Promise<Ticket> {
const result = await this.ticketDataAccess.findOne(id);
if (!result) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'چنین تیکتی یافت نشد',
},
HttpStatus.NOT_FOUND,
);
}
return result;
}
async findAll(paginationDto: PaginationDto, userId: string) {
const user = await this.userDataAccess.findOne(userId);
if (!user) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'چنین کاربری یافت نشد',
},
HttpStatus.NOT_FOUND,
);
}
if (user.roles.includes(Role.Admin)) {
const result = await this.ticketDataAccess.findAll(
paginationDto.page,
paginationDto.pageSize,
);
return result;
}
const result = await this.ticketDataAccess.findAllByUser(
userId,
paginationDto.page,
paginationDto.pageSize,
);
const count = await this.ticketDataAccess.count();
return { result, count };
}
async createMessage(
createTicketMessageDto: CreateTicketMessageDto,
userId: string,
): Promise<TicketMessage> {
const ticket = await this.ticketDataAccess.findOne(
createTicketMessageDto.ticketId,
);
if (!ticket) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'چنین تیکتی یافت نشد',
},
HttpStatus.NOT_FOUND,
);
}
if (ticket.status === 'closed') {
throw new HttpException(
{
status: HttpStatus.BAD_REQUEST,
error: 'تیکت بسته شده است و نمی‌توان پیام جدیدی ایجاد کرد',
},
HttpStatus.BAD_REQUEST,
);
}
const newTicketMessage = await this.ticketMessageDataAccess.createMessage(
createTicketMessageDto,
userId,
);
if (ticket) {
ticket.ticketMessages.push(
newTicketMessage._id ? newTicketMessage._id : newTicketMessage.id,
);
await ticket.save();
}
return newTicketMessage;
}
async closeTicket(id: string): Promise<{ message: string }> {
await this.ticketDataAccess.closeTicket(id);
return { message: 'تیکت با موفقیت بسته شد' };
}
}
+154
View File
@@ -0,0 +1,154 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { UserService } from './user.service';
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiTags,
} from '@nestjs/swagger';
import { Roles } from '../../decorators/role.decorators';
import { Role } from '../../common/eNums/role.enum';
import { AuthGuard } from '../../auth/auth.guard';
import {
AddBalanceDto,
RegisterCourseDto,
UserDto,
UserUpdateDto,
} from '../../DTO/user.dto';
import { CurrentUser } from '../../decorators/currentUser.decorator';
@ApiTags('user')
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
// profile user ************************************
@ApiOperation({
summary: ' پروفایل کاربر ',
})
@ApiOkResponse({
description: ' اطلاعات کاربر ',
type: UserDto,
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Get('')
async getProfile(@CurrentUser() user) {
try {
const result = await this.userService.profile(user.id);
return result;
} catch (err) {
throw err;
}
}
// update user ************************************
@ApiOperation({
summary: ' بروز رسانی پروفایل کاربر ',
})
@ApiOkResponse({
description: ' UserDto ',
type: UserDto,
})
@ApiBody({
type: UserUpdateDto,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard)
@Roles(Role.User, Role.Admin)
@ApiBearerAuth()
@Put('update')
async updateUser(
@CurrentUser() user,
@Body() userUpdateDto: UserUpdateDto,
): Promise<boolean> {
try {
const result = await this.userService.update(user.id, userUpdateDto);
return result;
} catch (err) {
throw err;
}
}
// update user ************************************
@ApiOperation({
summary: 'ثبت نام دوره',
})
@ApiBody({
type: RegisterCourseDto,
description: 'get body',
})
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Post('registerCourse')
async registerCourse(
@CurrentUser() user,
@Body() registerCourseDto: RegisterCourseDto,
) {
try {
const result = await this.userService.registerCourse(
user.id,
registerCourseDto,
);
return result;
} catch (err) {
throw err;
}
}
@ApiOperation({
summary: 'افزایش موجودی کیف پول',
})
@ApiBody({
type: AddBalanceDto,
description: 'get body',
})
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Post('add')
addBalance(@CurrentUser() user, @Body() addBalanceDto: AddBalanceDto) {
return this.userService.addBalance(
user.id,
addBalanceDto.amount,
addBalanceDto.paymentNumber,
addBalanceDto.reason,
);
}
@ApiOperation({
summary: 'موجودی کیف پول',
})
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Get('balance')
getBalance(@CurrentUser() user) {
return this.userService.getBalance(user.id);
}
@ApiOperation({
summary: 'لیست تراکنش ها',
})
@UseGuards(AuthGuard)
@Roles(Role.User)
@ApiBearerAuth()
@Get('payments')
getPayments(@CurrentUser() user) {
return this.userService.getPayments(user.id);
}
}
+24
View File
@@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { DiscountDataAccess } from '../../dataAccess/discount.dataAccess';
import { UserDiscountDataAccess } from '../../dataAccess/userDiscount.dataAccess';
import { DiscountHistoryDataAccess } from '../../dataAccess/discountHistory.dataAccess';
import { PaymentDataAccess } from '../../dataAccess/payment.dataAccess';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
@Module({
imports: [],
controllers: [UserController],
providers: [
UserService,
UserDataAccess,
UserDiscountDataAccess,
DiscountHistoryDataAccess,
DiscountDataAccess,
PaymentDataAccess,
CourseHeadlineDataAccess,
],
})
export class UserModule {}
+189
View File
@@ -0,0 +1,189 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import {
RegisterCourseDto,
UserDto,
userObj,
UserUpdateDto,
} from '../../DTO/user.dto';
import { UserDataAccess } from '../../dataAccess/user.dataAccess';
import { DiscountDataAccess } from '../../dataAccess/discount.dataAccess';
import { UserDiscountDataAccess } from '../../dataAccess/userDiscount.dataAccess';
import { DiscountHistoryDataAccess } from '../../dataAccess/discountHistory.dataAccess';
import { PaymentDataAccess } from '../../dataAccess/payment.dataAccess';
import { CourseHeadlineDataAccess } from '../../dataAccess/courseHeadline.dataAccess';
@Injectable()
export class UserService {
constructor(
private readonly userDataAccess: UserDataAccess,
private readonly courseHeadlineDataAccess: CourseHeadlineDataAccess,
private readonly discountDataAccess: DiscountDataAccess,
private readonly userDiscountDataAccess: UserDiscountDataAccess,
private readonly discountHistoryDataAccess: DiscountHistoryDataAccess,
private readonly paymentDataAccess: PaymentDataAccess,
) {}
async profile(id) {
const result = await this.userDataAccess.findOne(id);
let arr = [];
await Promise.all(
result.courseIds.map(async (item) => {
const courseHeadlines =
await this.courseHeadlineDataAccess.getCourseHeadlines(item._id);
const totalHeadlines = courseHeadlines.length;
let completionPercentage = 0;
console.log(result.courseHeadlineProgress);
if (result.courseHeadlineProgress) {
const completedHeadlines = result.courseHeadlineProgress.filter(
(progress) =>
courseHeadlines.some(
(headline) => headline._id.toString() === progress.toString(),
),
).length;
completionPercentage = (completedHeadlines / totalHeadlines) * 100;
}
arr.push({ item, completionPercentage });
}),
);
console.log(arr);
return { user: userObj(result), com: arr };
}
// update profile
async update(userId, userUpdateDto: UserUpdateDto): Promise<boolean> {
const { avatarMedia, name, lastName, userName, email } = userUpdateDto;
const user = await this.userDataAccess.findOne(userId);
if (!user) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' کاربر وجود ندارد ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
// TODO:cityId check
// TODO:proId check
// TODO:avatarId check
await this.userDataAccess.update(
userId,
avatarMedia,
name,
lastName,
userName,
email,
);
return true;
}
async registerCourse(userId: string, registerCourseDto: RegisterCourseDto) {
const { code } = registerCourseDto;
if (code) {
const result = await this.discountDataAccess.findCode(
registerCourseDto.code,
);
if (!result) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'تخفیف مورد نظر موجود نمی باشد',
},
HttpStatus.NOT_FOUND,
);
}
if (result.allUsers === true) {
const userDiscount = await this.userDiscountDataAccess.findOne(
result.id,
userId,
);
if (!userDiscount) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'تخفیف برای شما نمی باشد',
},
HttpStatus.NOT_FOUND,
);
}
const discountHistory = await this.discountHistoryDataAccess.findOne(
result.id,
userId,
);
if (discountHistory) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' شما از این تخفیف استفاده کردید ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
await this.discountHistoryDataAccess.create(
result,
userDiscount,
userId,
);
} else {
const discountHistory = await this.discountHistoryDataAccess.findOne(
result.id,
userId,
);
if (discountHistory) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' شما از این تخفیف استفاده کردید ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
await this.discountHistoryDataAccess.create(result, null, userId);
}
}
return await this.userDataAccess.registerCourse(
userId,
registerCourseDto.courseId,
);
}
async addBalance(
userId: string,
amount: number,
paymentNumber: string,
reason: string,
) {
const user = await this.userDataAccess.findOne(userId);
const payment = await this.paymentDataAccess.createPayment(
userId,
amount,
paymentNumber,
reason,
);
user.walletBalance += amount;
return await user.save();
}
async getBalance(userId: string): Promise<number> {
const user = await this.userDataAccess.findOne(userId);
return user.walletBalance;
}
async getPayments(userId: string) {
const user = await this.userDataAccess.findOne(userId);
if (!user) {
throw new HttpException(
{
status: HttpStatus.NOT_FOUND,
error: 'کاربر وجود ندارد',
},
HttpStatus.NOT_FOUND,
);
}
const payments = await this.paymentDataAccess.getPaymentsByUserId(userId);
return payments;
}
}
+52
View File
@@ -0,0 +1,52 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import {
LoginDto,
LoginRes,
LoginStepTwoDto,
UserMidDto,
} from '../DTO/user.dto';
import { IpAddress } from '../decorators/ipAddress.decorator';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@ApiOperation({
summary: ' ورود / ثبت نام مرحله یک ',
})
@ApiOkResponse({
description: ' ارسال پیام ',
type: LoginRes,
})
@ApiBody({
type: LoginDto,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@Post('login')
signIn(
@Body() loginDto: LoginDto,
@IpAddress() ip: string,
): Promise<LoginRes> {
return this.authService.signIn(loginDto, ip);
}
// login step two ********************
@ApiOperation({
summary: ' ورود / ثبت نام مرحله دو ',
})
@ApiOkResponse({
description: ' جزئیات کاربر ',
type: UserMidDto,
})
@ApiBody({
type: LoginStepTwoDto,
description: 'get body',
})
@HttpCode(HttpStatus.OK)
@Post('login-step-two')
signVerify(@Body() loginStepTwoDto: LoginStepTwoDto): Promise<UserMidDto> {
return this.authService.signInStepTwoVerify(loginStepTwoDto);
}
}
+62
View File
@@ -0,0 +1,62 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { Request } from 'express';
import { UserDataAccess } from '../dataAccess/user.dataAccess';
import { Reflector } from '@nestjs/core';
import { Role } from '../common/eNums/role.enum';
import { ROLES_KEY } from '../decorators/role.decorators';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
private readonly userDataAccess: UserDataAccess,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: jwtConstants.secret,
});
const user = await this.userDataAccess.findOne(payload.id);
if (!user) {
throw new UnauthorizedException();
}
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
request['user'] = payload;
return requiredRoles.some((role) => user.roles?.includes(role));
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
+43
View File
@@ -0,0 +1,43 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserModule } from '../application/user/user.module';
import { UserService } from '../application/user/user.service';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { jwtConstants } from './constants';
import { Tools } from '../common/helpers/tools.helpers';
import { UserSmsDataAccess } from '../dataAccess/userSms.dataAccess';
import { UserDataAccess } from '../dataAccess/user.dataAccess';
import { DiscountDataAccess } from '../dataAccess/discount.dataAccess';
import { connectionListener } from '../common/listeners/connection.listeners';
import { UserDiscountDataAccess } from '../dataAccess/userDiscount.dataAccess';
import { DiscountHistoryDataAccess } from '../dataAccess/discountHistory.dataAccess';
import { PaymentDataAccess } from '../dataAccess/payment.dataAccess';
import { CourseHeadlineDataAccess } from '../dataAccess/courseHeadline.dataAccess';
@Module({
imports: [
UserModule,
JwtModule.register({
global: true,
secret: jwtConstants.secret,
signOptions: { expiresIn: '604800s' },
}),
],
providers: [
AuthService,
UserService,
Tools,
UserSmsDataAccess,
UserDataAccess,
DiscountHistoryDataAccess,
connectionListener,
UserDiscountDataAccess,
DiscountDataAccess,
PaymentDataAccess,
CourseHeadlineDataAccess,
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}
+141
View File
@@ -0,0 +1,141 @@
import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Tools } from '../common/helpers/tools.helpers';
import {
LoginDto,
LoginStepTwoDto,
UserMidDto,
userMidObj,
} from '../DTO/user.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UserSmsDataAccess } from '../dataAccess/userSms.dataAccess';
import { UserDataAccess } from '../dataAccess/user.dataAccess';
import * as moment from 'moment';
import { PhoneEvent } from '../common/events/phone.event';
const env = process.env.NODE_ENV;
@Injectable()
export class AuthService {
constructor(
private eventEmitter: EventEmitter2,
private jwtService: JwtService,
private readonly tools: Tools,
private readonly userSmsDataAccess: UserSmsDataAccess,
private readonly userDataAccess: UserDataAccess,
) {}
async signIn(loginDto: LoginDto, ip): Promise<{ message: string }> {
const { phone, type } = loginDto;
const phoneConfirm = this.tools.removeZero(phone);
const validNumber = this.tools.validNumberWithRegex(phoneConfirm);
if (validNumber == false) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'شماره وارد شده معتبر نیست',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
const code = this.tools.codeCreator();
const createdEvent = new PhoneEvent();
createdEvent.phone = phone;
createdEvent.code = code;
const smsRq = await this.userSmsDataAccess.findSmsRq(phoneConfirm, ip);
if (parseInt(type) === 1 || parseInt(type) === 2) {
if (smsRq) {
if (smsRq.sendCount > 1000) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'شما بیش از حد تلاش کرده اید',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
const now = moment(new Date());
const secondsDiff = now.diff(moment(smsRq.lastSentTime), 'seconds');
if (secondsDiff < 60 && smsRq.phone === phoneConfirm) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'برای ارسال کد جدید 1 دقیقه صبر کنید',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
const sendCount = smsRq.sendCount + 1;
if (env !== 'development' && env !== 'test') {
if (parseInt(type) === 1) {
this.eventEmitter.emit('sms.created', createdEvent);
} else {
this.eventEmitter.emit('call.created', createdEvent);
}
}
await this.userSmsDataAccess.updateSmsRq(
smsRq.id,
phoneConfirm,
sendCount,
ip,
code,
);
return { message: 'کد با موفقیت ارسال شد' };
} else {
if (env !== 'development' && env !== 'test') {
if (parseInt(type) === 1) {
this.eventEmitter.emit('sms.created', createdEvent);
} else {
this.eventEmitter.emit('call.created', createdEvent);
}
}
await this.userSmsDataAccess.createSmsRq(phoneConfirm, ip, code);
return {
message: 'کد با موفقیت ارسال شد',
};
}
} else {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: ' نوع ارتباط به درستی وارد نشده است ',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
}
async signInStepTwoVerify(
loginStepTwoDto: LoginStepTwoDto,
): Promise<UserMidDto> {
const { phone, code } = loginStepTwoDto;
const smsRq = await this.userSmsDataAccess.validateCode(phone, code);
if (!smsRq) {
throw new HttpException(
{
status: HttpStatus.NOT_ACCEPTABLE,
error: 'کد اشتباه می باشد',
},
HttpStatus.NOT_ACCEPTABLE,
);
}
const userExist = await this.userDataAccess.findByPhone(phone);
if (!userExist) {
const user = await this.userDataAccess.create(phone);
const payload = { id: user.id };
const jwtConf = await this.jwtService.signAsync(payload);
await this.userDataAccess.updateJwt(user.id, jwtConf);
const userSubmit = await this.userDataAccess.findOne(user.id);
await this.userSmsDataAccess.deleteSms(smsRq.id);
return userMidObj(userSubmit);
} else {
const payload = { id: userExist.id };
const jwtConf = await this.jwtService.signAsync(payload);
await this.userDataAccess.updateJwt(userExist.id, jwtConf);
const finalUser = await this.userDataAccess.findOne(userExist.id);
await this.userSmsDataAccess.deleteSms(smsRq.id);
return userMidObj(finalUser);
}
}
}
+3
View File
@@ -0,0 +1,3 @@
export const jwtConstants = {
secret: '8FWQDF%$ERDFGH#$FDSJ&*#@R1!34Vh',
};
+9
View File
@@ -0,0 +1,9 @@
const commentType = {
Course: 'دوره',
BlogPost: 'پست بلاگ',
};
export default commentType;
export enum CommentType {
'Course' = 'Course',
'BlogPost' = 'BlogPost',
}
+11
View File
@@ -0,0 +1,11 @@
const discountStatus = {
active: {
code: true,
text: 'فعال',
},
deActive: {
code: false,
text: 'غیرفعال',
},
};
export default discountStatus;
+9
View File
@@ -0,0 +1,9 @@
const discountTypeEnum = {
percent: 'درصد',
price: 'قیمت واحد',
};
export default discountTypeEnum;
export enum DiscountTypeEnum {
'percent' = 'percent',
'price' = 'price',
}
+11
View File
@@ -0,0 +1,11 @@
const discountUser = {
public: {
code: true,
text: 'عمومی',
},
private: {
code: false,
text: 'خاص',
},
};
export default discountUser;
+9
View File
@@ -0,0 +1,9 @@
const gender = {
Men: 'مرد',
Women: 'زن',
};
export default gender;
export enum Gender {
'Men' = 'Men',
'Women' = 'Women',
}
+13
View File
@@ -0,0 +1,13 @@
const itemStatuses = {
Active: 'فعال',
DeActivate: 'غیر فعال',
Archived: 'بایگانی شده',
Pending: 'در حال بررسی',
};
export default itemStatuses;
export enum ItemStatus {
'Active' = 'Active',
'DeActivate' = 'DeActivate',
'Archived' = 'Archived',
'Pending' = 'Pending',
}
@@ -0,0 +1,13 @@
const itemStatusCourseHeadline = {
Completed: 'تکمیل شده',
DeActivate: 'غیر فعال',
Recording: 'درحال ضبط',
Soon: 'بزودی',
};
export default itemStatusCourseHeadline;
export enum ItemStatusCourseHeadline {
'Completed' = 'Completed',
'DeActivate' = 'DeActivate',
'Recording' = 'Recording',
'Soon' = 'Soon',
}
+13
View File
@@ -0,0 +1,13 @@
const role = {
Admin: 'ادمین',
Reader: 'مشاهده کننده',
Writer: 'نویسنده',
User: 'کاربر',
};
export default role;
export enum Role {
'Admin' = 'Admin',
'Reader' = 'Reader',
'Writer' = 'Writer',
'User' = 'User',
}
+4
View File
@@ -0,0 +1,4 @@
export class PhoneEvent {
phone: string;
code: number;
}
+148
View File
@@ -0,0 +1,148 @@
import jalali = require('jalali-moment');
export class ConvertDate {
jalaliDate(date) {
const jalaliDate = jalali(date).locale('fa');
const month = jalaliDate.format('MMMM');
const day = jalaliDate.format('DD');
const year = jalaliDate.format('YYYY');
return `${day}-${month}-${year}`;
}
jalaliDateWithTime(date) {
const jalaliDate = jalali(date).locale('fa');
const month = jalaliDate.format('MM');
const day = jalaliDate.format('DD');
const year = jalaliDate.format('YYYY');
const hour = jalaliDate.format('HH');
const minute = jalaliDate.format('mm');
const second = jalaliDate.format('ss');
return `${year}/${month}/${day} - ${hour}:${minute}:${second}`;
}
jalaliDateResume(date) {
const jalaliDate = jalali(date).locale('fa');
const month = jalaliDate.format('MM');
const day = jalaliDate.format('DD');
const year = jalaliDate.format('YYYY');
return `${year}/${month}/${day}`;
}
jalaliDateMonth(date) {
const jalaliDate = jalali(date).locale('fa');
const month = jalaliDate.format('MMMM');
return `${month}`;
}
jalaliDateDay(date) {
const jalaliDate = jalali(date).locale('fa');
const day = jalaliDate.format('DD');
return `${day}`;
}
jalaliDateYear(date) {
const jalaliDate = jalali(date).locale('fa');
const year = jalaliDate.format('YYYY');
return `${year}`;
}
jTime(date) {
const jalaliDate = jalali(date).locale('fa');
const hour = jalaliDate.format('HH');
const minute = jalaliDate.format('mm');
const second = jalaliDate.format('ss');
return `${hour}:${minute}:${second}`;
}
georgianDate(date) {
const georgianDate = jalali.from(date, 'fa', 'YYYY-MM-DD HH:mm:ss');
const month = georgianDate.format('MM');
const day = georgianDate.format('DD');
const year = georgianDate.format('YYYY');
const hour = georgianDate.format('HH');
const minute = georgianDate.format('mm');
const second = georgianDate.format('ss');
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
DateAndTimePublication(date) {
let kabise = 1403;
let kabiseStatus = false;
const georgianDate = jalali(date).locale('fa');
let month = parseInt(georgianDate.format('MM'));
let day = parseInt(georgianDate.format('DD'));
let year = parseInt(georgianDate.format('YYYY'));
let hour = parseInt(georgianDate.format('HH'));
let minute = parseInt(georgianDate.format('mm'));
let second = parseInt(georgianDate.format('ss'));
const month2 = georgianDate.format('MMMM');
const day2 = georgianDate.format('DD');
const year2 = georgianDate.format('YYYY');
const now = new Date();
const georgianDateNow = jalali(now).locale('fa');
let monthNow = parseInt(georgianDateNow.format('MM'));
let dayNow = parseInt(georgianDateNow.format('DD'));
let yearNow = parseInt(georgianDateNow.format('YYYY'));
let hourNow = parseInt(georgianDateNow.format('HH'));
let minuteNow = parseInt(georgianDateNow.format('mm'));
let secondNow = parseInt(georgianDateNow.format('ss'));
if (secondNow < second) {
minuteNow -= 1;
secondNow += 60;
}
if (minuteNow < minute) {
hourNow -= 1;
minuteNow += 60;
}
if (hourNow < hour) {
dayNow -= 1;
hourNow += 24;
}
if (dayNow < day) {
while (kabise < yearNow) {
kabise += 4;
if (kabise === yearNow) {
kabiseStatus = true;
}
}
if (monthNow < 7) {
dayNow += 31;
} else if (kabiseStatus === true && monthNow > 6) {
dayNow += 30;
} else if (monthNow === 12) {
dayNow += 29;
} else {
dayNow += 30;
}
monthNow -= 1;
}
if (monthNow < month) {
yearNow -= 1;
monthNow += 12;
}
second = secondNow - second;
minute = minuteNow - minute;
hour = hourNow - hour;
day = dayNow - day;
month = monthNow - month;
year = yearNow - year;
if (year === 0) {
if (month === 0) {
if (day === 0) {
if (hour === 0) {
if (minute === 0) {
if (second === 0) {
return 'هم اکنون';
}
return `${second} ثانیه قبل`;
}
return `${minute} دقیقه قبل`;
}
return `${hour} ساعت قبل`;
}
if (day < 8) {
return `${day} روز قبل`;
}
return `${day2} ${month2} ${year2}`;
}
return `${day2} ${month2} ${year2}`;
}
return `${day2} ${month2} ${year2}`;
}
}
+113
View File
@@ -0,0 +1,113 @@
import smsRequest = require('request');
const env = process.env.NODE_ENV;
const { getVideoDurationInSeconds } = require('get-video-duration');
export class Tools {
convertToSlug(Text) {
return Text.toLowerCase()
.replace(/ /g, '-')
.replace(/-{6}/g, '-')
.replace(/-{5}/g, '-')
.replace(/-{4}/g, '-')
.replace(/-{3}/g, '-')
.replace(/--/g, '-')
.replace(/,/g, '')
.replace(//g, '')
.replace(/،/g, '')
.replace(/#/g, '')
.replace(/@/g, '')
.replace(/$/g, '')
.replace(/!/g, '')
.replace(/&/g, '')
.replace(/؟/g, '')
.replace(/-{6}/g, '-')
.replace(/-{5}/g, '-')
.replace(/-{4}/g, '-')
.replace(/-{3}/g, '-');
// .replace(new RegExp('(', 'g'), '')
// .replace(new RegExp(')', 'g'), '')
// .replace(new RegExp('"', 'g'), '')
// .replace(new RegExp("'", 'g'), '')
// .replace(new RegExp(`/`, 'g'), '')
// .replace(new RegExp('?', 'g'), '');
}
codeCreator() {
if (env !== 'development' && env !== 'test') {
const val = Math.floor(1000 + Math.random() * 8999);
return val;
} else {
return 1234;
}
}
async sendSmsCode(phone, code) {
const receptor = phone;
const template = '';
const type = 'sms';
const apiKey = '';
const url = ``;
const options = {
url,
method: 'POST',
form: {
token: code,
receptor: receptor,
template: template,
type: type,
},
};
const result = await smsRequest(options);
return result;
}
async callCode(phone, code) {
const receptor = phone;
const template = '';
const type = 'call';
const apiKey = '';
const url = ``;
const options = {
url,
method: 'POST',
form: {
token: code,
receptor: receptor,
template: template,
type: type,
},
};
const result = await smsRequest(options);
return result;
}
async timeVideo(url) {
const time = await getVideoDurationInSeconds(url, 'ffprobe');
return time;
}
removeZero(phone) {
const tsNumberAsString = phone.toString();
if (tsNumberAsString.charAt(0) === '0') {
// If the first digit is zero, return the number as is
return phone;
} else {
// If the first digit is not zero, add a zero in front of it
const modifiedNumberAsString = '0' + tsNumberAsString;
return modifiedNumberAsString;
}
return tsNumberAsString;
}
validNumberWithRegex(phone: string) {
const regex = new RegExp(
/^(098|0098|98|\+98|0)?9(0[0-5]|[1 3]\d|2[0-3]|9[0-9]|41)\d{7}$/g,
);
return regex.test(phone);
}
}

Some files were not shown because too many files have changed in this diff Show More