import User from "../models/user.model.js" import asyncHandler from "../utils/asyncHandler.js" import ApiError from "../utils/ApiError.js" import ApiResponse from "../utils/ApiResponse.js" import { expressRepre } from "@vashuthegreat/vexpress" import client from "../utils/RedisClient.js" import validator from "validator" import jwt from "jsonwebtoken" import { uploadOnCloudinary,deleteOnCloudinary } from "../utils/cloudinary.utils.js" import logger from "../logger/create.logger.js" const token_option={ httpOnly: true, secure: true }; const generateAccessRefreshToken = async (user: any) => { const refreshToken = await user.generateRefreshToken(); const accessToken = await user.generateAccessToken(); return {accessToken, refreshToken} } async function ValidateAnything(dict: Record){ for(const [key,val] of Object.entries(dict)){ // Skip validation if value is null or undefined if(val === null || val === undefined) continue; if(key.toLowerCase()=='email'){ if(!validator.isEmail(String(val)) || (String(val)).trim()==''){ throw new ApiError(400,"Invalid email"); } } else if(key.toLowerCase()=='password'){ if(String(val).trim()=='' || String(val).length<6) { throw new ApiError(400,"password must be at least 6 characters long"); } } else if(key.toLowerCase()=='url'){ if(!validator.isURL(String(val)) || (String(val)).trim()==''){ throw new ApiError(400,"Invalid url"); } } } } export const createUser = expressRepre({ summary: "create user", body: { fullName: "Vansh Sharma", username:"vashuthegreat",email: "vanshsharma123@gmail.com", password: "122344544" }, response: "To create a user", }, asyncHandler(async(req,res)=>{ const {fullName, email, password, username} = req.body; logger.info(`Creating user with email: ${email}, username: ${username}`); if(!fullName || !email || !password || !username){ throw new ApiError(400,"All fields (fullName, email, password, username) are required"); } await ValidateAnything({email, password}); // Check if user already exists by email or username const existingUser = await User.findOne({ $or: [{email}, {username}] }); if (existingUser){ if (existingUser.email === email) { throw new ApiError(400,"email already taken, please choose another"); } throw new ApiError(400,"username already taken, please choose another"); } const user = new User({ fullName, email, password, username }); const {accessToken, refreshToken} = await generateAccessRefreshToken(user); user.refreshToken = refreshToken; await user.save(); const createdUser = await User.findById(user._id).select("-password -refreshToken"); if(!createdUser){ throw new ApiError(500,"Failed to register user"); } const userResponse = createdUser; logger.info(`User created successfully: ${userResponse?._id}`); res .cookie("refreshToken", refreshToken, token_option) .cookie("accessToken", accessToken, token_option) .json(new ApiResponse(200, userResponse, "User created successfully")); })) export const updateUser = expressRepre({ summary: "update user", body: { fullName: null, password: null, }, response: "user updated and the updated user " }, asyncHandler(async (req,res)=>{ const {fullName, password} = req.body; if(fullName && !fullName.trim()){ throw new ApiError(400,"Full name cannot be empty") } await ValidateAnything({password}); const user_id = req.user?._id; // Changed from .id to ._id if(!user_id){ throw new ApiError(401,"Unauthorized - User ID not found in token"); } const userInstance = await User.findById(user_id); if(!userInstance){ throw new ApiError(404,"User not found"); } if (fullName) userInstance.fullName = fullName; if (password) userInstance.password = password; await userInstance.save(); const userResponse = await User.findById(user_id).select("-password"); logger.info(`User updated: ${user_id}`); res.json(new ApiResponse(200, userResponse, "User updated successfully")); })) export const login = expressRepre({ summary: "login user by either email or email and password", body: { email: null, password: null, }, response: "user " }, asyncHandler(async (req,res)=>{ const {password, email: loginIdentifier} = req.body; logger.info(`Login attempt for: ${loginIdentifier}`); if(!password){ throw new ApiError(400,"Password is required"); } if(!loginIdentifier){ throw new ApiError(400,"Email or username is required"); } const trimmedIdentifier = loginIdentifier.trim(); const trimmedPassword = password.trim(); await ValidateAnything({password: trimmedPassword, email: trimmedIdentifier}); const userInstance = await User.findOne({ $or: [ { email: trimmedIdentifier }, { username: trimmedIdentifier.toLowerCase() } ] }); if(!userInstance){ throw new ApiError(404,"User not found"); } const isPasswordValid = await (userInstance as any).isPasswordCorrect(password); if(!isPasswordValid){ throw new ApiError(401,"Invalid credentials"); } const {accessToken, refreshToken} = await generateAccessRefreshToken(userInstance); userInstance.refreshToken = refreshToken; await userInstance.save(); const userResponse = await User.findById(userInstance._id).select("-password"); res .cookie("refreshToken", refreshToken, token_option) .cookie("accessToken", accessToken, token_option) .json(new ApiResponse(200, userResponse, "User logged in successfully")); })) export const Logout=expressRepre({ summary: "logout user ", response: "user logedout " }, asyncHandler(async (req,res)=>{ const user = await User.findById(req.user?._id); if(!user){ throw new ApiError(404,"User not found"); } user.refreshToken = null; await user.save(); res.clearCookie("refreshToken", token_option) .clearCookie("accessToken", token_option) .json(new ApiResponse(200, "User logged out successfully")); logger.info(`User logged out: ${req.user?._id}`); }) ) export const uploadAvatar=expressRepre( { summary: "upload avatar", FormData:{ avatar: "avatar.png" }, response: "avatar uploaded" }, asyncHandler(async (req,res)=>{ const user=await User.findById(req.user?._id); if(!user){ throw new ApiError(404,"User not found"); } if (user.avatar){ await deleteOnCloudinary(user.avatar); } const avatar = (req.files as any)?.avatar?.[0]; console.log(avatar) if (!avatar){ throw new ApiError(400,"Avatar is required"); } const avatarUrl=await uploadOnCloudinary(avatar.path); if (!avatarUrl){ throw new ApiError(500,"Failed to upload avatar"); } user.avatar=avatarUrl.url; await user.save(); res.status(200).json(new ApiResponse(200, user, "Avatar uploaded successfully")); }) ) export const deleteAvatar=expressRepre( { summary: "delete avatar", response: "avatar deleted" }, asyncHandler(async (req,res)=>{ const user=await User.findById(req.user?._id); if(!user){ throw new ApiError(404,"User not found"); } if (!user.avatar){ throw new ApiError(400,"Avatar not found"); } await deleteOnCloudinary(user.avatar); // Directly update the database to set profileImage to NULL user.avatar = null; await user.save(); res.status(200).json(new ApiResponse(200, await User.findById(user._id).select("-password"), "Avatar deleted successfully")); }) ) export const addResume=expressRepre( { summary: "upload resume", query:{ resume_id: "696c7ba53a981f5c038d009b" }, response: "resume uploaded list of links" }, asyncHandler(async (req,res)=>{ const user=await User.findById(req.user?._id); if(!user){ throw new ApiError(404,"User not found"); } const resume=req.query?.resume_id console.log(resume) if (!resume){ throw new ApiError(400,"Resume is required"); } // const resumeUrl=await uploadOnCloudinary(resume.path); // if (!resumeUrl){ // throw new ApiError(500,"Failed to upload resume"); // } user.resumes.push(resume); await user.save(); res.status(200).json(new ApiResponse(200, user, "Resume uploaded successfully")); }) ) export const addAboutUser=expressRepre( { summary: "add about user list of strings", body:{ aboutUser: "user persuing btech user is ai engineer user is ai enthusiasm" }, response: "about user added list of strings" }, asyncHandler(async (req,res)=>{ const aboutUser=req.body.aboutUser console.log(aboutUser) if (!aboutUser){ throw new ApiError(400,"About user is required"); } const user=await User.findByIdAndUpdate(req.user?._id,{aboutUser},{new:true}); if(!user){ throw new ApiError(404,"User not found"); } res.status(200).json(new ApiResponse(200, user, "About user added successfully")); }) ) export const deleteResume=expressRepre( { summary: "delete resume", params:{ idx: 0 }, response: "resume deleted" }, asyncHandler(async (req,res)=>{ const user=await User.findById(req.user?._id); const { idx } = req.params as { idx: string }; if(!user){ throw new ApiError(404,"User not found"); } if (user.resumes.length - 1 < Number(idx)) { throw new ApiError(400, "Resume not found"); } // Directly update the database to set profileImage to NULL user.resumes.splice(Number(idx), 1); await user.save(); res.status(200).json(new ApiResponse(200, await User.findById(user._id).select("-password"), "Resume deleted successfully")); }) ) export const getUserById = expressRepre( { summary: "send userId get userDetails", params: { id: '699a9bd773755482caa5996f' }, response: "userData", }, asyncHandler(async (req, res) => { const { id: _id } = req.params as { id: string }; console.log(req.params) if (!_id) { throw new ApiError(400, "User id is required"); } const user = await User.findById(_id).select("-password -refresstoken").lean(); if (!user) { throw new ApiError(404, "User does not exist"); } res.status(200).json(new ApiResponse(200, user, "User fetched successfully")); }) ); export const getUser=expressRepre( { summary:"simply hit get if logged in", response:"userData" }, asyncHandler( async(req,res)=>{ const _id=req.user?._id console.log(req.user._id) if(!_id){ throw new ApiError(400,"user id is required") } const user=await User.findById(_id).select("-password -refresstoken") if(!user){ throw new ApiError(300,"User does not exist") } res.status(200).json(new ApiResponse(200,user,"user fetched successfully")) } ) ) export const updateUserData=expressRepre( { summary:"Update user data json", body:{ "temp_data":`{ "personal": { "name": "John Doe", "title": "Software Engineer", "phone": "+1 234 567 890", "email": "john.doe@example.com", "location": "San Francisco, CA", "image": { "enabled": true, "url": "" } }, "summary": "Experienced software engineer with a strong background in full-stack development and a passion for building scalable applications.", "education": [ { "year": "2016 - 2020", "institute": "University of California, Berkeley", "degree": "Bachelor of Science in Computer Science" } ], "skills": [ "JavaScript", "Node.js", "React", "MongoDB", "Docker", "Leadership", "Communication", "Problem Solving" ], "soft_skills": [ "Leadership", "Communication", "Problem Solving" ], "languages": [ "English", "Spanish" ], "experience": [ { "role": "Senior Developer", "duration": "2021 - Present", "company": "Tech Solutions Inc.", "description": "Lead a team of 5 developers in building a high-traffic e-commerce platform. Implemented microservices architecture using Node.js and AWS. Optimized database queries, reducing response time by 30%." }, { "role": "Junior Developer", "duration": "2020 - 2021", "company": "Startup Hub", "description": "Developed and maintained RESTful APIs for a mobile application. Collaborated with UI/UX designers to implement responsive web interfaces. Participated in code reviews and unit testing." } ], "projects": [ { "title": "AI Resume Builder", "description": "A web application that generates multiple resume templates dynamically using AI and user data.", "tech_stack": ["React", "Node.js", "EJS"] } ], "certifications": [ "AWS Certified Developer", "Full Stack Web Development" ], "achievements": [ "Employee of the Month", "Winner of Hackathon 2023" ], "references": [ { "name": "Jane Smith", "company": "Tech Solutions Inc.", "position": "CTO", "phone": "+1 987 654 321", "email": "jane.smith@techsolutions.com" } ], "someImportantUrls": { "GitHub": "https://github.com/johndoe", "LinkedIn": "https://linkedin.com/in/johndoe", "Portfolio": "https://johndoe.com" } }` }, response:"updated user" }, asyncHandler(async (req,res)=>{ let {temp_data}=req.body; const user_id=req.user?._id; if (!temp_data){ throw new ApiError(400, "temp_data is required"); } if (typeof temp_data === 'string') { try { temp_data = JSON.parse(temp_data); } catch (error) { // If it fails to parse, it will just stay as a string, which is fine } } console.log(temp_data); const user=await User.findByIdAndUpdate(user_id,{temp_data:temp_data},{new:true}).select("-password -refreshToken"); if (!user){ throw new ApiError(404, "User not found"); } return res.status(200).json(new ApiResponse(200,user)); }) ) export const refreshAccessToken=expressRepre({ summary:"Use this get route for refreshing tokens", response:"Token refreshed" },asyncHandler(async (req,res)=>{ const incommingRefreshToken=req.cookies.refreshToken||req.body.refreshToken; if(!incommingRefreshToken) throw new ApiError(401,"unautherized request"); const decoded_token = jwt.verify(incommingRefreshToken, process.env.REFRESH_TOKEN_SECRET as string) as jwt.JwtPayload; const user = await User.findById(decoded_token?._id); if (incommingRefreshToken !== user?.refreshToken) { throw new ApiError(401, "Refresh token is expired or used"); } const { accessToken, refreshToken: newRefreshToken } = await generateAccessRefreshToken(user); if (user) { user.refreshToken = newRefreshToken; await user.save({ validateBeforeSave: false }); } return res.status(200) .cookie("accessToken",accessToken,token_option) .cookie("refreshToken",newRefreshToken,token_option) .json( new ApiResponse( 200, {accessToken,refreshToken:newRefreshToken}, "Access token refreshed" ) ) }))