From c1f196293e7a9016fb8c7961adaa02254361e48f Mon Sep 17 00:00:00 2001 From: Nick Nyanjui Date: Tue, 2 May 2023 01:51:24 +0300 Subject: [PATCH] Cleanup backend files --- backend/{.eslintrc.mjs => .eslintrc.cjs} | 1 + backend/babel.config.mjs | 7 +- backend/package.json | 6 +- backend/src/controllers/AppController.ts | 30 --- backend/src/controllers/DownloadController.ts | 40 ---- backend/src/controllers/StreamController.ts | 82 -------- backend/src/controllers/UserController.ts | 184 ------------------ backend/src/controllers/app.controller.ts | 29 +++ .../{AuthController.ts => auth.controller.ts} | 166 ++++++++-------- ...nelController.ts => channel.controller.ts} | 88 +++++---- ...entController.ts => comment.controller.ts} | 40 ++-- .../src/controllers/download.controller.ts | 40 ++++ backend/src/controllers/stream.controller.ts | 82 ++++++++ ...loadController.ts => upload.controller.ts} | 46 ++--- backend/src/controllers/user.controller.ts | 183 +++++++++++++++++ ...VideoController.ts => video.controller.ts} | 104 +++++----- backend/src/error.ts | 21 +- backend/src/middleware/getAuthToken.ts | 24 +-- backend/src/middleware/requireLogin.ts | 10 +- backend/src/middleware/validate.ts | 40 ++-- backend/src/models/channel.model.ts | 6 +- backend/src/models/comment.model.ts | 8 +- backend/src/models/user.model.ts | 8 +- backend/src/models/video.model.ts | 6 +- backend/src/routes/auth.route.ts | 32 ++- backend/src/routes/channel.route.ts | 24 ++- backend/src/routes/comment.route.ts | 26 ++- backend/src/routes/session.route.ts | 2 +- backend/src/routes/user.route.ts | 69 ++++--- backend/src/routes/video.route.ts | 37 ++-- backend/src/server.ts | 65 ++++--- backend/src/services/session.service.ts | 16 +- backend/src/services/user.service.ts | 8 +- backend/src/utils/aws.ts | 12 +- backend/src/utils/db.ts | 7 +- backend/src/utils/redis.ts | 85 ++++---- 36 files changed, 857 insertions(+), 777 deletions(-) rename backend/{.eslintrc.mjs => .eslintrc.cjs} (95%) delete mode 100644 backend/src/controllers/AppController.ts delete mode 100644 backend/src/controllers/DownloadController.ts delete mode 100644 backend/src/controllers/StreamController.ts delete mode 100644 backend/src/controllers/UserController.ts create mode 100644 backend/src/controllers/app.controller.ts rename backend/src/controllers/{AuthController.ts => auth.controller.ts} (52%) rename backend/src/controllers/{ChannelController.ts => channel.controller.ts} (65%) rename backend/src/controllers/{CommentController.ts => comment.controller.ts} (65%) create mode 100644 backend/src/controllers/download.controller.ts create mode 100644 backend/src/controllers/stream.controller.ts rename backend/src/controllers/{UploadController.ts => upload.controller.ts} (59%) create mode 100644 backend/src/controllers/user.controller.ts rename backend/src/controllers/{VideoController.ts => video.controller.ts} (73%) diff --git a/backend/.eslintrc.mjs b/backend/.eslintrc.cjs similarity index 95% rename from backend/.eslintrc.mjs rename to backend/.eslintrc.cjs index 20ef981..f8ffe38 100644 --- a/backend/.eslintrc.mjs +++ b/backend/.eslintrc.cjs @@ -37,6 +37,7 @@ module.exports = { } }, rules: { + quotes: [2, "single", { "avoidEscape": true }], 'no-console': 'off', 'no-shadow': 'off', 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'] diff --git a/backend/babel.config.mjs b/backend/babel.config.mjs index 3456301..93c4883 100644 --- a/backend/babel.config.mjs +++ b/backend/babel.config.mjs @@ -1,8 +1,3 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - '@babel/preset-typescript' - ] - ] + presets: [['@babel/preset-env', '@babel/preset-typescript']] }; diff --git a/backend/package.json b/backend/package.json index f00d2e2..06de75b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,8 +6,8 @@ "license": "MIT", "scripts": { "start": "ts-node-dev --respawn --transpile-only src/server.ts", - "lint": "./node_modules/.bin/eslint *.js *.ts --fix", - "check-lint": "./node_modules/.bin/eslint *.js *.ts", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "check-lint": "eslint . --ext .js,.jsx,.ts,.tsx", "start-server": "nodemon --exec babel-node --presets @babel/preset-env src/server.ts", "start-worker": "nodemon --exec babel-node --presets @babel/preset-env src/worker.ts", "test-types": "tsc --incremental --noEmit", @@ -39,6 +39,8 @@ "chai-http": "^4.3.0", "eslint": "^8.39.0", "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-base-typescript": "^1.1.0", + "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript-base": "^4.0.2", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest": "^27.2.1", diff --git a/backend/src/controllers/AppController.ts b/backend/src/controllers/AppController.ts deleted file mode 100644 index 44f4a69..0000000 --- a/backend/src/controllers/AppController.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import dbClient from '../utils/db'; -import redisClient from '../utils/redis'; -import User from "../models/user.model"; -import Video from "../models/video.model"; -import { ChannelModel as Channel} from "../models/channel.model"; -import Comment from "../models/comment.model" - -export default class AppController { - static getStatus(req: Request, resp: Response, next: NextFunction) { - resp.status(200).json( - { - redis: redisClient.isAlive(), - db: dbClient.isAlive() - }); - } - - static async getStats(req: Request, resp: Response, next: NextFunction) { - const userCount = await User.countDocuments(); - const videoCount = await Video.countDocuments(); - const channelCount = await Channel.countDocuments(); - const commentCount = await Comment.countDocuments(); - resp.status(200).json({ - "users": userCount, - "videos": videoCount, - "comments": commentCount, - "channels": channelCount - }); - } -} diff --git a/backend/src/controllers/DownloadController.ts b/backend/src/controllers/DownloadController.ts deleted file mode 100644 index e2afe8c..0000000 --- a/backend/src/controllers/DownloadController.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import createError from "../error"; -import { S3, GetObjectCommand } from "@aws-sdk/client-s3"; -import { Readable } from 'stream'; -import Video from "../models/video.model"; -import User from "../models/user.model"; -import { s3Config } from "../utils/aws"; - -const s3: S3 = new S3( s3Config ); - -class DownloadController { - static async downloadVideo ( req: Request, resp: Response, next: NextFunction ) { - const video = await Video.findById(req.params.id) - if (!video) { - return next(createError(404, "Video not found!")); - } - try { - // Get Video Metadata from AWS S3 Bucket - const params = { - Bucket: process.env.AWS_BUCKET_NAME, - Key: video.filename - } - - const command = new GetObjectCommand( params ); - const response = await s3.send( command ); - const stream = Readable.from( response.Body as any ); - - // Send Video to the client - resp.status(200) - resp.set( 'Content-Type', response.ContentType ); - resp.set( 'Content-Disposition', `attachment; filename=${ video.filename }` ); - stream.pipe( resp ); - } catch ( e ) { - console.error( e ); - return next(createError(500, "Failed to download video!")); - } - } -} - -export default DownloadController; diff --git a/backend/src/controllers/StreamController.ts b/backend/src/controllers/StreamController.ts deleted file mode 100644 index cb36aae..0000000 --- a/backend/src/controllers/StreamController.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import createError from "../error"; -import { S3, GetObjectCommand } from "@aws-sdk/client-s3"; -import ffmpeg from 'fluent-ffmpeg'; -import Video from "../models/video.model"; -import { s3Config } from "../utils/aws"; -import { config } from 'dotenv'; -import { Readable } from 'stream'; - -config(); - -const s3: S3 = new S3( s3Config ); -// Set the path to the ffmpeg binary - -// linux -// ffmpeg.setFfmpegPath('/usr/bin/ffmpeg'); - -// Windows -ffmpeg.setFfmpegPath("C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe"); - -class StreamingController { - static async getStream(req: Request, resp: Response, next: NextFunction) { - try { - const video: any = await Video.findById(req.params.id) - if (!video) { - return next(createError(404, "Video not found!")); - } - - // Convert the video file to a streamable format using ffmpeg - const command = new GetObjectCommand({ - Bucket: process.env.AWS_BUCKET_NAME, - Key: video.filename, - }); - const response = await s3.send( command); - const stream = Readable.from( response.Body as any ); - - resp.set('Content-Type', 'video/mp4'); - resp.set('Transfer-Encoding', 'chunked'); - - let totalTime: number; - // Stream the video - const proc = ffmpeg(stream) - //set the size - // .withSize('50%') - // set fps - .withFps(24) - .outputOptions(['-movflags isml+frag_keyframe']) - .toFormat('mp4') - .withAudioCodec('copy') - .on('start', commandLine => { - // something message for init process - console.log("Streaming started!") - }) - .on('codecData', data => { - // HERE YOU GET THE TOTAL TIME - totalTime = parseInt(data.duration.replace(/:/g, '')) - }) - .on('error', function(err: any,stdout:any,stderr:any) { - console.log('an error happened: ' + err.message); - console.log('ffmpeg stdout: ' + stdout); - console.log('ffmpeg stderr: ' + stderr); - }) - .on('end', function() { - console.log('Processing finished !'); - }) - .on('progress', function(progress: any) { - // console.log('Processing: ' + progress.percent + '% done'); - const time: number = parseInt(progress.timemark.replace(/:/g, '')) - - // AND HERE IS THE CALCULATION - const percent = (time / totalTime) * 100 - console.log(`Processing: ${percent >= 0 ? ~~percent : 0}% done`) - }) - .pipe(resp, { end: true }); - } catch (e) { - console.error(e); - return next(createError(500, "Failed to stream video!")); - } - } -} - -export default StreamingController; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts deleted file mode 100644 index dc423d9..0000000 --- a/backend/src/controllers/UserController.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import User from "../models/user.model"; -import Video from "../models/video.model"; -import { ChannelModel as Channel } from "../models/channel.model"; -import createError from "../error"; -import { exclude } from "./AuthController"; - -class UserController { - static async getMeHandler( - req: Request, - res: Response, - next: NextFunction - ) { - try { - const user = exclude(res.locals.user, ["password"]); - res.status(200).json({ - status: "success", - data: { - user: exclude(user, ["password"]), - }, - }); - } catch (err: any) { - next(err); - } - }; - - static async updateUser(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - console.log(userId) - if (req.params.id === userId) { - try { - const updatedUser = await User.findByIdAndUpdate( - userId, - { - $set: req.body, - }, - { new: true } - ); - resp.status(200).json(updatedUser); - } catch (err) { - next(err); - } - } else { - return next(createError(403, "You can only update your account!")); - } - }; - - static async deleteUser(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - if (req.params.id === userId) { - try { - await User.findByIdAndDelete(userId); - resp.status(200).json("User has been deleted."); - } catch (err) { - next(err); - } - } else { - return next(createError(403, "You can only delete your account!")); - } - }; - - static async getUser(req: Request, resp: Response, next: NextFunction) { - const userId = String(req.params.id) - try { - const user = await User.findById(userId); - resp.status(200).json(user); - } catch (err) { - next(err); - } - }; - - static async subscribe(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - try { - await User.findByIdAndUpdate(userId, { - $push: { subscriptions: req.params.channelId }, - }); - await Channel.findByIdAndUpdate(req.params.channelId, { - $inc: { subscribers: 1 }, - }); - resp.status(200).json("Subscription successfull.") - } catch (err) { - next(err); - } - }; - - static async unsubscribe(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - try { - await User.findByIdAndUpdate(userId, { - $pull: { subscriptions: req.params.channelId }, - }); - await Channel.findByIdAndUpdate(req.params.channelId, { - $inc: { subscribers: -1 }, - }); - resp.status(200).json("Unsubscription successfull.") - } catch (err) { - next(err); - } - }; - - static async like(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - console.log(userId) - const videoId = req.params.videoId; - try { - await Video.findByIdAndUpdate(videoId, { - $addToSet:{likes:userId}, - $pull:{dislikes:userId} - }) - resp.status(200).json("The video has been liked.") - } catch (err) { - next(err); - } - }; - - static async dislike(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const videoId = req.params.videoId; - try { - await Video.findByIdAndUpdate(videoId,{ - $addToSet:{dislikes:userId}, - $pull:{likes:userId} - }) - resp.status(200).json("The video has been disliked.") - } catch (err) { - next(err); - } - }; - - static async getSubscriptions(req: Request, resp: Response, next: NextFunction) { - try { - const userId = String(resp.locals.user._id); - const user = await User.findById(userId); - if (!user) { - return next(createError(403, "You are not logged in!")); - } - const subscribedChannels = user.subscriptions; - - const list = await Promise.all( - subscribedChannels.map(async (channelId: any) => { - return await Channel.findById(channelId); - }) - ); - if (!list) return next(createError(404, "Channels not found!")); - resp.status(200).json(list.flat().sort((a: any, b: any) => b.createdAt - a.createdAt)); - } catch (err) { - next(err); - } - }; - - static async getHistory(req: Request, resp: Response, next: NextFunction) { - try { - const userId = String(resp.locals.user._id); - const user = await User.findById(userId); - if (!user) { - return next(createError(403, "You are not logged in!")); - } - const watchedVideos = user.history; - const list = await Promise.all( - watchedVideos.map(async (videoId) => { - return await Video.findById(videoId); - }) - ); - - resp.status(200).json(list.flat().sort((a: any, b: any) => b.updatedAt - a.updatedAt)); - } catch (err) { - next(err); - } - }; - - static async getAllUsers(req: Request, resp: Response, next: NextFunction) { - try { - const users = await User.find({ }); - if (!users) return next(createError(404, "No Users found!")); - resp.status(200).json(users); - } catch (err) { - next(err); - } - }; - -} - -export default UserController; \ No newline at end of file diff --git a/backend/src/controllers/app.controller.ts b/backend/src/controllers/app.controller.ts new file mode 100644 index 0000000..9098949 --- /dev/null +++ b/backend/src/controllers/app.controller.ts @@ -0,0 +1,29 @@ +import { NextFunction, Request, Response } from 'express'; +import dbClient from '../utils/db'; +import redisClient from '../utils/redis'; +import User from '../models/user.model'; +import Video from '../models/video.model'; +import { ChannelModel as Channel } from '../models/channel.model'; +import Comment from '../models/comment.model'; + +export default class AppController { + static getStatus(req: Request, resp: Response, next: NextFunction) { + resp.status(200).json({ + // redis: redisClient.isAlive(), + db: dbClient.isAlive(), + }); + } + + static async getStats(req: Request, resp: Response, next: NextFunction) { + const userCount = await User.countDocuments(); + const videoCount = await Video.countDocuments(); + const channelCount = await Channel.countDocuments(); + const commentCount = await Comment.countDocuments(); + resp.status(200).json({ + users: userCount, + videos: videoCount, + comments: commentCount, + channels: channelCount, + }); + } +} diff --git a/backend/src/controllers/AuthController.ts b/backend/src/controllers/auth.controller.ts similarity index 52% rename from backend/src/controllers/AuthController.ts rename to backend/src/controllers/auth.controller.ts index a1fc864..d75a28b 100644 --- a/backend/src/controllers/AuthController.ts +++ b/backend/src/controllers/auth.controller.ts @@ -1,22 +1,22 @@ -import { NextFunction, Request, Response } from "express"; -import { CreateUserInput, LoginUserInput } from "../services/user.service"; +import { NextFunction, Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; +import bcrypt from 'bcryptjs'; +import { randomUUID } from 'crypto'; +import { CreateUserInput, LoginUserInput } from '../services/user.service'; import { getGoogleOauthToken, getGoogleUser, -} from "../services/session.service"; -import ChannelController from "../controllers/ChannelController"; -import jwt from "jsonwebtoken"; -import User from "../models/user.model" -import bcrypt from "bcryptjs"; -import createError from "../error"; -import { ChannelModel as Channel } from "../models/channel.model"; -import { randomUUID } from "crypto"; +} from '../services/session.service'; +import ChannelController from './channel.controller'; +import User from '../models/user.model'; +import createError from '../error'; +import { ChannelModel as Channel } from '../models/channel.model'; function exclude( user: User, - keys: Key[] + keys: Key[], ): Omit { - for (let key of keys) { + for (const key of keys) { delete user[key]; } return user; @@ -28,211 +28,217 @@ class AuthController { static async registerHandler( req: Request<{}, {}, CreateUserInput>, resp: Response, - next: NextFunction + next: NextFunction, ) { try { const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync(req.body.password, salt); const user = new User({ - ...req.body, - "password": hash, - "avatar": DEFAULT_AVATAR, + ...req.body, + password: hash, + avatar: DEFAULT_AVATAR, }); await user.save(); // create default channel const channel = new Channel({ - "name": randomUUID(), - "userId": user._id, - "imgUrl": user.avatar, + name: randomUUID(), + userId: user._id, + imgUrl: user.avatar, }); if (!channel) { - return next(createError(404, "Default User Channel could not be created!")); + return next( + createError(404, 'Default User Channel could not be created!'), + ); } - await channel.save() - user.channels.push(channel) - await user.save() + await channel.save(); + user.channels.push(channel); + await user.save(); - const TOKEN_EXPIRES_IN = process.env.TOKEN_EXPIRES_IN as unknown as number; + const TOKEN_EXPIRES_IN = process.env + .TOKEN_EXPIRES_IN as unknown as number; const TOKEN_SECRET = process.env.JWT_SECRET as unknown as string; const token = jwt.sign({ sub: user.id }, TOKEN_SECRET); - resp.cookie("auth_token", token, { + resp.cookie('auth_token', token, { expires: new Date(Date.now() + TOKEN_EXPIRES_IN * 60 * 1000), }); resp.status(201).json({ - status: "success", + status: 'success', data: { - user: exclude(user, ["password"]), + user: exclude(user, ['password']), }, }); } catch (err: any) { - if (err.code === "P2002") { + if (err.code === 'P2002') { return resp.status(409).json({ - status: "fail", - message: "Email already exist", + status: 'fail', + message: 'Email already exist', }); } next(err); } - }; + } - static async loginHandler ( + static async loginHandler( req: Request<{}, {}, LoginUserInput>, resp: Response, - next: NextFunction + next: NextFunction, ) { try { const user = await User.findOne({ email: req.body.email }); - if (!user) return next(createError(404, "User not found!")); + if (!user) return next(createError(404, 'User not found!')); if (user.fromGoogle) { return resp.status(401).json({ - status: "fail", - message: `Use Google OAuth2 instead`, + status: 'fail', + message: 'Use Google OAuth2 instead', }); } - if (!user.password) return next(createError(400, "Invalid user!")); + if (!user.password) return next(createError(400, 'Invalid user!')); const isCorrect = await bcrypt.compare(req.body.password, user.password); - if (!isCorrect) return next(createError(400, "Wrong Credentials!")); + if (!isCorrect) return next(createError(400, 'Wrong Credentials!')); - const TOKEN_EXPIRES_IN = process.env.TOKEN_EXPIRES_IN as unknown as number; + const TOKEN_EXPIRES_IN = process.env + .TOKEN_EXPIRES_IN as unknown as number; const TOKEN_SECRET = process.env.JWT_SECRET as unknown as string; const token = jwt.sign({ sub: user.id }, TOKEN_SECRET); - resp.cookie("auth_token", token, { + resp.cookie('auth_token', token, { expires: new Date(Date.now() + TOKEN_EXPIRES_IN * 60 * 1000), }); resp.status(200).json({ - status: "success", + status: 'success', }); } catch (err: any) { next(err); } - }; + } - static async logoutHandler ( - req: Request, - res: Response, - next: NextFunction - ) { + static async logoutHandler(req: Request, res: Response, next: NextFunction) { try { - res.cookie("auth_token", "", { maxAge: -1 }); - res.status(200).json({ status: "success" }); + res.cookie('auth_token', '', { maxAge: -1 }); + res.status(200).json({ status: 'success' }); } catch (err: any) { next(err); } - }; + } - static async resetPasswordHandler ( + static async resetPasswordHandler( req: Request, res: Response, - next: NextFunction + next: NextFunction, ) { try { const user = await User.findOne({ email: req.body.email }); if (!user) { return res.status(401).json({ - status: "fail", - message: "Invalid user email", + status: 'fail', + message: 'Invalid user email', }); } if (user.fromGoogle) { return res.status(401).json({ - status: "fail", - message: `Use Google OAuth2 instead`, + status: 'fail', + message: 'Use Google OAuth2 instead', }); } - if (!user.password) return next(createError(400, "Invalid user!")); - const isUsed = await bcrypt.compare(req.body.password, user.password) - if (isUsed) return next(createError(400, "Use new Credentials!")); + if (!user.password) return next(createError(400, 'Invalid user!')); + const isUsed = await bcrypt.compare(req.body.password, user.password); + if (isUsed) return next(createError(400, 'Use new Credentials!')); const salt = bcrypt.genSaltSync(10); const hash = bcrypt.hashSync(req.body.password, salt); user.password = hash; user.save(); - const TOKEN_EXPIRES_IN = process.env.TOKEN_EXPIRES_IN as unknown as number; + const TOKEN_EXPIRES_IN = process.env + .TOKEN_EXPIRES_IN as unknown as number; const TOKEN_SECRET = process.env.JWT_SECRET as unknown as string; const token = jwt.sign({ sub: user.id }, TOKEN_SECRET); - res.cookie("auth_token", token, { + res.cookie('auth_token', token, { expires: new Date(Date.now() + TOKEN_EXPIRES_IN * 60 * 1000), }); res.status(200).json({ - status: "success", + status: 'success', }); } catch (err: any) { next(err); } - }; + } - static async googleOauthHandler (req: Request, res: Response) { - const FRONTEND_ENDPOINT = process.env.FRONTEND_ENDPOINT as unknown as string; + static async googleOauthHandler(req: Request, res: Response) { + const FRONTEND_ENDPOINT = process.env + .FRONTEND_ENDPOINT as unknown as string; try { const code = req.query.code as string; - const pathUrl = (req.query.state as string) || "/"; + const pathUrl = (req.query.state as string) || '/'; if (!code) { return res.status(401).json({ - status: "fail", - message: "Authorization code not provided!", + status: 'fail', + message: 'Authorization code not provided!', }); } const { id_token, access_token } = await getGoogleOauthToken({ code }); - const { name, verified_email, email, picture } = await getGoogleUser({ + const { + name, verified_email, email, picture, + } = await getGoogleUser({ id_token, access_token, }); if (!verified_email) { return res.status(403).json({ - status: "fail", - message: "Google account not verified", + status: 'fail', + message: 'Google account not verified', }); } - const user = await User.findOneAndUpdate({ email }, + const user = await User.findOneAndUpdate( + { email }, { createdAt: new Date(), username: email, email, avatar: picture || DEFAULT_AVATAR, - password: "", + password: '', verified: true, fromGoogle: true, }, { new: true, - upsert: true - } + upsert: true, + }, ); if (!user) return res.redirect(`${FRONTEND_ENDPOINT}/oauth/error`); - const TOKEN_EXPIRES_IN = process.env.TOKEN_EXPIRES_IN as unknown as number; + const TOKEN_EXPIRES_IN = process.env + .TOKEN_EXPIRES_IN as unknown as number; const TOKEN_SECRET = process.env.JWT_SECRET as unknown as string; const token = jwt.sign({ sub: user.id }, TOKEN_SECRET, { expiresIn: `${TOKEN_EXPIRES_IN}m`, }); - res.cookie("auth_token", token, { + res.cookie('auth_token', token, { expires: new Date(Date.now() + TOKEN_EXPIRES_IN * 60 * 1000), }); res.redirect(`${FRONTEND_ENDPOINT}${pathUrl}`); } catch (err: any) { - console.log("Failed to authorize Google User", err); + console.log('Failed to authorize Google User', err); return res.redirect(`${FRONTEND_ENDPOINT}/oauth/error`); } - }; + } } -export { AuthController, exclude } \ No newline at end of file +export { AuthController, exclude }; diff --git a/backend/src/controllers/ChannelController.ts b/backend/src/controllers/channel.controller.ts similarity index 65% rename from backend/src/controllers/ChannelController.ts rename to backend/src/controllers/channel.controller.ts index f7fa008..7c5586a 100644 --- a/backend/src/controllers/ChannelController.ts +++ b/backend/src/controllers/channel.controller.ts @@ -1,46 +1,48 @@ -import { NextFunction, Request, Response } from "express"; -import User from "../models/user.model"; -import Video from "../models/video.model"; -import { ChannelModel as Channel } from "../models/channel.model"; +import { NextFunction, Request, Response } from 'express'; +import { randomUUID } from 'crypto'; +import User from '../models/user.model'; +import Video from '../models/video.model'; +import { ChannelModel as Channel } from '../models/channel.model'; import createError from '../error'; -import { randomUUID } from "crypto"; const DEFAULT_AVATAR = process.env.DEFAULT_AVATAR as unknown as string; const DEFAULT_THUMBNAIL = process.env.DEFAULT_THUMBNAIL as unknown as string; class ChannelController { static async createChannel(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const user = await User.findById(userId) + const userId = String(resp.locals.user._id); + const user = await User.findById(userId); if (!user) { return resp.status(401).json({ error: 'User not found' }); } try { const channel = new Channel({ - "name": req.body.channelName || randomUUID(), - "userId": userId, - "imgUrl": req.body.imgUrl || user.avatar, - ...req.body + name: req.body.channelName || randomUUID(), + userId, + imgUrl: req.body.imgUrl || user.avatar, + ...req.body, }); if (!channel) { - return next(createError(404, "Channel could not be created!")); + return next(createError(404, 'Channel could not be created!')); } - await channel.save() - user.channels.push(channel) - await user.save() + await channel.save(); + user.channels.push(channel); + await user.save(); resp.status(200).json(channel); } catch (err) { next(err); } - }; - + } + static async updateChannel(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const user = await User.findById(userId) + const userId = String(resp.locals.user._id); + const user = await User.findById(userId); if (!user) { return resp.status(401).json({ error: 'User not found' }); } - const ownedChannel = user.channels.filter((channel) => channel?._id?.toString() === req.params.id) + const ownedChannel = user.channels.filter( + (channel) => channel?._id?.toString() === req.params.id, + ); if (ownedChannel) { try { const updatedChannel = await Channel.findByIdAndUpdate( @@ -48,41 +50,41 @@ class ChannelController { { $set: req.body, }, - { new: true } + { new: true }, ); resp.status(200).json(updatedChannel); } catch (err) { next(err); } } else { - return next(createError(403, "You can only update your channel!")); + return next(createError(403, 'You can only update your channel!')); } - }; + } static async deleteChannel(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const channel = await Channel.findById(req.params.id) + const userId = String(resp.locals.user._id); + const channel = await Channel.findById(req.params.id); if (!channel) { return resp.status(401).json({ error: 'Channel not found' }); } if (channel.userId === userId) { try { await Channel.findByIdAndDelete(req.params.id); - resp.status(200).json("Channel has been deleted."); + resp.status(200).json('Channel has been deleted.'); } catch (err) { next(err); } } else { - return next(createError(403, "You can only delete your channel!")); + return next(createError(403, 'You can only delete your channel!')); } - }; + } static async uploadVideo(req: Request, resp: Response, next: NextFunction) { const userId = String(resp.locals.user._id); if (!userId) { return resp.status(401).json({ error: 'User has not logged in' }); } - const user = await User.findById(userId) + const user = await User.findById(userId); if (!user) { return resp.status(401).json({ error: 'Unauthorized user' }); } @@ -94,14 +96,15 @@ class ChannelController { if (!channel) { return resp.status(401).json({ error: 'Channel not found' }); } - const newVideo = new Video({ - userId: userId, + const newVideo = new Video({ + userId, channelId: channel.id, imgUrl: req.body.imgUrl || DEFAULT_THUMBNAIL, - ...req.body }); + ...req.body, + }); try { const savedVideo = await newVideo.save(); - channel.videos.push(savedVideo.id) + channel.videos.push(savedVideo.id); channel.save(); resp.status(201).json(savedVideo); } catch (err) { @@ -113,39 +116,38 @@ class ChannelController { try { const channels = await Channel.aggregate([{ $sample: { size: 20 } }]); if (!channels) { - return next(createError(404, "No Channels found!")); + return next(createError(404, 'No Channels found!')); } resp.status(200).json(channels); } catch (err) { next(err); } - }; - + } + static async viewChannel(req: Request, resp: Response, next: NextFunction) { try { const channel = await Channel.findById(req.params.id); if (!channel) { - return next(createError(404, "Channel not found!")); + return next(createError(404, 'Channel not found!')); } resp.status(200).json(channel); } catch (err) { next(err); } - }; + } - static async search(req: Request, resp: Response, next: NextFunction){ + static async search(req: Request, resp: Response, next: NextFunction) { const query = req.query.q; try { const channels = await Channel.find({ - name: { $regex: query, $options: "i" }, + name: { $regex: query, $options: 'i' }, }).limit(20); - if (!channels) return next(createError(404, "Channels not found!")); + if (!channels) return next(createError(404, 'Channels not found!')); resp.status(200).json(channels); } catch (err) { next(err); } - }; - + } } export default ChannelController; diff --git a/backend/src/controllers/CommentController.ts b/backend/src/controllers/comment.controller.ts similarity index 65% rename from backend/src/controllers/CommentController.ts rename to backend/src/controllers/comment.controller.ts index 2640404..648af84 100644 --- a/backend/src/controllers/CommentController.ts +++ b/backend/src/controllers/comment.controller.ts @@ -1,15 +1,15 @@ -import { NextFunction, Request, Response } from "express"; -import Comment from "../models/comment.model"; -import Video from "../models/video.model"; -import createError from "../error"; +import { NextFunction, Request, Response } from 'express'; +import Comment from '../models/comment.model'; +import Video from '../models/video.model'; +import createError from '../error'; class CommentController { static async createComment(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const newComment = new Comment({ + const userId = String(resp.locals.user._id); + const newComment = new Comment({ ...req.body, - userId: userId, - videoId: req.params.videoId + userId, + videoId: req.params.videoId, }); try { const savedComment = await newComment.save(); @@ -17,45 +17,45 @@ class CommentController { } catch (err) { next(err); } - }; + } static async updateComment(req: Request, resp: Response, next: NextFunction) { try { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); const comment = await Comment.findById(req.params.id); const video = await Video.findById(comment?.videoId); if (userId === comment?.userId || userId === video?.userId) { const updatedComment = await Comment.findByIdAndUpdate( req.params.id, { - $set: {...req.body, updatedAt: new Date()} + $set: { ...req.body, updatedAt: new Date() }, }, - { new: true } + { new: true }, ); resp.status(200).json(updatedComment); } else { - return next(createError(403, "You can only update your comment!")); + return next(createError(403, 'You can only update your comment!')); } } catch (err) { next(err); } - }; + } static async deleteComment(req: Request, resp: Response, next: NextFunction) { try { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); const comment = await Comment.findById(req.params.id); const video = await Video.findById(req.params.id); if (userId === comment?.userId || userId === video?.userId) { await Comment.findByIdAndDelete(req.params.id); - resp.status(204).json("The comment has been deleted."); + resp.status(204).json('The comment has been deleted.'); } else { - return next(createError(403, "You can only delete your comment!")); + return next(createError(403, 'You can only delete your comment!')); } } catch (err) { next(err); } - }; + } static async getComments(req: Request, resp: Response, next: NextFunction) { try { @@ -64,7 +64,7 @@ class CommentController { } catch (err) { next(err); } - }; + } } -export default CommentController; \ No newline at end of file +export default CommentController; diff --git a/backend/src/controllers/download.controller.ts b/backend/src/controllers/download.controller.ts new file mode 100644 index 0000000..d587e5d --- /dev/null +++ b/backend/src/controllers/download.controller.ts @@ -0,0 +1,40 @@ +import { NextFunction, Request, Response } from 'express'; +import { S3, GetObjectCommand } from '@aws-sdk/client-s3'; +import { Readable } from 'stream'; +import createError from '../error'; +import Video from '../models/video.model'; +import User from '../models/user.model'; +import { s3Config } from '../utils/aws'; + +const s3: S3 = new S3(s3Config); + +class DownloadController { + static async downloadVideo(req: Request, resp: Response, next: NextFunction) { + const video = await Video.findById(req.params.id); + if (!video) { + return next(createError(404, 'Video not found!')); + } + try { + // Get Video Metadata from AWS S3 Bucket + const params = { + Bucket: process.env.AWS_BUCKET_NAME, + Key: video.filename, + }; + + const command = new GetObjectCommand(params); + const response = await s3.send(command); + const stream = Readable.from(response.Body as any); + + // Send Video to the client + resp.status(200); + resp.set('Content-Type', response.ContentType); + resp.set('Content-Disposition', `attachment; filename=${video.filename}`); + stream.pipe(resp); + } catch (e) { + console.error(e); + return next(createError(500, 'Failed to download video!')); + } + } +} + +export default DownloadController; diff --git a/backend/src/controllers/stream.controller.ts b/backend/src/controllers/stream.controller.ts new file mode 100644 index 0000000..074b679 --- /dev/null +++ b/backend/src/controllers/stream.controller.ts @@ -0,0 +1,82 @@ +import { NextFunction, Request, Response } from 'express'; +import { S3, GetObjectCommand } from '@aws-sdk/client-s3'; +import ffmpeg from 'fluent-ffmpeg'; +import { config } from 'dotenv'; +import { Readable } from 'stream'; +import Video from '../models/video.model'; +import { s3Config } from '../utils/aws'; +import createError from '../error'; + +config(); + +const s3: S3 = new S3(s3Config); +// Set the path to the ffmpeg binary + +// linux +// ffmpeg.setFfmpegPath('/usr/bin/ffmpeg'); + +// Windows +ffmpeg.setFfmpegPath('C:\\ProgramData\\chocolatey\\bin\\ffmpeg.exe'); + +class StreamingController { + static async getStream(req: Request, resp: Response, next: NextFunction) { + try { + const video: any = await Video.findById(req.params.id); + if (!video) { + return next(createError(404, 'Video not found!')); + } + + // Convert the video file to a streamable format using ffmpeg + const command = new GetObjectCommand({ + Bucket: process.env.AWS_BUCKET_NAME, + Key: video.filename, + }); + const response = await s3.send(command); + const stream = Readable.from(response.Body as any); + + resp.set('Content-Type', 'video/mp4'); + resp.set('Transfer-Encoding', 'chunked'); + + let totalTime: number; + // Stream the video + const proc = ffmpeg(stream) + // set the size + // .withSize('50%') + // set fps + .withFps(24) + .outputOptions(['-movflags isml+frag_keyframe']) + .toFormat('mp4') + .withAudioCodec('copy') + .on('start', (commandLine) => { + // something message for init process + console.log('Streaming started!'); + }) + .on('codecData', (data) => { + // HERE YOU GET THE TOTAL TIME + totalTime = parseInt(data.duration.replace(/:/g, '')); + }) + .on('error', (err: any, stdout: any, stderr: any) => { + console.log(`an error happened: ${err.message}`); + console.log(`ffmpeg stdout: ${stdout}`); + console.log(`ffmpeg stderr: ${stderr}`); + }) + .on('end', () => { + console.log('Processing finished !'); + }) + .on('progress', (progress: any) => { + // console.log('Processing: ' + progress.percent + '% done'); + const time: number = parseInt(progress.timemark.replace(/:/g, '')); + + // AND HERE IS THE CALCULATION + const percent = (time / totalTime) * 100; + console.log(`Processing: ${percent >= 0 ? ~~percent : 0}% done`); + }) + .pipe(resp, { end: true }); + } catch (e) { + console.error(e); + return next(createError(500, 'Failed to stream video!')); + } + } +} + +export default StreamingController; diff --git a/backend/src/controllers/UploadController.ts b/backend/src/controllers/upload.controller.ts similarity index 59% rename from backend/src/controllers/UploadController.ts rename to backend/src/controllers/upload.controller.ts index 8440dad..0f80d51 100644 --- a/backend/src/controllers/UploadController.ts +++ b/backend/src/controllers/upload.controller.ts @@ -1,46 +1,46 @@ -import { Upload } from "@aws-sdk/lib-storage"; -import { NextFunction, Request, Response } from "express"; -import createError from "../error"; -import { S3 } from "@aws-sdk/client-s3"; +import { Upload } from '@aws-sdk/lib-storage'; +import { NextFunction, Request, Response } from 'express'; +import { S3 } from '@aws-sdk/client-s3'; import multer from 'multer'; -import Video from "../models/video.model"; -import User from "../models/user.model"; -import { ChannelModel as Channel } from "../models/channel.model"; -import { s3Config } from "../utils/aws"; -import { randomUUID } from "crypto"; +import { randomUUID } from 'crypto'; import { config } from 'dotenv'; +import createError from '../error'; +import Video from '../models/video.model'; +import User from '../models/user.model'; +import { ChannelModel as Channel } from '../models/channel.model'; +import { s3Config } from '../utils/aws'; config(); -const s3: S3 = new S3( s3Config ); +const s3: S3 = new S3(s3Config); const storage = multer.memoryStorage(); -const upload = multer({ storage: storage }).single('video'); +const upload = multer({ storage }).single('video'); class UploadController { static async uploadVideo(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) - const user = await User.findById(userId) + const userId = String(resp.locals.user._id); + const user = await User.findById(userId); if (!user) { - return next(createError(401, "User not found!")); + return next(createError(401, 'User not found!')); } - const channel = await Channel.findById(req.params.id) + const channel = await Channel.findById(req.params.id); if (!channel) { - return next(createError(401, "Channel not found!")); + return next(createError(401, 'Channel not found!')); } try { // Create a unique ID for the video const videoUUID = randomUUID(); // Call multer middleware to get the video file - upload(req, resp, async function (err: any) { + upload(req, resp, async (err: any) => { if (err instanceof multer.MulterError) { // A Multer error occurred when uploading. console.error(err); - return next(createError(400, "Error uploading video file!")); - } else if (err) { + return next(createError(400, 'Error uploading video file!')); + } if (err) { // An unknown error occurred when uploading. console.error(err); - return next(createError(500, "Failed to upload video file!")); + return next(createError(500, 'Failed to upload video file!')); } // Create a temporary path to store the video @@ -56,14 +56,14 @@ class UploadController { }).done(); // Save the video metadata to MongoDB const video = new Video({ - userId: userId, + userId, channelId: channel._id, imgUrl: req.body.imgUrl || process.env.DEFAULT_THUMBNAIL, title: req.body.title, filename: `${videoUUID}.mp4`, description: req.body.description || videoUUID, videoUrl: (data as any)?.Location, - ...req.body + ...req.body, }); await video.save(); @@ -74,7 +74,7 @@ class UploadController { }); } catch (e) { console.error(e); - return next(createError(500, "Failed to upload video file!")); + return next(createError(500, 'Failed to upload video file!')); } } } diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts new file mode 100644 index 0000000..410c8e5 --- /dev/null +++ b/backend/src/controllers/user.controller.ts @@ -0,0 +1,183 @@ +import { NextFunction, Request, Response } from 'express'; +import User from '../models/user.model'; +import Video from '../models/video.model'; +import { ChannelModel as Channel } from '../models/channel.model'; +import createError from '../error'; +import { exclude } from './auth.controller'; + +class UserController { + static async getMeHandler(req: Request, res: Response, next: NextFunction) { + try { + const user = exclude(res.locals.user, ['password']); + res.status(200).json({ + status: 'success', + data: { + user: exclude(user, ['password']), + }, + }); + } catch (err: any) { + next(err); + } + } + + static async updateUser(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + console.log(userId); + if (req.params.id === userId) { + try { + const updatedUser = await User.findByIdAndUpdate( + userId, + { + $set: req.body, + }, + { new: true }, + ); + resp.status(200).json(updatedUser); + } catch (err) { + next(err); + } + } else { + return next(createError(403, 'You can only update your account!')); + } + } + + static async deleteUser(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + if (req.params.id === userId) { + try { + await User.findByIdAndDelete(userId); + resp.status(200).json('User has been deleted.'); + } catch (err) { + next(err); + } + } else { + return next(createError(403, 'You can only delete your account!')); + } + } + + static async getUser(req: Request, resp: Response, next: NextFunction) { + const userId = String(req.params.id); + try { + const user = await User.findById(userId); + resp.status(200).json(user); + } catch (err) { + next(err); + } + } + + static async subscribe(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + try { + await User.findByIdAndUpdate(userId, { + $push: { subscriptions: req.params.channelId }, + }); + await Channel.findByIdAndUpdate(req.params.channelId, { + $inc: { subscribers: 1 }, + }); + resp.status(200).json('Subscription successfull.'); + } catch (err) { + next(err); + } + } + + static async unsubscribe(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + try { + await User.findByIdAndUpdate(userId, { + $pull: { subscriptions: req.params.channelId }, + }); + await Channel.findByIdAndUpdate(req.params.channelId, { + $inc: { subscribers: -1 }, + }); + resp.status(200).json('Unsubscription successfull.'); + } catch (err) { + next(err); + } + } + + static async like(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + console.log(userId); + const { videoId } = req.params; + try { + await Video.findByIdAndUpdate(videoId, { + $addToSet: { likes: userId }, + $pull: { dislikes: userId }, + }); + resp.status(200).json('The video has been liked.'); + } catch (err) { + next(err); + } + } + + static async dislike(req: Request, resp: Response, next: NextFunction) { + const userId = String(resp.locals.user._id); + const { videoId } = req.params; + try { + await Video.findByIdAndUpdate(videoId, { + $addToSet: { dislikes: userId }, + $pull: { likes: userId }, + }); + resp.status(200).json('The video has been disliked.'); + } catch (err) { + next(err); + } + } + + static async getSubscriptions( + req: Request, + resp: Response, + next: NextFunction, + ) { + try { + const userId = String(resp.locals.user._id); + const user = await User.findById(userId); + if (!user) { + return next(createError(403, 'You are not logged in!')); + } + const subscribedChannels = user.subscriptions; + + const list = await Promise.all( + subscribedChannels.map(async (channelId: any) => Channel.findById(channelId)), + ); + if (!list) return next(createError(404, 'Channels not found!')); + resp + .status(200) + .json(list.flat().sort((a: any, b: any) => b.createdAt - a.createdAt)); + } catch (err) { + next(err); + } + } + + static async getHistory(req: Request, resp: Response, next: NextFunction) { + try { + const userId = String(resp.locals.user._id); + const user = await User.findById(userId); + if (!user) { + return next(createError(403, 'You are not logged in!')); + } + const watchedVideos = user.history; + const list = await Promise.all( + watchedVideos.map(async (videoId) => Video.findById(videoId)), + ); + + resp + .status(200) + .json(list.flat().sort((a: any, b: any) => b.updatedAt - a.updatedAt)); + } catch (err) { + next(err); + } + } + + static async getAllUsers(req: Request, resp: Response, next: NextFunction) { + try { + const users = await User.find({}); + if (!users) return next(createError(404, 'No Users found!')); + resp.status(200).json(users); + } catch (err) { + next(err); + } + } +} + +export default UserController; diff --git a/backend/src/controllers/VideoController.ts b/backend/src/controllers/video.controller.ts similarity index 73% rename from backend/src/controllers/VideoController.ts rename to backend/src/controllers/video.controller.ts index ac5388e..267fb70 100644 --- a/backend/src/controllers/VideoController.ts +++ b/backend/src/controllers/video.controller.ts @@ -1,12 +1,12 @@ import mime from 'mime-types'; -import { NextFunction, Request, Response } from "express"; -import User from "../models/user.model"; -import Video from "../models/video.model"; +import { NextFunction, Request, Response } from 'express'; +import User from '../models/user.model'; +import Video from '../models/video.model'; import createError from '../error'; class VideoController { static async setPublic(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); if (!userId) { return resp.status(401).json({ error: 'Unauthorized' }); } @@ -18,9 +18,9 @@ class VideoController { const updatedVideo = await Video.findByIdAndUpdate( req.params.id, { - $set: { isPublic: true}, + $set: { isPublic: true }, }, - { new: true } + { new: true }, ); if (!updatedVideo?.isModified) { return resp.status(404).json({ error: 'Video not found' }); @@ -29,7 +29,7 @@ class VideoController { } static async setPrivate(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); if (!userId) { return resp.status(401).json({ error: 'Unauthorized' }); } @@ -41,9 +41,9 @@ class VideoController { const updatedVideo = await Video.findByIdAndUpdate( req.params.id, { - $set: { isPublic: false } + $set: { isPublic: false }, }, - { new: true } + { new: true }, ); if (!updatedVideo?.isModified) { return resp.status(404).json({ error: 'Video not found' }); @@ -55,7 +55,7 @@ class VideoController { if (!req?.body?.tags) { return resp.status(404).json({ error: 'No tag found' }); } - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); if (!userId) { return resp.status(401).json({ error: 'Unauthorized' }); } @@ -64,9 +64,9 @@ class VideoController { console.error('Video was not found!!!'); return resp.status(404).json({ error: 'Video not found' }); } - const updatedVideo = await Video.findByIdAndUpdate(req.params.id,{ - $addToSet:{tags:req.body.tag} - }) + const updatedVideo = await Video.findByIdAndUpdate(req.params.id, { + $addToSet: { tags: req.body.tag }, + }); if (!updatedVideo?.isModified) { return resp.status(404).json({ error: 'Video not found' }); } @@ -77,7 +77,7 @@ class VideoController { if (!req?.body?.tags) { return resp.status(404).json({ error: 'No tag found' }); } - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); if (!userId) { return resp.status(401).json({ error: 'Unauthorized' }); } @@ -86,9 +86,9 @@ class VideoController { console.error('Video was not found!!!'); return resp.status(404).json({ error: 'Video not found' }); } - const updatedVideo = await Video.findByIdAndUpdate(req.params.id,{ - $pull:{tags:req.body.tag} - }) + const updatedVideo = await Video.findByIdAndUpdate(req.params.id, { + $pull: { tags: req.body.tag }, + }); if (!updatedVideo?.isModified) { return resp.status(404).json({ error: 'Video not found' }); } @@ -96,62 +96,62 @@ class VideoController { } static async updateVideo(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); try { const video = await Video.findById(req.params.id); - if (!video) return next(createError(404, "Video not found!")); + if (!video) return next(createError(404, 'Video not found!')); if (userId === video.userId) { const updatedVideo = await Video.findByIdAndUpdate( req.params.id, { - $set: {...req.body} + $set: { ...req.body }, }, - { new: true } + { new: true }, ); resp.status(200).json(updatedVideo); } else { - return next(createError(403, "You can only update your video!")); + return next(createError(403, 'You can only update your video!')); } } catch (err) { next(err); } - }; - + } + static async deleteVideo(req: Request, resp: Response, next: NextFunction) { - const userId = String(resp.locals.user._id) + const userId = String(resp.locals.user._id); try { const video = await Video.findById(req.params.id); - if (!video) return next(createError(404, "Video not found!")); + if (!video) return next(createError(404, 'Video not found!')); if (userId === video.userId) { await Video.findByIdAndDelete(req.params.id); - resp.status(200).json("The video has been deleted."); + resp.status(200).json('The video has been deleted.'); } else { - return next(createError(403, "You can only delete your video!")); + return next(createError(403, 'You can only delete your video!')); } } catch (err) { next(err); } - }; - + } + static async getVideo(req: Request, resp: Response, next: NextFunction) { try { const video = await Video.findById(req.params.id); if (!video) { - return next(createError(404, "Video not found!")); + return next(createError(404, 'Video not found!')); } resp.status(200).json(video); } catch (err) { next(err); } - }; - + } + static async addView(req: Request, resp: Response, next: NextFunction) { try { const video = await Video.findByIdAndUpdate(req.params.id, { $inc: { views: 1 }, }); if (!video) { - return next(createError(404, "Video not found!")); + return next(createError(404, 'Video not found!')); } if (resp?.locals?.user?._id) { const user = await User.findById(String(resp.locals.user._id)); @@ -160,56 +160,58 @@ class VideoController { user.save(); } } - resp.status(200).json("The view has been increased."); + resp.status(200).json('The view has been increased.'); } catch (err) { next(err); } - }; - + } + static async getRandom(req: Request, resp: Response, next: NextFunction) { try { const videos = await Video.aggregate([{ $sample: { size: 20 } }]); - if (!videos) return next(createError(404, "Videos not found!")); + if (!videos) return next(createError(404, 'Videos not found!')); resp.status(200).json(videos); } catch (err) { next(err); } - }; - + } + static async getTrending(req: Request, resp: Response, next: NextFunction) { try { const videos = await Video.find().sort({ views: -1 }); - if (!videos) return next(createError(404, "Videos not found!")); + if (!videos) return next(createError(404, 'Videos not found!')); resp.status(200).json(videos); } catch (err) { next(err); } - }; - + } + static async getByTag(req: Request, resp: Response, next: NextFunction) { - let tags = req?.query?.tags?.toString().split(","); - tags = tags?.map((item: string) => (String(item.charAt(0))).toUpperCase() + item.slice(1)) + let tags = req?.query?.tags?.toString().split(','); + tags = tags?.map( + (item: string) => String(item.charAt(0)).toUpperCase() + item.slice(1), + ); try { const videos = await Video.find({ tags: { $in: tags } }).limit(20); - if (!videos) return next(createError(404, "Videos not found!")); + if (!videos) return next(createError(404, 'Videos not found!')); resp.status(200).json(videos); } catch (err) { next(err); } - }; + } - static async search(req: Request, resp: Response, next: NextFunction){ + static async search(req: Request, resp: Response, next: NextFunction) { const query = req.query.q; try { const videos = await Video.find({ - title: { $regex: query, $options: "i" }, + title: { $regex: query, $options: 'i' }, }).limit(20); - if (!videos) return next(createError(404, "Videos not found!")); + if (!videos) return next(createError(404, 'Videos not found!')); resp.status(200).json(videos); } catch (err) { next(err); } - }; + } } export default VideoController; diff --git a/backend/src/error.ts b/backend/src/error.ts index e8a3b85..ca43251 100644 --- a/backend/src/error.ts +++ b/backend/src/error.ts @@ -1,15 +1,16 @@ class CustomError extends Error { - statusCode; - constructor(statusCode: any, message: any) { - super(); - this.message = message; - this.statusCode = statusCode || "error"; - } + statusCode; + + constructor(statusCode: any, message: any) { + super(); + this.message = message; + this.statusCode = statusCode || 'error'; + } } const createError = (status: any, message: any) => { - const err: CustomError = new CustomError(status, message); - return err -} + const err: CustomError = new CustomError(status, message); + return err; +}; -export default createError; \ No newline at end of file +export default createError; diff --git a/backend/src/middleware/getAuthToken.ts b/backend/src/middleware/getAuthToken.ts index b54811f..c1949f2 100644 --- a/backend/src/middleware/getAuthToken.ts +++ b/backend/src/middleware/getAuthToken.ts @@ -1,28 +1,28 @@ -import { NextFunction, Request, Response } from "express"; -import jwt from "jsonwebtoken"; -import { exclude } from "../controllers/AuthController"; -import User from '../models/user.model' +import { NextFunction, Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; +import { exclude } from '../controllers/auth.controller'; +import User from '../models/user.model'; // Get token from request header or from cookie to authenticate user export const getAuthToken = async ( req: Request, res: Response, - next: NextFunction + next: NextFunction, ) => { try { let token; if ( - req.headers.authorization && - req.headers.authorization.startsWith("Bearer") + req.headers.authorization + && req.headers.authorization.startsWith('Bearer') ) { - token = req.headers.authorization.split(" ")[1]; + token = req.headers.authorization.split(' ')[1]; } else if (req.cookies.auth_token) { token = req.cookies.auth_token; } if (!token) { return res.status(401).json({ - message: "You are not logged in", + message: 'You are not logged in', }); } @@ -38,12 +38,12 @@ export const getAuthToken = async ( if (!user) { return res.status(401).json({ - status: "fail", - message: "User with that token no longer exist", + status: 'fail', + message: 'User with that token no longer exist', }); } - res.locals.user = exclude(user, ["password", "history", "subscriptions"]); + res.locals.user = exclude(user, ['password', 'history', 'subscriptions']); next(); } catch (err: any) { next(err); diff --git a/backend/src/middleware/requireLogin.ts b/backend/src/middleware/requireLogin.ts index c16c9d1..4138a5d 100644 --- a/backend/src/middleware/requireLogin.ts +++ b/backend/src/middleware/requireLogin.ts @@ -1,18 +1,18 @@ -import { NextFunction, Request, Response } from "express"; +import { NextFunction, Request, Response } from 'express'; // Read from the local response object to get the stored user json data // for endpoints that require a logged in user export const requireLogin = ( req: Request, resp: Response, - next: NextFunction + next: NextFunction, ) => { try { - const user = resp.locals.user; + const { user } = resp.locals; if (!user) { return resp.status(401).json({ - status: "fail", - message: "Invalid token or session has expired", + status: 'fail', + message: 'Invalid token or session has expired', }); } next(); diff --git a/backend/src/middleware/validate.ts b/backend/src/middleware/validate.ts index 8b478f2..9927260 100644 --- a/backend/src/middleware/validate.ts +++ b/backend/src/middleware/validate.ts @@ -1,24 +1,22 @@ -import { NextFunction, Request, Response } from "express"; -import { AnyZodObject, ZodError } from "zod"; +import { NextFunction, Request, Response } from 'express'; +import { AnyZodObject, ZodError } from 'zod'; -export const validate = - (schema: AnyZodObject) => - (req: Request, res: Response, next: NextFunction) => { - try { - schema.parse({ - params: req.params, - query: req.query, - body: req.body, - }); +export const validate = (schema: AnyZodObject) => (req: Request, res: Response, next: NextFunction) => { + try { + schema.parse({ + params: req.params, + query: req.query, + body: req.body, + }); - next(); - } catch (err: any) { - if (err instanceof ZodError) { - return res.status(400).json({ - status: "fail", - error: err.errors, - }); - } - next(err); + next(); + } catch (err: any) { + if (err instanceof ZodError) { + return res.status(400).json({ + status: 'fail', + error: err.errors, + }); } - }; + next(err); + } +}; diff --git a/backend/src/models/channel.model.ts b/backend/src/models/channel.model.ts index 6263a9c..85aaa2c 100644 --- a/backend/src/models/channel.model.ts +++ b/backend/src/models/channel.model.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose from 'mongoose'; // Channel model const ChannelSchema = new mongoose.Schema( @@ -41,8 +41,8 @@ const ChannelSchema = new mongoose.Schema( default: true, }, }, - { timestamps: true } + { timestamps: true }, ); -const ChannelModel = mongoose.model("Channel", ChannelSchema); +const ChannelModel = mongoose.model('Channel', ChannelSchema); export { ChannelSchema, ChannelModel }; diff --git a/backend/src/models/comment.model.ts b/backend/src/models/comment.model.ts index 88d5905..b874cac 100644 --- a/backend/src/models/comment.model.ts +++ b/backend/src/models/comment.model.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose from 'mongoose'; // Comments model const CommentSchema = new mongoose.Schema( @@ -27,8 +27,8 @@ const CommentSchema = new mongoose.Schema( default: [], }, }, - { timestamps: true } + { timestamps: true }, ); -const CommentModel = mongoose.model("Comment", CommentSchema); -export default CommentModel; \ No newline at end of file +const CommentModel = mongoose.model('Comment', CommentSchema); +export default CommentModel; diff --git a/backend/src/models/user.model.ts b/backend/src/models/user.model.ts index fcab3a5..f29bf65 100644 --- a/backend/src/models/user.model.ts +++ b/backend/src/models/user.model.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose from 'mongoose'; import { ChannelSchema } from './channel.model'; // User model @@ -35,8 +35,8 @@ const UserSchema = new mongoose.Schema( type: [ChannelSchema], }, }, - { timestamps: true } + { timestamps: true }, ); -const UserModel = mongoose.model("User", UserSchema); -export default UserModel; \ No newline at end of file +const UserModel = mongoose.model('User', UserSchema); +export default UserModel; diff --git a/backend/src/models/video.model.ts b/backend/src/models/video.model.ts index 51d31e2..5d0f2df 100644 --- a/backend/src/models/video.model.ts +++ b/backend/src/models/video.model.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose from 'mongoose'; // Videos model const VideoSchema = new mongoose.Schema( @@ -51,8 +51,8 @@ const VideoSchema = new mongoose.Schema( default: true, }, }, - { timestamps: true } + { timestamps: true }, ); -const VideoModel = mongoose.model("Video", VideoSchema); +const VideoModel = mongoose.model('Video', VideoSchema); export default VideoModel; diff --git a/backend/src/routes/auth.route.ts b/backend/src/routes/auth.route.ts index 14ff7ff..3c5fa01 100644 --- a/backend/src/routes/auth.route.ts +++ b/backend/src/routes/auth.route.ts @@ -1,22 +1,34 @@ -import express from "express"; -import { AuthController } from "../controllers/AuthController"; -import { requireLogin } from "../middleware/requireLogin"; -import { getAuthToken } from "../middleware/getAuthToken"; -import { validate } from "../middleware/validate"; -import { registerUserModel, loginUserModel, resetPasswordModel } from "../services/user.service"; +import express from 'express'; +import { AuthController } from '../controllers/auth.controller'; +import { requireLogin } from '../middleware/requireLogin'; +import { getAuthToken } from '../middleware/getAuthToken'; +import { validate } from '../middleware/validate'; +import { + registerUserModel, + loginUserModel, + resetPasswordModel, +} from '../services/user.service'; const router = express.Router(); // Register user route -router.post("/register", validate(registerUserModel), AuthController.registerHandler); // POST /auth/register +router.post( + '/register', + validate(registerUserModel), + AuthController.registerHandler, +); // POST /auth/register // Login user route -router.post("/login", validate(loginUserModel), AuthController.loginHandler); // POST /auth/login +router.post('/login', validate(loginUserModel), AuthController.loginHandler); // POST /auth/login // Logout user route -router.get("/logout", getAuthToken, requireLogin, AuthController.logoutHandler); // GET /auth/logout +router.get('/logout', getAuthToken, requireLogin, AuthController.logoutHandler); // GET /auth/logout // Reset user password route -router.put("/reset-password", validate(resetPasswordModel), AuthController.resetPasswordHandler); // PUT /auth/reset-password +router.put( + '/reset-password', + validate(resetPasswordModel), + AuthController.resetPasswordHandler, +); // PUT /auth/reset-password export default router; diff --git a/backend/src/routes/channel.route.ts b/backend/src/routes/channel.route.ts index 0068f23..ac0ab97 100644 --- a/backend/src/routes/channel.route.ts +++ b/backend/src/routes/channel.route.ts @@ -1,8 +1,8 @@ -import express from "express"; -import ChannelController from "../controllers/ChannelController"; -import UploadController from "../controllers/UploadController" -import { getAuthToken } from "../middleware/getAuthToken"; -import { requireLogin } from "../middleware/requireLogin"; +import express from 'express'; +import ChannelController from '../controllers/channel.controller'; +import UploadController from '../controllers/upload.controller'; +import { getAuthToken } from '../middleware/getAuthToken'; +import { requireLogin } from '../middleware/requireLogin'; const router = express.Router(); @@ -13,8 +13,18 @@ router.get('/', ChannelController.getChannels); // GET /channels router.get('/:id/view', ChannelController.viewChannel); // GET /channels/:id/view router.put('/:id', getAuthToken, requireLogin, ChannelController.updateChannel); // PUT /channels/:id router.post('/', getAuthToken, requireLogin, ChannelController.createChannel); // POST /channels -router.post('/:id/upload', getAuthToken, requireLogin, UploadController.uploadVideo); // POST /channels/:id +router.post( + '/:id/upload', + getAuthToken, + requireLogin, + UploadController.uploadVideo, +); // POST /channels/:id router.get('/search', ChannelController.search); // GET /channels/search -router.delete('/:id', getAuthToken, requireLogin, ChannelController.deleteChannel); // DELETE /channels/:id +router.delete( + '/:id', + getAuthToken, + requireLogin, + ChannelController.deleteChannel, +); // DELETE /channels/:id export default router; diff --git a/backend/src/routes/comment.route.ts b/backend/src/routes/comment.route.ts index 7499128..14fbe12 100644 --- a/backend/src/routes/comment.route.ts +++ b/backend/src/routes/comment.route.ts @@ -1,16 +1,26 @@ -import express from "express"; -import CommentController from "../controllers/CommentController"; -import { getAuthToken } from "../middleware/getAuthToken"; -import { requireLogin } from "../middleware/requireLogin"; +import express from 'express'; +import CommentController from '../controllers/comment.controller'; +import { getAuthToken } from '../middleware/getAuthToken'; +import { requireLogin } from '../middleware/requireLogin'; const router = express.Router(); // router.use(getAuthToken, requireLogin); // Comments route -router.put("/:id", getAuthToken, requireLogin, CommentController.updateComment) // PUT /comments/:id -router.delete("/:id", getAuthToken, requireLogin, CommentController.deleteComment) // DELETE /comments/:id -router.get("/:videoId", CommentController.getComments) // GET /comments/:videoId -router.post("/:videoId", getAuthToken, requireLogin, CommentController.createComment) // POST /comments/ +router.put('/:id', getAuthToken, requireLogin, CommentController.updateComment); // PUT /comments/:id +router.delete( + '/:id', + getAuthToken, + requireLogin, + CommentController.deleteComment, +); // DELETE /comments/:id +router.get('/:videoId', CommentController.getComments); // GET /comments/:videoId +router.post( + '/:videoId', + getAuthToken, + requireLogin, + CommentController.createComment, +); // POST /comments/ export default router; diff --git a/backend/src/routes/session.route.ts b/backend/src/routes/session.route.ts index 1f5dec7..9c4830f 100644 --- a/backend/src/routes/session.route.ts +++ b/backend/src/routes/session.route.ts @@ -1,5 +1,5 @@ import express from 'express'; -import {AuthController} from '../controllers/AuthController'; +import { AuthController } from '../controllers/auth.controller'; const router = express.Router(); diff --git a/backend/src/routes/user.route.ts b/backend/src/routes/user.route.ts index 11fa889..4f8e5ce 100644 --- a/backend/src/routes/user.route.ts +++ b/backend/src/routes/user.route.ts @@ -1,43 +1,68 @@ -import express from "express"; -import UserController from "../controllers/UserController"; -import { getAuthToken } from "../middleware/getAuthToken"; -import { requireLogin } from "../middleware/requireLogin"; +import express from 'express'; +import UserController from '../controllers/user.controller'; +import { getAuthToken } from '../middleware/getAuthToken'; +import { requireLogin } from '../middleware/requireLogin'; const router = express.Router(); // router.use(getAuthToken, requireLogin); // Get all users -router.get("/", getAuthToken, requireLogin, UserController.getAllUsers); // GET /users +router.get('/', getAuthToken, requireLogin, UserController.getAllUsers); // GET /users // Get my profile route -router.get("/me", getAuthToken, requireLogin, UserController.getMeHandler); // GET /users/me +router.get('/me', getAuthToken, requireLogin, UserController.getMeHandler); // GET /users/me -//get a user -router.get("/:id", UserController.getUser); // GET /users/:id +// get a user +router.get('/:id', UserController.getUser); // GET /users/:id -//update user -router.put("/:id", getAuthToken, requireLogin, UserController.updateUser); // PUT /users/:id +// update user +router.put('/:id', getAuthToken, requireLogin, UserController.updateUser); // PUT /users/:id -//delete user -router.delete("/:id", getAuthToken, requireLogin, UserController.deleteUser); // DELETE /users/:id +// delete user +router.delete('/:id', getAuthToken, requireLogin, UserController.deleteUser); // DELETE /users/:id -//subscribe a user channel -router.put("/subscribe/:channelId", getAuthToken, requireLogin, UserController.subscribe); // PUT /users/subscribe/:channelId +// subscribe a user channel +router.put( + '/subscribe/:channelId', + getAuthToken, + requireLogin, + UserController.subscribe, +); // PUT /users/subscribe/:channelId -//unsubscribe a user channel -router.put("/unsubscribe/:channelId", getAuthToken, requireLogin, UserController.unsubscribe); // PUT /users/unsubscribe/:channelId +// unsubscribe a user channel +router.put( + '/unsubscribe/:channelId', + getAuthToken, + requireLogin, + UserController.unsubscribe, +); // PUT /users/unsubscribe/:channelId -//like a video -router.put("/like/:videoId", getAuthToken, requireLogin, UserController.like); // PUT /users/like/:videoId +// like a video +router.put('/like/:videoId', getAuthToken, requireLogin, UserController.like); // PUT /users/like/:videoId -//dislike a video -router.put("/dislike/:videoId", getAuthToken, requireLogin, UserController.dislike); // PUT /users/dislike/:videoId +// dislike a video +router.put( + '/dislike/:videoId', + getAuthToken, + requireLogin, + UserController.dislike, +); // PUT /users/dislike/:videoId // Get user subscriptions -router.get('/:id/subscriptions', getAuthToken, requireLogin, UserController.getSubscriptions); // GET /users/:id/subscriptions +router.get( + '/:id/subscriptions', + getAuthToken, + requireLogin, + UserController.getSubscriptions, +); // GET /users/:id/subscriptions // Get user watched views history -router.get('/:id/history', getAuthToken, requireLogin, UserController.getHistory); // GET /users/:id/history +router.get( + '/:id/history', + getAuthToken, + requireLogin, + UserController.getHistory, +); // GET /users/:id/history export default router; diff --git a/backend/src/routes/video.route.ts b/backend/src/routes/video.route.ts index ac22cd1..fc40823 100644 --- a/backend/src/routes/video.route.ts +++ b/backend/src/routes/video.route.ts @@ -1,9 +1,9 @@ -import express from "express"; -import VideoController from "../controllers/VideoController"; -import DownloadController from "../controllers/DownloadController"; -import StreamController from "../controllers/StreamController"; -import { getAuthToken } from "../middleware/getAuthToken"; -import { requireLogin } from "../middleware/requireLogin"; +import express from 'express'; +import VideoController from '../controllers/video.controller'; +import DownloadController from '../controllers/download.controller'; +import StreamController from '../controllers/stream.controller'; +import { getAuthToken } from '../middleware/getAuthToken'; +import { requireLogin } from '../middleware/requireLogin'; const router = express.Router(); @@ -16,13 +16,28 @@ router.get('/tags', VideoController.getByTag); // GET /videos/tags router.get('/search', VideoController.search); // GET /videos/search router.get('/:id/view', VideoController.getVideo); // GET /videos/:id router.put('/:id/view', VideoController.addView); // PUT /videos/:id -router.put('/:id/edit', getAuthToken, requireLogin, VideoController.updateVideo); // PUT /videos/:id/edit +router.put( + '/:id/edit', + getAuthToken, + requireLogin, + VideoController.updateVideo, +); // PUT /videos/:id/edit router.put('/:id/tag', getAuthToken, requireLogin, VideoController.addTag); // PUT /videos/:id/tag router.put('/:id/untag', getAuthToken, requireLogin, VideoController.removeTag); // PUT /videos/:id/untag -router.put('/:id/publish', getAuthToken, requireLogin, VideoController.setPublic); // PUT /videos/:id/publish -router.put('/:id/unpublish', getAuthToken, requireLogin, VideoController.setPrivate); // PUT /videos/:id/unpublish +router.put( + '/:id/publish', + getAuthToken, + requireLogin, + VideoController.setPublic, +); // PUT /videos/:id/publish +router.put( + '/:id/unpublish', + getAuthToken, + requireLogin, + VideoController.setPrivate, +); // PUT /videos/:id/unpublish router.delete('/:id', getAuthToken, requireLogin, VideoController.deleteVideo); // DELETE /videos/:id -router.get( '/:id/download', DownloadController.downloadVideo ); // GET /download/:id -router.get( '/:id/stream', StreamController.getStream ); // GET /stream/:id +router.get('/:id/download', DownloadController.downloadVideo); // GET /download/:id +router.get('/:id/stream', StreamController.getStream); // GET /stream/:id export default router; diff --git a/backend/src/server.ts b/backend/src/server.ts index 6ee7cf6..c0d8b41 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,57 +1,60 @@ -require("dotenv").config(); -import path from "path"; -import express, { NextFunction, Request, Response } from "express"; -import morgan from "morgan"; -import cors from "cors"; -import cookieParser from "cookie-parser"; -import userRouter from "./routes/user.route"; -import authRouter from "./routes/auth.route"; -import sessionRouter from "./routes/session.route"; -import commentRouter from "./routes/comment.route"; -import channelRouter from "./routes/channel.route"; -import videoRouter from "./routes/video.route"; +import path from 'path'; +import express, { NextFunction, Request, Response } from 'express'; +import morgan from 'morgan'; +import cors from 'cors'; +import cookieParser from 'cookie-parser'; +import userRouter from './routes/user.route'; +import authRouter from './routes/auth.route'; +import sessionRouter from './routes/session.route'; +import commentRouter from './routes/comment.route'; +import channelRouter from './routes/channel.route'; +import videoRouter from './routes/video.route'; import dbClient from './utils/db'; -import AppController from "./controllers/AppController"; +import AppController from './controllers/app.controller'; + +require('dotenv').config(); const app = express(); -app.use(express.json({ limit: "10kb" })); +app.use(express.json({ limit: '10kb' })); app.use(cookieParser()); -if (process.env.NODE_ENV === "development") app.use(morgan("dev")); -app.use("/api/v1/thumbnails", express.static(path.join(__dirname, "../public"))); +if (process.env.NODE_ENV === 'development') app.use(morgan('dev')); +app.use( + '/api/v1/thumbnails', + express.static(path.join(__dirname, '../public')), +); const FRONTEND_ENDPOINT = process.env.FRONTEND_ENDPOINT as unknown as string; app.use( cors({ credentials: true, origin: [FRONTEND_ENDPOINT], - }) + }), ); -app.use("/api/v1/users", userRouter); -app.use("/api/v1/auth", authRouter); -app.use("/api/v1/sessions", sessionRouter); -app.use("/api/v1/channels", channelRouter); -app.use("/api/v1/videos", videoRouter); -app.use("/api/v1/comments", commentRouter); - +app.use('/api/v1/users', userRouter); +app.use('/api/v1/auth', authRouter); +app.use('/api/v1/sessions', sessionRouter); +app.use('/api/v1/channels', channelRouter); +app.use('/api/v1/videos', videoRouter); +app.use('/api/v1/comments', commentRouter); // GET /status. -app.get('/status', AppController.getStatus); +app.get('/status', AppController.getStatus); // GET /stats. -app.get('/stats', AppController.getStats); +app.get('/stats', AppController.getStats); // Get health status -app.get("/health", (req: Request, res: Response) => { +app.get('/health', (req: Request, res: Response) => { res.status(200).json({ - status: "success", - message: "100% online", + status: 'success', + message: '100% online', }); }); // Unknown Routes -app.all("*", (req: Request, res: Response, next: NextFunction) => { +app.all('*', (req: Request, res: Response, next: NextFunction) => { const err = new Error(`Route ${req.originalUrl} not found`) as any; err.statusCode = 404; next(err); @@ -59,7 +62,7 @@ app.all("*", (req: Request, res: Response, next: NextFunction) => { // Error handling app.use((err: any, req: Request, res: Response, next: NextFunction) => { - err.status = err.status || "error"; + err.status = err.status || 'error'; err.statusCode = err.statusCode || 500; res.status(err.statusCode).json({ diff --git a/backend/src/services/session.service.ts b/backend/src/services/session.service.ts index 649c251..18e41cc 100644 --- a/backend/src/services/session.service.ts +++ b/backend/src/services/session.service.ts @@ -1,5 +1,5 @@ -import axios from "axios"; -import qs from "qs"; +import axios from 'axios'; +import qs from 'qs'; // Oauth authentication @@ -24,14 +24,14 @@ export const getGoogleOauthToken = async ({ }: { code: string; }): Promise => { - const rootURl = "https://oauth2.googleapis.com/token"; + const rootURl = 'https://oauth2.googleapis.com/token'; const options = { code, client_id: GOOGLE_OAUTH_CLIENT_ID, client_secret: GOOGLE_OAUTH_CLIENT_SECRET, redirect_uri: GOOGLE_OAUTH_REDIRECT, - grant_type: "authorization_code", + grant_type: 'authorization_code', }; try { const { data } = await axios.post( @@ -39,14 +39,14 @@ export const getGoogleOauthToken = async ({ qs.stringify(options), { headers: { - "Content-Type": "application/x-www-form-urlencoded", + 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, ); return data; } catch (err: any) { - console.error("Failed to fetch Google Oauth Tokens"); + console.error('Failed to fetch Google Oauth Tokens'); throw new Error(err); } }; @@ -76,7 +76,7 @@ export async function getGoogleUser({ headers: { Authorization: `Bearer ${id_token}`, }, - } + }, ); return data; diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index b1db8f6..e95f500 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -5,7 +5,7 @@ export const registerUserModel = object({ body: object({ username: string({ required_error: 'Username is required' }), email: string({ required_error: 'Email is required' }).email( - 'Invalid email' + 'Invalid email', ), password: string({ required_error: 'Password is required' }) .min(8, 'Password must be more than 8 characters') @@ -21,7 +21,7 @@ export const registerUserModel = object({ export const resetPasswordModel = object({ body: object({ email: string({ required_error: 'Email is required' }).email( - 'Invalid email' + 'Invalid email', ), password: string({ required_error: 'Password is required' }) .min(8, 'Password must be more than 8 characters') @@ -37,11 +37,11 @@ export const resetPasswordModel = object({ export const loginUserModel = object({ body: object({ email: string({ required_error: 'Email is required' }).email( - 'Invalid email or password' + 'Invalid email or password', ), password: string({ required_error: 'Password is required' }).min( 8, - 'Invalid email or password' + 'Invalid email or password', ), }), }); diff --git a/backend/src/utils/aws.ts b/backend/src/utils/aws.ts index 8e063ef..6e777fe 100644 --- a/backend/src/utils/aws.ts +++ b/backend/src/utils/aws.ts @@ -1,14 +1,14 @@ -import { S3ClientConfig } from "@aws-sdk/client-s3"; +import { S3ClientConfig } from '@aws-sdk/client-s3'; import { config } from 'dotenv'; config(); -const s3Config: S3ClientConfig = { +const s3Config: S3ClientConfig = { region: process.env.S3_REGION, credentials: { - accessKeyId: process.env.AWS_ACCESS_KEY_ID || "", - secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "", - } + accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '', + }, }; -export {s3Config}; +export { s3Config }; diff --git a/backend/src/utils/db.ts b/backend/src/utils/db.ts index 4b96742..54886a0 100644 --- a/backend/src/utils/db.ts +++ b/backend/src/utils/db.ts @@ -1,5 +1,5 @@ import * as dotenv from 'dotenv'; -import mongoose from "mongoose"; +import mongoose from 'mongoose'; class DBClient { constructor() { @@ -10,12 +10,12 @@ class DBClient { mongoose .connect(String(process.env.DB_CONN_STRING)) .then(() => { - console.log("🚀 Mongo Database connected successfully"); + console.log('🚀 Mongo Database connected successfully'); }) .catch((err) => { throw err; }); - }; + } isAlive() { // 0: disconnected @@ -27,7 +27,6 @@ class DBClient { } return false; } - } const dbClient = new DBClient(); diff --git a/backend/src/utils/redis.ts b/backend/src/utils/redis.ts index 1d6f9f2..0e1b41b 100644 --- a/backend/src/utils/redis.ts +++ b/backend/src/utils/redis.ts @@ -1,52 +1,53 @@ /* eslint-disable no-inline-comments */ -import { RedisClientType, createClient } from "redis"; +import { RedisClientType, createClient } from 'redis'; import * as dotenv from 'dotenv'; -const retryStrategy = function(options: any) { - if (options.error && options.error.code === "ECONNREFUSED") { - // End reconnecting on a specific error - return new Error("The server refused the connection"); - } - if (options.total_retry_time > 1000 * 60 * 60) { - // End reconnecting after a specific timeout - return new Error("Retry time exhausted"); - } - if (options.attempt > 10) { - // End reconnecting with built in error - return undefined; - } - - // reconnect after - return Math.min(options.attempt * 100, 3000); +const retryStrategy = function (options: any) { + if (options.error && options.error.code === 'ECONNREFUSED') { + // End reconnecting on a specific error + return new Error('The server refused the connection'); + } + if (options.total_retry_time > 1000 * 60 * 60) { + // End reconnecting after a specific timeout + return new Error('Retry time exhausted'); + } + if (options.attempt > 10) { + // End reconnecting with built in error + return undefined; } + // reconnect after + return Math.min(options.attempt * 100, 3000); +}; + class RedisClient { - _client: RedisClientType; - connected: boolean = false; - constructor() { - dotenv.config(); - this._client = createClient(); - (async () => { - await this._client.connect(); - if (this._client.isOpen) { - this.connected = true; - } - this._client.on('connect', () => console.log("🚀 Redis Database connected successfully")); - this._client.on('ready', () => console.log('Redis is ready')); - this._client.on('reconnecting', () => console.log('Redis is reconnecting')); - this._client.on('error', () => console.log('Redis error')); - this._client.on('end', () => console.log('Redis end')); - } - )(); - } + _client: RedisClientType; - isAlive() { - return this.connected; - } + connected = false; - get client() { - return this._client; - } + constructor() { + dotenv.config(); + this._client = createClient(); + (async () => { + await this._client.connect(); + if (this._client.isOpen) { + this.connected = true; + } + this._client.on('connect', () => console.log('🚀 Redis Database connected successfully')); + this._client.on('ready', () => console.log('Redis is ready')); + this._client.on('reconnecting', () => console.log('Redis is reconnecting')); + this._client.on('error', () => console.log('Redis error')); + this._client.on('end', () => console.log('Redis end')); + })(); + } + + isAlive() { + return this.connected; + } + + get client() { + return this._client; + } } const redisClient = new RedisClient(); -export default redisClient; \ No newline at end of file +export default redisClient;