[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n.env.production\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# End of https://mrkandreev.name/snippets/gitignore-generator/#Node\n.DS_Store\n"
  },
  {
    "path": ".prettierignore",
    "content": "/.vscode\n/node_modules\n./dist\n\n*.env\n.env\n.env.*"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"singleQuote\": false,\n    \"bracketSpacing\": true,\n    \"tabWidth\": 2,\n    \"trailingComma\": \"es5\",\n    \"semi\": true\n}"
  },
  {
    "path": "Readme.md",
    "content": "# chai aur backend  series \n\nThis is a video series on backend with javascript\n- [Model link](https://app.eraser.io/workspace/YtPqZ1VogxGy1jzIDkzj?origin=share)\n\n- [Video playlist](https://www.youtube.com/watch?v=EH3vGeqeIAo&list=PLu71SKxNbfoBGh_8p_NS-ZAh6v7HhYqHW)\n\n---\n# Summary of this project\n\nThis project is a complex backend project that is built with nodejs, expressjs, mongodb, mongoose, jwt, bcrypt, and many more. This project is a complete backend project that has all the features that a backend project should have.\nWe are building a complete video hosting website similar to youtube with all the features like login, signup, upload video, like, dislike, comment, reply, subscribe, unsubscribe, and many more.\n\nProject uses all standard practices like JWT, bcrypt, access tokens, refresh Tokens and many more. We have spent a lot of time in building this project and we are sure that you will learn a lot from this project.\n\n---\nTop Contributer to complete all TODOs\n\n1. Spiderman (just sample)  [Link to Repo](https://www.youtube.com/@chaiaurcode)\n\n--- \n## How to contribute in this open source Project\n\nFirst, please understand that this is not your regular project to merge your PR. This repo requires you to finish all assignments that are in controller folder. We don't accept half work, please finish all controllers and then reach us out on [Discord](https://hitesh.ai/discord) or [Twitter](https://twitter.com/@hiteshdotcom) and after checking your repo, I will add link to your repo in this readme."
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"chai-backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"a backend at chai aur code channel - youtube\",\n  \"type\": \"module\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"nodemon -r dotenv/config --experimental-json-modules src/index.js\"\n  },\n  \"keywords\": [\n    \"javascript\",\n    \"backend\",\n    \"chai\"\n  ],\n  \"author\": \"Hitesh Choudhary\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"nodemon\": \"^3.0.1\",\n    \"prettier\": \"^3.0.3\"\n  },\n  \"dependencies\": {\n    \"bcrypt\": \"^5.1.1\",\n    \"cloudinary\": \"^1.41.0\",\n    \"cookie-parser\": \"^1.4.6\",\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^16.3.1\",\n    \"express\": \"^4.18.2\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"mongoose\": \"^8.0.0\",\n    \"mongoose-aggregate-paginate-v2\": \"^1.0.6\",\n    \"multer\": \"^1.4.5-lts.1\"\n  }\n}\n"
  },
  {
    "path": "public/temp/.gitkeep",
    "content": ""
  },
  {
    "path": "src/app.js",
    "content": "import express from \"express\"\nimport cors from \"cors\"\nimport cookieParser from \"cookie-parser\"\n\nconst app = express()\n\napp.use(cors({\n    origin: process.env.CORS_ORIGIN,\n    credentials: true\n}))\n\napp.use(express.json({limit: \"16kb\"}))\napp.use(express.urlencoded({extended: true, limit: \"16kb\"}))\napp.use(express.static(\"public\"))\napp.use(cookieParser())\n\n\n//routes import\nimport userRouter from './routes/user.routes.js'\nimport healthcheckRouter from \"./routes/healthcheck.routes.js\"\nimport tweetRouter from \"./routes/tweet.routes.js\"\nimport subscriptionRouter from \"./routes/subscription.routes.js\"\nimport videoRouter from \"./routes/video.routes.js\"\nimport commentRouter from \"./routes/comment.routes.js\"\nimport likeRouter from \"./routes/like.routes.js\"\nimport playlistRouter from \"./routes/playlist.routes.js\"\nimport dashboardRouter from \"./routes/dashboard.routes.js\"\n\n//routes declaration\napp.use(\"/api/v1/healthcheck\", healthcheckRouter)\napp.use(\"/api/v1/users\", userRouter)\napp.use(\"/api/v1/tweets\", tweetRouter)\napp.use(\"/api/v1/subscriptions\", subscriptionRouter)\napp.use(\"/api/v1/videos\", videoRouter)\napp.use(\"/api/v1/comments\", commentRouter)\napp.use(\"/api/v1/likes\", likeRouter)\napp.use(\"/api/v1/playlist\", playlistRouter)\napp.use(\"/api/v1/dashboard\", dashboardRouter)\n\n// http://localhost:8000/api/v1/users/register\n\nexport { app }"
  },
  {
    "path": "src/constants.js",
    "content": "export const DB_NAME = \"videotube\""
  },
  {
    "path": "src/controllers/comment.controller.js",
    "content": "import mongoose from \"mongoose\"\nimport {Comment} from \"../models/comment.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\nconst getVideoComments = asyncHandler(async (req, res) => {\n    //TODO: get all comments for a video\n    const {videoId} = req.params\n    const {page = 1, limit = 10} = req.query\n\n})\n\nconst addComment = asyncHandler(async (req, res) => {\n    // TODO: add a comment to a video\n})\n\nconst updateComment = asyncHandler(async (req, res) => {\n    // TODO: update a comment\n})\n\nconst deleteComment = asyncHandler(async (req, res) => {\n    // TODO: delete a comment\n})\n\nexport {\n    getVideoComments, \n    addComment, \n    updateComment,\n     deleteComment\n    }\n"
  },
  {
    "path": "src/controllers/dashboard.controller.js",
    "content": "import mongoose from \"mongoose\"\nimport {Video} from \"../models/video.model.js\"\nimport {Subscription} from \"../models/subscription.model.js\"\nimport {Like} from \"../models/like.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\nconst getChannelStats = asyncHandler(async (req, res) => {\n    // TODO: Get the channel stats like total video views, total subscribers, total videos, total likes etc.\n})\n\nconst getChannelVideos = asyncHandler(async (req, res) => {\n    // TODO: Get all the videos uploaded by the channel\n})\n\nexport {\n    getChannelStats, \n    getChannelVideos\n    }"
  },
  {
    "path": "src/controllers/healthcheck.controller.js",
    "content": "import {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\n\nconst healthcheck = asyncHandler(async (req, res) => {\n    //TODO: build a healthcheck response that simply returns the OK status as json with a message\n})\n\nexport {\n    healthcheck\n    }\n    "
  },
  {
    "path": "src/controllers/like.controller.js",
    "content": "import mongoose, {isValidObjectId} from \"mongoose\"\nimport {Like} from \"../models/like.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\nconst toggleVideoLike = asyncHandler(async (req, res) => {\n    const {videoId} = req.params\n    //TODO: toggle like on video\n})\n\nconst toggleCommentLike = asyncHandler(async (req, res) => {\n    const {commentId} = req.params\n    //TODO: toggle like on comment\n\n})\n\nconst toggleTweetLike = asyncHandler(async (req, res) => {\n    const {tweetId} = req.params\n    //TODO: toggle like on tweet\n}\n)\n\nconst getLikedVideos = asyncHandler(async (req, res) => {\n    //TODO: get all liked videos\n})\n\nexport {\n    toggleCommentLike,\n    toggleTweetLike,\n    toggleVideoLike,\n    getLikedVideos\n}"
  },
  {
    "path": "src/controllers/playlist.controller.js",
    "content": "import mongoose, {isValidObjectId} from \"mongoose\"\nimport {Playlist} from \"../models/playlist.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\n\nconst createPlaylist = asyncHandler(async (req, res) => {\n    const {name, description} = req.body\n\n    //TODO: create playlist\n})\n\nconst getUserPlaylists = asyncHandler(async (req, res) => {\n    const {userId} = req.params\n    //TODO: get user playlists\n})\n\nconst getPlaylistById = asyncHandler(async (req, res) => {\n    const {playlistId} = req.params\n    //TODO: get playlist by id\n})\n\nconst addVideoToPlaylist = asyncHandler(async (req, res) => {\n    const {playlistId, videoId} = req.params\n})\n\nconst removeVideoFromPlaylist = asyncHandler(async (req, res) => {\n    const {playlistId, videoId} = req.params\n    // TODO: remove video from playlist\n\n})\n\nconst deletePlaylist = asyncHandler(async (req, res) => {\n    const {playlistId} = req.params\n    // TODO: delete playlist\n})\n\nconst updatePlaylist = asyncHandler(async (req, res) => {\n    const {playlistId} = req.params\n    const {name, description} = req.body\n    //TODO: update playlist\n})\n\nexport {\n    createPlaylist,\n    getUserPlaylists,\n    getPlaylistById,\n    addVideoToPlaylist,\n    removeVideoFromPlaylist,\n    deletePlaylist,\n    updatePlaylist\n}\n"
  },
  {
    "path": "src/controllers/subscription.controller.js",
    "content": "import mongoose, {isValidObjectId} from \"mongoose\"\nimport {User} from \"../models/user.model.js\"\nimport { Subscription } from \"../models/subscription.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\n\nconst toggleSubscription = asyncHandler(async (req, res) => {\n    const {channelId} = req.params\n    // TODO: toggle subscription\n})\n\n// controller to return subscriber list of a channel\nconst getUserChannelSubscribers = asyncHandler(async (req, res) => {\n    const {channelId} = req.params\n})\n\n// controller to return channel list to which user has subscribed\nconst getSubscribedChannels = asyncHandler(async (req, res) => {\n    const { subscriberId } = req.params\n})\n\nexport {\n    toggleSubscription,\n    getUserChannelSubscribers,\n    getSubscribedChannels\n}"
  },
  {
    "path": "src/controllers/tweet.controller.js",
    "content": "import mongoose, { isValidObjectId } from \"mongoose\"\nimport {Tweet} from \"../models/tweet.model.js\"\nimport {User} from \"../models/user.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\n\nconst createTweet = asyncHandler(async (req, res) => {\n    //TODO: create tweet\n})\n\nconst getUserTweets = asyncHandler(async (req, res) => {\n    // TODO: get user tweets\n})\n\nconst updateTweet = asyncHandler(async (req, res) => {\n    //TODO: update tweet\n})\n\nconst deleteTweet = asyncHandler(async (req, res) => {\n    //TODO: delete tweet\n})\n\nexport {\n    createTweet,\n    getUserTweets,\n    updateTweet,\n    deleteTweet\n}\n"
  },
  {
    "path": "src/controllers/user.controller.js",
    "content": "import { asyncHandler } from \"../utils/asyncHandler.js\";\nimport {ApiError} from \"../utils/ApiError.js\"\nimport { User} from \"../models/user.model.js\"\nimport {uploadOnCloudinary} from \"../utils/cloudinary.js\"\nimport { ApiResponse } from \"../utils/ApiResponse.js\";\nimport jwt from \"jsonwebtoken\"\nimport mongoose from \"mongoose\";\n\n\nconst generateAccessAndRefereshTokens = async(userId) =>{\n    try {\n        const user = await User.findById(userId)\n        const accessToken = user.generateAccessToken()\n        const refreshToken = user.generateRefreshToken()\n\n        user.refreshToken = refreshToken\n        await user.save({ validateBeforeSave: false })\n\n        return {accessToken, refreshToken}\n\n\n    } catch (error) {\n        throw new ApiError(500, \"Something went wrong while generating referesh and access token\")\n    }\n}\n\nconst registerUser = asyncHandler( async (req, res) => {\n    // get user details from frontend\n    // validation - not empty\n    // check if user already exists: username, email\n    // check for images, check for avatar\n    // upload them to cloudinary, avatar\n    // create user object - create entry in db\n    // remove password and refresh token field from response\n    // check for user creation\n    // return res\n\n\n    const {fullName, email, username, password } = req.body\n    //console.log(\"email: \", email);\n\n    if (\n        [fullName, email, username, password].some((field) => field?.trim() === \"\")\n    ) {\n        throw new ApiError(400, \"All fields are required\")\n    }\n\n    const existedUser = await User.findOne({\n        $or: [{ username }, { email }]\n    })\n\n    if (existedUser) {\n        throw new ApiError(409, \"User with email or username already exists\")\n    }\n    //console.log(req.files);\n\n    const avatarLocalPath = req.files?.avatar[0]?.path;\n    //const coverImageLocalPath = req.files?.coverImage[0]?.path;\n\n    let coverImageLocalPath;\n    if (req.files && Array.isArray(req.files.coverImage) && req.files.coverImage.length > 0) {\n        coverImageLocalPath = req.files.coverImage[0].path\n    }\n    \n\n    if (!avatarLocalPath) {\n        throw new ApiError(400, \"Avatar file is required\")\n    }\n\n    const avatar = await uploadOnCloudinary(avatarLocalPath)\n    const coverImage = await uploadOnCloudinary(coverImageLocalPath)\n\n    if (!avatar) {\n        throw new ApiError(400, \"Avatar file is required\")\n    }\n   \n\n    const user = await User.create({\n        fullName,\n        avatar: avatar.url,\n        coverImage: coverImage?.url || \"\",\n        email, \n        password,\n        username: username.toLowerCase()\n    })\n\n    const createdUser = await User.findById(user._id).select(\n        \"-password -refreshToken\"\n    )\n\n    if (!createdUser) {\n        throw new ApiError(500, \"Something went wrong while registering the user\")\n    }\n\n    return res.status(201).json(\n        new ApiResponse(200, createdUser, \"User registered Successfully\")\n    )\n\n} )\n\nconst loginUser = asyncHandler(async (req, res) =>{\n    // req body -> data\n    // username or email\n    //find the user\n    //password check\n    //access and referesh token\n    //send cookie\n\n    const {email, username, password} = req.body\n    console.log(email);\n\n    if (!username && !email) {\n        throw new ApiError(400, \"username or email is required\")\n    }\n    \n    // Here is an alternative of above code based on logic discussed in video:\n    // if (!(username || email)) {\n    //     throw new ApiError(400, \"username or email is required\")\n        \n    // }\n\n    const user = await User.findOne({\n        $or: [{username}, {email}]\n    })\n\n    if (!user) {\n        throw new ApiError(404, \"User does not exist\")\n    }\n\n   const isPasswordValid = await user.isPasswordCorrect(password)\n\n   if (!isPasswordValid) {\n    throw new ApiError(401, \"Invalid user credentials\")\n    }\n\n   const {accessToken, refreshToken} = await generateAccessAndRefereshTokens(user._id)\n\n    const loggedInUser = await User.findById(user._id).select(\"-password -refreshToken\")\n\n    const options = {\n        httpOnly: true,\n        secure: true\n    }\n\n    return res\n    .status(200)\n    .cookie(\"accessToken\", accessToken, options)\n    .cookie(\"refreshToken\", refreshToken, options)\n    .json(\n        new ApiResponse(\n            200, \n            {\n                user: loggedInUser, accessToken, refreshToken\n            },\n            \"User logged In Successfully\"\n        )\n    )\n\n})\n\nconst logoutUser = asyncHandler(async(req, res) => {\n    await User.findByIdAndUpdate(\n        req.user._id,\n        {\n            $unset: {\n                refreshToken: 1 // this removes the field from document\n            }\n        },\n        {\n            new: true\n        }\n    )\n\n    const options = {\n        httpOnly: true,\n        secure: true\n    }\n\n    return res\n    .status(200)\n    .clearCookie(\"accessToken\", options)\n    .clearCookie(\"refreshToken\", options)\n    .json(new ApiResponse(200, {}, \"User logged Out\"))\n})\n\nconst refreshAccessToken = asyncHandler(async (req, res) => {\n    const incomingRefreshToken = req.cookies.refreshToken || req.body.refreshToken\n\n    if (!incomingRefreshToken) {\n        throw new ApiError(401, \"unauthorized request\")\n    }\n\n    try {\n        const decodedToken = jwt.verify(\n            incomingRefreshToken,\n            process.env.REFRESH_TOKEN_SECRET\n        )\n    \n        const user = await User.findById(decodedToken?._id)\n    \n        if (!user) {\n            throw new ApiError(401, \"Invalid refresh token\")\n        }\n    \n        if (incomingRefreshToken !== user?.refreshToken) {\n            throw new ApiError(401, \"Refresh token is expired or used\")\n            \n        }\n    \n        const options = {\n            httpOnly: true,\n            secure: true\n        }\n    \n        const {accessToken, newRefreshToken} = await generateAccessAndRefereshTokens(user._id)\n    \n        return res\n        .status(200)\n        .cookie(\"accessToken\", accessToken, options)\n        .cookie(\"refreshToken\", newRefreshToken, options)\n        .json(\n            new ApiResponse(\n                200, \n                {accessToken, refreshToken: newRefreshToken},\n                \"Access token refreshed\"\n            )\n        )\n    } catch (error) {\n        throw new ApiError(401, error?.message || \"Invalid refresh token\")\n    }\n\n})\n\nconst changeCurrentPassword = asyncHandler(async(req, res) => {\n    const {oldPassword, newPassword} = req.body\n\n    \n\n    const user = await User.findById(req.user?._id)\n    const isPasswordCorrect = await user.isPasswordCorrect(oldPassword)\n\n    if (!isPasswordCorrect) {\n        throw new ApiError(400, \"Invalid old password\")\n    }\n\n    user.password = newPassword\n    await user.save({validateBeforeSave: false})\n\n    return res\n    .status(200)\n    .json(new ApiResponse(200, {}, \"Password changed successfully\"))\n})\n\n\nconst getCurrentUser = asyncHandler(async(req, res) => {\n    return res\n    .status(200)\n    .json(new ApiResponse(\n        200,\n        req.user,\n        \"User fetched successfully\"\n    ))\n})\n\nconst updateAccountDetails = asyncHandler(async(req, res) => {\n    const {fullName, email} = req.body\n\n    if (!fullName || !email) {\n        throw new ApiError(400, \"All fields are required\")\n    }\n\n    const user = await User.findByIdAndUpdate(\n        req.user?._id,\n        {\n            $set: {\n                fullName,\n                email: email\n            }\n        },\n        {new: true}\n        \n    ).select(\"-password\")\n\n    return res\n    .status(200)\n    .json(new ApiResponse(200, user, \"Account details updated successfully\"))\n});\n\nconst updateUserAvatar = asyncHandler(async(req, res) => {\n    const avatarLocalPath = req.file?.path\n\n    if (!avatarLocalPath) {\n        throw new ApiError(400, \"Avatar file is missing\")\n    }\n\n    //TODO: delete old image - assignment\n\n    const avatar = await uploadOnCloudinary(avatarLocalPath)\n\n    if (!avatar.url) {\n        throw new ApiError(400, \"Error while uploading on avatar\")\n        \n    }\n\n    const user = await User.findByIdAndUpdate(\n        req.user?._id,\n        {\n            $set:{\n                avatar: avatar.url\n            }\n        },\n        {new: true}\n    ).select(\"-password\")\n\n    return res\n    .status(200)\n    .json(\n        new ApiResponse(200, user, \"Avatar image updated successfully\")\n    )\n})\n\nconst updateUserCoverImage = asyncHandler(async(req, res) => {\n    const coverImageLocalPath = req.file?.path\n\n    if (!coverImageLocalPath) {\n        throw new ApiError(400, \"Cover image file is missing\")\n    }\n\n    //TODO: delete old image - assignment\n\n\n    const coverImage = await uploadOnCloudinary(coverImageLocalPath)\n\n    if (!coverImage.url) {\n        throw new ApiError(400, \"Error while uploading on avatar\")\n        \n    }\n\n    const user = await User.findByIdAndUpdate(\n        req.user?._id,\n        {\n            $set:{\n                coverImage: coverImage.url\n            }\n        },\n        {new: true}\n    ).select(\"-password\")\n\n    return res\n    .status(200)\n    .json(\n        new ApiResponse(200, user, \"Cover image updated successfully\")\n    )\n})\n\n\nconst getUserChannelProfile = asyncHandler(async(req, res) => {\n    const {username} = req.params\n\n    if (!username?.trim()) {\n        throw new ApiError(400, \"username is missing\")\n    }\n\n    const channel = await User.aggregate([\n        {\n            $match: {\n                username: username?.toLowerCase()\n            }\n        },\n        {\n            $lookup: {\n                from: \"subscriptions\",\n                localField: \"_id\",\n                foreignField: \"channel\",\n                as: \"subscribers\"\n            }\n        },\n        {\n            $lookup: {\n                from: \"subscriptions\",\n                localField: \"_id\",\n                foreignField: \"subscriber\",\n                as: \"subscribedTo\"\n            }\n        },\n        {\n            $addFields: {\n                subscribersCount: {\n                    $size: \"$subscribers\"\n                },\n                channelsSubscribedToCount: {\n                    $size: \"$subscribedTo\"\n                },\n                isSubscribed: {\n                    $cond: {\n                        if: {$in: [req.user?._id, \"$subscribers.subscriber\"]},\n                        then: true,\n                        else: false\n                    }\n                }\n            }\n        },\n        {\n            $project: {\n                fullName: 1,\n                username: 1,\n                subscribersCount: 1,\n                channelsSubscribedToCount: 1,\n                isSubscribed: 1,\n                avatar: 1,\n                coverImage: 1,\n                email: 1\n\n            }\n        }\n    ])\n\n    if (!channel?.length) {\n        throw new ApiError(404, \"channel does not exists\")\n    }\n\n    return res\n    .status(200)\n    .json(\n        new ApiResponse(200, channel[0], \"User channel fetched successfully\")\n    )\n})\n\nconst getWatchHistory = asyncHandler(async(req, res) => {\n    const user = await User.aggregate([\n        {\n            $match: {\n                _id: new mongoose.Types.ObjectId(req.user._id)\n            }\n        },\n        {\n            $lookup: {\n                from: \"videos\",\n                localField: \"watchHistory\",\n                foreignField: \"_id\",\n                as: \"watchHistory\",\n                pipeline: [\n                    {\n                        $lookup: {\n                            from: \"users\",\n                            localField: \"owner\",\n                            foreignField: \"_id\",\n                            as: \"owner\",\n                            pipeline: [\n                                {\n                                    $project: {\n                                        fullName: 1,\n                                        username: 1,\n                                        avatar: 1\n                                    }\n                                }\n                            ]\n                        }\n                    },\n                    {\n                        $addFields:{\n                            owner:{\n                                $first: \"$owner\"\n                            }\n                        }\n                    }\n                ]\n            }\n        }\n    ])\n\n    return res\n    .status(200)\n    .json(\n        new ApiResponse(\n            200,\n            user[0].watchHistory,\n            \"Watch history fetched successfully\"\n        )\n    )\n})\n\n\nexport {\n    registerUser,\n    loginUser,\n    logoutUser,\n    refreshAccessToken,\n    changeCurrentPassword,\n    getCurrentUser,\n    updateAccountDetails,\n    updateUserAvatar,\n    updateUserCoverImage,\n    getUserChannelProfile,\n    getWatchHistory\n}"
  },
  {
    "path": "src/controllers/video.controller.js",
    "content": "import mongoose, {isValidObjectId} from \"mongoose\"\nimport {Video} from \"../models/video.model.js\"\nimport {User} from \"../models/user.model.js\"\nimport {ApiError} from \"../utils/ApiError.js\"\nimport {ApiResponse} from \"../utils/ApiResponse.js\"\nimport {asyncHandler} from \"../utils/asyncHandler.js\"\nimport {uploadOnCloudinary} from \"../utils/cloudinary.js\"\n\n\nconst getAllVideos = asyncHandler(async (req, res) => {\n    const { page = 1, limit = 10, query, sortBy, sortType, userId } = req.query\n    //TODO: get all videos based on query, sort, pagination\n})\n\nconst publishAVideo = asyncHandler(async (req, res) => {\n    const { title, description} = req.body\n    // TODO: get video, upload to cloudinary, create video\n})\n\nconst getVideoById = asyncHandler(async (req, res) => {\n    const { videoId } = req.params\n    //TODO: get video by id\n})\n\nconst updateVideo = asyncHandler(async (req, res) => {\n    const { videoId } = req.params\n    //TODO: update video details like title, description, thumbnail\n\n})\n\nconst deleteVideo = asyncHandler(async (req, res) => {\n    const { videoId } = req.params\n    //TODO: delete video\n})\n\nconst togglePublishStatus = asyncHandler(async (req, res) => {\n    const { videoId } = req.params\n})\n\nexport {\n    getAllVideos,\n    publishAVideo,\n    getVideoById,\n    updateVideo,\n    deleteVideo,\n    togglePublishStatus\n}\n"
  },
  {
    "path": "src/db/index.js",
    "content": "import mongoose from \"mongoose\";\nimport { DB_NAME } from \"../constants.js\";\n\n\nconst connectDB = async () => {\n    try {\n        const connectionInstance = await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`)\n        console.log(`\\n MongoDB connected !! DB HOST: ${connectionInstance.connection.host}`);\n    } catch (error) {\n        console.log(\"MONGODB connection FAILED \", error);\n        process.exit(1)\n    }\n}\n\nexport default connectDB"
  },
  {
    "path": "src/index.js",
    "content": "// require('dotenv').config({path: './env'})\nimport dotenv from \"dotenv\"\nimport connectDB from \"./db/index.js\";\nimport {app} from './app.js'\ndotenv.config({\n    path: './.env'\n})\n\n\n\nconnectDB()\n.then(() => {\n    app.listen(process.env.PORT || 8000, () => {\n        console.log(`⚙️ Server is running at port : ${process.env.PORT}`);\n    })\n})\n.catch((err) => {\n    console.log(\"MONGO db connection failed !!! \", err);\n})\n\n\n\n\n\n\n\n\n\n\n/*\nimport express from \"express\"\nconst app = express()\n( async () => {\n    try {\n        await mongoose.connect(`${process.env.MONGODB_URI}/${DB_NAME}`)\n        app.on(\"errror\", (error) => {\n            console.log(\"ERRR: \", error);\n            throw error\n        })\n\n        app.listen(process.env.PORT, () => {\n            console.log(`App is listening on port ${process.env.PORT}`);\n        })\n\n    } catch (error) {\n        console.error(\"ERROR: \", error)\n        throw err\n    }\n})()\n\n*/"
  },
  {
    "path": "src/middlewares/auth.middleware.js",
    "content": "import { ApiError } from \"../utils/ApiError.js\";\nimport { asyncHandler } from \"../utils/asyncHandler.js\";\nimport jwt from \"jsonwebtoken\"\nimport { User } from \"../models/user.model.js\";\n\nexport const verifyJWT = asyncHandler(async(req, _, next) => {\n    try {\n        const token = req.cookies?.accessToken || req.header(\"Authorization\")?.replace(\"Bearer \", \"\")\n        \n        // console.log(token);\n        if (!token) {\n            throw new ApiError(401, \"Unauthorized request\")\n        }\n    \n        const decodedToken = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET)\n    \n        const user = await User.findById(decodedToken?._id).select(\"-password -refreshToken\")\n    \n        if (!user) {\n            \n            throw new ApiError(401, \"Invalid Access Token\")\n        }\n    \n        req.user = user;\n        next()\n    } catch (error) {\n        throw new ApiError(401, error?.message || \"Invalid access token\")\n    }\n    \n})"
  },
  {
    "path": "src/middlewares/multer.middleware.js",
    "content": "import multer from \"multer\";\n\nconst storage = multer.diskStorage({\n    destination: function (req, file, cb) {\n      cb(null, \"./public/temp\")\n    },\n    filename: function (req, file, cb) {\n      \n      cb(null, file.originalname)\n    }\n  })\n  \nexport const upload = multer({ \n    storage, \n})"
  },
  {
    "path": "src/models/comment.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\nimport mongooseAggregatePaginate from \"mongoose-aggregate-paginate-v2\";\n\nconst commentSchema = new Schema(\n    {\n        content: {\n            type: String,\n            required: true\n        },\n        video: {\n            type: Schema.Types.ObjectId,\n            ref: \"Video\"\n        },\n        owner: {\n            type: Schema.Types.ObjectId,\n            ref: \"User\"\n        }\n    },\n    {\n        timestamps: true\n    }\n)\n\n\ncommentSchema.plugin(mongooseAggregatePaginate)\n\nexport const Comment = mongoose.model(\"Comment\", commentSchema)"
  },
  {
    "path": "src/models/like.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\n\n\nconst likeSchema = new Schema({\n    video: {\n        type: Schema.Types.ObjectId,\n        ref: \"Video\"\n    },\n    comment: {\n        type: Schema.Types.ObjectId,\n        ref: \"Comment\"\n    },\n    tweet: {\n        type: Schema.Types.ObjectId,\n        ref: \"Tweet\"\n    },\n    likedBy: {\n        type: Schema.Types.ObjectId,\n        ref: \"User\"\n    },\n    \n}, {timestamps: true})\n\nexport const Like = mongoose.model(\"Like\", likeSchema)"
  },
  {
    "path": "src/models/playlist.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\n\nconst playlistSchema = new Schema({\n    name: {\n        type: String,\n        required: true\n    },\n    description: {\n        type: String,\n        required: true\n    },\n    videos: [\n        {\n            type: Schema.Types.ObjectId,\n            ref: \"Video\"\n        }\n    ],\n    owner: {\n        type: Schema.Types.ObjectId,\n        ref: \"User\"\n    },\n}, {timestamps: true})\n\n\n\nexport const Playlist = mongoose.model(\"Playlist\", playlistSchema)"
  },
  {
    "path": "src/models/subscription.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\"\n\nconst subscriptionSchema = new Schema({\n    subscriber: {\n        type: Schema.Types.ObjectId, // one who is subscribing\n        ref: \"User\"\n    },\n    channel: {\n        type: Schema.Types.ObjectId, // one to whom 'subscriber' is subscribing\n        ref: \"User\"\n    }\n}, {timestamps: true})\n\n\n\nexport const Subscription = mongoose.model(\"Subscription\", subscriptionSchema)"
  },
  {
    "path": "src/models/tweet.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\n\nconst tweetSchema = new Schema({\n    content: {\n        type: String,\n        required: true\n    },\n    owner: {\n        type: Schema.Types.ObjectId,\n        ref: \"User\"\n    }\n}, {timestamps: true})\n\n\nexport const Tweet = mongoose.model(\"Tweet\", tweetSchema)"
  },
  {
    "path": "src/models/user.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\nimport jwt from \"jsonwebtoken\"\nimport bcrypt from \"bcrypt\"\n\nconst userSchema = new Schema(\n    {\n        username: {\n            type: String,\n            required: true,\n            unique: true,\n            lowercase: true,\n            trim: true, \n            index: true\n        },\n        email: {\n            type: String,\n            required: true,\n            unique: true,\n            lowecase: true,\n            trim: true, \n        },\n        fullName: {\n            type: String,\n            required: true,\n            trim: true, \n            index: true\n        },\n        avatar: {\n            type: String, // cloudinary url\n            required: true,\n        },\n        coverImage: {\n            type: String, // cloudinary url\n        },\n        watchHistory: [\n            {\n                type: Schema.Types.ObjectId,\n                ref: \"Video\"\n            }\n        ],\n        password: {\n            type: String,\n            required: [true, 'Password is required']\n        },\n        refreshToken: {\n            type: String\n        }\n\n    },\n    {\n        timestamps: true\n    }\n)\n\nuserSchema.pre(\"save\", async function (next) {\n    if(!this.isModified(\"password\")) return next();\n\n    this.password = await bcrypt.hash(this.password, 10)\n    next()\n})\n\nuserSchema.methods.isPasswordCorrect = async function(password){\n    return await bcrypt.compare(password, this.password)\n}\n\nuserSchema.methods.generateAccessToken = function(){\n    return jwt.sign(\n        {\n            _id: this._id,\n            email: this.email,\n            username: this.username,\n            fullName: this.fullName\n        },\n        process.env.ACCESS_TOKEN_SECRET,\n        {\n            expiresIn: process.env.ACCESS_TOKEN_EXPIRY\n        }\n    )\n}\nuserSchema.methods.generateRefreshToken = function(){\n    return jwt.sign(\n        {\n            _id: this._id,\n            \n        },\n        process.env.REFRESH_TOKEN_SECRET,\n        {\n            expiresIn: process.env.REFRESH_TOKEN_EXPIRY\n        }\n    )\n}\n\nexport const User = mongoose.model(\"User\", userSchema)"
  },
  {
    "path": "src/models/video.model.js",
    "content": "import mongoose, {Schema} from \"mongoose\";\nimport mongooseAggregatePaginate from \"mongoose-aggregate-paginate-v2\";\n\nconst videoSchema = new Schema(\n    {\n        videoFile: {\n            type: String, //cloudinary url\n            required: true\n        },\n        thumbnail: {\n            type: String, //cloudinary url\n            required: true\n        },\n        title: {\n            type: String, \n            required: true\n        },\n        description: {\n            type: String, \n            required: true\n        },\n        duration: {\n            type: Number, \n            required: true\n        },\n        views: {\n            type: Number,\n            default: 0\n        },\n        isPublished: {\n            type: Boolean,\n            default: true\n        },\n        owner: {\n            type: Schema.Types.ObjectId,\n            ref: \"User\"\n        }\n\n    }, \n    {\n        timestamps: true\n    }\n)\n\nvideoSchema.plugin(mongooseAggregatePaginate)\n\nexport const Video = mongoose.model(\"Video\", videoSchema)"
  },
  {
    "path": "src/routes/comment.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    addComment,\n    deleteComment,\n    getVideoComments,\n    updateComment,\n} from \"../controllers/comment.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\n\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter.route(\"/:videoId\").get(getVideoComments).post(addComment);\nrouter.route(\"/c/:commentId\").delete(deleteComment).patch(updateComment);\n\nexport default router"
  },
  {
    "path": "src/routes/dashboard.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    getChannelStats,\n    getChannelVideos,\n} from \"../controllers/dashboard.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\n\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter.route(\"/stats\").get(getChannelStats);\nrouter.route(\"/videos\").get(getChannelVideos);\n\nexport default router"
  },
  {
    "path": "src/routes/healthcheck.routes.js",
    "content": "import { Router } from 'express';\nimport { healthcheck } from \"../controllers/healthcheck.controller.js\"\n\nconst router = Router();\n\nrouter.route('/').get(healthcheck);\n\nexport default router"
  },
  {
    "path": "src/routes/like.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    getLikedVideos,\n    toggleCommentLike,\n    toggleVideoLike,\n    toggleTweetLike,\n} from \"../controllers/like.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter.route(\"/toggle/v/:videoId\").post(toggleVideoLike);\nrouter.route(\"/toggle/c/:commentId\").post(toggleCommentLike);\nrouter.route(\"/toggle/t/:tweetId\").post(toggleTweetLike);\nrouter.route(\"/videos\").get(getLikedVideos);\n\nexport default router"
  },
  {
    "path": "src/routes/playlist.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    addVideoToPlaylist,\n    createPlaylist,\n    deletePlaylist,\n    getPlaylistById,\n    getUserPlaylists,\n    removeVideoFromPlaylist,\n    updatePlaylist,\n} from \"../controllers/playlist.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\n\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter.route(\"/\").post(createPlaylist)\n\nrouter\n    .route(\"/:playlistId\")\n    .get(getPlaylistById)\n    .patch(updatePlaylist)\n    .delete(deletePlaylist);\n\nrouter.route(\"/add/:videoId/:playlistId\").patch(addVideoToPlaylist);\nrouter.route(\"/remove/:videoId/:playlistId\").patch(removeVideoFromPlaylist);\n\nrouter.route(\"/user/:userId\").get(getUserPlaylists);\n\nexport default router"
  },
  {
    "path": "src/routes/subscription.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    getSubscribedChannels,\n    getUserChannelSubscribers,\n    toggleSubscription,\n} from \"../controllers/subscription.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter\n    .route(\"/c/:channelId\")\n    .get(getSubscribedChannels)\n    .post(toggleSubscription);\n\nrouter.route(\"/u/:subscriberId\").get(getUserChannelSubscribers);\n\nexport default router"
  },
  {
    "path": "src/routes/tweet.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    createTweet,\n    deleteTweet,\n    getUserTweets,\n    updateTweet,\n} from \"../controllers/tweet.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\n\nconst router = Router();\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter.route(\"/\").post(createTweet);\nrouter.route(\"/user/:userId\").get(getUserTweets);\nrouter.route(\"/:tweetId\").patch(updateTweet).delete(deleteTweet);\n\nexport default router"
  },
  {
    "path": "src/routes/user.routes.js",
    "content": "import { Router } from \"express\";\nimport { \n    loginUser, \n    logoutUser, \n    registerUser, \n    refreshAccessToken, \n    changeCurrentPassword, \n    getCurrentUser, \n    updateUserAvatar, \n    updateUserCoverImage, \n    getUserChannelProfile, \n    getWatchHistory, \n    updateAccountDetails\n} from \"../controllers/user.controller.js\";\nimport {upload} from \"../middlewares/multer.middleware.js\"\nimport { verifyJWT } from \"../middlewares/auth.middleware.js\";\n\n\nconst router = Router()\n\nrouter.route(\"/register\").post(\n    upload.fields([\n        {\n            name: \"avatar\",\n            maxCount: 1\n        }, \n        {\n            name: \"coverImage\",\n            maxCount: 1\n        }\n    ]),\n    registerUser\n    )\n\nrouter.route(\"/login\").post(loginUser)\n\n//secured routes\nrouter.route(\"/logout\").post(verifyJWT,  logoutUser)\nrouter.route(\"/refresh-token\").post(refreshAccessToken)\nrouter.route(\"/change-password\").post(verifyJWT, changeCurrentPassword)\nrouter.route(\"/current-user\").get(verifyJWT, getCurrentUser)\nrouter.route(\"/update-account\").patch(verifyJWT, updateAccountDetails)\n\nrouter.route(\"/avatar\").patch(verifyJWT, upload.single(\"avatar\"), updateUserAvatar)\nrouter.route(\"/cover-image\").patch(verifyJWT, upload.single(\"coverImage\"), updateUserCoverImage)\n\nrouter.route(\"/c/:username\").get(verifyJWT, getUserChannelProfile)\nrouter.route(\"/history\").get(verifyJWT, getWatchHistory)\n\nexport default router"
  },
  {
    "path": "src/routes/video.routes.js",
    "content": "import { Router } from 'express';\nimport {\n    deleteVideo,\n    getAllVideos,\n    getVideoById,\n    publishAVideo,\n    togglePublishStatus,\n    updateVideo,\n} from \"../controllers/video.controller.js\"\nimport {verifyJWT} from \"../middlewares/auth.middleware.js\"\nimport {upload} from \"../middlewares/multer.middleware.js\"\n\nconst router = Router();\nrouter.use(verifyJWT); // Apply verifyJWT middleware to all routes in this file\n\nrouter\n    .route(\"/\")\n    .get(getAllVideos)\n    .post(\n        upload.fields([\n            {\n                name: \"videoFile\",\n                maxCount: 1,\n            },\n            {\n                name: \"thumbnail\",\n                maxCount: 1,\n            },\n            \n        ]),\n        publishAVideo\n    );\n\nrouter\n    .route(\"/:videoId\")\n    .get(getVideoById)\n    .delete(deleteVideo)\n    .patch(upload.single(\"thumbnail\"), updateVideo);\n\nrouter.route(\"/toggle/publish/:videoId\").patch(togglePublishStatus);\n\nexport default router"
  },
  {
    "path": "src/utils/ApiError.js",
    "content": "class ApiError extends Error {\n    constructor(\n        statusCode,\n        message= \"Something went wrong\",\n        errors = [],\n        stack = \"\"\n    ){\n        super(message)\n        this.statusCode = statusCode\n        this.data = null\n        this.message = message\n        this.success = false;\n        this.errors = errors\n\n        if (stack) {\n            this.stack = stack\n        } else{\n            Error.captureStackTrace(this, this.constructor)\n        }\n\n    }\n}\n\nexport {ApiError}"
  },
  {
    "path": "src/utils/ApiResponse.js",
    "content": "class ApiResponse {\n    constructor(statusCode, data, message = \"Success\"){\n        this.statusCode = statusCode\n        this.data = data\n        this.message = message\n        this.success = statusCode < 400\n    }\n}\n\nexport { ApiResponse }"
  },
  {
    "path": "src/utils/asyncHandler.js",
    "content": "const asyncHandler = (requestHandler) => {\n    return (req, res, next) => {\n        Promise.resolve(requestHandler(req, res, next)).catch((err) => next(err))\n    }\n}\n\n\nexport { asyncHandler }\n\n\n\n\n// const asyncHandler = () => {}\n// const asyncHandler = (func) => () => {}\n// const asyncHandler = (func) => async () => {}\n\n\n// const asyncHandler = (fn) => async (req, res, next) => {\n//     try {\n//         await fn(req, res, next)\n//     } catch (error) {\n//         res.status(err.code || 500).json({\n//             success: false,\n//             message: err.message\n//         })\n//     }\n// }"
  },
  {
    "path": "src/utils/cloudinary.js",
    "content": "import {v2 as cloudinary} from \"cloudinary\"\nimport fs from \"fs\"\n\n\ncloudinary.config({ \n  cloud_name: process.env.CLOUDINARY_CLOUD_NAME, \n  api_key: process.env.CLOUDINARY_API_KEY, \n  api_secret: process.env.CLOUDINARY_API_SECRET \n});\n\nconst uploadOnCloudinary = async (localFilePath) => {\n    try {\n        if (!localFilePath) return null\n        //upload the file on cloudinary\n        const response = await cloudinary.uploader.upload(localFilePath, {\n            resource_type: \"auto\"\n        })\n        // file has been uploaded successfull\n        //console.log(\"file is uploaded on cloudinary \", response.url);\n        fs.unlinkSync(localFilePath)\n        return response;\n\n    } catch (error) {\n        fs.unlinkSync(localFilePath) // remove the locally saved temporary file as the upload operation got failed\n        return null;\n    }\n}\n\n\n\nexport {uploadOnCloudinary}"
  }
]