├── src ├── modules │ ├── groups │ │ ├── index.ts │ │ ├── dtos │ │ │ ├── set_manager.dto.ts │ │ │ └── create_group.dto.ts │ │ ├── groups.interface.ts │ │ ├── groups.model.ts │ │ ├── groups.route.ts │ │ ├── groups.controllers.ts │ │ └── groups.service.ts │ ├── conversations │ │ ├── index.ts │ │ ├── dtos │ │ │ └── send_message.dto.ts │ │ ├── conversations.interface.ts │ │ ├── conversations.route.ts │ │ ├── conversations.controller.ts │ │ ├── conversations.model.ts │ │ └── conversations.service.ts │ ├── posts │ │ ├── index.ts │ │ ├── dtos │ │ │ ├── create_post.dto.ts │ │ │ └── create_comment.dto.ts │ │ ├── posts.interface.ts │ │ ├── posts.model.ts │ │ ├── posts.route.ts │ │ ├── posts.controller.ts │ │ └── posts.service.ts │ ├── index │ │ ├── index.ts │ │ ├── index.controller.ts │ │ └── index.route.ts │ ├── auth │ │ ├── index.ts │ │ ├── auth.dto.ts │ │ ├── auth.route.ts │ │ ├── auth.controller.ts │ │ └── auth.service.ts │ ├── users │ │ ├── index.ts │ │ ├── users.interface.ts │ │ ├── dtos │ │ │ └── register.dto.ts │ │ ├── users.model.ts │ │ ├── user.route.ts │ │ ├── users.controller.ts │ │ └── users.service.ts │ ├── refresh_token │ │ ├── index.ts │ │ ├── refresh_token.interface.ts │ │ └── refresh_token.model.ts │ └── profile │ │ ├── index.ts │ │ ├── dtos │ │ ├── add_experience.dto.ts │ │ ├── add_education.dto.ts │ │ └── create_profile.dto.ts │ │ ├── profile.interface.ts │ │ ├── profile.route.ts │ │ ├── profile.model.ts │ │ ├── profile.controller.ts │ │ └── profile.service.ts ├── core │ ├── exceptions │ │ ├── index.ts │ │ └── http.exception.ts │ ├── interfaces │ │ ├── pagination.interface.ts │ │ ├── routes.interface.ts │ │ ├── auth.interface.ts │ │ └── index.ts │ ├── utils │ │ ├── index.ts │ │ ├── validate_env.ts │ │ ├── helpers.ts │ │ └── logger.ts │ └── middleware │ │ ├── index.ts │ │ ├── error.middleware.ts │ │ ├── validation.middleware.ts │ │ └── auth.middleware.ts ├── types │ └── express │ │ └── index.d.ts ├── server.ts ├── swagger.yaml └── app.ts ├── .eslintignore ├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── diagrams ├── Login.png ├── Server express.png ├── Entity relationship.png └── TEDUSocial functional.png ├── .prettierrc ├── nodemon.json ├── .env ├── .eslintrc ├── webpack.config.js ├── README.md ├── package.json ├── tsconfig.json └── postman └── TEDU Social.postman_collection.json /src/modules/groups/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /src/modules/conversations/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | logs/ 3 | dist/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "tedu" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/modules/posts/index.ts: -------------------------------------------------------------------------------- 1 | import PostSchema from './posts.model'; 2 | export { PostSchema }; 3 | -------------------------------------------------------------------------------- /diagrams/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teduinternational/tedu_social/HEAD/diagrams/Login.png -------------------------------------------------------------------------------- /src/core/exceptions/index.ts: -------------------------------------------------------------------------------- 1 | import HttpException from './http.exception'; 2 | 3 | export { HttpException }; 4 | -------------------------------------------------------------------------------- /diagrams/Server express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teduinternational/tedu_social/HEAD/diagrams/Server express.png -------------------------------------------------------------------------------- /src/modules/posts/dtos/create_post.dto.ts: -------------------------------------------------------------------------------- 1 | export default class CreatePostDto { 2 | public text: string | undefined; 3 | } 4 | -------------------------------------------------------------------------------- /diagrams/Entity relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teduinternational/tedu_social/HEAD/diagrams/Entity relationship.png -------------------------------------------------------------------------------- /diagrams/TEDUSocial functional.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teduinternational/tedu_social/HEAD/diagrams/TEDUSocial functional.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts,.js,.yaml", 4 | "ignore": [], 5 | "exec": "ts-node -r tsconfig-paths/register" 6 | } 7 | -------------------------------------------------------------------------------- /src/types/express/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | interface Request { 3 | user: { 4 | id: string; 5 | }; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/groups/dtos/set_manager.dto.ts: -------------------------------------------------------------------------------- 1 | export default class SetManagerDto { 2 | public userId: string | undefined; 3 | public role: string | undefined; 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/index/index.ts: -------------------------------------------------------------------------------- 1 | import IndexController from './index.controller'; 2 | import IndexRoute from './index.route'; 3 | 4 | export { IndexController, IndexRoute }; 5 | -------------------------------------------------------------------------------- /src/core/interfaces/pagination.interface.ts: -------------------------------------------------------------------------------- 1 | export default interface IPagination { 2 | total: number; 3 | page: number; 4 | pageSize: number; 5 | items: T[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/core/interfaces/routes.interface.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | interface Route { 4 | path: string; 5 | router: Router; 6 | } 7 | 8 | export default Route; 9 | -------------------------------------------------------------------------------- /src/core/interfaces/auth.interface.ts: -------------------------------------------------------------------------------- 1 | export interface DataStoredInToken { 2 | id: string; 3 | } 4 | 5 | export interface TokenData { 6 | token: string; 7 | refreshToken: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/core/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | import IPagination from './pagination.interface'; 2 | import Route from './routes.interface'; 3 | export * from './auth.interface'; 4 | export { Route, IPagination }; 5 | -------------------------------------------------------------------------------- /src/core/utils/index.ts: -------------------------------------------------------------------------------- 1 | import Logger from './logger'; 2 | import { isEmptyObject } from './helpers'; 3 | import validateEnv from './validate_env'; 4 | 5 | export { Logger, validateEnv, isEmptyObject }; 6 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | 2 | NODE_ENV ="Production" 3 | 4 | MONGODB_URI = "mongodb+srv://tedu:LLrvB941gFsxcQmz@master.0rrty.mongodb.net/tedu_social?retryWrites=true&w=majority" 5 | 6 | JWT_TOKEN_SECRET = "tokensecret" 7 | 8 | PAGE_SIZE= 10 9 | -------------------------------------------------------------------------------- /src/modules/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { DataStoredInToken, TokenData } from '../../core/interfaces/auth.interface'; 2 | 3 | import IUser from '@modules/users/users.interface'; 4 | 5 | export { DataStoredInToken, TokenData, IUser }; 6 | -------------------------------------------------------------------------------- /src/modules/users/index.ts: -------------------------------------------------------------------------------- 1 | import IUser from '@modules/users/users.interface'; 2 | import UserSchema from './users.model'; 3 | import UsersRoute from '@modules/users/user.route'; 4 | 5 | export { UsersRoute, UserSchema, IUser }; 6 | -------------------------------------------------------------------------------- /src/modules/users/users.interface.ts: -------------------------------------------------------------------------------- 1 | export default interface IUser { 2 | _id: string; 3 | first_name: string; 4 | last_name: string; 5 | email: string; 6 | password: string; 7 | avatar: string; 8 | date: Date; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/refresh_token/index.ts: -------------------------------------------------------------------------------- 1 | import { IRefreshToken } from '@modules/refresh_token/refresh_token.interface'; 2 | import RefreshTokenSchema from '@modules/refresh_token/refresh_token.model'; 3 | 4 | export { IRefreshToken, RefreshTokenSchema }; 5 | -------------------------------------------------------------------------------- /src/core/utils/validate_env.ts: -------------------------------------------------------------------------------- 1 | import { cleanEnv, str } from 'envalid'; 2 | 3 | const validateEnv = () => { 4 | cleanEnv(process.env, { 5 | NODE_ENV: str(), 6 | MONGODB_URI: str(), 7 | }); 8 | }; 9 | 10 | export default validateEnv; 11 | -------------------------------------------------------------------------------- /src/modules/profile/index.ts: -------------------------------------------------------------------------------- 1 | import { IProfile } from '@modules/profile/profile.interface'; 2 | import ProfileRoute from '@modules/profile/profile.route'; 3 | import ProfileSchema from './profile.model'; 4 | 5 | export { ProfileRoute, ProfileSchema, IProfile }; 6 | -------------------------------------------------------------------------------- /src/modules/refresh_token/refresh_token.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshToken { 2 | user: string; 3 | token: string; 4 | expires: Date; 5 | created: Date; 6 | revoked: Date; 7 | replacedByToken: string; 8 | isActive: boolean; 9 | isExpired: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/core/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import authMiddleware from '@core/middleware/auth.middleware'; 2 | import errorMiddleware from './error.middleware'; 3 | import validationMiddleware from '@core/middleware/validation.middleware'; 4 | 5 | export { errorMiddleware, authMiddleware, validationMiddleware }; 6 | -------------------------------------------------------------------------------- /src/modules/groups/dtos/create_group.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | export default class CreateGroupDto { 3 | @IsNotEmpty() 4 | public name: string | undefined; 5 | @IsNotEmpty() 6 | public code: string | undefined; 7 | public description: string | undefined; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/conversations/dtos/send_message.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export default class SendMessageDto { 4 | public conversationId: string | undefined; 5 | @IsNotEmpty() 6 | public to: string | undefined; 7 | @IsNotEmpty() 8 | public text: string | undefined; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/posts/dtos/create_comment.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export default class CreateCommentDto { 4 | @IsNotEmpty() 5 | public text: string | undefined; 6 | @IsNotEmpty() 7 | public userId: string | undefined; 8 | @IsNotEmpty() 9 | public postId: string | undefined; 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/index/index.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | export default class IndexController { 4 | public index = (req: Request, res: Response, next: NextFunction) => { 5 | try { 6 | res.status(200).send('API is running...'); 7 | } catch (error) { 8 | next(error); 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/profile/dtos/add_experience.dto.ts: -------------------------------------------------------------------------------- 1 | export default class AddExperienceDto { 2 | public title: string | undefined; 3 | public company: string | undefined; 4 | public location: string | undefined; 5 | public from: Date | undefined; 6 | public to: Date | undefined; 7 | public current: boolean | undefined; 8 | public description: string | undefined; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/profile/dtos/add_education.dto.ts: -------------------------------------------------------------------------------- 1 | export default class AddEducationDto { 2 | public school: string | undefined; 3 | public degree: string | undefined; 4 | public fieldofstudy: string | undefined; 5 | public from: Date | undefined; 6 | public to: Date | undefined; 7 | public current: boolean | undefined; 8 | public description: string | undefined; 9 | } 10 | -------------------------------------------------------------------------------- /src/core/exceptions/http.exception.ts: -------------------------------------------------------------------------------- 1 | import { Http } from 'winston/lib/winston/transports'; 2 | 3 | class HttpException extends Error { 4 | public status: number; 5 | public message: string; 6 | 7 | constructor(status: number, message: string) { 8 | super(message); 9 | this.status = status; 10 | this.message = message; 11 | } 12 | } 13 | 14 | export default HttpException; 15 | -------------------------------------------------------------------------------- /src/modules/auth/auth.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'; 2 | export default class LoginDto { 3 | constructor(email: string, password: string) { 4 | this.email = email; 5 | this.password = password; 6 | } 7 | @IsNotEmpty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @IsNotEmpty() 12 | @MinLength(6) 13 | public password: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/conversations/conversations.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IConversation { 2 | user1: string; 3 | user2: string; 4 | date: Date; 5 | recent_date: Date; 6 | messages: IMessage[]; 7 | } 8 | 9 | export interface IMessage { 10 | from: string; 11 | to: string; 12 | read: boolean; 13 | text: string; 14 | date: Date; 15 | show_on_from: boolean; 16 | show_on_to: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | // "plugin:@shopify/esnext" 12 | ], 13 | "rules": { 14 | "no-console": "error" 15 | } 16 | } -------------------------------------------------------------------------------- /src/modules/groups/groups.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IGroup { 2 | _id: string; 3 | name: string; 4 | code: string; 5 | description: string; 6 | members: IMember[]; 7 | member_requests: IMember[]; 8 | managers: IManager[]; 9 | date: Date; 10 | creator: string; 11 | } 12 | 13 | export interface IMember { 14 | user: string; 15 | date: Date; 16 | } 17 | 18 | export interface IManager { 19 | user: string; 20 | role: string; 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Node: Nodemon", 11 | "processId": "${command:PickProcess}", 12 | "restart": true, 13 | "protocol": "inspector" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/index/index.route.ts: -------------------------------------------------------------------------------- 1 | import IndexController from './index.controller'; 2 | import { Route } from '@core/interfaces'; 3 | import { Router } from 'express'; 4 | 5 | export default class IndexRoute implements Route { 6 | public path = '/'; 7 | public router = Router(); 8 | 9 | public indexController = new IndexController(); 10 | 11 | constructor() { 12 | this.initializeRoutes(); 13 | } 14 | 15 | private initializeRoutes() { 16 | this.router.get(this.path, this.indexController.index); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/posts/posts.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IPost { 2 | _id: string; 3 | user: string; 4 | text: string; 5 | name: string; 6 | avatar: string; 7 | likes: ILike[]; 8 | comments: IComment[]; 9 | shares: IShare[]; 10 | date: Date; 11 | } 12 | 13 | export interface ILike { 14 | user: string; 15 | } 16 | 17 | export interface IShare { 18 | user: string; 19 | } 20 | 21 | export interface IComment { 22 | _id: string; 23 | user: string; 24 | text: string; 25 | name: string; 26 | avatar: string; 27 | date: Date; 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/users/dtos/register.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'; 2 | export default class RegisterDto { 3 | constructor( 4 | first_name: string, 5 | last_name: string, 6 | email: string, 7 | password: string 8 | ) { 9 | this.first_name = first_name; 10 | this.last_name = last_name; 11 | this.email = email; 12 | this.password = password; 13 | } 14 | @IsNotEmpty() 15 | public first_name: string; 16 | 17 | @IsNotEmpty() 18 | public last_name: string; 19 | 20 | @IsNotEmpty() 21 | @IsEmail() 22 | public email: string; 23 | 24 | @IsNotEmpty() 25 | @MinLength(6) 26 | public password: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/users/users.model.ts: -------------------------------------------------------------------------------- 1 | import IUser from './users.interface'; 2 | import mongoose from 'mongoose'; 3 | 4 | const UserSchema = new mongoose.Schema({ 5 | first_name: { 6 | type: String, 7 | required: true, 8 | }, 9 | last_name: { 10 | type: String, 11 | required: true, 12 | }, 13 | email: { 14 | type: String, 15 | unique: true, 16 | index: true, 17 | required: true, 18 | }, 19 | password: { 20 | type: String, 21 | required: true, 22 | }, 23 | avatar: { 24 | type: String, 25 | }, 26 | date: { 27 | type: Date, 28 | default: Date.now, 29 | }, 30 | }); 31 | 32 | export default mongoose.model('user', UserSchema); 33 | -------------------------------------------------------------------------------- /src/core/middleware/error.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import HerokuLogger from 'heroku-logger'; 4 | import { HttpException } from '@core/exceptions'; 5 | import { Logger } from '@core/utils'; 6 | 7 | const errorMiddleware = (error: HttpException, req: Request, res: Response, next: NextFunction) => { 8 | const status: number = error.status || 500; 9 | const message: string = error.message || 'Some thing when wrong'; 10 | 11 | Logger.error(`[ERROR] - Status: ${status} - Msg: ${message}`); 12 | HerokuLogger.error(`[ERROR] - Status: ${status} - Msg: ${message}`); 13 | res.status(status).json({ message: message }); 14 | }; 15 | 16 | export default errorMiddleware; 17 | -------------------------------------------------------------------------------- /src/core/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import { DataStoredInToken, TokenData } from '@core/interfaces'; 2 | 3 | import crypto from 'crypto'; 4 | import jwt from 'jsonwebtoken'; 5 | 6 | export const isEmptyObject = (obj: any): boolean => { 7 | return !Object.keys(obj).length; 8 | }; 9 | 10 | export const randomTokenString = (): string => { 11 | return crypto.randomBytes(40).toString('hex'); 12 | }; 13 | 14 | export const generateJwtToken = (userId: string, refreshToken: string): TokenData => { 15 | const dataInToken: DataStoredInToken = { id: userId }; 16 | const secret: string = process.env.JWT_TOKEN_SECRET ?? ''; 17 | const expiresIn = 60; //in seconds 18 | return { 19 | token: jwt.sign(dataInToken, secret, { expiresIn: expiresIn }), 20 | refreshToken: refreshToken, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import App from './app'; 4 | import AuthRoute from '@modules/auth/auth.route'; 5 | import ConversationsRoute from '@modules/conversations/conversations.route'; 6 | import GroupsRoute from '@modules/groups/groups.route'; 7 | import { IndexRoute } from '@modules/index'; 8 | import PostsRoute from '@modules/posts/posts.route'; 9 | import ProfileRoute from '@modules/profile/profile.route'; 10 | import UsersRoute from '@modules/users/user.route'; 11 | import { validateEnv } from '@core/utils'; 12 | 13 | validateEnv(); 14 | 15 | const routes = [ 16 | new IndexRoute(), 17 | new UsersRoute(), 18 | new AuthRoute(), 19 | new ProfileRoute(), 20 | new PostsRoute(), 21 | new GroupsRoute(), 22 | new ConversationsRoute(), 23 | ]; 24 | 25 | const app = new App(routes); 26 | 27 | app.listen(); 28 | -------------------------------------------------------------------------------- /src/modules/auth/auth.route.ts: -------------------------------------------------------------------------------- 1 | import AuthController from './auth.controller'; 2 | import { Route } from '@core/interfaces'; 3 | import { Router } from 'express'; 4 | import { authMiddleware } from '@core/middleware'; 5 | 6 | export default class AuthRoute implements Route { 7 | public path = '/api/v1/auth'; 8 | public router = Router(); 9 | 10 | public authController = new AuthController(); 11 | 12 | constructor() { 13 | this.initializeRoutes(); 14 | } 15 | 16 | private initializeRoutes() { 17 | this.router.post(this.path, this.authController.login); //POST: http://localhost:5000/api/auth 18 | this.router.post(this.path + '/refresh-token', this.authController.refreshToken); 19 | this.router.post(this.path + '/revoke-token', authMiddleware, this.authController.revokeToken); 20 | 21 | this.router.get(this.path, authMiddleware, this.authController.getCurrentLoginUser); //GET: http://localhost:5000/api/auth --> Require login 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/core/middleware/validation.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, RequestHandler, Response } from 'express'; 2 | import { ValidationError, validate } from 'class-validator'; 3 | 4 | import { HttpException } from '@core/exceptions'; 5 | import { plainToClass } from 'class-transformer'; 6 | 7 | const validationMiddleware = (type: any, skipMissingProperties = false): RequestHandler => { 8 | return (req: Request, res: Response, next: NextFunction) => { 9 | validate(plainToClass(type, req.body), { skipMissingProperties }).then((errors: ValidationError[]) => { 10 | if (errors.length > 0) { 11 | const messages = errors 12 | .map((error: ValidationError) => { 13 | return Object.values(error.constraints!); 14 | }) 15 | .join(', '); 16 | next(new HttpException(400, messages)); 17 | } else { 18 | next(); 19 | } 20 | }); 21 | }; 22 | }; 23 | 24 | export default validationMiddleware; 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const nodeExternals = require('webpack-node-externals'); // Fix bug for express 3 | const WebpackShellPlugin = require('webpack-shell-plugin'); // Run command after build finish 4 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); // Resolve module in tsconfig.json for webpack 5 | 6 | const { NODE_ENV = 'production' } = process.env; 7 | module.exports = { 8 | entry: './src/server.ts', 9 | watch: NODE_ENV === 'development', 10 | mode: NODE_ENV, 11 | target: 'node', 12 | externals: [nodeExternals()], 13 | output: { 14 | path: path.resolve(__dirname, 'dist'), 15 | filename: 'server.js', 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.js'], 19 | plugins: [ 20 | new TsconfigPathsPlugin({ 21 | /* options: see below */ 22 | }), 23 | ], 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.ts$/, 29 | use: ['ts-loader'], 30 | }, 31 | ], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/core/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | 3 | const Logger: winston.Logger = winston.createLogger({ 4 | transports: [ 5 | // 6 | // - Write all logs with level `error` and below to `error.log` 7 | // - Write all logs with level `info` and below to `combined.log` 8 | // 9 | new winston.transports.File({ 10 | filename: 'logs/error.log', 11 | level: 'error', 12 | }), 13 | new winston.transports.File({ filename: 'logs/combined.log' }), 14 | ], 15 | format: winston.format.combine( 16 | winston.format.colorize({ all: true }), 17 | winston.format.simple() 18 | ), 19 | }); 20 | 21 | // 22 | // If we're not in production then log to the `console` with the format: 23 | // `${info.level}: ${info.message} JSON.stringify({ ...rest }) ` 24 | // 25 | if (process.env.NODE_ENV === 'development') { 26 | Logger.add( 27 | new winston.transports.Console({ 28 | format: winston.format.simple(), 29 | }) 30 | ); 31 | } 32 | 33 | export default Logger; 34 | -------------------------------------------------------------------------------- /src/core/middleware/auth.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import { DataStoredInToken } from '../interfaces/auth.interface'; 4 | import { Logger } from '@core/utils'; 5 | import jwt from 'jsonwebtoken'; 6 | 7 | const authMiddleware = (req: Request, res: Response, next: NextFunction) => { 8 | const token = req.header('x-auth-token'); 9 | 10 | if (!token) return res.status(401).json({ message: 'No token, authorization denied.' }); 11 | 12 | try { 13 | const user = jwt.verify(token, process.env.JWT_TOKEN_SECRET ?? '') as DataStoredInToken; 14 | 15 | if (!req.user) req.user = { id: '' }; 16 | 17 | req.user.id = user.id; 18 | next(); 19 | } catch (error) { 20 | Logger.error(`[ERROR] Msg: ${token}`); 21 | if (error.name == 'TokenExpiredError') { 22 | res.status(401).json({ message: 'Token is expired' }); 23 | } else { 24 | res.status(401).json({ message: 'Token is not valid' }); 25 | } 26 | } 27 | }; 28 | 29 | export default authMiddleware; 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Social network for TEDU Community 2 | 3 | ## Technologies stack 4 | 5 | - NodeJS 6 | - MongoDB 7 | - Express 8 | - TypeScript 9 | 10 | ## Command remembers 11 | 12 | ### Lession 9 13 | 14 | - Open terminal command windows: Ctrl + ` 15 | - npm init or yarn init 16 | - git init 17 | - git commit -m "Initial commit" 18 | - git add \* 19 | - git config --global user.name "Your Name" 20 | - git config --global user.email "Your Email" 21 | - git remote add origin https://github.com/teduinternational/tedu_social.git 22 | - git push -u origin master 23 | 24 | ### ESLINT 25 | yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -D 26 | "husky": { 27 | "hooks": { 28 | "pre-commit": "yarn lint" 29 | } 30 | }, 31 | ### Lession 10 32 | 33 | - tsc --init 34 | 35 | ### Reference 36 | 37 | - http://expressjs.com/en/resources/middleware/morgan.html 38 | - https://www.typescriptlang.org/tsconfig 39 | - https://github.com/winstonjs/winston 40 | - https://www.npmjs.com/package/bcryptjs 41 | - https://www.npmjs.com/package/class-validator 42 | -------------------------------------------------------------------------------- /src/modules/conversations/conversations.route.ts: -------------------------------------------------------------------------------- 1 | import ConversationsController from './conversations.controller'; 2 | import { Route } from '@core/interfaces'; 3 | import { Router } from 'express'; 4 | import SendMessageDto from './dtos/send_message.dto'; 5 | import { authMiddleware } from '@core/middleware'; 6 | import validationMiddleware from '@core/middleware/validation.middleware'; 7 | 8 | export default class ConversationsRoute implements Route { 9 | public path = '/api/v1/conversations'; 10 | public router = Router(); 11 | 12 | public conversationController = new ConversationsController(); 13 | 14 | constructor() { 15 | this.initializeRoutes(); 16 | } 17 | 18 | private initializeRoutes() { 19 | // Group 20 | this.router.post( 21 | this.path, 22 | authMiddleware, 23 | validationMiddleware(SendMessageDto, true), 24 | this.conversationController.sendMessage 25 | ); 26 | 27 | this.router.get( 28 | this.path, 29 | authMiddleware, 30 | this.conversationController.getMyConversation 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/profile/dtos/create_profile.dto.ts: -------------------------------------------------------------------------------- 1 | export default class CreateProfileDto { 2 | constructor( 3 | company: string, 4 | location: string, 5 | website: string, 6 | bio: string, 7 | skills: string, 8 | status: string, 9 | youtube: string, 10 | twitter: string, 11 | instagram: string, 12 | linkedin: string, 13 | facebook: string 14 | ) { 15 | this.company = company; 16 | this.location = location; 17 | this.website = website; 18 | this.bio = bio; 19 | this.skills = skills; 20 | this.status = status; 21 | this.youtube = youtube; 22 | this.twitter = twitter; 23 | this.instagram = instagram; 24 | this.linkedin = linkedin; 25 | this.facebook = facebook; 26 | } 27 | 28 | public company: string; 29 | public location: string; 30 | public website: string; 31 | public bio: string; 32 | public skills: string; 33 | public status: string; 34 | public youtube: string; 35 | public twitter: string; 36 | public instagram: string; 37 | public linkedin: string; 38 | public facebook: string; 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/conversations/conversations.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import ConversationService from './conversations.service'; 4 | import SendMessageDto from './dtos/send_message.dto'; 5 | 6 | export default class ConversationsController { 7 | private conversationService = new ConversationService(); 8 | 9 | public sendMessage = async ( 10 | req: Request, 11 | res: Response, 12 | next: NextFunction 13 | ) => { 14 | try { 15 | const model: SendMessageDto = req.body; 16 | const result = await this.conversationService.sendMessage( 17 | req.user.id, 18 | model 19 | ); 20 | res.status(201).json(result); 21 | } catch (error) { 22 | next(error); 23 | } 24 | }; 25 | 26 | public getMyConversation = async ( 27 | req: Request, 28 | res: Response, 29 | next: NextFunction 30 | ) => { 31 | try { 32 | const result = await this.conversationService.getMyConversation( 33 | req.user.id 34 | ); 35 | res.status(200).json(result); 36 | } catch (error) { 37 | next(error); 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/refresh_token/refresh_token.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from 'mongoose'; 2 | 3 | import { IRefreshToken } from './refresh_token.interface'; 4 | 5 | const RefreshTokenSchema = new mongoose.Schema({ 6 | user: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'user', 9 | }, 10 | token: String, 11 | expires: Date, 12 | created: { 13 | type: Date, 14 | default: Date.now, 15 | }, 16 | createdByIp: String, 17 | revoked: Date, 18 | revokedByIp: String, 19 | replacedByToken: String, 20 | }); 21 | 22 | RefreshTokenSchema.virtual('isExpired').get(function (this: { expires: Date }) { 23 | return Date.now() >= this.expires.getTime(); 24 | }); 25 | 26 | RefreshTokenSchema.virtual('isActive').get(function (this: { revoked: Date; isExpired: boolean }) { 27 | return !this.revoked && !this.isExpired; 28 | }); 29 | 30 | RefreshTokenSchema.set('toJSON', { 31 | virtuals: true, 32 | versionKey: false, 33 | transform: function (doc, ret) { 34 | // remove these props when object is serialized 35 | delete ret._id; 36 | delete ret.id; 37 | delete ret.user; 38 | }, 39 | }); 40 | 41 | export default mongoose.model('refreshToken', RefreshTokenSchema); 42 | -------------------------------------------------------------------------------- /src/modules/profile/profile.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IProfile { 2 | _id: string; 3 | user: string; 4 | company: string; 5 | website: string; 6 | location: string; 7 | status: string; 8 | skills: string[]; 9 | bio: string; 10 | experience: IExperience[]; 11 | education: IEducation[]; 12 | social: ISocial; 13 | followings: IFollower[]; 14 | followers: IFollower[]; 15 | friends: IFriend[]; 16 | friend_requests: IFriend[]; 17 | date: Date; 18 | } 19 | 20 | export interface IFollower { 21 | user: string; 22 | } 23 | 24 | export interface IFriend { 25 | user: string; 26 | date: Date; 27 | } 28 | export interface IExperience { 29 | _id: string; 30 | title: string; 31 | company: string; 32 | location: string; 33 | from: Date; 34 | to: Date; 35 | current: boolean; 36 | description: string; 37 | } 38 | export interface IEducation { 39 | _id: string; 40 | school: string; 41 | degree: string; 42 | fieldofstudy: string; 43 | from: Date; 44 | to: Date; 45 | current: boolean; 46 | description: string; 47 | } 48 | export interface ISocial extends Record { 49 | youtube: string; 50 | twitter: string; 51 | linkedin: string; 52 | facebook: string; 53 | instagram: string; 54 | } 55 | -------------------------------------------------------------------------------- /src/modules/posts/posts.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from 'mongoose'; 2 | 3 | import { IPost } from './posts.interface'; 4 | 5 | const PostSchema = new mongoose.Schema({ 6 | user: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | ref: 'user', 9 | }, 10 | text: { 11 | type: String, 12 | required: true, 13 | }, 14 | name: { 15 | type: String, 16 | }, 17 | avatar: { 18 | type: String, 19 | }, 20 | likes: [ 21 | { 22 | user: { 23 | type: mongoose.Schema.Types.ObjectId, 24 | }, 25 | }, 26 | ], 27 | comments: [ 28 | { 29 | user: { 30 | type: mongoose.Schema.Types.ObjectId, 31 | }, 32 | text: { 33 | type: String, 34 | required: true, 35 | }, 36 | name: { 37 | type: String, 38 | }, 39 | avatar: { 40 | type: String, 41 | }, 42 | date: { 43 | type: Date, 44 | default: Date.now, 45 | }, 46 | }, 47 | ], 48 | shares: [ 49 | { 50 | user: { 51 | type: mongoose.Schema.Types.ObjectId, 52 | }, 53 | }, 54 | ], 55 | date: { 56 | type: Date, 57 | default: Date.now, 58 | }, 59 | }); 60 | export default mongoose.model('post', PostSchema); 61 | -------------------------------------------------------------------------------- /src/modules/groups/groups.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from 'mongoose'; 2 | 3 | import { IGroup } from './groups.interface'; 4 | 5 | const GroupSchema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | }, 10 | code: { 11 | type: String, 12 | required: true, 13 | }, 14 | description: { 15 | type: String, 16 | }, 17 | managers: [ 18 | { 19 | user: { 20 | type: mongoose.Schema.Types.ObjectId, 21 | }, 22 | role: { 23 | type: String, 24 | enum: ['admin', 'mod'], 25 | default: 'admin', 26 | }, 27 | }, 28 | ], 29 | members: [ 30 | { 31 | user: { 32 | type: mongoose.Schema.Types.ObjectId, 33 | }, 34 | date: { 35 | type: Date, 36 | default: Date.now, 37 | }, 38 | }, 39 | ], 40 | member_requests: [ 41 | { 42 | user: { 43 | type: mongoose.Schema.Types.ObjectId, 44 | }, 45 | date: { 46 | type: Date, 47 | default: Date.now, 48 | }, 49 | }, 50 | ], 51 | creator: { 52 | type: mongoose.Schema.Types.ObjectId, 53 | ref: 'user', 54 | }, 55 | date: { 56 | type: Date, 57 | default: Date.now, 58 | }, 59 | }); 60 | export default mongoose.model('group', GroupSchema); 61 | -------------------------------------------------------------------------------- /src/modules/conversations/conversations.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from 'mongoose'; 2 | 3 | import { IConversation } from './conversations.interface'; 4 | 5 | const ConversationSchema = new mongoose.Schema({ 6 | user1: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | }, 10 | user2: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | required: true, 13 | }, 14 | date: { 15 | type: Date, 16 | default: Date.now, 17 | }, 18 | recent_date: { 19 | type: Date, 20 | default: Date.now, 21 | }, 22 | messages: [ 23 | { 24 | from: { 25 | type: mongoose.Schema.Types.ObjectId, 26 | required: true, 27 | }, 28 | to: { 29 | type: mongoose.Schema.Types.ObjectId, 30 | required: true, 31 | }, 32 | read: { 33 | type: Boolean, 34 | default: false, 35 | }, 36 | date: { 37 | type: Date, 38 | default: Date.now, 39 | }, 40 | show_on_from: { 41 | type: Boolean, 42 | default: true, 43 | }, 44 | show_on_to: { 45 | type: Boolean, 46 | default: true, 47 | }, 48 | text: { 49 | type: String, 50 | required: true, 51 | }, 52 | }, 53 | ], 54 | }); 55 | export default mongoose.model( 56 | 'conversation', 57 | ConversationSchema 58 | ); 59 | -------------------------------------------------------------------------------- /src/modules/users/user.route.ts: -------------------------------------------------------------------------------- 1 | import RegisterDto from './dtos/register.dto'; 2 | import { Route } from '@core/interfaces'; 3 | import { Router } from 'express'; 4 | import UsersController from './users.controller'; 5 | import { authMiddleware } from '@core/middleware'; 6 | import validationMiddleware from '@core/middleware/validation.middleware'; 7 | 8 | export default class UsersRoute implements Route { 9 | public path = '/api/v1/users'; 10 | public router = Router(); 11 | 12 | public usersController = new UsersController(); 13 | 14 | constructor() { 15 | this.initializeRoutes(); 16 | } 17 | 18 | private initializeRoutes() { 19 | this.router.post(this.path, validationMiddleware(RegisterDto, true), this.usersController.register); 20 | 21 | this.router.put( 22 | this.path + '/:id', 23 | authMiddleware, 24 | validationMiddleware(RegisterDto, true), 25 | this.usersController.updateUser, 26 | ); 27 | 28 | this.router.get(this.path + '/:id', this.usersController.getUserById); 29 | 30 | this.router.get(this.path, this.usersController.getAll); 31 | 32 | this.router.get(this.path + '/paging/:page', this.usersController.getAllPaging); 33 | 34 | this.router.delete(this.path + '/:id', authMiddleware, this.usersController.deleteUser); 35 | 36 | this.router.delete(this.path, authMiddleware, this.usersController.deleteUsers); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: '2.0' 2 | info: 3 | description: 'TEDU Social API docs' 4 | version: '1.0.0' 5 | title: 'TEDU Social' 6 | termOfService: 'http://tedu.com.vn' 7 | contact: 8 | email: 'tedu.international@gmail.com' 9 | license: 10 | name: 'MIT' 11 | url: 'https://opensource.org/licenses/MIT' 12 | host: 'localhost:5000' 13 | basePath: '/api/v1' 14 | tags: 15 | - name: 'auth' 16 | description: 'Authentication APIs' 17 | - name: 'Conversations' 18 | description: 'Message APIs' 19 | - name: 'Groups' 20 | description: 'Group APIs' 21 | - name: 'Posts' 22 | description: 'Post APIs' 23 | - name: 'Profile' 24 | description: 'Profile APIs' 25 | - name: 'Users' 26 | description: 'User APIs' 27 | schemes: 28 | - 'http' 29 | paths: 30 | /auth: 31 | post: 32 | tags: 33 | - 'auth' 34 | summary: 'Login API and get token' 35 | description: 'Input email and password' 36 | operationId: "login" 37 | consumes: 38 | - 'application/json' 39 | produces: 40 | - 'application/json' 41 | parameters: 42 | - in: 'body' 43 | name: 'body' 44 | description: 'Login for user' 45 | required: true 46 | schema: 47 | $ref: '#/definitions/LoginDto' 48 | responses: 49 | '400': 50 | description: 'Invalid input' 51 | definitions: 52 | LoginDto: 53 | type: 'object' 54 | properties: 55 | email: 56 | type: 'string' 57 | password: 58 | type: 'string' 59 | -------------------------------------------------------------------------------- /src/modules/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import AuthService from './auth.service'; 4 | import LoginDto from './auth.dto'; 5 | import { TokenData } from '@modules/auth'; 6 | 7 | export default class AuthController { 8 | private authService = new AuthService(); 9 | 10 | public login = async (req: Request, res: Response, next: NextFunction) => { 11 | try { 12 | const model: LoginDto = req.body; 13 | const tokenData: TokenData = await this.authService.login(model); 14 | res.status(200).json(tokenData); 15 | } catch (error) { 16 | next(error); 17 | } 18 | }; 19 | 20 | public refreshToken = async (req: Request, res: Response, next: NextFunction) => { 21 | try { 22 | const refreshToken = req.body.refreshToken; 23 | const tokenData: TokenData = await this.authService.refreshToken(refreshToken); 24 | res.status(200).json(tokenData); 25 | } catch (error) { 26 | next(error); 27 | } 28 | }; 29 | 30 | public revokeToken = async (req: Request, res: Response, next: NextFunction) => { 31 | try { 32 | const token = req.body.token; 33 | await this.authService.revokeToken(token); 34 | res.status(200); 35 | } catch (error) { 36 | next(error); 37 | } 38 | }; 39 | 40 | public getCurrentLoginUser = async (req: Request, res: Response, next: NextFunction) => { 41 | try { 42 | const user = await this.authService.getCurrentLoginUser(req.user.id); 43 | res.status(200).json(user); 44 | } catch (error) { 45 | next(error); 46 | } 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tedu_social", 3 | "version": "1.0.0", 4 | "description": "A mini social network for TEDU Community", 5 | "main": "src/server.ts", 6 | "author": "Toan Bach", 7 | "license": "MIT", 8 | "scripts": { 9 | "server": "nodemon src/server.ts", 10 | "build": "webpack", 11 | "start": "node dist/server.js", 12 | "lint": "eslint . --ext .ts", 13 | "lint-and-fix": "eslint . --ext .ts --fix" 14 | }, 15 | "dependencies": { 16 | "@types/heroku-logger": "^1.0.0", 17 | "bcryptjs": "^2.4.3", 18 | "class-transformer": "^0.3.1", 19 | "class-validator": "^0.12.2", 20 | "cors": "^2.8.5", 21 | "dotenv": "^8.2.0", 22 | "envalid": "^6.0.2", 23 | "express": "^4.17.1", 24 | "gravatar": "^1.8.1", 25 | "helmet": "^4.1.1", 26 | "heroku-logger": "^0.3.3", 27 | "hpp": "^0.2.3", 28 | "jsonwebtoken": "^8.5.1", 29 | "mongoose": "^5.10.9", 30 | "morgan": "^1.10.0", 31 | "normalize-url": "^5.3.0", 32 | "socket.io": "^3.1.2", 33 | "swagger-ui-express": "^4.1.5", 34 | "winston": "^3.3.3", 35 | "yamljs": "^0.3.0" 36 | }, 37 | "devDependencies": { 38 | "@shopify/eslint-plugin": "^39.0.3", 39 | "@types/bcryptjs": "^2.4.2", 40 | "@types/cors": "^2.8.8", 41 | "@types/express": "^4.17.8", 42 | "@types/gravatar": "^1.8.1", 43 | "@types/hpp": "^0.2.1", 44 | "@types/jsonwebtoken": "^8.5.0", 45 | "@types/mongoose": "^5.7.36", 46 | "@types/morgan": "^1.9.1", 47 | "@types/socket.io": "^2.1.13", 48 | "@types/swagger-ui-express": "^4.1.2", 49 | "@types/yamljs": "^0.2.31", 50 | "@typescript-eslint/eslint-plugin": "^4.9.1", 51 | "@typescript-eslint/parser": "^4.9.1", 52 | "cross-env": "^7.0.2", 53 | "eslint": "^7.15.0", 54 | "husky": "^4.3.5", 55 | "nodemon": "^2.0.5", 56 | "prettier": "^2.2.1", 57 | "ts-loader": "^8.0.11", 58 | "ts-node": "^9.0.0", 59 | "tsconfig-paths": "^3.9.0", 60 | "tsconfig-paths-webpack-plugin": "^3.3.0", 61 | "typescript": "^4.0.3", 62 | "webpack": "^5.10.0", 63 | "webpack-cli": "^4.2.0", 64 | "webpack-node-externals": "^2.5.2", 65 | "webpack-shell-plugin": "^0.5.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/modules/groups/groups.route.ts: -------------------------------------------------------------------------------- 1 | import CreateGroupDto from './dtos/create_group.dto'; 2 | import GroupsController from './groups.controllers'; 3 | import { Route } from '@core/interfaces'; 4 | import { Router } from 'express'; 5 | import SetManagerDto from './dtos/set_manager.dto'; 6 | import { authMiddleware } from '@core/middleware'; 7 | import validationMiddleware from '@core/middleware/validation.middleware'; 8 | 9 | export default class GroupsRoute implements Route { 10 | public path = '/api/v1/groups'; 11 | public router = Router(); 12 | 13 | public groupsController = new GroupsController(); 14 | 15 | constructor() { 16 | this.initializeRoutes(); 17 | } 18 | 19 | private initializeRoutes() { 20 | // Group 21 | this.router.post( 22 | this.path, 23 | authMiddleware, 24 | validationMiddleware(CreateGroupDto, true), 25 | this.groupsController.createGroup 26 | ); 27 | 28 | this.router.put( 29 | this.path + '/:id', 30 | authMiddleware, 31 | validationMiddleware(CreateGroupDto, true), 32 | this.groupsController.updateGroup 33 | ); 34 | this.router.get(this.path, this.groupsController.getAll); 35 | 36 | this.router.delete(this.path + '/:id', this.groupsController.deleteGroup); 37 | 38 | //Members 39 | this.router.post( 40 | this.path + '/members/:id', 41 | authMiddleware, 42 | this.groupsController.joinGroup 43 | ); 44 | 45 | this.router.put( 46 | this.path + '/members/:user_id/:group_id', 47 | this.groupsController.approveJoinRequest 48 | ); 49 | 50 | this.router.delete( 51 | this.path + '/members/:user_id/:group_id', 52 | authMiddleware, 53 | this.groupsController.removeMember 54 | ); 55 | 56 | this.router.get( 57 | this.path + '/members/:id', 58 | this.groupsController.getAllMembers 59 | ); 60 | 61 | //Managers 62 | this.router.post( 63 | this.path + '/managers/:id', 64 | authMiddleware, 65 | validationMiddleware(SetManagerDto, true), 66 | this.groupsController.addManager 67 | ); 68 | 69 | this.router.delete( 70 | this.path + '/managers/:group_id/:user_id', 71 | authMiddleware, 72 | this.groupsController.removeManager 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/posts/posts.route.ts: -------------------------------------------------------------------------------- 1 | import CreateCommentDto from './dtos/create_comment.dto'; 2 | import CreatePostDto from './dtos/create_post.dto'; 3 | import PostsController from './posts.controller'; 4 | import { Route } from '@core/interfaces'; 5 | import { Router } from 'express'; 6 | import { authMiddleware } from '@core/middleware'; 7 | import validationMiddleware from '@core/middleware/validation.middleware'; 8 | 9 | export default class PostsRoute implements Route { 10 | public path = '/api/v1/posts'; 11 | public router = Router(); 12 | 13 | public postController = new PostsController(); 14 | 15 | constructor() { 16 | this.initializeRoutes(); 17 | } 18 | 19 | private initializeRoutes() { 20 | this.router.post( 21 | this.path, 22 | authMiddleware, 23 | validationMiddleware(CreatePostDto, true), 24 | this.postController.createPost 25 | ); 26 | 27 | this.router.put( 28 | this.path + '/:id', 29 | authMiddleware, 30 | validationMiddleware(CreatePostDto, true), 31 | this.postController.updatePost 32 | ); 33 | 34 | this.router.get(this.path, this.postController.getAllPosts); 35 | this.router.get(this.path + '/:id', this.postController.getPostById); 36 | this.router.get( 37 | this.path + '/paging/:page', 38 | this.postController.getAllPaging 39 | ); 40 | 41 | this.router.delete( 42 | this.path + '/:id', 43 | authMiddleware, 44 | this.postController.deletePost 45 | ); 46 | 47 | this.router.post( 48 | this.path + '/like/:id', 49 | authMiddleware, 50 | this.postController.likePost 51 | ); 52 | this.router.delete( 53 | this.path + '/like/:id', 54 | authMiddleware, 55 | this.postController.unlikePost 56 | ); 57 | 58 | this.router.post( 59 | this.path + '/comments/:id', 60 | authMiddleware, 61 | validationMiddleware(CreateCommentDto, true), 62 | this.postController.addComment 63 | ); 64 | this.router.delete( 65 | this.path + '/comments/:id/:comment_id', 66 | authMiddleware, 67 | this.postController.removeComment 68 | ); 69 | 70 | this.router.post( 71 | this.path + '/shares/:id', 72 | authMiddleware, 73 | this.postController.sharePost 74 | ); 75 | this.router.delete( 76 | this.path + '/shares/:id', 77 | authMiddleware, 78 | this.postController.removeSharePost 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/modules/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import RegisterDto from './dtos/register.dto'; 4 | import { TokenData } from '@modules/auth'; 5 | import UserService from './users.service'; 6 | 7 | export default class UsersController { 8 | private userService = new UserService(); 9 | 10 | public register = async (req: Request, res: Response, next: NextFunction) => { 11 | try { 12 | const model: RegisterDto = req.body; 13 | const tokenData: TokenData = await this.userService.createUser(model); 14 | const io = req.app.get('socketio'); 15 | io.emit('user_created', `${model.email} has been registered`); 16 | res.status(201).json(tokenData); 17 | } catch (error) { 18 | next(error); 19 | } 20 | }; 21 | 22 | public getUserById = async (req: Request, res: Response, next: NextFunction) => { 23 | try { 24 | const user = await this.userService.getUserById(req.params.id); 25 | res.status(200).json(user); 26 | } catch (error) { 27 | next(error); 28 | } 29 | }; 30 | 31 | public getAll = async (req: Request, res: Response, next: NextFunction) => { 32 | try { 33 | const users = await this.userService.getAll(); 34 | res.status(200).json(users); 35 | } catch (error) { 36 | next(error); 37 | } 38 | }; 39 | 40 | public getAllPaging = async (req: Request, res: Response, next: NextFunction) => { 41 | try { 42 | const page = Number(req.params.page); 43 | const keyword = req.query.keyword || ''; 44 | 45 | const paginationResult = await this.userService.getAllPaging(keyword.toString(), page); 46 | res.status(200).json(paginationResult); 47 | } catch (error) { 48 | next(error); 49 | } 50 | }; 51 | 52 | public updateUser = async (req: Request, res: Response, next: NextFunction) => { 53 | try { 54 | const model: RegisterDto = req.body; 55 | const user = await this.userService.updateUser(req.params.id, model); 56 | const io = req.app.get('socketio'); 57 | io.emit('user_updated', `User ${model.email} has been updated.`); 58 | res.status(200).json(user); 59 | } catch (error) { 60 | next(error); 61 | } 62 | }; 63 | 64 | public deleteUser = async (req: Request, res: Response, next: NextFunction) => { 65 | try { 66 | const result = await this.userService.deleteUser(req.params.id); 67 | const io = req.app.get('socketio'); 68 | io.emit('user_deleted', `User ${result.email} has been deleted.`); 69 | res.status(200).json(result); 70 | } catch (error) { 71 | next(error); 72 | } 73 | }; 74 | 75 | public deleteUsers = async (req: Request, res: Response, next: NextFunction) => { 76 | try { 77 | const ids: string[] = req.body; 78 | const result = await this.userService.deleteUsers(ids); 79 | res.status(200).json(result); 80 | } catch (error) { 81 | next(error); 82 | } 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /src/modules/conversations/conversations.service.ts: -------------------------------------------------------------------------------- 1 | import ConversationSchema from './conversations.model'; 2 | import { HttpException } from '@core/exceptions'; 3 | import { IConversation } from './conversations.interface'; 4 | import { IMessage } from './conversations.interface'; 5 | import SendMessageDto from './dtos/send_message.dto'; 6 | import { UserSchema } from '@modules/users'; 7 | 8 | export default class ConversationService { 9 | public async sendMessage( 10 | userId: string, 11 | dto: SendMessageDto 12 | ): Promise { 13 | const user = await UserSchema.findById(userId).select('-password').exec(); 14 | if (!user) throw new HttpException(400, 'User id is not exist'); 15 | 16 | const toUser = await UserSchema.findById(dto.to).select('-password').exec(); 17 | if (!toUser) throw new HttpException(400, 'To user id is not exist'); 18 | 19 | if (!dto.conversationId) { 20 | let newConversation = await ConversationSchema.findOne({ 21 | $or: [ 22 | { $and: [{ user1: userId }, { user2: dto.to }] }, 23 | { $and: [{ user1: dto.to }, { user2: userId }] }, 24 | ], 25 | }).exec(); 26 | if (newConversation) { 27 | newConversation.messages.unshift({ 28 | to: dto.to, 29 | text: dto.text, 30 | from: userId, 31 | } as IMessage); 32 | } else { 33 | newConversation = new ConversationSchema({ 34 | user1: userId, 35 | user2: dto.to, 36 | messages: [ 37 | { 38 | from: userId, 39 | to: dto.to, 40 | text: dto.text, 41 | }, 42 | ], 43 | }); 44 | } 45 | 46 | await newConversation.save(); 47 | return newConversation; 48 | } else { 49 | const conversation = await ConversationSchema.findById( 50 | dto.conversationId 51 | ).exec(); 52 | 53 | if (!conversation) { 54 | throw new HttpException(400, 'Conversation id is not exist'); 55 | } 56 | if ( 57 | (conversation.user1 !== userId && conversation.user2 !== dto.to) || 58 | (conversation.user1 !== dto.to && conversation.user2 !== userId) 59 | ) { 60 | throw new HttpException(400, 'Conversation id is not valid'); 61 | } 62 | conversation.messages.unshift({ 63 | to: dto.to, 64 | text: dto.text, 65 | from: userId, 66 | } as IMessage); 67 | await conversation.save(); 68 | return conversation; 69 | } 70 | } 71 | 72 | public async getMyConversation(userId: string): Promise { 73 | const user = await UserSchema.findById(userId).select('-password').exec(); 74 | if (!user) throw new HttpException(400, 'User id is not exist'); 75 | 76 | const conversations = await ConversationSchema.find({ 77 | $or: [{ user1: userId }, { user2: userId }], 78 | }) 79 | .sort({ recent_date: -1 }) 80 | .exec(); 81 | return conversations; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/modules/profile/profile.route.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware, validationMiddleware } from '@core/middleware'; 2 | 3 | import AddEducationDto from './dtos/add_education.dto'; 4 | import AddExperienceDto from './dtos/add_experience.dto'; 5 | import CreateProfileDto from './dtos/create_profile.dto'; 6 | import ProfileController from './profile.controller'; 7 | import { Profiler } from 'winston'; 8 | import { Route } from '@core/interfaces'; 9 | import { Router } from 'express'; 10 | 11 | class ProfileRoute implements Route { 12 | public path = '/api/v1/profile'; 13 | public router = Router(); 14 | public profileController = new ProfileController(); 15 | 16 | constructor() { 17 | this.initializeRoutes(); 18 | } 19 | 20 | private initializeRoutes() { 21 | this.router.get(`${this.path}`, this.profileController.getAllProfiles); 22 | this.router.get( 23 | `${this.path}/user/:id`, 24 | this.profileController.getByUserId 25 | ); 26 | this.router.get( 27 | `${this.path}/me`, 28 | authMiddleware, 29 | this.profileController.getCurrentProfile 30 | ); 31 | this.router.post( 32 | `${this.path}`, 33 | authMiddleware, 34 | validationMiddleware(CreateProfileDto), 35 | this.profileController.createProfile 36 | ); 37 | this.router.delete( 38 | `${this.path}/:id`, 39 | authMiddleware, 40 | this.profileController.deleteProfile 41 | ); 42 | 43 | this.router.put( 44 | `${this.path}/experience`, 45 | authMiddleware, 46 | validationMiddleware(AddExperienceDto), 47 | this.profileController.createExperience 48 | ); 49 | this.router.delete( 50 | `${this.path}/experience/:exp_id`, 51 | authMiddleware, 52 | this.profileController.deleteExperience 53 | ); 54 | 55 | this.router.put( 56 | `${this.path}/education`, 57 | authMiddleware, 58 | validationMiddleware(AddEducationDto), 59 | this.profileController.createEducation 60 | ); 61 | this.router.delete( 62 | `${this.path}/education/:edu_id`, 63 | authMiddleware, 64 | this.profileController.deleteEducation 65 | ); 66 | 67 | this.router.post( 68 | `${this.path}/following/:id`, 69 | authMiddleware, 70 | this.profileController.follow 71 | ); 72 | 73 | this.router.delete( 74 | `${this.path}/following/:id`, 75 | authMiddleware, 76 | this.profileController.unFollow 77 | ); 78 | 79 | this.router.post( 80 | `${this.path}/friends/:id`, 81 | authMiddleware, 82 | this.profileController.addFriend 83 | ); 84 | 85 | this.router.delete( 86 | `${this.path}/friends/:id`, 87 | authMiddleware, 88 | this.profileController.unFriend 89 | ); 90 | 91 | this.router.put( 92 | `${this.path}/friends/:id`, 93 | authMiddleware, 94 | this.profileController.acceptFriendRequest 95 | ); 96 | } 97 | } 98 | 99 | export default ProfileRoute; 100 | -------------------------------------------------------------------------------- /src/modules/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { IUser, TokenData } from '@modules/auth'; 2 | import { Logger, isEmptyObject } from '@core/utils'; 3 | import { generateJwtToken, randomTokenString } from '@core/utils/helpers'; 4 | 5 | import { HttpException } from '@core/exceptions'; 6 | import LoginDto from './auth.dto'; 7 | import { RefreshTokenSchema } from '@modules/refresh_token'; 8 | import { UserSchema } from '@modules/users'; 9 | import bcryptjs from 'bcryptjs'; 10 | 11 | class AuthService { 12 | public userSchema = UserSchema; 13 | 14 | public async login(model: LoginDto): Promise { 15 | if (isEmptyObject(model)) { 16 | throw new HttpException(400, 'Model is empty'); 17 | } 18 | 19 | const user = await this.userSchema.findOne({ email: model.email }).exec(); 20 | if (!user) { 21 | throw new HttpException(409, `Your email ${model.email} is not exist.`); 22 | } 23 | const isMatchPassword = await bcryptjs.compare(model.password, user.password); 24 | if (!isMatchPassword) throw new HttpException(400, 'Credential is not valid'); 25 | 26 | const refreshToken = await this.generateRefreshToken(user._id); 27 | const jwtToken = generateJwtToken(user._id, refreshToken.token); 28 | 29 | // save refresh token 30 | await refreshToken.save(); 31 | 32 | return jwtToken; 33 | } 34 | 35 | public async refreshToken(token: string): Promise { 36 | const refreshToken = await this.getRefreshTokenFromDb(token); 37 | const { user } = refreshToken; 38 | 39 | // replace old refresh token with a new one and save 40 | const newRefreshToken = await this.generateRefreshToken(user); 41 | refreshToken.revoked = new Date(Date.now()); 42 | refreshToken.replacedByToken = newRefreshToken.token; 43 | await refreshToken.save(); 44 | await newRefreshToken.save(); 45 | 46 | // return basic details and tokens 47 | return generateJwtToken(user, newRefreshToken.token); 48 | } 49 | 50 | public async revokeToken(token: string): Promise { 51 | const refreshToken = await this.getRefreshTokenFromDb(token); 52 | 53 | // revoke token and save 54 | refreshToken.revoked = new Date(Date.now()); 55 | await refreshToken.save(); 56 | } 57 | 58 | public async getCurrentLoginUser(userId: string): Promise { 59 | const user = await this.userSchema.findById(userId).exec(); 60 | if (!user) { 61 | throw new HttpException(404, `User is not exists`); 62 | } 63 | return user; 64 | } 65 | 66 | private async getRefreshTokenFromDb(refreshToken: string) { 67 | const token = await RefreshTokenSchema.findOne({ token: refreshToken }).populate('user').exec(); 68 | Logger.info(token); 69 | if (!token || !token.isActive) throw new HttpException(400, `Invalid refresh token`); 70 | return token; 71 | } 72 | 73 | private async generateRefreshToken(userId: string) { 74 | // create a refresh token that expires in 7 days 75 | return new RefreshTokenSchema({ 76 | user: userId, 77 | token: randomTokenString(), 78 | expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // in 7 days 79 | }); 80 | } 81 | } 82 | export default AuthService; 83 | -------------------------------------------------------------------------------- /src/modules/profile/profile.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from 'mongoose'; 2 | 3 | import { IProfile } from './profile.interface'; 4 | 5 | const ProfileSchema = new mongoose.Schema({ 6 | user: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | ref: 'user', 9 | }, 10 | company: { 11 | type: String, 12 | }, 13 | website: { 14 | type: String, 15 | }, 16 | location: { 17 | type: String, 18 | }, 19 | status: { 20 | type: String, 21 | required: true, 22 | }, 23 | skills: { 24 | type: [String], 25 | required: true, 26 | }, 27 | bio: { 28 | type: String, 29 | }, 30 | experience: [ 31 | { 32 | title: { 33 | type: String, 34 | required: true, 35 | }, 36 | company: { 37 | type: String, 38 | required: true, 39 | }, 40 | location: { 41 | type: String, 42 | }, 43 | from: { 44 | type: Date, 45 | required: true, 46 | }, 47 | to: { 48 | type: Date, 49 | }, 50 | current: { 51 | type: Boolean, 52 | default: false, 53 | }, 54 | description: { 55 | type: String, 56 | }, 57 | }, 58 | ], 59 | education: [ 60 | { 61 | school: { 62 | type: String, 63 | required: true, 64 | }, 65 | degree: { 66 | type: String, 67 | required: true, 68 | }, 69 | fieldofstudy: { 70 | type: String, 71 | required: true, 72 | }, 73 | from: { 74 | type: Date, 75 | required: true, 76 | }, 77 | to: { 78 | type: Date, 79 | }, 80 | current: { 81 | type: Boolean, 82 | default: false, 83 | }, 84 | description: { 85 | type: String, 86 | }, 87 | }, 88 | ], 89 | social: { 90 | youtube: { 91 | type: String, 92 | }, 93 | twitter: { 94 | type: String, 95 | }, 96 | facebook: { 97 | type: String, 98 | }, 99 | linkedin: { 100 | type: String, 101 | }, 102 | instagram: { 103 | type: String, 104 | }, 105 | }, 106 | followings: [ 107 | { 108 | user: { 109 | type: mongoose.Schema.Types.ObjectId, 110 | ref: 'user', 111 | }, 112 | }, 113 | ], 114 | followers: [ 115 | { 116 | user: { 117 | type: mongoose.Schema.Types.ObjectId, 118 | ref: 'user', 119 | }, 120 | }, 121 | ], 122 | friends: [ 123 | { 124 | user: { 125 | type: mongoose.Schema.Types.ObjectId, 126 | ref: 'user', 127 | }, 128 | date: { 129 | type: Date, 130 | default: Date.now, 131 | }, 132 | }, 133 | ], 134 | friend_requests: [ 135 | { 136 | user: { 137 | type: mongoose.Schema.Types.ObjectId, 138 | ref: 'user', 139 | }, 140 | date: { 141 | type: Date, 142 | default: Date.now, 143 | }, 144 | }, 145 | ], 146 | date: { 147 | type: Date, 148 | default: Date.now, 149 | }, 150 | }); 151 | 152 | export default mongoose.model('profile', ProfileSchema); 153 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import HerokuLogger from 'heroku-logger'; 2 | import { Logger } from '@core/utils'; 3 | import { Route } from '@core/interfaces'; 4 | import YAML from 'yamljs'; 5 | import cors from 'cors'; 6 | import { errorMiddleware } from '@core/middleware'; 7 | import express from 'express'; 8 | import helmet from 'helmet'; 9 | import hpp from 'hpp'; 10 | import http from 'http'; 11 | import mongoose from 'mongoose'; 12 | import morgan from 'morgan'; 13 | import socketIo from 'socket.io'; 14 | import swaggerUi from 'swagger-ui-express'; 15 | 16 | class App { 17 | public port: string | number; 18 | public production: boolean; 19 | public app: express.Application; 20 | public server: http.Server; 21 | public io: socketIo.Server; 22 | 23 | constructor(routes: Route[]) { 24 | this.app = express(); 25 | this.server = http.createServer(this.app); 26 | this.io = new socketIo.Server(this.server); 27 | 28 | this.port = process.env.PORT || 15000; 29 | this.production = process.env.NODE_ENV == 'production' ? true : false; 30 | 31 | this.connectToDatabase(); 32 | this.initializeMiddleware(); 33 | this.initializeRoutes(routes); 34 | this.initializeErrorMiddleware(); 35 | this.initializeSwagger(); 36 | this.initSocketIo(); 37 | } 38 | private initSocketIo() { 39 | this.server = http.createServer(this.app); 40 | this.io = new socketIo.Server(this.server, { 41 | cors: { 42 | origin: '*', 43 | }, 44 | }); 45 | this.app.set('socketio', this.io); 46 | 47 | const users: any = {}; 48 | this.io.on('connection', (socket: socketIo.Socket) => { 49 | Logger.warn('a user connected : ' + socket.id); 50 | socket.emit('message', 'Hello ' + socket.id); 51 | 52 | socket.on('login', function (data) { 53 | Logger.warn('a user ' + data.userId + ' connected'); 54 | // saving userId to object with socket ID 55 | users[socket.id] = data.userId; 56 | }); 57 | 58 | socket.on('disconnect', function () { 59 | Logger.warn('user ' + users[socket.id] + ' disconnected'); 60 | // remove saved socket from users object 61 | delete users[socket.id]; 62 | Logger.warn('socket disconnected : ' + socket.id); 63 | }); 64 | }); 65 | } 66 | 67 | public listen() { 68 | this.server.listen(this.port, () => { 69 | Logger.info(`Server is listening on port ${this.port}`); 70 | HerokuLogger.info(`Server is listening on port ${this.port}`); 71 | }); 72 | } 73 | 74 | private initializeRoutes(routes: Route[]) { 75 | routes.forEach((route) => { 76 | this.app.use('/', route.router); 77 | }); 78 | } 79 | 80 | private initializeMiddleware() { 81 | if (this.production) { 82 | // this.app.use(hpp()); 83 | // this.app.use(helmet()); 84 | this.app.use(morgan('combined')); 85 | this.app.use(cors({ origin: 'social-admin-prod.herokuapp.com', credentials: true })); 86 | } else { 87 | this.app.use(morgan('dev')); 88 | this.app.use(cors({ origin: true, credentials: true })); 89 | } 90 | this.app.use(express.json()); 91 | this.app.use(express.urlencoded({ extended: true })); 92 | } 93 | private initializeErrorMiddleware() { 94 | this.app.use(errorMiddleware); 95 | } 96 | 97 | private connectToDatabase() { 98 | const connectString = process.env.MONGODB_URI; 99 | if (!connectString) { 100 | Logger.error('Connection string is invalid'); 101 | HerokuLogger.error(`Connection string is invalid`); 102 | return; 103 | } 104 | mongoose 105 | .connect(connectString, { 106 | useNewUrlParser: true, 107 | useUnifiedTopology: true, 108 | useFindAndModify: false, 109 | useCreateIndex: true, 110 | }) 111 | .catch((reason) => { 112 | Logger.error(reason); 113 | }); 114 | Logger.info('Database connected...'); 115 | HerokuLogger.info(`Database connected...`); 116 | } 117 | 118 | private initializeSwagger() { 119 | const swaggerDocument = YAML.load('./src/swagger.yaml'); 120 | 121 | this.app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); 122 | } 123 | } 124 | 125 | export default App; 126 | -------------------------------------------------------------------------------- /src/modules/groups/groups.controllers.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import CreateGroupDto from './dtos/create_group.dto'; 4 | import GroupService from './groups.service'; 5 | import SetManagerDto from './dtos/set_manager.dto'; 6 | 7 | export default class GroupsController { 8 | private groupService = new GroupService(); 9 | 10 | public createGroup = async ( 11 | req: Request, 12 | res: Response, 13 | next: NextFunction 14 | ) => { 15 | try { 16 | const model: CreateGroupDto = req.body; 17 | const result = await this.groupService.createGroup(req.user.id, model); 18 | res.status(201).json(result); 19 | } catch (error) { 20 | next(error); 21 | } 22 | }; 23 | 24 | public getAll = async (req: Request, res: Response, next: NextFunction) => { 25 | try { 26 | const groups = await this.groupService.getAllGroup(); 27 | res.status(200).json(groups); 28 | } catch (error) { 29 | next(error); 30 | } 31 | }; 32 | 33 | public updateGroup = async ( 34 | req: Request, 35 | res: Response, 36 | next: NextFunction 37 | ) => { 38 | try { 39 | const model: CreateGroupDto = req.body; 40 | const groupId = req.params.id; 41 | const result = await this.groupService.updateGroup(groupId, model); 42 | res.status(200).json(result); 43 | } catch (error) { 44 | next(error); 45 | } 46 | }; 47 | 48 | public deleteGroup = async ( 49 | req: Request, 50 | res: Response, 51 | next: NextFunction 52 | ) => { 53 | try { 54 | const groupId = req.params.id; 55 | const groups = await this.groupService.deleteGroup(groupId); 56 | res.status(200).json(groups); 57 | } catch (error) { 58 | next(error); 59 | } 60 | }; 61 | 62 | public joinGroup = async ( 63 | req: Request, 64 | res: Response, 65 | next: NextFunction 66 | ) => { 67 | try { 68 | const groupId = req.params.id; 69 | const userId = req.user.id; 70 | const group = await this.groupService.joinGroup(userId, groupId); 71 | res.status(200).json(group); 72 | } catch (error) { 73 | next(error); 74 | } 75 | }; 76 | 77 | public approveJoinRequest = async ( 78 | req: Request, 79 | res: Response, 80 | next: NextFunction 81 | ) => { 82 | try { 83 | const groupId = req.params.group_id; 84 | const userId = req.params.user_id; 85 | const group = await this.groupService.approveJoinRequest(userId, groupId); 86 | res.status(200).json(group); 87 | } catch (error) { 88 | next(error); 89 | } 90 | }; 91 | 92 | public addManager = async ( 93 | req: Request, 94 | res: Response, 95 | next: NextFunction 96 | ) => { 97 | try { 98 | const groupId = req.params.id; 99 | const model: SetManagerDto = req.body; 100 | const group = await this.groupService.addManager(groupId, model); 101 | res.status(200).json(group); 102 | } catch (error) { 103 | next(error); 104 | } 105 | }; 106 | 107 | public removeManager = async ( 108 | req: Request, 109 | res: Response, 110 | next: NextFunction 111 | ) => { 112 | try { 113 | const groupId = req.params.group_id; 114 | const userId = req.params.user_id; 115 | const group = await this.groupService.removeManager(groupId, userId); 116 | res.status(200).json(group); 117 | } catch (error) { 118 | next(error); 119 | } 120 | }; 121 | 122 | public getAllMembers = async ( 123 | req: Request, 124 | res: Response, 125 | next: NextFunction 126 | ) => { 127 | try { 128 | const groupId = req.params.id; 129 | const members = await this.groupService.getAllMembers(groupId); 130 | res.status(200).json(members); 131 | } catch (error) { 132 | next(error); 133 | } 134 | }; 135 | 136 | public removeMember = async ( 137 | req: Request, 138 | res: Response, 139 | next: NextFunction 140 | ) => { 141 | try { 142 | const groupId = req.params.group_id; 143 | const userId = req.params.user_id; 144 | const group = await this.groupService.removeMember(groupId, userId); 145 | res.status(200).json(group); 146 | } catch (error) { 147 | next(error); 148 | } 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /src/modules/posts/posts.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import CreatePostDto from './dtos/create_post.dto'; 4 | import { IPost } from './posts.interface'; 5 | import PostService from './posts.service'; 6 | 7 | export default class PostsController { 8 | private postService = new PostService(); 9 | 10 | public createPost = async ( 11 | req: Request, 12 | res: Response, 13 | next: NextFunction 14 | ) => { 15 | try { 16 | const model: CreatePostDto = req.body; 17 | const userId = req.user.id; 18 | const result = await this.postService.createPost(userId, model); 19 | res.status(201).json(result); 20 | } catch (error) { 21 | next(error); 22 | } 23 | }; 24 | 25 | public updatePost = async ( 26 | req: Request, 27 | res: Response, 28 | next: NextFunction 29 | ) => { 30 | try { 31 | const model: CreatePostDto = req.body; 32 | const postId = req.params.id; 33 | const result = await this.postService.updatePost(postId, model); 34 | res.status(200).json(result); 35 | } catch (error) { 36 | next(error); 37 | } 38 | }; 39 | 40 | public getAllPosts = async ( 41 | req: Request, 42 | res: Response, 43 | next: NextFunction 44 | ) => { 45 | try { 46 | const posts: IPost[] = await this.postService.getAllPosts(); 47 | res.status(200).json(posts); 48 | } catch (error) { 49 | next(error); 50 | } 51 | }; 52 | 53 | public getPostById = async ( 54 | req: Request, 55 | res: Response, 56 | next: NextFunction 57 | ) => { 58 | try { 59 | const id = req.params.id; 60 | const post: IPost = await this.postService.getPostById(id); 61 | res.status(200).json(post); 62 | } catch (error) { 63 | next(error); 64 | } 65 | }; 66 | 67 | public getAllPaging = async ( 68 | req: Request, 69 | res: Response, 70 | next: NextFunction 71 | ) => { 72 | try { 73 | const page: number = Number(req.params.page); 74 | const keyword = req.query.keyword || ''; 75 | 76 | const paginationResult = await this.postService.getAllPaging( 77 | keyword.toString(), 78 | page 79 | ); 80 | res.status(200).json(paginationResult); 81 | } catch (error) { 82 | next(error); 83 | } 84 | }; 85 | 86 | public deletePost = async ( 87 | req: Request, 88 | res: Response, 89 | next: NextFunction 90 | ) => { 91 | try { 92 | const postId = req.params.id; 93 | 94 | const post = await this.postService.deletePost(req.user.id, postId); 95 | res.status(200).json(post); 96 | } catch (error) { 97 | next(error); 98 | } 99 | }; 100 | 101 | public likePost = async (req: Request, res: Response, next: NextFunction) => { 102 | try { 103 | const postId = req.params.id; 104 | 105 | const likes = await this.postService.likePost(req.user.id, postId); 106 | res.status(200).json(likes); 107 | } catch (error) { 108 | next(error); 109 | } 110 | }; 111 | 112 | public unlikePost = async ( 113 | req: Request, 114 | res: Response, 115 | next: NextFunction 116 | ) => { 117 | try { 118 | const postId = req.params.id; 119 | 120 | const likes = await this.postService.unlikePost(req.user.id, postId); 121 | res.status(200).json(likes); 122 | } catch (error) { 123 | next(error); 124 | } 125 | }; 126 | 127 | public addComment = async ( 128 | req: Request, 129 | res: Response, 130 | next: NextFunction 131 | ) => { 132 | try { 133 | const postId = req.params.id; 134 | 135 | const result = await this.postService.addComment({ 136 | text: req.body.text, 137 | userId: req.user.id, 138 | postId: postId, 139 | }); 140 | res.status(200).json(result); 141 | } catch (error) { 142 | next(error); 143 | } 144 | }; 145 | 146 | public removeComment = async ( 147 | req: Request, 148 | res: Response, 149 | next: NextFunction 150 | ) => { 151 | try { 152 | const postId = req.params.id; 153 | 154 | const result = await this.postService.removeComment( 155 | req.params.comment_id, 156 | postId, 157 | req.user.id 158 | ); 159 | res.status(200).json(result); 160 | } catch (error) { 161 | next(error); 162 | } 163 | }; 164 | 165 | public sharePost = async ( 166 | req: Request, 167 | res: Response, 168 | next: NextFunction 169 | ) => { 170 | try { 171 | const postId = req.params.id; 172 | 173 | const shares = await this.postService.sharePost(req.user.id, postId); 174 | res.status(200).json(shares); 175 | } catch (error) { 176 | next(error); 177 | } 178 | }; 179 | 180 | public removeSharePost = async ( 181 | req: Request, 182 | res: Response, 183 | next: NextFunction 184 | ) => { 185 | try { 186 | const postId = req.params.id; 187 | 188 | const shares = await this.postService.removeShare(req.user.id, postId); 189 | res.status(200).json(shares); 190 | } catch (error) { 191 | next(error); 192 | } 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /src/modules/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { generateJwtToken, randomTokenString } from '@core/utils/helpers'; 2 | 3 | import { HttpException } from '@core/exceptions'; 4 | import { IPagination } from '@core/interfaces'; 5 | import IUser from './users.interface'; 6 | import { RefreshTokenSchema } from '@modules/refresh_token'; 7 | import RegisterDto from './dtos/register.dto'; 8 | import { TokenData } from '@modules/auth'; 9 | import UserSchema from './users.model'; 10 | import bcryptjs from 'bcryptjs'; 11 | import gravatar from 'gravatar'; 12 | import { isEmptyObject } from '@core/utils'; 13 | 14 | class UserService { 15 | public userSchema = UserSchema; 16 | 17 | public async createUser(model: RegisterDto): Promise { 18 | if (isEmptyObject(model)) { 19 | throw new HttpException(400, 'Model is empty'); 20 | } 21 | 22 | const user = await this.userSchema.findOne({ email: model.email }).exec(); 23 | if (user) { 24 | throw new HttpException(409, `Your email ${model.email} already exist.`); 25 | } 26 | 27 | const avatar = gravatar.url(model.email, { 28 | size: '200', 29 | rating: 'g', 30 | default: 'mm', 31 | }); 32 | 33 | const salt = await bcryptjs.genSalt(10); 34 | 35 | const hashedPassword = await bcryptjs.hash(model.password, salt); 36 | const createdUser = await this.userSchema.create({ 37 | ...model, 38 | password: hashedPassword, 39 | avatar: avatar, 40 | date: Date.now(), 41 | }); 42 | const refreshToken = await this.generateRefreshToken(createdUser._id); 43 | await refreshToken.save(); 44 | 45 | return generateJwtToken(createdUser._id, refreshToken.token); 46 | } 47 | 48 | public async updateUser(userId: string, model: RegisterDto): Promise { 49 | if (isEmptyObject(model)) { 50 | throw new HttpException(400, 'Model is empty'); 51 | } 52 | 53 | const user = await this.userSchema.findById(userId).exec(); 54 | if (!user) { 55 | throw new HttpException(400, `User id is not exist`); 56 | } 57 | 58 | let avatar = user.avatar; 59 | 60 | const checkEmailExist = await this.userSchema 61 | .find({ 62 | $and: [{ email: { $eq: model.email } }, { _id: { $ne: userId } }], 63 | }) 64 | .exec(); 65 | if (checkEmailExist.length !== 0) { 66 | throw new HttpException(400, 'Your email has been used by another user'); 67 | } 68 | 69 | avatar = gravatar.url(model.email!, { 70 | size: '200', 71 | rating: 'g', 72 | default: 'mm', 73 | }); 74 | 75 | let updateUserById; 76 | if (model.password) { 77 | const salt = await bcryptjs.genSalt(10); 78 | const hashedPassword = await bcryptjs.hash(model.password, salt); 79 | updateUserById = await this.userSchema 80 | .findByIdAndUpdate( 81 | userId, 82 | { 83 | ...model, 84 | avatar: avatar, 85 | password: hashedPassword, 86 | }, 87 | { new: true }, 88 | ) 89 | .exec(); 90 | } else { 91 | updateUserById = await this.userSchema 92 | .findByIdAndUpdate( 93 | userId, 94 | { 95 | ...model, 96 | avatar: avatar, 97 | }, 98 | { new: true }, 99 | ) 100 | .exec(); 101 | } 102 | 103 | if (!updateUserById) throw new HttpException(409, 'You are not an user'); 104 | 105 | return updateUserById; 106 | } 107 | 108 | public async getUserById(userId: string): Promise { 109 | const user = await this.userSchema.findById(userId).exec(); 110 | if (!user) { 111 | throw new HttpException(404, `User is not exists`); 112 | } 113 | return user; 114 | } 115 | 116 | public async getAll(): Promise { 117 | const users = await this.userSchema.find().exec(); 118 | return users; 119 | } 120 | 121 | public async getAllPaging(keyword: string, page: number): Promise> { 122 | const pageSize = Number(process.env.PAGE_SIZE || 10); 123 | 124 | let query = {}; 125 | if (keyword) { 126 | query = { 127 | $or: [{ email: keyword }, { first_name: keyword }, { last_name: keyword }], 128 | }; 129 | } 130 | 131 | const users = await this.userSchema 132 | .find(query) 133 | .skip((page - 1) * pageSize) 134 | .limit(pageSize) 135 | .exec(); 136 | 137 | const rowCount = await this.userSchema.find(query).countDocuments().exec(); 138 | 139 | return { 140 | total: rowCount, 141 | page: page, 142 | pageSize: pageSize, 143 | items: users, 144 | } as IPagination; 145 | } 146 | 147 | public async deleteUser(userId: string): Promise { 148 | const deletedUser = await this.userSchema.findByIdAndDelete(userId).exec(); 149 | if (!deletedUser) throw new HttpException(409, 'Your id is invalid'); 150 | return deletedUser; 151 | } 152 | 153 | public async deleteUsers(userIds: string[]): Promise { 154 | const result = await this.userSchema.deleteMany({ _id: [...userIds] }).exec(); 155 | if (!result.ok) throw new HttpException(409, 'Your id is invalid'); 156 | return result.deletedCount; 157 | } 158 | private async generateRefreshToken(userId: string) { 159 | // create a refresh token that expires in 7 days 160 | return new RefreshTokenSchema({ 161 | user: userId, 162 | token: randomTokenString(), 163 | expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), 164 | }); 165 | } 166 | } 167 | export default UserService; 168 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist" /* Redirect output structure to the directory. */, 18 | "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 45 | "baseUrl": "./src" /* Base directory to resolve non-absolute module names. */, 46 | "paths": { 47 | "*": ["node_modules/*"], 48 | "@modules/*": ["modules/*"], 49 | "@core/*": ["core/*"] 50 | } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, 51 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 52 | "typeRoots": [ 53 | "./src/types" 54 | ] /* List of folders to include type definitions from. */, 55 | // "types": [], /* Type declaration files to be included in compilation. */ 56 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 57 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 58 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 59 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 60 | 61 | /* Source Map Options */ 62 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 64 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 65 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 66 | 67 | /* Experimental Options */ 68 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 69 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 70 | 71 | /* Advanced Options */ 72 | "skipLibCheck": true /* Skip type checking of declaration files. */, 73 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/profile/profile.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import AddEducationDto from './dtos/add_education.dto'; 4 | import AddExperienceDto from './dtos/add_experience.dto'; 5 | import CreateProfileDto from './dtos/create_profile.dto'; 6 | import { IProfile } from './profile.interface'; 7 | import { IUser } from '@modules/users'; 8 | import ProfileService from './profile.service'; 9 | 10 | class ProfileController { 11 | private profileService = new ProfileService(); 12 | 13 | public getCurrentProfile = async ( 14 | req: Request, 15 | res: Response, 16 | next: NextFunction 17 | ) => { 18 | try { 19 | const userId = req.user.id; 20 | const resultObj: Partial = await this.profileService.getCurrentProfile( 21 | userId 22 | ); 23 | res.status(200).json(resultObj); 24 | } catch (error) { 25 | next(error); 26 | } 27 | }; 28 | 29 | public getByUserId = async ( 30 | req: Request, 31 | res: Response, 32 | next: NextFunction 33 | ) => { 34 | const userId: string = req.params.id; 35 | try { 36 | const updateUserData: Partial = await this.profileService.getCurrentProfile( 37 | userId 38 | ); 39 | res.status(200).json({ data: updateUserData, message: 'updated' }); 40 | } catch (error) { 41 | next(error); 42 | } 43 | }; 44 | 45 | public getAllProfiles = async ( 46 | req: Request, 47 | res: Response, 48 | next: NextFunction 49 | ) => { 50 | try { 51 | const resultObj: Partial[] = await this.profileService.getAllProfiles(); 52 | res.status(200).json(resultObj); 53 | } catch (error) { 54 | next(error); 55 | } 56 | }; 57 | 58 | public createProfile = async ( 59 | req: Request, 60 | res: Response, 61 | next: NextFunction 62 | ) => { 63 | const userData: CreateProfileDto = req.body; 64 | const userId = req.user.id; 65 | try { 66 | const createUserData: IProfile = await this.profileService.createProfile( 67 | userId, 68 | userData 69 | ); 70 | res.status(201).json({ data: createUserData }); 71 | } catch (error) { 72 | next(error); 73 | } 74 | }; 75 | 76 | public deleteProfile = async ( 77 | req: Request, 78 | res: Response, 79 | next: NextFunction 80 | ) => { 81 | const userId: string = req.params.id; 82 | 83 | try { 84 | await this.profileService.deleteProfile(userId); 85 | res.status(200); 86 | } catch (error) { 87 | next(error); 88 | } 89 | }; 90 | 91 | public createExperience = async ( 92 | req: Request, 93 | res: Response, 94 | next: NextFunction 95 | ) => { 96 | const data: AddExperienceDto = req.body; 97 | const userId = req.user.id; 98 | try { 99 | const createUserData: IProfile = await this.profileService.addExperience( 100 | userId, 101 | data 102 | ); 103 | res.status(201).json(createUserData); 104 | } catch (error) { 105 | next(error); 106 | } 107 | }; 108 | 109 | public deleteExperience = async ( 110 | req: Request, 111 | res: Response, 112 | next: NextFunction 113 | ) => { 114 | const expId: string = req.params.exp_id; 115 | 116 | try { 117 | const profile = await this.profileService.deleteExperience( 118 | req.user.id, 119 | expId 120 | ); 121 | res.status(200).json(profile); 122 | } catch (error) { 123 | next(error); 124 | } 125 | }; 126 | 127 | public createEducation = async ( 128 | req: Request, 129 | res: Response, 130 | next: NextFunction 131 | ) => { 132 | const data: AddEducationDto = req.body; 133 | const userId = req.user.id; 134 | try { 135 | const createUserData: IProfile = await this.profileService.addEducation( 136 | userId, 137 | data 138 | ); 139 | res.status(201).json(createUserData); 140 | } catch (error) { 141 | next(error); 142 | } 143 | }; 144 | 145 | public deleteEducation = async ( 146 | req: Request, 147 | res: Response, 148 | next: NextFunction 149 | ) => { 150 | const eduId: string = req.params.edu_id; 151 | 152 | try { 153 | const profile = await this.profileService.deleteEducation( 154 | req.user.id, 155 | eduId 156 | ); 157 | res.status(200).json(profile); 158 | } catch (error) { 159 | next(error); 160 | } 161 | }; 162 | 163 | public follow = async (req: Request, res: Response, next: NextFunction) => { 164 | const toUserId: string = req.params.id; 165 | try { 166 | const profile = await this.profileService.follow(req.user.id, toUserId); 167 | res.status(200).json(profile); 168 | } catch (error) { 169 | next(error); 170 | } 171 | }; 172 | 173 | public unFollow = async (req: Request, res: Response, next: NextFunction) => { 174 | const toUserId: string = req.params.id; 175 | try { 176 | const profile = await this.profileService.unFollow(req.user.id, toUserId); 177 | res.status(200).json(profile); 178 | } catch (error) { 179 | next(error); 180 | } 181 | }; 182 | 183 | public addFriend = async ( 184 | req: Request, 185 | res: Response, 186 | next: NextFunction 187 | ) => { 188 | const toUserId: string = req.params.id; 189 | try { 190 | const profile = await this.profileService.addFriend( 191 | req.user.id, 192 | toUserId 193 | ); 194 | res.status(200).json(profile); 195 | } catch (error) { 196 | next(error); 197 | } 198 | }; 199 | 200 | public unFriend = async (req: Request, res: Response, next: NextFunction) => { 201 | const toUserId: string = req.params.id; 202 | try { 203 | const profile = await this.profileService.unFriend(req.user.id, toUserId); 204 | res.status(200).json(profile); 205 | } catch (error) { 206 | next(error); 207 | } 208 | }; 209 | 210 | public acceptFriendRequest = async ( 211 | req: Request, 212 | res: Response, 213 | next: NextFunction 214 | ) => { 215 | const toUserId: string = req.params.id; 216 | try { 217 | const profile = await this.profileService.acceptFriendRequest( 218 | req.user.id, 219 | toUserId 220 | ); 221 | res.status(200).json(profile); 222 | } catch (error) { 223 | next(error); 224 | } 225 | }; 226 | } 227 | export default ProfileController; 228 | -------------------------------------------------------------------------------- /src/modules/posts/posts.service.ts: -------------------------------------------------------------------------------- 1 | import { IComment, ILike, IPost } from './posts.interface'; 2 | 3 | import CreateCommentDto from './dtos/create_comment.dto'; 4 | import CreatePostDto from './dtos/create_post.dto'; 5 | import { HttpException } from '@core/exceptions'; 6 | import { IPagination } from '@core/interfaces'; 7 | import { PostSchema } from '.'; 8 | import { UserSchema } from '@modules/users'; 9 | import usersModel from '@modules/users/users.model'; 10 | 11 | export default class PostService { 12 | public async createPost( 13 | userId: string, 14 | postDto: CreatePostDto 15 | ): Promise { 16 | const user = await UserSchema.findById(userId).select('-password').exec(); 17 | if (!user) throw new HttpException(400, 'User id is not exist'); 18 | 19 | const newPost = new PostSchema({ 20 | text: postDto.text, 21 | name: user.first_name + ' ' + user.last_name, 22 | avatar: user.avatar, 23 | user: userId, 24 | }); 25 | const post = await newPost.save(); 26 | return post; 27 | } 28 | 29 | public async updatePost( 30 | postId: string, 31 | postDto: CreatePostDto 32 | ): Promise { 33 | const updatePostById = await PostSchema.findByIdAndUpdate( 34 | postId, 35 | { 36 | ...postDto, 37 | }, 38 | { new: true } 39 | ).exec(); 40 | 41 | if (!updatePostById) throw new HttpException(400, 'Post is not found'); 42 | 43 | return updatePostById; 44 | } 45 | 46 | public async getAllPosts(): Promise { 47 | const posts = await PostSchema.find().sort({ date: -1 }).exec(); 48 | return posts; 49 | } 50 | 51 | public async getPostById(postId: string): Promise { 52 | const post = await PostSchema.findById(postId).exec(); 53 | if (!post) throw new HttpException(404, 'Post is not found'); 54 | return post; 55 | } 56 | 57 | public async getAllPaging( 58 | keyword: string, 59 | page: number 60 | ): Promise> { 61 | const pageSize: number = Number(process.env.PAGE_SIZE || 10); 62 | 63 | let query = {}; 64 | if (keyword) { 65 | query = { 66 | $or: [{ text: keyword }], 67 | }; 68 | } 69 | 70 | const users = await PostSchema.find(query) 71 | .skip((page - 1) * pageSize) 72 | .limit(pageSize) 73 | .exec(); 74 | 75 | const rowCount = await PostSchema.find(query).countDocuments().exec(); 76 | 77 | return { 78 | total: rowCount, 79 | page: page, 80 | pageSize: pageSize, 81 | items: users, 82 | } as IPagination; 83 | } 84 | 85 | public async deletePost(userId: string, postId: string): Promise { 86 | const post = await PostSchema.findById(postId).exec(); 87 | if (!post) throw new HttpException(400, 'Post not found'); 88 | 89 | if (post.user.toString() !== userId) 90 | throw new HttpException(400, 'User is not authorized'); 91 | 92 | await post.remove(); 93 | 94 | return post; 95 | } 96 | 97 | public async likePost(userId: string, postId: string): Promise { 98 | const post = await PostSchema.findById(postId).exec(); 99 | if (!post) throw new HttpException(400, 'Post not found'); 100 | 101 | if (post.likes.some((like: ILike) => like.user.toString() === userId)) { 102 | throw new HttpException(400, 'Post already liked'); 103 | } 104 | 105 | post.likes.unshift({ user: userId }); 106 | 107 | await post.save(); 108 | return post.likes; 109 | } 110 | 111 | public async unlikePost(userId: string, postId: string): Promise { 112 | const post = await PostSchema.findById(postId).exec(); 113 | if (!post) throw new HttpException(400, 'Post not found'); 114 | 115 | if (!post.likes.some((like: ILike) => like.user.toString() === userId)) { 116 | throw new HttpException(400, 'Post has not yet been liked'); 117 | } 118 | 119 | post.likes = post.likes.filter(({ user }) => user.toString() !== userId); 120 | 121 | await post.save(); 122 | return post.likes; 123 | } 124 | 125 | public async addComment(comment: CreateCommentDto): Promise { 126 | const post = await PostSchema.findById(comment.postId).exec(); 127 | if (!post) throw new HttpException(400, 'Post not found'); 128 | 129 | const user = await UserSchema.findById(comment.userId) 130 | .select('-password') 131 | .exec(); 132 | 133 | if (!user) throw new HttpException(400, 'User not found'); 134 | 135 | const newComment = { 136 | text: comment.text, 137 | name: user.first_name + ' ' + user.last_name, 138 | avatar: user.avatar, 139 | user: comment.userId, 140 | }; 141 | 142 | post.comments.unshift(newComment as IComment); 143 | await post.save(); 144 | return post.comments; 145 | } 146 | 147 | public async removeComment( 148 | commentId: string, 149 | postId: string, 150 | userId: string 151 | ): Promise { 152 | const post = await PostSchema.findById(postId).exec(); 153 | if (!post) throw new HttpException(400, 'Post not found'); 154 | 155 | const comment = post.comments.find((c) => c._id.toString() === commentId); 156 | if (!comment) throw new HttpException(400, 'Comment not found'); 157 | 158 | if (comment.user.toString() !== userId) 159 | throw new HttpException(401, 'User not authorized'); 160 | 161 | post.comments = post.comments.filter( 162 | ({ _id }) => _id.toString() !== commentId 163 | ); 164 | await post.save(); 165 | return post.comments; 166 | } 167 | 168 | public async sharePost(userId: string, postId: string): Promise { 169 | const post = await PostSchema.findById(postId).exec(); 170 | if (!post) throw new HttpException(400, 'Post not found'); 171 | 172 | if ( 173 | post.shares && 174 | post.shares.some((like: ILike) => like.user.toString() === userId) 175 | ) { 176 | throw new HttpException(400, 'Post already liked'); 177 | } 178 | if (!post.shares) post.shares = []; 179 | 180 | post.shares.unshift({ user: userId }); 181 | 182 | await post.save(); 183 | return post.shares; 184 | } 185 | 186 | public async removeShare(userId: string, postId: string): Promise { 187 | const post = await PostSchema.findById(postId).exec(); 188 | if (!post) throw new HttpException(400, 'Post not found'); 189 | 190 | if ( 191 | post.shares && 192 | !post.shares.some((share: ILike) => share.user.toString() === userId) 193 | ) { 194 | throw new HttpException(400, 'Post has not yet been shared'); 195 | } 196 | if (!post.shares) post.shares = []; 197 | post.shares = post.shares.filter(({ user }) => user.toString() !== userId); 198 | 199 | await post.save(); 200 | return post.shares; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/modules/groups/groups.service.ts: -------------------------------------------------------------------------------- 1 | import { IGroup, IManager, IMember } from './groups.interface'; 2 | 3 | import CreateGroupDto from './dtos/create_group.dto'; 4 | import GroupSchema from './groups.model'; 5 | import { HttpException } from '@core/exceptions'; 6 | import IUser from '@modules/users/users.interface'; 7 | import SetManagerDto from './dtos/set_manager.dto'; 8 | import { UserSchema } from '@modules/users'; 9 | 10 | export default class GroupService { 11 | public async createGroup( 12 | userId: string, 13 | groupDto: CreateGroupDto 14 | ): Promise { 15 | const user = await UserSchema.findById(userId).select('-password').exec(); 16 | if (!user) throw new HttpException(400, 'User id is not exist'); 17 | 18 | const existingGroup = await GroupSchema.find({ 19 | $or: [{ name: groupDto.name }, { code: groupDto.code }], 20 | }).exec(); 21 | if (existingGroup.length > 0) 22 | throw new HttpException(400, 'Name or code existed'); 23 | 24 | const newGroup = new GroupSchema({ 25 | ...groupDto, 26 | }); 27 | const post = await newGroup.save(); 28 | return post; 29 | } 30 | 31 | public async getAllGroup(): Promise { 32 | const groups = GroupSchema.find().exec(); 33 | return groups; 34 | } 35 | 36 | public async getAllMembers(groupId: string): Promise { 37 | const group = await GroupSchema.findById(groupId).exec(); 38 | if (!group) throw new HttpException(400, 'Group id is not exist'); 39 | 40 | const userIds = group.members.map((member) => { 41 | return member.user; 42 | }); 43 | 44 | const users = UserSchema.find({ _id: userIds }).select('-password').exec(); 45 | return users; 46 | } 47 | 48 | public async updateGroup( 49 | groupId: string, 50 | groupDto: CreateGroupDto 51 | ): Promise { 52 | const group = await GroupSchema.findById(groupId).exec(); 53 | if (!group) throw new HttpException(400, 'Group id is not exist'); 54 | 55 | const existingGroup = await GroupSchema.find({ 56 | $and: [ 57 | { $or: [{ name: groupDto.name }, { code: groupDto.code }] }, 58 | { _id: { $ne: groupId } }, 59 | ], 60 | }).exec(); 61 | 62 | if (existingGroup.length > 0) 63 | throw new HttpException(400, 'Name or code existed'); 64 | 65 | const groupFields = { ...groupDto }; 66 | 67 | const updatedGroup = await GroupSchema.findOneAndUpdate( 68 | { _id: groupId }, 69 | { $set: groupFields }, 70 | { new: true } 71 | ).exec(); 72 | 73 | if (!updatedGroup) throw new HttpException(400, 'Update is not success'); 74 | 75 | return updatedGroup; 76 | } 77 | 78 | public async deleteGroup(groupId: string): Promise { 79 | const group = await GroupSchema.findById(groupId).exec(); 80 | if (!group) throw new HttpException(400, 'Group id is not exist'); 81 | 82 | const deletedGroup = await GroupSchema.findOneAndDelete({ 83 | _id: groupId, 84 | }).exec(); 85 | if (!deletedGroup) throw new HttpException(400, 'Update is not success'); 86 | 87 | return deletedGroup; 88 | } 89 | 90 | public async joinGroup(userId: string, groupId: string): Promise { 91 | const group = await GroupSchema.findById(groupId).exec(); 92 | if (!group) throw new HttpException(400, 'Group id is not exist'); 93 | 94 | const user = await UserSchema.findById(userId).select('-password').exec(); 95 | if (!user) throw new HttpException(400, 'User id is not exist'); 96 | if ( 97 | group.member_requests && 98 | group.member_requests.some( 99 | (item: IMember) => item.user.toString() === userId 100 | ) 101 | ) { 102 | throw new HttpException( 103 | 400, 104 | 'You has already been requested to join this group' 105 | ); 106 | } 107 | 108 | if ( 109 | group.members && 110 | group.members.some((item: IMember) => item.user.toString() === userId) 111 | ) { 112 | throw new HttpException( 113 | 400, 114 | 'You has already been be member of this group' 115 | ); 116 | } 117 | 118 | group.member_requests.unshift({ 119 | user: userId, 120 | } as IMember); 121 | 122 | await group.save(); 123 | return group; 124 | } 125 | 126 | public async approveJoinRequest( 127 | userId: string, 128 | groupId: string 129 | ): Promise { 130 | const group = await GroupSchema.findById(groupId).exec(); 131 | if (!group) throw new HttpException(400, 'Group id is not exist'); 132 | 133 | const user = await UserSchema.findById(userId).select('-password').exec(); 134 | if (!user) throw new HttpException(400, 'User id is not exist'); 135 | 136 | if ( 137 | group.member_requests && 138 | group.member_requests.some( 139 | (item: IMember) => item.user.toString() !== userId 140 | ) 141 | ) { 142 | throw new HttpException(400, 'There is not any request of this user'); 143 | } 144 | 145 | if ( 146 | group.members && 147 | group.members.some((item: IMember) => item.user.toString() === userId) 148 | ) { 149 | throw new HttpException(400, 'This user has already been in group'); 150 | } 151 | 152 | group.member_requests = group.member_requests.filter( 153 | ({ user }) => user.toString() !== userId 154 | ); 155 | 156 | group.members.unshift({ user: userId } as IMember); 157 | 158 | await group.save(); 159 | return group; 160 | } 161 | 162 | public async addManager( 163 | groupId: string, 164 | request: SetManagerDto 165 | ): Promise { 166 | const group = await GroupSchema.findById(groupId).exec(); 167 | if (!group) throw new HttpException(400, 'Group id is not exist'); 168 | 169 | const user = await UserSchema.findById(request.userId) 170 | .select('-password') 171 | .exec(); 172 | if (!user) throw new HttpException(400, 'User id is not exist'); 173 | if ( 174 | group.managers && 175 | group.managers.some( 176 | (item: IManager) => item.user.toString() === request.userId 177 | ) 178 | ) { 179 | throw new HttpException( 180 | 400, 181 | 'You has already been set manger to this group' 182 | ); 183 | } 184 | 185 | group.managers.unshift({ 186 | user: request.userId, 187 | role: request.role, 188 | } as IManager); 189 | 190 | await group.save(); 191 | return group; 192 | } 193 | 194 | public async removeMember(groupId: string, userId: string): Promise { 195 | const group = await GroupSchema.findById(groupId).exec(); 196 | if (!group) throw new HttpException(400, 'Group id is not exist'); 197 | 198 | const user = await UserSchema.findById(userId).select('-password').exec(); 199 | if (!user) throw new HttpException(400, 'User id is not exist'); 200 | 201 | if ( 202 | group.members && 203 | group.members.findIndex( 204 | (item: IMember) => item.user.toString() === userId 205 | ) == -1 206 | ) { 207 | throw new HttpException(400, 'You has not yet been member of this group'); 208 | } 209 | 210 | if (group.members.length == 1) { 211 | throw new HttpException( 212 | 400, 213 | 'You are last member of this group. Cannot delete' 214 | ); 215 | } 216 | 217 | group.members = group.members.filter( 218 | ({ user }) => user.toString() !== userId 219 | ); 220 | 221 | await group.save(); 222 | return group; 223 | } 224 | 225 | public async removeManager(groupId: string, userId: string): Promise { 226 | const group = await GroupSchema.findById(groupId).exec(); 227 | if (!group) throw new HttpException(400, 'Group id is not exist'); 228 | 229 | const user = await UserSchema.findById(userId).select('-password').exec(); 230 | if (!user) throw new HttpException(400, 'User id is not exist'); 231 | 232 | if ( 233 | group.managers && 234 | group.members.findIndex( 235 | (item: IMember) => item.user.toString() === userId 236 | ) == -1 237 | ) { 238 | throw new HttpException( 239 | 400, 240 | 'You has not yet been manager of this group' 241 | ); 242 | } 243 | 244 | group.managers = group.managers.filter( 245 | ({ user }) => user.toString() !== userId 246 | ); 247 | 248 | await group.save(); 249 | return group; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/modules/profile/profile.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IEducation, 3 | IExperience, 4 | IFollower, 5 | IFriend, 6 | IProfile, 7 | ISocial, 8 | } from './profile.interface'; 9 | import { IUser, UserSchema } from '@modules/users'; 10 | 11 | import AddEducationDto from './dtos/add_education.dto'; 12 | import AddExperienceDto from './dtos/add_experience.dto'; 13 | import CreateProfileDto from './dtos/create_profile.dto'; 14 | import { HttpException } from '@core/exceptions'; 15 | import ProfileSchema from './profile.model'; 16 | import normalize from 'normalize-url'; 17 | 18 | class ProfileService { 19 | public async getCurrentProfile(userId: string): Promise> { 20 | const user = await ProfileSchema.findOne({ 21 | user: userId, 22 | }) 23 | .populate('user', ['name', 'avatar']) 24 | .exec(); 25 | if (!user) { 26 | throw new HttpException(400, 'There is no profile for this user'); 27 | } 28 | return user; 29 | } 30 | public async createProfile( 31 | userId: string, 32 | profileDto: CreateProfileDto 33 | ): Promise { 34 | const { 35 | company, 36 | location, 37 | website, 38 | bio, 39 | skills, 40 | status, 41 | youtube, 42 | twitter, 43 | instagram, 44 | linkedin, 45 | facebook, 46 | } = profileDto; 47 | 48 | const profileFields: Partial = { 49 | user: userId, 50 | company, 51 | location, 52 | website: 53 | website && website != '' 54 | ? normalize(website.toString(), { forceHttps: true }) 55 | : '', 56 | bio, 57 | skills: Array.isArray(skills) 58 | ? skills 59 | : skills.split(',').map((skill: string) => ' ' + skill.trim()), 60 | status, 61 | }; 62 | 63 | const socialFields: ISocial = { 64 | youtube, 65 | twitter, 66 | instagram, 67 | linkedin, 68 | facebook, 69 | }; 70 | 71 | for (const [key, value] of Object.entries(socialFields)) { 72 | if (value && value.length > 0) { 73 | socialFields[key] = normalize(value, { forceHttps: true }); 74 | } 75 | } 76 | profileFields.social = socialFields; 77 | 78 | const profile = await ProfileSchema.findOneAndUpdate( 79 | { user: userId }, 80 | { $set: profileFields }, 81 | { new: true, upsert: true, setDefaultsOnInsert: true } 82 | ).exec(); 83 | 84 | return profile; 85 | } 86 | 87 | public async deleteProfile(userId: string) { 88 | // Remove profile 89 | await ProfileSchema.findOneAndRemove({ user: userId }).exec(); 90 | // Remove user 91 | await UserSchema.findOneAndRemove({ _id: userId }).exec(); 92 | } 93 | 94 | public async getAllProfiles(): Promise[]> { 95 | const profiles = await ProfileSchema.find() 96 | .populate('user', ['name', 'avatar']) 97 | .exec(); 98 | return profiles; 99 | } 100 | 101 | public addExperience = async ( 102 | userId: string, 103 | experience: AddExperienceDto 104 | ) => { 105 | const newExp = { 106 | ...experience, 107 | }; 108 | 109 | const profile = await ProfileSchema.findOne({ user: userId }).exec(); 110 | if (!profile) { 111 | throw new HttpException(400, 'There is not profile for this user'); 112 | } 113 | 114 | profile.experience.unshift(newExp as IExperience); 115 | await profile.save(); 116 | 117 | return profile; 118 | }; 119 | 120 | public deleteExperience = async (userId: string, experienceId: string) => { 121 | const profile = await ProfileSchema.findOne({ user: userId }).exec(); 122 | 123 | if (!profile) { 124 | throw new HttpException(400, 'There is not profile for this user'); 125 | } 126 | 127 | profile.experience = profile.experience.filter( 128 | (exp) => exp._id.toString() !== experienceId 129 | ); 130 | await profile.save(); 131 | return profile; 132 | }; 133 | 134 | public addEducation = async (userId: string, education: AddEducationDto) => { 135 | const newEdu = { 136 | ...education, 137 | }; 138 | 139 | const profile = await ProfileSchema.findOne({ user: userId }).exec(); 140 | if (!profile) { 141 | throw new HttpException(400, 'There is not profile for this user'); 142 | } 143 | 144 | profile.education.unshift(newEdu as IEducation); 145 | await profile.save(); 146 | 147 | return profile; 148 | }; 149 | 150 | public deleteEducation = async (userId: string, educationId: string) => { 151 | const profile = await ProfileSchema.findOne({ user: userId }).exec(); 152 | 153 | if (!profile) { 154 | throw new HttpException(400, 'There is not profile for this user'); 155 | } 156 | 157 | profile.education = profile.education.filter( 158 | (edu) => edu._id.toString() !== educationId 159 | ); 160 | await profile.save(); 161 | return profile; 162 | }; 163 | 164 | public follow = async (fromUserId: string, toUserId: string) => { 165 | const fromProfile = await ProfileSchema.findOne({ 166 | user: fromUserId, 167 | }).exec(); 168 | 169 | if (!fromProfile) { 170 | throw new HttpException(400, 'There is not profile for your user'); 171 | } 172 | 173 | const toProfile = await ProfileSchema.findOne({ user: toUserId }).exec(); 174 | if (!toProfile) { 175 | throw new HttpException(400, 'There is not profile for target user'); 176 | } 177 | 178 | if ( 179 | toProfile.followers && 180 | toProfile.followers.some( 181 | (follower: IFollower) => follower.user.toString() === fromUserId 182 | ) 183 | ) { 184 | throw new HttpException( 185 | 400, 186 | 'Target user has already been followed by from user' 187 | ); 188 | } 189 | 190 | if ( 191 | fromProfile.followings && 192 | fromProfile.followings.some( 193 | (follower: IFollower) => follower.user.toString() === toUserId 194 | ) 195 | ) { 196 | throw new HttpException(400, 'You has been already followed this user'); 197 | } 198 | if (!fromProfile.followings) fromProfile.followings = []; 199 | fromProfile.followings.unshift({ user: toUserId }); 200 | if (!toProfile.followers) toProfile.followers = []; 201 | toProfile.followers.unshift({ user: fromUserId }); 202 | 203 | await fromProfile.save(); 204 | await toProfile.save(); 205 | 206 | return fromProfile; 207 | }; 208 | 209 | public unFollow = async (fromUserId: string, toUserId: string) => { 210 | const fromProfile = await ProfileSchema.findOne({ 211 | user: fromUserId, 212 | }).exec(); 213 | 214 | if (!fromProfile) { 215 | throw new HttpException(400, 'There is not profile for your user'); 216 | } 217 | 218 | const toProfile = await ProfileSchema.findOne({ user: toUserId }).exec(); 219 | if (!toProfile) { 220 | throw new HttpException(400, 'There is not profile for target user'); 221 | } 222 | 223 | if ( 224 | toProfile.followers && 225 | toProfile.followers.findIndex( 226 | (follower: IFollower) => follower.user.toString() === fromUserId 227 | ) !== -1 228 | ) { 229 | throw new HttpException(400, 'You has not being followed this user'); 230 | } 231 | 232 | if ( 233 | fromProfile.followings && 234 | fromProfile.followings.some( 235 | (follower: IFollower) => follower.user.toString() !== toUserId 236 | ) 237 | ) { 238 | throw new HttpException(400, 'You has not been yet followed this user'); 239 | } 240 | if (!fromProfile.followings) fromProfile.followings = []; 241 | fromProfile.followings = fromProfile.followings.filter( 242 | ({ user }) => user.toString() !== toUserId 243 | ); 244 | if (!toProfile.followers) toProfile.followers = []; 245 | toProfile.followers = toProfile.followers.filter( 246 | ({ user }) => user.toString() !== fromUserId 247 | ); 248 | 249 | await fromProfile.save(); 250 | await toProfile.save(); 251 | 252 | return fromProfile; 253 | }; 254 | 255 | public addFriend = async (fromUserId: string, toUserId: string) => { 256 | const fromProfile = await ProfileSchema.findOne({ 257 | user: fromUserId, 258 | }).exec(); 259 | 260 | if (!fromProfile) { 261 | throw new HttpException(400, 'There is not profile for your user'); 262 | } 263 | 264 | const toProfile = await ProfileSchema.findOne({ user: toUserId }).exec(); 265 | if (!toProfile) { 266 | throw new HttpException(400, 'There is not profile for target user'); 267 | } 268 | 269 | if ( 270 | toProfile.friends && 271 | toProfile.friends.some( 272 | (follower: IFollower) => follower.user.toString() === fromUserId 273 | ) 274 | ) { 275 | throw new HttpException( 276 | 400, 277 | 'Target user has already been be friend by from user' 278 | ); 279 | } 280 | 281 | if ( 282 | fromProfile.friend_requests && 283 | fromProfile.friend_requests.some( 284 | (follower: IFollower) => follower.user.toString() === toUserId 285 | ) 286 | ) { 287 | throw new HttpException( 288 | 400, 289 | 'You has been already send a friend request to this user' 290 | ); 291 | } 292 | 293 | if (!toProfile.friend_requests) toProfile.friend_requests = []; 294 | toProfile.friend_requests.unshift({ user: fromUserId } as IFriend); 295 | 296 | await toProfile.save(); 297 | 298 | return toProfile; 299 | }; 300 | 301 | public unFriend = async (fromUserId: string, toUserId: string) => { 302 | const fromProfile = await ProfileSchema.findOne({ 303 | user: fromUserId, 304 | }).exec(); 305 | 306 | if (!fromProfile) { 307 | throw new HttpException(400, 'There is not profile for your user'); 308 | } 309 | 310 | const toProfile = await ProfileSchema.findOne({ user: toUserId }).exec(); 311 | if (!toProfile) { 312 | throw new HttpException(400, 'There is not profile for target user'); 313 | } 314 | 315 | if ( 316 | toProfile.friends && 317 | toProfile.friends.findIndex( 318 | (follower: IFollower) => follower.user.toString() === fromUserId 319 | ) === -1 320 | ) { 321 | throw new HttpException(400, 'You has not yet be friend this user'); 322 | } 323 | 324 | if (!fromProfile.friends) fromProfile.friends = []; 325 | fromProfile.friends = fromProfile.friends.filter( 326 | ({ user }) => user.toString() !== toUserId 327 | ); 328 | 329 | if (!toProfile.friends) toProfile.friends = []; 330 | toProfile.friends = toProfile.friends.filter( 331 | ({ user }) => user.toString() !== fromUserId 332 | ); 333 | 334 | await fromProfile.save(); 335 | await toProfile.save(); 336 | 337 | return fromProfile; 338 | }; 339 | 340 | public acceptFriendRequest = async ( 341 | currentUserId: string, 342 | requestUserId: string 343 | ) => { 344 | const currentProfile = await ProfileSchema.findOne({ 345 | user: currentUserId, 346 | }).exec(); 347 | 348 | if (!currentProfile) { 349 | throw new HttpException(400, 'There is not profile for your user'); 350 | } 351 | 352 | const requestProfile = await ProfileSchema.findOne({ 353 | user: requestUserId, 354 | }).exec(); 355 | if (!requestProfile) { 356 | throw new HttpException(400, 'There is not profile for target user'); 357 | } 358 | 359 | if ( 360 | requestProfile.friends && 361 | requestProfile.friends.some( 362 | (follower: IFollower) => follower.user.toString() === currentUserId 363 | ) 364 | ) { 365 | throw new HttpException(400, 'You has already been friend'); 366 | } 367 | 368 | if ( 369 | currentProfile.friends && 370 | currentProfile.friends.some( 371 | (follower: IFollower) => follower.user.toString() === requestUserId 372 | ) 373 | ) { 374 | throw new HttpException(400, 'You has already been friend'); 375 | } 376 | 377 | if ( 378 | currentProfile.friend_requests && 379 | currentProfile.friend_requests.some( 380 | (follower: IFollower) => follower.user.toString() !== requestUserId 381 | ) 382 | ) { 383 | throw new HttpException( 384 | 400, 385 | 'You has not any friend request related to this user' 386 | ); 387 | } 388 | 389 | if (!currentProfile.friend_requests) currentProfile.friend_requests = []; 390 | currentProfile.friend_requests = currentProfile.friend_requests.filter( 391 | ({ user }) => user.toString() !== requestUserId 392 | ); 393 | 394 | if (!currentProfile.friends) currentProfile.friends = []; 395 | currentProfile.friends.unshift({ user: requestUserId } as IFriend); 396 | 397 | if (!requestProfile.friends) requestProfile.friends = []; 398 | requestProfile.friends.unshift({ user: currentUserId } as IFriend); 399 | 400 | await currentProfile.save(); 401 | await requestProfile.save(); 402 | return currentProfile; 403 | }; 404 | } 405 | export default ProfileService; 406 | -------------------------------------------------------------------------------- /postman/TEDU Social.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "3fd202b2-2c54-453a-8600-937b12979760", 4 | "name": "TEDU Social", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "1. API check", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://localhost:5000", 15 | "protocol": "http", 16 | "host": [ 17 | "localhost" 18 | ], 19 | "port": "5000" 20 | } 21 | }, 22 | "response": [] 23 | }, 24 | { 25 | "name": "2. Register", 26 | "request": { 27 | "method": "POST", 28 | "header": [ 29 | { 30 | "key": "Content-Type", 31 | "value": "application/json", 32 | "type": "text" 33 | } 34 | ], 35 | "body": { 36 | "mode": "raw", 37 | "raw": "{\n \"first_name\": \"Toan\",\n \"last_name\": \"10\",\n \"email\": \"ngoctoan10@gmail.com\",\n \"password\": \"123654$\"\n}", 38 | "options": { 39 | "raw": { 40 | "language": "json" 41 | } 42 | } 43 | }, 44 | "url": { 45 | "raw": "http://localhost:5000/api/v1/users", 46 | "protocol": "http", 47 | "host": [ 48 | "localhost" 49 | ], 50 | "port": "5000", 51 | "path": [ 52 | "api", 53 | "v1", 54 | "users" 55 | ] 56 | } 57 | }, 58 | "response": [] 59 | }, 60 | { 61 | "name": "39. Send message", 62 | "request": { 63 | "method": "POST", 64 | "header": [ 65 | { 66 | "key": "Content-Type", 67 | "type": "text", 68 | "value": "application/json" 69 | }, 70 | { 71 | "key": "x-auth-token", 72 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzQ0MTM2OCwiZXhwIjoxNjA3NDQ0OTY4fQ.PI_SGVVI5OY6i5alA1gjUZyCwr0gQsC0xnpdQxB8m1I", 73 | "type": "text" 74 | } 75 | ], 76 | "body": { 77 | "mode": "raw", 78 | "raw": "{\n \"to\": \"5f8c044654cb102bed03688d\",\n \"text\":\"test 4\"\n\n}", 79 | "options": { 80 | "raw": { 81 | "language": "json" 82 | } 83 | } 84 | }, 85 | "url": { 86 | "raw": "http://localhost:5000/api/v1/conversations", 87 | "protocol": "http", 88 | "host": [ 89 | "localhost" 90 | ], 91 | "port": "5000", 92 | "path": [ 93 | "api", 94 | "v1", 95 | "conversations" 96 | ] 97 | } 98 | }, 99 | "response": [] 100 | }, 101 | { 102 | "name": "40. Get my conversations", 103 | "protocolProfileBehavior": { 104 | "disableBodyPruning": true 105 | }, 106 | "request": { 107 | "method": "GET", 108 | "header": [ 109 | { 110 | "key": "Content-Type", 111 | "type": "text", 112 | "value": "application/json" 113 | }, 114 | { 115 | "key": "x-auth-token", 116 | "type": "text", 117 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzQ0MjY4MywiZXhwIjoxNjA3NDQ2MjgzfQ.ylPp5h2ZV7G24cOuNXouq7VC_2sVC3ZqRG9usBvrnJM" 118 | } 119 | ], 120 | "body": { 121 | "mode": "raw", 122 | "raw": "{\n \"to\": \"5f8c042454cb102bed036885\",\n \"text\":\"test 3\"\n\n}", 123 | "options": { 124 | "raw": { 125 | "language": "json" 126 | } 127 | } 128 | }, 129 | "url": { 130 | "raw": "http://localhost:5000/api/v1/conversations", 131 | "protocol": "http", 132 | "host": [ 133 | "localhost" 134 | ], 135 | "port": "5000", 136 | "path": [ 137 | "api", 138 | "v1", 139 | "conversations" 140 | ] 141 | } 142 | }, 143 | "response": [] 144 | }, 145 | { 146 | "name": "9. Create profile", 147 | "request": { 148 | "method": "POST", 149 | "header": [ 150 | { 151 | "key": "Content-Type", 152 | "type": "text", 153 | "value": "application/json" 154 | }, 155 | { 156 | "key": "x-auth-token", 157 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzE0ODE0NCwiZXhwIjoxNjA3MTQ4MjA0fQ.g2j1EjjBFb90_hkaJFA5JsRqvZaMfzFibEnURSac4zs", 158 | "type": "text" 159 | } 160 | ], 161 | "body": { 162 | "mode": "raw", 163 | "raw": "{\n \"company\": \"TEDU\",\n \"location\": \"ha noi\",\n \"website\": \"tedu.com.vn\",\n \"bio\": \"sample\",\n \"skills\": \"sql, c#, asp.net\",\n \"status\": \"single\",\n \"youtube\": \"youtube.com/abc\",\n \"twitter\": \"twitter.com/abc\",\n \"instagram\": \"instagram.com/abc\",\n \"linkedin\": \"linkedin.com/abc\",\n \"facebook\": \"facebook.com/abc\"\n}", 164 | "options": { 165 | "raw": { 166 | "language": "json" 167 | } 168 | } 169 | }, 170 | "url": { 171 | "raw": "http://localhost:5000/api/v1/profile", 172 | "protocol": "http", 173 | "host": [ 174 | "localhost" 175 | ], 176 | "port": "5000", 177 | "path": [ 178 | "api", 179 | "v1", 180 | "profile" 181 | ] 182 | } 183 | }, 184 | "response": [] 185 | }, 186 | { 187 | "name": "32. Create group", 188 | "request": { 189 | "method": "POST", 190 | "header": [ 191 | { 192 | "key": "Content-Type", 193 | "type": "text", 194 | "value": "application/json" 195 | }, 196 | { 197 | "key": "x-auth-token", 198 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzI3MDk1MSwiZXhwIjoxNjA3Mjc0NTUxfQ.vVfYBdATR42wi0SOY9K6PGrZDHwleiF0AqGZllFhXto", 199 | "type": "text" 200 | } 201 | ], 202 | "body": { 203 | "mode": "raw", 204 | "raw": "{\n \"name\": \"Group 1\",\n \"code\": \"group1\",\n \"description\": \"test desc\"\n}", 205 | "options": { 206 | "raw": { 207 | "language": "json" 208 | } 209 | } 210 | }, 211 | "url": { 212 | "raw": "http://localhost:5000/api/v1/groups", 213 | "protocol": "http", 214 | "host": [ 215 | "localhost" 216 | ], 217 | "port": "5000", 218 | "path": [ 219 | "api", 220 | "v1", 221 | "groups" 222 | ] 223 | } 224 | }, 225 | "response": [] 226 | }, 227 | { 228 | "name": "34. Join group", 229 | "request": { 230 | "method": "POST", 231 | "header": [ 232 | { 233 | "key": "Content-Type", 234 | "type": "text", 235 | "value": "application/json" 236 | }, 237 | { 238 | "key": "x-auth-token", 239 | "type": "text", 240 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzM1MzcyMSwiZXhwIjoxNjA3MzU3MzIxfQ.hMJpZVo39mWxbG8QwTp4jJV8s9eQ0NzabS0EBX4x6fU" 241 | } 242 | ], 243 | "body": { 244 | "mode": "raw", 245 | "raw": "", 246 | "options": { 247 | "raw": { 248 | "language": "json" 249 | } 250 | } 251 | }, 252 | "url": { 253 | "raw": "http://localhost:5000/api/v1/groups/members/5fcd030ad2b8c907f69e51b1", 254 | "protocol": "http", 255 | "host": [ 256 | "localhost" 257 | ], 258 | "port": "5000", 259 | "path": [ 260 | "api", 261 | "v1", 262 | "groups", 263 | "members", 264 | "5fcd030ad2b8c907f69e51b1" 265 | ] 266 | } 267 | }, 268 | "response": [] 269 | }, 270 | { 271 | "name": "36. Add manager group", 272 | "request": { 273 | "method": "POST", 274 | "header": [ 275 | { 276 | "key": "Content-Type", 277 | "type": "text", 278 | "value": "application/json" 279 | }, 280 | { 281 | "key": "x-auth-token", 282 | "type": "text", 283 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzMwMjk4MCwiZXhwIjoxNjA3MzA2NTgwfQ.QzcZG0A9qsUITEhK067qZE7Y1VtdE5m4KuuxtK5nowY" 284 | } 285 | ], 286 | "body": { 287 | "mode": "raw", 288 | "raw": "{\n \"userId\":\"5f8c041d54cb102bed036884\",\n \"role\":\"admin\"\n}", 289 | "options": { 290 | "raw": { 291 | "language": "json" 292 | } 293 | } 294 | }, 295 | "url": { 296 | "raw": "http://localhost:5000/api/v1/groups/managers/5fcd030ad2b8c907f69e51b1", 297 | "protocol": "http", 298 | "host": [ 299 | "localhost" 300 | ], 301 | "port": "5000", 302 | "path": [ 303 | "api", 304 | "v1", 305 | "groups", 306 | "managers", 307 | "5fcd030ad2b8c907f69e51b1" 308 | ] 309 | } 310 | }, 311 | "response": [] 312 | }, 313 | { 314 | "name": "35. Approve group join request", 315 | "request": { 316 | "method": "PUT", 317 | "header": [ 318 | { 319 | "key": "Content-Type", 320 | "type": "text", 321 | "value": "application/json" 322 | }, 323 | { 324 | "key": "x-auth-token", 325 | "type": "text", 326 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzM1MzQ2NCwiZXhwIjoxNjA3MzU3MDY0fQ.MMrPX1TDs0CvLPOTqcsDFyps7I4VHJCQTJe4rL-Km-w" 327 | } 328 | ], 329 | "body": { 330 | "mode": "raw", 331 | "raw": "", 332 | "options": { 333 | "raw": { 334 | "language": "json" 335 | } 336 | } 337 | }, 338 | "url": { 339 | "raw": "http://localhost:5000/api/v1/groups/members/5f8c042454cb102bed036885/5fcd030ad2b8c907f69e51b1", 340 | "protocol": "http", 341 | "host": [ 342 | "localhost" 343 | ], 344 | "port": "5000", 345 | "path": [ 346 | "api", 347 | "v1", 348 | "groups", 349 | "members", 350 | "5f8c042454cb102bed036885", 351 | "5fcd030ad2b8c907f69e51b1" 352 | ] 353 | } 354 | }, 355 | "response": [] 356 | }, 357 | { 358 | "name": "38. Remove member", 359 | "request": { 360 | "method": "DELETE", 361 | "header": [ 362 | { 363 | "key": "Content-Type", 364 | "type": "text", 365 | "value": "application/json" 366 | }, 367 | { 368 | "key": "x-auth-token", 369 | "type": "text", 370 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzM1NDIxOCwiZXhwIjoxNjA3MzU3ODE4fQ.iBDSD_FMRvqjQZGmD2JLoZeG0XjaGXSLtroToJGDusU" 371 | } 372 | ], 373 | "body": { 374 | "mode": "raw", 375 | "raw": "", 376 | "options": { 377 | "raw": { 378 | "language": "json" 379 | } 380 | } 381 | }, 382 | "url": { 383 | "raw": "http://localhost:5000/api/v1/groups/members/5f8c041d54cb102bed036884/5fcd030ad2b8c907f69e51b1", 384 | "protocol": "http", 385 | "host": [ 386 | "localhost" 387 | ], 388 | "port": "5000", 389 | "path": [ 390 | "api", 391 | "v1", 392 | "groups", 393 | "members", 394 | "5f8c041d54cb102bed036884", 395 | "5fcd030ad2b8c907f69e51b1" 396 | ] 397 | } 398 | }, 399 | "response": [] 400 | }, 401 | { 402 | "name": "37. Remove manager", 403 | "request": { 404 | "method": "DELETE", 405 | "header": [ 406 | { 407 | "key": "Content-Type", 408 | "type": "text", 409 | "value": "application/json" 410 | }, 411 | { 412 | "key": "x-auth-token", 413 | "type": "text", 414 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzMwMzA5MywiZXhwIjoxNjA3MzA2NjkzfQ.IkOjBDu6tTuVL0ObdjNjQJ-_eJqcp1i0_0jFC4wAVRI" 415 | } 416 | ], 417 | "body": { 418 | "mode": "raw", 419 | "raw": "", 420 | "options": { 421 | "raw": { 422 | "language": "json" 423 | } 424 | } 425 | }, 426 | "url": { 427 | "raw": "http://localhost:5000/api/v1/groups/managers/5fcd030ad2b8c907f69e51b1/5f8c041d54cb102bed036884", 428 | "protocol": "http", 429 | "host": [ 430 | "localhost" 431 | ], 432 | "port": "5000", 433 | "path": [ 434 | "api", 435 | "v1", 436 | "groups", 437 | "managers", 438 | "5fcd030ad2b8c907f69e51b1", 439 | "5f8c041d54cb102bed036884" 440 | ] 441 | } 442 | }, 443 | "response": [] 444 | }, 445 | { 446 | "name": "33. Update group", 447 | "request": { 448 | "method": "PUT", 449 | "header": [ 450 | { 451 | "key": "Content-Type", 452 | "type": "text", 453 | "value": "application/json" 454 | }, 455 | { 456 | "key": "x-auth-token", 457 | "type": "text", 458 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzMwMDMzOCwiZXhwIjoxNjA3MzAzOTM4fQ.Ng7EdkyjPP43kw6jvg3F9IigMT_E9Z0_ZgqktnHypWk" 459 | } 460 | ], 461 | "body": { 462 | "mode": "raw", 463 | "raw": "{\n \"name\": \"Group 4\",\n \"code\": \"group4\",\n \"description\": \"test desc 4\"\n}", 464 | "options": { 465 | "raw": { 466 | "language": "json" 467 | } 468 | } 469 | }, 470 | "url": { 471 | "raw": "http://localhost:5000/api/v1/groups/5fcd030ad2b8c907f69e51b1", 472 | "protocol": "http", 473 | "host": [ 474 | "localhost" 475 | ], 476 | "port": "5000", 477 | "path": [ 478 | "api", 479 | "v1", 480 | "groups", 481 | "5fcd030ad2b8c907f69e51b1" 482 | ] 483 | } 484 | }, 485 | "response": [] 486 | }, 487 | { 488 | "name": "17. Create new post", 489 | "request": { 490 | "method": "POST", 491 | "header": [ 492 | { 493 | "key": "Content-Type", 494 | "type": "text", 495 | "value": "application/json" 496 | }, 497 | { 498 | "key": "x-auth-token", 499 | "type": "text", 500 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzAwNzIxMSwiZXhwIjoxNjA3MDA3MjcxfQ.S5ddhEY-gn7fAqYpCiHAH9l-R-ttl5Br6-RC-cnp7FM" 501 | } 502 | ], 503 | "body": { 504 | "mode": "raw", 505 | "raw": "{\n \"text\": \"TEDU 5\"\n}", 506 | "options": { 507 | "raw": { 508 | "language": "json" 509 | } 510 | } 511 | }, 512 | "url": { 513 | "raw": "http://localhost:5000/api/v1/posts", 514 | "protocol": "http", 515 | "host": [ 516 | "localhost" 517 | ], 518 | "port": "5000", 519 | "path": [ 520 | "api", 521 | "v1", 522 | "posts" 523 | ] 524 | } 525 | }, 526 | "response": [] 527 | }, 528 | { 529 | "name": "18. Update post", 530 | "request": { 531 | "method": "PUT", 532 | "header": [ 533 | { 534 | "key": "Content-Type", 535 | "type": "text", 536 | "value": "application/json" 537 | }, 538 | { 539 | "key": "x-auth-token", 540 | "type": "text", 541 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzAwNjIxNiwiZXhwIjoxNjA3MDA2Mjc2fQ.LpPYe1z0OjLI2SwNhrjWbNhS2rWCHm9EfgVluD0PcSs" 542 | } 543 | ], 544 | "body": { 545 | "mode": "raw", 546 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 547 | "options": { 548 | "raw": { 549 | "language": "json" 550 | } 551 | } 552 | }, 553 | "url": { 554 | "raw": "http://localhost:5000/api/v1/posts/5fc8f790d6d9a90af27cb9b0", 555 | "protocol": "http", 556 | "host": [ 557 | "localhost" 558 | ], 559 | "port": "5000", 560 | "path": [ 561 | "api", 562 | "v1", 563 | "posts", 564 | "5fc8f790d6d9a90af27cb9b0" 565 | ] 566 | } 567 | }, 568 | "response": [] 569 | }, 570 | { 571 | "name": "22. Like post", 572 | "request": { 573 | "method": "POST", 574 | "header": [ 575 | { 576 | "key": "Content-Type", 577 | "type": "text", 578 | "value": "application/json" 579 | }, 580 | { 581 | "key": "x-auth-token", 582 | "type": "text", 583 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzA0MTk5NywiZXhwIjoxNjA3MDQyMDU3fQ.o50zmFc2ldimDI3NqFyLpuOt9ejNK7fh2puacOA0tFY" 584 | } 585 | ], 586 | "body": { 587 | "mode": "raw", 588 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 589 | "options": { 590 | "raw": { 591 | "language": "json" 592 | } 593 | } 594 | }, 595 | "url": { 596 | "raw": "http://localhost:5000/api/v1/posts/like/5fc8fc0d305a6a0dea2ba7ac", 597 | "protocol": "http", 598 | "host": [ 599 | "localhost" 600 | ], 601 | "port": "5000", 602 | "path": [ 603 | "api", 604 | "v1", 605 | "posts", 606 | "like", 607 | "5fc8fc0d305a6a0dea2ba7ac" 608 | ] 609 | } 610 | }, 611 | "response": [] 612 | }, 613 | { 614 | "name": "28. Follow profile", 615 | "request": { 616 | "method": "POST", 617 | "header": [ 618 | { 619 | "key": "Content-Type", 620 | "type": "text", 621 | "value": "application/json" 622 | }, 623 | { 624 | "key": "x-auth-token", 625 | "type": "text", 626 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzE0OTMzMSwiZXhwIjoxNjA3MTQ5MzkxfQ.UofC-aUNQGlRSBOF0P_mFUzp5oYNzoLn5S8yiznb-l4" 627 | } 628 | ], 629 | "body": { 630 | "mode": "raw", 631 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 632 | "options": { 633 | "raw": { 634 | "language": "json" 635 | } 636 | } 637 | }, 638 | "url": { 639 | "raw": "http://localhost:5000/api/v1/profile/following/5f8c041d54cb102bed036884", 640 | "protocol": "http", 641 | "host": [ 642 | "localhost" 643 | ], 644 | "port": "5000", 645 | "path": [ 646 | "api", 647 | "v1", 648 | "profile", 649 | "following", 650 | "5f8c041d54cb102bed036884" 651 | ] 652 | } 653 | }, 654 | "response": [] 655 | }, 656 | { 657 | "name": "30. Add friend", 658 | "request": { 659 | "method": "POST", 660 | "header": [ 661 | { 662 | "key": "Content-Type", 663 | "type": "text", 664 | "value": "application/json" 665 | }, 666 | { 667 | "key": "x-auth-token", 668 | "type": "text", 669 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzE1MDgwNSwiZXhwIjoxNjA3MTU0NDA1fQ.3AxybE60KKOzirvUJ-d58nWtyUu_75a9ImKKx4ZAv9Y" 670 | } 671 | ], 672 | "body": { 673 | "mode": "raw", 674 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 675 | "options": { 676 | "raw": { 677 | "language": "json" 678 | } 679 | } 680 | }, 681 | "url": { 682 | "raw": "http://localhost:5000/api/v1/profile/friends/5f8c041d54cb102bed036884", 683 | "protocol": "http", 684 | "host": [ 685 | "localhost" 686 | ], 687 | "port": "5000", 688 | "path": [ 689 | "api", 690 | "v1", 691 | "profile", 692 | "friends", 693 | "5f8c041d54cb102bed036884" 694 | ] 695 | } 696 | }, 697 | "response": [] 698 | }, 699 | { 700 | "name": "31. Accept friend request", 701 | "request": { 702 | "method": "PUT", 703 | "header": [ 704 | { 705 | "key": "Content-Type", 706 | "type": "text", 707 | "value": "application/json" 708 | }, 709 | { 710 | "key": "x-auth-token", 711 | "type": "text", 712 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzE1MTU0MSwiZXhwIjoxNjA3MTU1MTQxfQ.q21LJgeq3m9JEWP3vlNSugZ1djC4RMIVfzCsI725k90" 713 | } 714 | ], 715 | "body": { 716 | "mode": "raw", 717 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 718 | "options": { 719 | "raw": { 720 | "language": "json" 721 | } 722 | } 723 | }, 724 | "url": { 725 | "raw": "http://localhost:5000/api/v1/profile/friends/5f8c042454cb102bed036885", 726 | "protocol": "http", 727 | "host": [ 728 | "localhost" 729 | ], 730 | "port": "5000", 731 | "path": [ 732 | "api", 733 | "v1", 734 | "profile", 735 | "friends", 736 | "5f8c042454cb102bed036885" 737 | ] 738 | } 739 | }, 740 | "response": [] 741 | }, 742 | { 743 | "name": "29. Unfollow profile", 744 | "request": { 745 | "method": "DELETE", 746 | "header": [ 747 | { 748 | "key": "Content-Type", 749 | "type": "text", 750 | "value": "application/json" 751 | }, 752 | { 753 | "key": "x-auth-token", 754 | "type": "text", 755 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzE0OTM5OSwiZXhwIjoxNjA3MTQ5NDU5fQ.cSq-FrBCYTOyxWxlffsn4RrhJvzPabNEuJlsDP--so4" 756 | } 757 | ], 758 | "body": { 759 | "mode": "raw", 760 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 761 | "options": { 762 | "raw": { 763 | "language": "json" 764 | } 765 | } 766 | }, 767 | "url": { 768 | "raw": "http://localhost:5000/api/v1/profile/following/5f8c041d54cb102bed036884", 769 | "protocol": "http", 770 | "host": [ 771 | "localhost" 772 | ], 773 | "port": "5000", 774 | "path": [ 775 | "api", 776 | "v1", 777 | "profile", 778 | "following", 779 | "5f8c041d54cb102bed036884" 780 | ] 781 | } 782 | }, 783 | "response": [] 784 | }, 785 | { 786 | "name": "26. Share a post", 787 | "request": { 788 | "method": "POST", 789 | "header": [ 790 | { 791 | "key": "Content-Type", 792 | "type": "text", 793 | "value": "application/json" 794 | }, 795 | { 796 | "key": "x-auth-token", 797 | "type": "text", 798 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzA0NDIyNCwiZXhwIjoxNjA3MDQ0Mjg0fQ.4qtMX7qGD31bumxG5DN9nxxgxm84tulL8_4YAMqO7XI" 799 | } 800 | ], 801 | "body": { 802 | "mode": "raw", 803 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 804 | "options": { 805 | "raw": { 806 | "language": "json" 807 | } 808 | } 809 | }, 810 | "url": { 811 | "raw": "http://localhost:5000/api/v1/posts/shares/5fc8fc0d305a6a0dea2ba7ac", 812 | "protocol": "http", 813 | "host": [ 814 | "localhost" 815 | ], 816 | "port": "5000", 817 | "path": [ 818 | "api", 819 | "v1", 820 | "posts", 821 | "shares", 822 | "5fc8fc0d305a6a0dea2ba7ac" 823 | ] 824 | } 825 | }, 826 | "response": [] 827 | }, 828 | { 829 | "name": "27. Remove share a post", 830 | "request": { 831 | "method": "DELETE", 832 | "header": [ 833 | { 834 | "key": "Content-Type", 835 | "type": "text", 836 | "value": "application/json" 837 | }, 838 | { 839 | "key": "x-auth-token", 840 | "type": "text", 841 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDI0NTRjYjEwMmJlZDAzNjg4NSIsImlhdCI6MTYwNzA0NDI1NSwiZXhwIjoxNjA3MDQ0MzE1fQ.RsVvlIflnlGUt7JvsQ2lhA-jy8Wl0u16QxD2utwAHsU" 842 | } 843 | ], 844 | "body": { 845 | "mode": "raw", 846 | "raw": "{\n \"text\": \"TEDU 12\"\n}", 847 | "options": { 848 | "raw": { 849 | "language": "json" 850 | } 851 | } 852 | }, 853 | "url": { 854 | "raw": "http://localhost:5000/api/v1/posts/shares/5fc8fc0d305a6a0dea2ba7ac", 855 | "protocol": "http", 856 | "host": [ 857 | "localhost" 858 | ], 859 | "port": "5000", 860 | "path": [ 861 | "api", 862 | "v1", 863 | "posts", 864 | "shares", 865 | "5fc8fc0d305a6a0dea2ba7ac" 866 | ] 867 | } 868 | }, 869 | "response": [] 870 | }, 871 | { 872 | "name": "24. Add comment", 873 | "request": { 874 | "method": "POST", 875 | "header": [ 876 | { 877 | "key": "Content-Type", 878 | "type": "text", 879 | "value": "application/json" 880 | }, 881 | { 882 | "key": "x-auth-token", 883 | "type": "text", 884 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzA0MzE2NywiZXhwIjoxNjA3MDQzMjI3fQ.JDRxR-LdWNN063b98lXhVfrnkJYxVwpignu0KpKFSqg" 885 | } 886 | ], 887 | "body": { 888 | "mode": "raw", 889 | "raw": "{\n \"text\": \"test comment 3\"\n}", 890 | "options": { 891 | "raw": { 892 | "language": "json" 893 | } 894 | } 895 | }, 896 | "url": { 897 | "raw": "http://localhost:5000/api/v1/posts/comments/5fc8fc0d305a6a0dea2ba7ac", 898 | "protocol": "http", 899 | "host": [ 900 | "localhost" 901 | ], 902 | "port": "5000", 903 | "path": [ 904 | "api", 905 | "v1", 906 | "posts", 907 | "comments", 908 | "5fc8fc0d305a6a0dea2ba7ac" 909 | ] 910 | } 911 | }, 912 | "response": [] 913 | }, 914 | { 915 | "name": "25. Remove comment", 916 | "request": { 917 | "method": "DELETE", 918 | "header": [ 919 | { 920 | "key": "Content-Type", 921 | "type": "text", 922 | "value": "application/json" 923 | }, 924 | { 925 | "key": "x-auth-token", 926 | "type": "text", 927 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzA0MzQ4NSwiZXhwIjoxNjA3MDQzNTQ1fQ.MFn5pbKXOtUdNE4zmGJIYS_HlQ86YwxlHSclTSbG5Jo" 928 | } 929 | ], 930 | "body": { 931 | "mode": "raw", 932 | "raw": "", 933 | "options": { 934 | "raw": { 935 | "language": "json" 936 | } 937 | } 938 | }, 939 | "url": { 940 | "raw": "http://localhost:5000/api/v1/posts/comments/5fc8fc0d305a6a0dea2ba7ac/5fc98868c60e0b09013da2e0", 941 | "protocol": "http", 942 | "host": [ 943 | "localhost" 944 | ], 945 | "port": "5000", 946 | "path": [ 947 | "api", 948 | "v1", 949 | "posts", 950 | "comments", 951 | "5fc8fc0d305a6a0dea2ba7ac", 952 | "5fc98868c60e0b09013da2e0" 953 | ] 954 | } 955 | }, 956 | "response": [] 957 | }, 958 | { 959 | "name": "23. unlike post", 960 | "request": { 961 | "method": "DELETE", 962 | "header": [ 963 | { 964 | "key": "Content-Type", 965 | "type": "text", 966 | "value": "application/json" 967 | }, 968 | { 969 | "key": "x-auth-token", 970 | "type": "text", 971 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzA0MjA5NSwiZXhwIjoxNjA3MDQyMTU1fQ.ZK-cTYjB5b9eU-K8NNTiY6NEaWVQg-eU-HkhWBhdGo0" 972 | } 973 | ], 974 | "body": { 975 | "mode": "raw", 976 | "raw": "", 977 | "options": { 978 | "raw": { 979 | "language": "json" 980 | } 981 | } 982 | }, 983 | "url": { 984 | "raw": "http://localhost:5000/api/v1/posts/unlike/5fc8fc0d305a6a0dea2ba7ac", 985 | "protocol": "http", 986 | "host": [ 987 | "localhost" 988 | ], 989 | "port": "5000", 990 | "path": [ 991 | "api", 992 | "v1", 993 | "posts", 994 | "unlike", 995 | "5fc8fc0d305a6a0dea2ba7ac" 996 | ] 997 | } 998 | }, 999 | "response": [] 1000 | }, 1001 | { 1002 | "name": "6. Update user", 1003 | "request": { 1004 | "method": "PUT", 1005 | "header": [ 1006 | { 1007 | "key": "x-auth-token", 1008 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwODU2MCwiZXhwIjoxNjAzMDA4NjIwfQ.cnlkm6QcKlbd7TekxLTY3lGJbCFEyyVqHZYdTZ8c318", 1009 | "type": "text" 1010 | } 1011 | ], 1012 | "body": { 1013 | "mode": "raw", 1014 | "raw": "{\n \"first_name\": \"Toan test\",\n \"last_name\": \"Bach test\",\n \"email\": \"ngoctoan89@gmail.com\",\n \"password\": \"123654$\"\n}", 1015 | "options": { 1016 | "raw": { 1017 | "language": "json" 1018 | } 1019 | } 1020 | }, 1021 | "url": { 1022 | "raw": "http://localhost:5000/api/v1/users/5f8c042454cb102bed036885", 1023 | "protocol": "http", 1024 | "host": [ 1025 | "localhost" 1026 | ], 1027 | "port": "5000", 1028 | "path": [ 1029 | "api", 1030 | "v1", 1031 | "users", 1032 | "5f8c042454cb102bed036885" 1033 | ] 1034 | } 1035 | }, 1036 | "response": [] 1037 | }, 1038 | { 1039 | "name": "13. Add experience", 1040 | "request": { 1041 | "method": "PUT", 1042 | "header": [ 1043 | { 1044 | "key": "x-auth-token", 1045 | "type": "text", 1046 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjg2OTg1MSwiZXhwIjoxNjA2ODY5OTExfQ.ph4XkPbjPrJZzPE3O9WRAk9jsxBb1On3lTo5av1itEE" 1047 | } 1048 | ], 1049 | "body": { 1050 | "mode": "raw", 1051 | "raw": "{\n \"title\": \"Senior .NET Developer\",\n \"company\": \"TEDU\",\n \"location\": \"Ha Noi\",\n \"from\": \"2020/02/01\",\n \"to\": \"2020/12/01\",\n \"current\": true,\n \"description\": \"Build a new CRM with ASP.NET Core\"\n}", 1052 | "options": { 1053 | "raw": { 1054 | "language": "json" 1055 | } 1056 | } 1057 | }, 1058 | "url": { 1059 | "raw": "http://localhost:5000/api/v1/profile/experience", 1060 | "protocol": "http", 1061 | "host": [ 1062 | "localhost" 1063 | ], 1064 | "port": "5000", 1065 | "path": [ 1066 | "api", 1067 | "v1", 1068 | "profile", 1069 | "experience" 1070 | ] 1071 | } 1072 | }, 1073 | "response": [] 1074 | }, 1075 | { 1076 | "name": "15. Add education", 1077 | "request": { 1078 | "method": "PUT", 1079 | "header": [ 1080 | { 1081 | "key": "x-auth-token", 1082 | "type": "text", 1083 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjg3MDcxMSwiZXhwIjoxNjA2ODcwNzcxfQ.VUj_8_5_sbtuCbaoAj7oDnUaxRK20HEaxHtE-06KQnw" 1084 | } 1085 | ], 1086 | "body": { 1087 | "mode": "raw", 1088 | "raw": "{\n \"school\": \"Standford\",\n \"degree\": \"PhD\",\n \"fieldofstudy\": \"Data Sience\",\n \"from\": \"2021/02/01\",\n \"to\": \"2023/12/01\",\n \"current\": true,\n \"description\": \"Learn Big Data\"\n}", 1089 | "options": { 1090 | "raw": { 1091 | "language": "json" 1092 | } 1093 | } 1094 | }, 1095 | "url": { 1096 | "raw": "http://localhost:5000/api/v1/profile/education", 1097 | "protocol": "http", 1098 | "host": [ 1099 | "localhost" 1100 | ], 1101 | "port": "5000", 1102 | "path": [ 1103 | "api", 1104 | "v1", 1105 | "profile", 1106 | "education" 1107 | ] 1108 | } 1109 | }, 1110 | "response": [] 1111 | }, 1112 | { 1113 | "name": "14. Delete expererience", 1114 | "request": { 1115 | "method": "DELETE", 1116 | "header": [ 1117 | { 1118 | "key": "x-auth-token", 1119 | "type": "text", 1120 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjg2OTk3OSwiZXhwIjoxNjA2ODcwMDM5fQ.jDo0XTi2jO5m5JUQvfPnRx-4UVQoViotERtYFyceZSE" 1121 | } 1122 | ], 1123 | "body": { 1124 | "mode": "raw", 1125 | "raw": "{\n \"title\": \"Senior .NET Developer\",\n \"company\": \"TEDU\",\n \"location\": \"Ha Noi\",\n \"from\": \"2020/02/01\",\n \"to\": \"2020/12/01\",\n \"current\": true,\n \"description\": \"Build a new CRM with ASP.NET Core\"\n}", 1126 | "options": { 1127 | "raw": { 1128 | "language": "json" 1129 | } 1130 | } 1131 | }, 1132 | "url": { 1133 | "raw": "http://localhost:5000/api/v1/profile/experience/5fc6e36b291d8a064968b952", 1134 | "protocol": "http", 1135 | "host": [ 1136 | "localhost" 1137 | ], 1138 | "port": "5000", 1139 | "path": [ 1140 | "api", 1141 | "v1", 1142 | "profile", 1143 | "experience", 1144 | "5fc6e36b291d8a064968b952" 1145 | ] 1146 | } 1147 | }, 1148 | "response": [] 1149 | }, 1150 | { 1151 | "name": "34. Delete group", 1152 | "request": { 1153 | "method": "DELETE", 1154 | "header": [ 1155 | { 1156 | "key": "x-auth-token", 1157 | "type": "text", 1158 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzMwMDMzOCwiZXhwIjoxNjA3MzAzOTM4fQ.Ng7EdkyjPP43kw6jvg3F9IigMT_E9Z0_ZgqktnHypWk" 1159 | } 1160 | ], 1161 | "body": { 1162 | "mode": "raw", 1163 | "raw": "{\n \"title\": \"Senior .NET Developer\",\n \"company\": \"TEDU\",\n \"location\": \"Ha Noi\",\n \"from\": \"2020/02/01\",\n \"to\": \"2020/12/01\",\n \"current\": true,\n \"description\": \"Build a new CRM with ASP.NET Core\"\n}", 1164 | "options": { 1165 | "raw": { 1166 | "language": "json" 1167 | } 1168 | } 1169 | }, 1170 | "url": { 1171 | "raw": "http://localhost:5000/api/v1/groups/5fcd750330edea054ce7b41b", 1172 | "protocol": "http", 1173 | "host": [ 1174 | "localhost" 1175 | ], 1176 | "port": "5000", 1177 | "path": [ 1178 | "api", 1179 | "v1", 1180 | "groups", 1181 | "5fcd750330edea054ce7b41b" 1182 | ] 1183 | } 1184 | }, 1185 | "response": [] 1186 | }, 1187 | { 1188 | "name": "16. Delete education", 1189 | "request": { 1190 | "method": "DELETE", 1191 | "header": [ 1192 | { 1193 | "key": "x-auth-token", 1194 | "type": "text", 1195 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjg3MDc1OSwiZXhwIjoxNjA2ODcwODE5fQ.a0zhH7ow7Bh4p-n3X6HurSwV_JflSdDOqeVIH-BAgoo" 1196 | } 1197 | ], 1198 | "body": { 1199 | "mode": "raw", 1200 | "raw": "{\n \"title\": \"Senior .NET Developer\",\n \"company\": \"TEDU\",\n \"location\": \"Ha Noi\",\n \"from\": \"2020/02/01\",\n \"to\": \"2020/12/01\",\n \"current\": true,\n \"description\": \"Build a new CRM with ASP.NET Core\"\n}", 1201 | "options": { 1202 | "raw": { 1203 | "language": "json" 1204 | } 1205 | } 1206 | }, 1207 | "url": { 1208 | "raw": "http://localhost:5000/api/v1/profile/education/5fc6e67840089c080e7e0e1f", 1209 | "protocol": "http", 1210 | "host": [ 1211 | "localhost" 1212 | ], 1213 | "port": "5000", 1214 | "path": [ 1215 | "api", 1216 | "v1", 1217 | "profile", 1218 | "education", 1219 | "5fc6e67840089c080e7e0e1f" 1220 | ] 1221 | } 1222 | }, 1223 | "response": [] 1224 | }, 1225 | { 1226 | "name": "3. Login", 1227 | "request": { 1228 | "method": "POST", 1229 | "header": [ 1230 | { 1231 | "key": "Content-Type", 1232 | "type": "text", 1233 | "value": "application/json" 1234 | } 1235 | ], 1236 | "body": { 1237 | "mode": "raw", 1238 | "raw": "{\n \"email\": \"ngoctoan89@gmail.com\",\n \"password\": \"123654$\"\n}", 1239 | "options": { 1240 | "raw": { 1241 | "language": "json" 1242 | } 1243 | } 1244 | }, 1245 | "url": { 1246 | "raw": "http://localhost:5000/api/v1/auth", 1247 | "protocol": "http", 1248 | "host": [ 1249 | "localhost" 1250 | ], 1251 | "port": "5000", 1252 | "path": [ 1253 | "api", 1254 | "v1", 1255 | "auth" 1256 | ] 1257 | } 1258 | }, 1259 | "response": [] 1260 | }, 1261 | { 1262 | "name": "4. Get current User login", 1263 | "protocolProfileBehavior": { 1264 | "disableBodyPruning": true 1265 | }, 1266 | "request": { 1267 | "method": "GET", 1268 | "header": [ 1269 | { 1270 | "key": "x-auth-token", 1271 | "type": "text", 1272 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwODM5OCwiZXhwIjoxNjAzMDA4NDU4fQ.z0WJR7C6l9E-PU0CI621abgxANu0D0dTr5LnPLj6wYI" 1273 | } 1274 | ], 1275 | "body": { 1276 | "mode": "raw", 1277 | "raw": "", 1278 | "options": { 1279 | "raw": { 1280 | "language": "json" 1281 | } 1282 | } 1283 | }, 1284 | "url": { 1285 | "raw": "http://localhost:5000/api/v1/auth", 1286 | "protocol": "http", 1287 | "host": [ 1288 | "localhost" 1289 | ], 1290 | "port": "5000", 1291 | "path": [ 1292 | "api", 1293 | "v1", 1294 | "auth" 1295 | ] 1296 | } 1297 | }, 1298 | "response": [] 1299 | }, 1300 | { 1301 | "name": "10. Get current profile", 1302 | "protocolProfileBehavior": { 1303 | "disableBodyPruning": true 1304 | }, 1305 | "request": { 1306 | "method": "GET", 1307 | "header": [ 1308 | { 1309 | "key": "x-auth-token", 1310 | "type": "text", 1311 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjY5ODQ3NiwiZXhwIjoxNjA2Njk4NTM2fQ.C1buqro3Yfsec_5_QQWLB7vcIulC4_S4Bb9PMpwM4uQ" 1312 | } 1313 | ], 1314 | "body": { 1315 | "mode": "raw", 1316 | "raw": "", 1317 | "options": { 1318 | "raw": { 1319 | "language": "json" 1320 | } 1321 | } 1322 | }, 1323 | "url": { 1324 | "raw": "http://localhost:5000/api/v1/profile/me", 1325 | "protocol": "http", 1326 | "host": [ 1327 | "localhost" 1328 | ], 1329 | "port": "5000", 1330 | "path": [ 1331 | "api", 1332 | "v1", 1333 | "profile", 1334 | "me" 1335 | ] 1336 | } 1337 | }, 1338 | "response": [] 1339 | }, 1340 | { 1341 | "name": "12. Get profile by id", 1342 | "protocolProfileBehavior": { 1343 | "disableBodyPruning": true 1344 | }, 1345 | "request": { 1346 | "method": "GET", 1347 | "header": [ 1348 | { 1349 | "key": "x-auth-token", 1350 | "type": "text", 1351 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjY5ODQ3NiwiZXhwIjoxNjA2Njk4NTM2fQ.C1buqro3Yfsec_5_QQWLB7vcIulC4_S4Bb9PMpwM4uQ" 1352 | } 1353 | ], 1354 | "body": { 1355 | "mode": "raw", 1356 | "raw": "", 1357 | "options": { 1358 | "raw": { 1359 | "language": "json" 1360 | } 1361 | } 1362 | }, 1363 | "url": { 1364 | "raw": "http://localhost:5000/api/v1/profile/user/5f8c041d54cb102bed036884", 1365 | "protocol": "http", 1366 | "host": [ 1367 | "localhost" 1368 | ], 1369 | "port": "5000", 1370 | "path": [ 1371 | "api", 1372 | "v1", 1373 | "profile", 1374 | "user", 1375 | "5f8c041d54cb102bed036884" 1376 | ] 1377 | } 1378 | }, 1379 | "response": [] 1380 | }, 1381 | { 1382 | "name": "11. Get all profile", 1383 | "protocolProfileBehavior": { 1384 | "disableBodyPruning": true 1385 | }, 1386 | "request": { 1387 | "method": "GET", 1388 | "header": [ 1389 | { 1390 | "key": "x-auth-token", 1391 | "type": "text", 1392 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjY5ODQ3NiwiZXhwIjoxNjA2Njk4NTM2fQ.C1buqro3Yfsec_5_QQWLB7vcIulC4_S4Bb9PMpwM4uQ" 1393 | } 1394 | ], 1395 | "body": { 1396 | "mode": "raw", 1397 | "raw": "", 1398 | "options": { 1399 | "raw": { 1400 | "language": "json" 1401 | } 1402 | } 1403 | }, 1404 | "url": { 1405 | "raw": "http://localhost:5000/api/v1/profile/", 1406 | "protocol": "http", 1407 | "host": [ 1408 | "localhost" 1409 | ], 1410 | "port": "5000", 1411 | "path": [ 1412 | "api", 1413 | "v1", 1414 | "profile", 1415 | "" 1416 | ] 1417 | } 1418 | }, 1419 | "response": [] 1420 | }, 1421 | { 1422 | "name": "37. Get all members", 1423 | "protocolProfileBehavior": { 1424 | "disableBodyPruning": true 1425 | }, 1426 | "request": { 1427 | "method": "GET", 1428 | "header": [ 1429 | { 1430 | "key": "x-auth-token", 1431 | "type": "text", 1432 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNjY5ODQ3NiwiZXhwIjoxNjA2Njk4NTM2fQ.C1buqro3Yfsec_5_QQWLB7vcIulC4_S4Bb9PMpwM4uQ" 1433 | } 1434 | ], 1435 | "body": { 1436 | "mode": "raw", 1437 | "raw": "", 1438 | "options": { 1439 | "raw": { 1440 | "language": "json" 1441 | } 1442 | } 1443 | }, 1444 | "url": { 1445 | "raw": "http://localhost:5000/api/v1/groups/members/5fcd030ad2b8c907f69e51b1", 1446 | "protocol": "http", 1447 | "host": [ 1448 | "localhost" 1449 | ], 1450 | "port": "5000", 1451 | "path": [ 1452 | "api", 1453 | "v1", 1454 | "groups", 1455 | "members", 1456 | "5fcd030ad2b8c907f69e51b1" 1457 | ] 1458 | } 1459 | }, 1460 | "response": [] 1461 | }, 1462 | { 1463 | "name": "5. Get user by id", 1464 | "protocolProfileBehavior": { 1465 | "disableBodyPruning": true 1466 | }, 1467 | "request": { 1468 | "method": "GET", 1469 | "header": [ 1470 | { 1471 | "key": "x-auth-token", 1472 | "type": "text", 1473 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1474 | } 1475 | ], 1476 | "body": { 1477 | "mode": "raw", 1478 | "raw": "", 1479 | "options": { 1480 | "raw": { 1481 | "language": "json" 1482 | } 1483 | } 1484 | }, 1485 | "url": { 1486 | "raw": "http://localhost:5000/api/v1/users/5f8bc8067f2541187c722d94", 1487 | "protocol": "http", 1488 | "host": [ 1489 | "localhost" 1490 | ], 1491 | "port": "5000", 1492 | "path": [ 1493 | "api", 1494 | "v1", 1495 | "users", 1496 | "5f8bc8067f2541187c722d94" 1497 | ] 1498 | } 1499 | }, 1500 | "response": [] 1501 | }, 1502 | { 1503 | "name": "20. Get post by id", 1504 | "protocolProfileBehavior": { 1505 | "disableBodyPruning": true 1506 | }, 1507 | "request": { 1508 | "method": "GET", 1509 | "header": [ 1510 | { 1511 | "key": "x-auth-token", 1512 | "type": "text", 1513 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1514 | } 1515 | ], 1516 | "body": { 1517 | "mode": "raw", 1518 | "raw": "", 1519 | "options": { 1520 | "raw": { 1521 | "language": "json" 1522 | } 1523 | } 1524 | }, 1525 | "url": { 1526 | "raw": "http://localhost:5000/api/v1/posts/5fc8fbff305a6a0dea2ba7a9", 1527 | "protocol": "http", 1528 | "host": [ 1529 | "localhost" 1530 | ], 1531 | "port": "5000", 1532 | "path": [ 1533 | "api", 1534 | "v1", 1535 | "posts", 1536 | "5fc8fbff305a6a0dea2ba7a9" 1537 | ] 1538 | } 1539 | }, 1540 | "response": [] 1541 | }, 1542 | { 1543 | "name": "6. Get all user", 1544 | "protocolProfileBehavior": { 1545 | "disableBodyPruning": true 1546 | }, 1547 | "request": { 1548 | "method": "GET", 1549 | "header": [ 1550 | { 1551 | "key": "x-auth-token", 1552 | "type": "text", 1553 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1554 | } 1555 | ], 1556 | "body": { 1557 | "mode": "raw", 1558 | "raw": "", 1559 | "options": { 1560 | "raw": { 1561 | "language": "json" 1562 | } 1563 | } 1564 | }, 1565 | "url": { 1566 | "raw": "http://localhost:5000/api/v1/users", 1567 | "protocol": "http", 1568 | "host": [ 1569 | "localhost" 1570 | ], 1571 | "port": "5000", 1572 | "path": [ 1573 | "api", 1574 | "v1", 1575 | "users" 1576 | ] 1577 | } 1578 | }, 1579 | "response": [] 1580 | }, 1581 | { 1582 | "name": "7. Get all user paging", 1583 | "protocolProfileBehavior": { 1584 | "disableBodyPruning": true 1585 | }, 1586 | "request": { 1587 | "method": "GET", 1588 | "header": [ 1589 | { 1590 | "key": "x-auth-token", 1591 | "type": "text", 1592 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1593 | } 1594 | ], 1595 | "body": { 1596 | "mode": "raw", 1597 | "raw": "", 1598 | "options": { 1599 | "raw": { 1600 | "language": "json" 1601 | } 1602 | } 1603 | }, 1604 | "url": { 1605 | "raw": "http://localhost:5000/api/v1/users/paging/1?keyword=1", 1606 | "protocol": "http", 1607 | "host": [ 1608 | "localhost" 1609 | ], 1610 | "port": "5000", 1611 | "path": [ 1612 | "api", 1613 | "v1", 1614 | "users", 1615 | "paging", 1616 | "1" 1617 | ], 1618 | "query": [ 1619 | { 1620 | "key": "keyword", 1621 | "value": "1" 1622 | } 1623 | ] 1624 | } 1625 | }, 1626 | "response": [] 1627 | }, 1628 | { 1629 | "name": "21. Get post paging", 1630 | "protocolProfileBehavior": { 1631 | "disableBodyPruning": true 1632 | }, 1633 | "request": { 1634 | "method": "GET", 1635 | "header": [ 1636 | { 1637 | "key": "x-auth-token", 1638 | "type": "text", 1639 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1640 | } 1641 | ], 1642 | "body": { 1643 | "mode": "raw", 1644 | "raw": "", 1645 | "options": { 1646 | "raw": { 1647 | "language": "json" 1648 | } 1649 | } 1650 | }, 1651 | "url": { 1652 | "raw": "http://localhost:5000/api/v1/posts/paging/1", 1653 | "protocol": "http", 1654 | "host": [ 1655 | "localhost" 1656 | ], 1657 | "port": "5000", 1658 | "path": [ 1659 | "api", 1660 | "v1", 1661 | "posts", 1662 | "paging", 1663 | "1" 1664 | ] 1665 | } 1666 | }, 1667 | "response": [] 1668 | }, 1669 | { 1670 | "name": "19. Get all posts", 1671 | "protocolProfileBehavior": { 1672 | "disableBodyPruning": true 1673 | }, 1674 | "request": { 1675 | "method": "GET", 1676 | "header": [ 1677 | { 1678 | "key": "x-auth-token", 1679 | "type": "text", 1680 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1681 | } 1682 | ], 1683 | "body": { 1684 | "mode": "raw", 1685 | "raw": "", 1686 | "options": { 1687 | "raw": { 1688 | "language": "json" 1689 | } 1690 | } 1691 | }, 1692 | "url": { 1693 | "raw": "http://localhost:5000/api/v1/posts", 1694 | "protocol": "http", 1695 | "host": [ 1696 | "localhost" 1697 | ], 1698 | "port": "5000", 1699 | "path": [ 1700 | "api", 1701 | "v1", 1702 | "posts" 1703 | ] 1704 | } 1705 | }, 1706 | "response": [] 1707 | }, 1708 | { 1709 | "name": "33. Get all groups", 1710 | "protocolProfileBehavior": { 1711 | "disableBodyPruning": true 1712 | }, 1713 | "request": { 1714 | "method": "GET", 1715 | "header": [ 1716 | { 1717 | "key": "x-auth-token", 1718 | "type": "text", 1719 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGJjODA2N2YyNTQxMTg3YzcyMmQ5NCIsImlhdCI6MTYwMzAwNjIwMCwiZXhwIjoxNjAzMDA2MjYwfQ.wpoRVhomyrFmwR0MLjRqTa1lQiDs4oJpyICxFX3kv1I" 1720 | } 1721 | ], 1722 | "body": { 1723 | "mode": "raw", 1724 | "raw": "", 1725 | "options": { 1726 | "raw": { 1727 | "language": "json" 1728 | } 1729 | } 1730 | }, 1731 | "url": { 1732 | "raw": "http://localhost:5000/api/v1/groups", 1733 | "protocol": "http", 1734 | "host": [ 1735 | "localhost" 1736 | ], 1737 | "port": "5000", 1738 | "path": [ 1739 | "api", 1740 | "v1", 1741 | "groups" 1742 | ] 1743 | } 1744 | }, 1745 | "response": [] 1746 | }, 1747 | { 1748 | "name": "8. Delete user", 1749 | "request": { 1750 | "method": "DELETE", 1751 | "header": [ 1752 | { 1753 | "key": "x-auth-token", 1754 | "type": "text", 1755 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwMzE1NzI0NywiZXhwIjoxNjAzMTU3MzA3fQ.hfm9zEfxMAGdbHgGNAczw6Lana4cOcDa6W-6A2JABGg" 1756 | } 1757 | ], 1758 | "body": { 1759 | "mode": "raw", 1760 | "raw": "", 1761 | "options": { 1762 | "raw": { 1763 | "language": "json" 1764 | } 1765 | } 1766 | }, 1767 | "url": { 1768 | "raw": "http://localhost:5000/api/v1/users/5f8bc8067f2541187c722d94", 1769 | "protocol": "http", 1770 | "host": [ 1771 | "localhost" 1772 | ], 1773 | "port": "5000", 1774 | "path": [ 1775 | "api", 1776 | "v1", 1777 | "users", 1778 | "5f8bc8067f2541187c722d94" 1779 | ] 1780 | } 1781 | }, 1782 | "response": [] 1783 | }, 1784 | { 1785 | "name": "22. Delete post", 1786 | "request": { 1787 | "method": "DELETE", 1788 | "header": [ 1789 | { 1790 | "key": "x-auth-token", 1791 | "type": "text", 1792 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVmOGMwNDFkNTRjYjEwMmJlZDAzNjg4NCIsImlhdCI6MTYwNzAwNzkyMywiZXhwIjoxNjA3MDA3OTgzfQ.GhqnfVVpwfVso3w3pjYUsrHeIYfX5O0u1LckF2la0Ak" 1793 | } 1794 | ], 1795 | "body": { 1796 | "mode": "raw", 1797 | "raw": "", 1798 | "options": { 1799 | "raw": { 1800 | "language": "json" 1801 | } 1802 | } 1803 | }, 1804 | "url": { 1805 | "raw": "http://localhost:5000/api/v1/posts/5fc8f790d6d9a90af27cb9b0", 1806 | "protocol": "http", 1807 | "host": [ 1808 | "localhost" 1809 | ], 1810 | "port": "5000", 1811 | "path": [ 1812 | "api", 1813 | "v1", 1814 | "posts", 1815 | "5fc8f790d6d9a90af27cb9b0" 1816 | ] 1817 | } 1818 | }, 1819 | "response": [] 1820 | } 1821 | ], 1822 | "protocolProfileBehavior": {} 1823 | } --------------------------------------------------------------------------------