diff --git a/.github/workflows/issue-assigned.yaml b/.github/workflows/issue-assigned.yaml new file mode 100644 index 00000000..88da2127 --- /dev/null +++ b/.github/workflows/issue-assigned.yaml @@ -0,0 +1,25 @@ +name: Issue Assigned Comment + +on: + issues: + types: [assigned] + +jobs: + comment-on-issue: + runs-on: ubuntu-latest + steps: + - name: Comment on the assigned issue + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue_number = context.issue.number; + const issue_assignee = context.payload.assignee.login; + const contributing_guidelines_url = 'https://github.com/krishnaacharyaa/wanderlust/blob/main/.github/CONTRIBUTING.md'; + + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: `Hey @${issue_assignee} 🎉! Thanks for jumping on this issue. Before you dive in, please check out our [contributing guidelines](${contributing_guidelines_url}) to ensure we're all on the same page. Happy coding! 🚀` + }); diff --git a/.husky/commit-msg b/.husky/commit-msg index d4e6e3d5..56b715e6 100644 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -19,14 +19,14 @@ else echo ": # if you don't have relevant issue" echo "" echo "Examples:" - echo "❌ \033[31mFixed a bug\033[0m" - echo "✅ \033[32mfix-#123: Fixed a bug\033[0m" - echo "❌ \033[31mUpdated documentation file\033[0m" - echo "✅ \033[32mchore: Updated documentation\033[0m" + echo "❌ Fixed a bug" + echo "✅ fix-#123: Fixed a bug" + echo "❌ Updated documentation file" + echo "✅ chore: Updated documentation" echo "" echo "To know all the available tags and for more details" - echo "Kindly refer to \033[0m\033[4;34m\033]8;;https://github.com/krishnaacharyaa/wanderlust/blob/9b11b769bb23150b746296cf9008056633d21921/.github/CONTRIBUTING.md#guidelines-for-contributions\a\033[4;34mContributing Guidelines\033]8;;\a\033[0m." + echo "Kindly refer to \033]8;;https://github.com/krishnaacharyaa/wanderlust/blob/9b11b769bb23150b746296cf9008056633d21921/.github/CONTRIBUTING.md#guidelines-for-contributions\aContributing Guidelines\033]8;;\a." echo "" exit 1 fi \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 39ce42e6..6632443d 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,5 @@ #!/usr/bin/env sh + . "$(dirname -- "$0")/_/husky.sh" cd frontend diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json index c0210c6f..19637633 100644 --- a/backend/.eslintrc.json +++ b/backend/.eslintrc.json @@ -6,14 +6,15 @@ }, "extends": ["eslint:recommended", "plugin:prettier/recommended"], "env": { + "node": true, "jest": true, - "es2021": true, - "node": true + "es2021": true }, "ignorePatterns": ["note_modules/"], - + "plugins": ["babel", "jest", "prettier"], "rules": { + "react/react-in-jsx-scope": "off", "no-console": "off", "import/extensions": "off" } diff --git a/backend/app.js b/backend/app.js index 68b150bb..58e82b78 100644 --- a/backend/app.js +++ b/backend/app.js @@ -10,33 +10,34 @@ import errorMiddleware from './middlewares/error-middleware.js'; const app = express(); -app.use(cors({ +app.use( + cors({ // added origin origin: FRONTEND_URL, - credentials: true -})); + credentials: true, + }) +); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use(compression()); - // API route app.use('/api/posts', postsRouter); app.use('/api/auth', authRouter); app.use('/api/user', userRouter); app.get('/', (req, res) => { - res.send('Yay!! Backend of wanderlust app is now accessible'); + res.send('Yay!! Backend of wanderlust app is now accessible'); }); -app.all("*", (req, res) => { - res.status(404).json({ - status: 404, - success: false, - message: "!Oops page not found" - }) -}) +app.all('*', (req, res) => { + res.status(404).json({ + status: 404, + success: false, + message: '!Oops page not found', + }); +}); -app.use(errorMiddleware) -export default app; \ No newline at end of file +app.use(errorMiddleware); +export default app; diff --git a/backend/config/db.js b/backend/config/db.js index e907c665..51117b35 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -4,8 +4,7 @@ import { MONGODB_URI } from './utils.js'; export default async function connectDB() { try { await mongoose.connect(MONGODB_URI, { - // set database name - dbName: 'wanderlust' + dbName: 'wanderlust', }); console.log(`Database connected: ${MONGODB_URI}`); } catch (err) { diff --git a/backend/config/utils.js b/backend/config/utils.js index 22f40b0d..4a8ed758 100644 --- a/backend/config/utils.js +++ b/backend/config/utils.js @@ -10,7 +10,7 @@ const REFRESH_COOKIE_MAXAGE = process.env.REFRESH_COOKIE_MAXAGE; const REFRESH_TOKEN_EXPIRES_IN = process.env.REFRESH_TOKEN_EXPIRES_IN; const JWT_SECRET = process.env.JWT_SECRET; const FRONTEND_URL = process.env.FRONTEND_URL; -const NODE_ENV = process.env.NODE_ENV +const NODE_ENV = process.env.NODE_ENV; export { MONGODB_URI, @@ -22,5 +22,5 @@ export { REFRESH_TOKEN_EXPIRES_IN, JWT_SECRET, FRONTEND_URL, - NODE_ENV + NODE_ENV, }; diff --git a/backend/controllers/auth-controller.js b/backend/controllers/auth-controller.js index 87158136..bdeb3fea 100644 --- a/backend/controllers/auth-controller.js +++ b/backend/controllers/auth-controller.js @@ -1,10 +1,8 @@ import User from '../models/user.js'; import jwt from 'jsonwebtoken'; -import axios from 'axios'; import { HTTP_STATUS, RESPONSE_MESSAGES } from '../utils/constants.js'; import { cookieOptions } from '../utils/cookie_options.js'; import { JWT_SECRET } from '../config/utils.js'; -const { sign } = jwt; import { ApiError } from '../utils/api-error.js'; import { ApiResponse } from '../utils/api-response.js'; import { asyncHandler } from '../utils/async-handler.js'; @@ -14,13 +12,13 @@ import { asyncHandler } from '../utils/async-handler.js'; export const signUpWithEmail = asyncHandler(async (req, res) => { const { userName, fullName, email, password } = req.body; if (!userName || !fullName || !email || !password) { - throw new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.COMMON.REQUIRED_FIELDS) + throw new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.COMMON.REQUIRED_FIELDS); } - const existingUser = await User.findOne({ - $or: [{ userName }, { email }] + const existingUser = await User.findOne({ + $or: [{ userName }, { email }], }); - + if (existingUser) { if (existingUser.userName === userName) { throw new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.USERS.USER_USERNAME_EXISTS); @@ -34,11 +32,11 @@ export const signUpWithEmail = asyncHandler(async (req, res) => { userName, fullName, email, - password - }) + password, + }); try { - await user.validate() + await user.validate(); } catch (error) { const validationErrors = []; for (const key in error.errors) { @@ -47,24 +45,29 @@ export const signUpWithEmail = asyncHandler(async (req, res) => { throw new ApiError(HTTP_STATUS.BAD_REQUEST, validationErrors.join(', ')); } - const accessToken = await user.generateAccessToken() - const refreshToken = await user.generateRefreshToken() + const accessToken = await user.generateAccessToken(); + const refreshToken = await user.generateRefreshToken(); - user.refreshToken = refreshToken + user.refreshToken = refreshToken; - await user.save() - user.password = undefined + await user.save(); + user.password = undefined; res .status(HTTP_STATUS.OK) .cookie('access_token', accessToken, cookieOptions) .cookie('refresh_token', refreshToken, cookieOptions) - .json(new ApiResponse(HTTP_STATUS.OK, { - accessToken, - refreshToken, - user - }, RESPONSE_MESSAGES.USERS.SIGNED_UP)) - + .json( + new ApiResponse( + HTTP_STATUS.OK, + { + accessToken, + refreshToken, + user, + }, + RESPONSE_MESSAGES.USERS.SIGNED_UP + ) + ); }); //2.Sign In @@ -75,312 +78,41 @@ export const signInWithEmailOrUsername = asyncHandler(async (req, res) => { } const user = await User.findOne({ - $or: [{ email: userNameOrEmail }, { userName: userNameOrEmail }] - }).select("+password") + $or: [{ email: userNameOrEmail }, { userName: userNameOrEmail }], + }).select('+password'); if (!user) { throw new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS); } - const isCorrectPassword = await user.isPasswordCorrect(password) + const isCorrectPassword = await user.isPasswordCorrect(password); if (!isCorrectPassword) { - throw new ApiError(HTTP_STATUS.UNAUTHORIZED, RESPONSE_MESSAGES.USERS.INVALID_PASSWORD) + throw new ApiError(HTTP_STATUS.UNAUTHORIZED, RESPONSE_MESSAGES.USERS.INVALID_PASSWORD); } - const accessToken = await user.generateAccessToken() - const refreshToken = await user.generateRefreshToken() + const accessToken = await user.generateAccessToken(); + const refreshToken = await user.generateRefreshToken(); - user.refreshToken = refreshToken - await user.save() - user.password = undefined + user.refreshToken = refreshToken; + await user.save(); + user.password = undefined; res .status(HTTP_STATUS.OK) .cookie('access_token', accessToken, cookieOptions) .cookie('refresh_token', refreshToken, cookieOptions) - .json(new ApiResponse(HTTP_STATUS.OK, { - accessToken, - refreshToken, - user - }, RESPONSE_MESSAGES.USERS.SIGNED_IN)) - -}); - -//GOOGLE STRTEGY -//1.Open google auth window -export const openGoogleAuthWindow = (req, res, next) => { - const googleAuthUrl = 'https://accounts.google.com/o/oauth2/v2/auth?'; - const params = new URLSearchParams({ - client_id: process.env.GAUTH_CLIENT_ID, - redirect_uri: process.env.REDIRECTION_URL, - state: 'google-auth-provider', - scope: 'profile email', - response_type: 'code', - }); - - res.redirect(googleAuthUrl + params.toString()); -}; - -//2.Sign Up -export const signUpWithGoogle = async (req, res, next) => { - const code = req.query.code; - if (!code) { - res.status(HTTP_STATUS.BAD_REQUEST).json({ - success: false, - message: RESPONSE_MESSAGES.USERS.CODE_NOT_FOUND, - }); - } - const tokenUrl = process.env.GAUTH_TOKEN_URL; - try { - const tokenResponse = await axios.post( - tokenUrl, - { - client_id: process.env.GAUTH_CLIENT_ID, - client_secret: process.env.GAUTH_CLIENT_SECRET, - code, - redirect_uri: process.env.REDIRECTION_URL, - grant_type: 'authorization_code', - }, - { - headers: { Accept: 'application/json' }, - } - ); - const userInfo = await axios.get(process.env.GAUTH_USER_URL, { - headers: { Authorization: `Bearer ${tokenResponse.data.access_token}` }, - }); - const { email, name } = userInfo.data; - const isUserAlreadyExists = await User.findOne({ email }); - if (isUserAlreadyExists) { - throw new Error(RESPONSE_MESSAGES.USERS.EMAIL_ALREADY_IN_USE); - } - const newUser = await User.create({ - name, - email, - }); - const payload = { name, _id: newUser._id }; - const accessToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN, - }); - const refreshToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, - }); - res.cookie('access_token', accessToken, accessCookieOptions); - res.cookie('refresh_token', refreshToken, refreshCookieOptions); - res.status(HTTP_STATUS.OK).json({ - success: true, - message: RESPONSE_MESSAGES.USERS.SIGNED_UP, - user: { - name: name, - id: newUser._id, - }, - accessToken, - refreshToken, - }); - } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ - success: false, - message: error.message, - }); - } -}; - -//3.Sign In -export const signInWithGoogle = async (req, res, next) => { - const code = req.query.code; - if (!code) { - res.status(HTTP_STATUS.BAD_REQUEST).json({ - success: false, - message: RESPONSE_MESSAGES.USERS.CODE_NOT_FOUND, - }); - } - const tokenUrl = process.env.GAUTH_TOKEN_URL; - try { - const tokenResponse = await axios.post( - tokenUrl, - { - client_id: process.env.GAUTH_CLIENT_ID, - client_secret: process.env.GAUTH_CLIENT_SECRET, - code, - redirect_uri: process.env.REDIRECTION_URL, - grant_type: 'authorization_code', - }, - { - headers: { Accept: 'application/json' }, - } - ); - const userInfo = await axios.get(process.env.GAUTH_USER_URL, { - headers: { Authorization: `Bearer ${tokenResponse.data.access_token}` }, - }); - const { email, name } = userInfo.data; - const isUserExists = await User.findOne({ email }); - if (!isUserExists) { - throw new Error(RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS); - } - const payload = { name, _id: isUserExists._id }; - const accessToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN, - }); - const refreshToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, - }); - res.cookie('access_token', accessToken, accessCookieOptions); - res.cookie('refresh_token', refreshToken, refreshCookieOptions); - - res.status(HTTP_STATUS.OK).json({ - success: true, - message: RESPONSE_MESSAGES.USERS.SIGNED_IN, - user: { - name: name, - id: isUserExists._id, - }, - accessToken, - refreshToken, - }); - } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ - success: false, - message: error.message, - }); - } -}; - -//GITHUB STRATEGY -//1.Open Github auth window -export const openGithubAuthWindow = (req, res, next) => { - const githubAuthUrl = 'https://github.com/login/oauth/authorize?'; - const params = new URLSearchParams({ - client_id: process.env.GITHUB_CLIENT_ID, - redirect_uri: process.env.REDIRECTION_URL, - state: 'github-auth-provider', - scope: 'user:read user:email', - response_type: 'code', - }); - - res.redirect(githubAuthUrl + params.toString()); -}; - -//2.Sign up -export const signUpWithGithub = async (req, res, next) => { - const code = req.query.code; - if (!code) { - res.status(HTTP_STATUS.BAD_REQUEST).json({ - success: false, - message: RESPONSE_MESSAGES.USERS.CODE_NOT_FOUND, - }); - } - const tokenUrl = process.env.GITHUB_TOKEN_URL; - try { - const tokenResponse = await axios.post( - tokenUrl, - { - client_id: process.env.GITHUB_CLIENT_ID, - client_secret: process.env.GITHUB_CLIENT_SECRET, - code, - }, - { - headers: { Accept: 'application/json' }, - } - ); - const userInfo = await axios.get(process.env.GITHUB_USER_URL, { - headers: { Authorization: `Bearer ${tokenResponse.data.access_token}` }, - }); - if (userInfo.data.email == null) { - throw new Error("Your github account's email is not publically available."); - } - const { name, email } = userInfo.data; - const isUserAlreadyExists = await User.findOne({ email }); - if (isUserAlreadyExists) { - throw new Error(RESPONSE_MESSAGES.USERS.EMAIL_ALREADY_IN_USE); - } - const newUser = await User.create({ - name, - email, - }); - const payload = { name, _id: newUser._id }; - const accessToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN, - }); - const refreshToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, - }); - res.cookie('access_token', accessToken, accessCookieOptions); - res.cookie('refresh_token', refreshToken, refreshCookieOptions); - - res.status(HTTP_STATUS.OK).json({ - success: true, - message: RESPONSE_MESSAGES.USERS.SIGNED_UP, - user: { - name, - id: newUser._id, - }, - accessToken, - refreshToken, - }); - } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ - success: false, - message: error.message, - }); - } -}; - -//3.Sign In -export const signInWithGithub = async (req, res, next) => { - const code = req.query.code; - if (!code) { - res.status(HTTP_STATUS.BAD_REQUEST).json({ - success: false, - message: RESPONSE_MESSAGES.USERS.CODE_NOT_FOUND, - }); - } - const tokenUrl = process.env.GITHUB_TOKEN_URL; - try { - const tokenResponse = await axios.post( - tokenUrl, - { - client_id: process.env.GITHUB_CLIENT_ID, - client_secret: process.env.GITHUB_CLIENT_SECRET, - code, - }, - { - headers: { Accept: 'application/json' }, - } + .json( + new ApiResponse( + HTTP_STATUS.OK, + { + accessToken, + refreshToken, + user, + }, + RESPONSE_MESSAGES.USERS.SIGNED_IN + ) ); - const userInfo = await axios.get(process.env.GITHUB_USER_URL, { - headers: { Authorization: `Bearer ${tokenResponse.data.access_token}` }, - }); - const { name, email } = userInfo.data; - const isUserExists = await User.findOne({ email }); - if (!isUserExists) { - throw new Error(RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS); - } - const payload = { name, _id: isUserExists._id }; - const accessToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN, - }); - const refreshToken = sign(payload, process.env.JWT_SECRET, { - expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN, - }); - res.cookie('access_token', accessToken, accessCookieOptions); - res.cookie('refresh_token', refreshToken, refreshCookieOptions); - - res.status(HTTP_STATUS.OK).json({ - success: true, - message: RESPONSE_MESSAGES.USERS.SIGNED_IN, - user: { - name, - id: isUserExists._id, - }, - accessToken, - refreshToken, - }); - } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ - success: false, - message: error.message, - }); - } -}; +}); //Sign Out export const signOutUser = asyncHandler(async (req, res) => { @@ -388,85 +120,86 @@ export const signOutUser = asyncHandler(async (req, res) => { req.user?._id, { $set: { - refreshToken: '' - } + refreshToken: '', + }, }, { - new: true + new: true, } - ) + ); res .status(HTTP_STATUS.OK) - .clearCookie("access_token", cookieOptions) - .clearCookie("refresh_token", cookieOptions) - .json( - new ApiResponse(HTTP_STATUS.OK, '', RESPONSE_MESSAGES.USERS.SIGNED_OUT) - ) + .clearCookie('access_token', cookieOptions) + .clearCookie('refresh_token', cookieOptions) + .json(new ApiResponse(HTTP_STATUS.OK, '', RESPONSE_MESSAGES.USERS.SIGNED_OUT)); }); // check user export const isLoggedIn = asyncHandler(async (req, res) => { - let access_token = req.cookies?.access_token - let refresh_token = req.cookies?.refresh_token - const { _id } = req.params + let access_token = req.cookies?.access_token; + let refresh_token = req.cookies?.refresh_token; + const { _id } = req.params; if (access_token) { try { - await jwt.verify(access_token, JWT_SECRET) - return res.status(HTTP_STATUS.OK).json( - new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN) - ) + await jwt.verify(access_token, JWT_SECRET); + return res + .status(HTTP_STATUS.OK) + .json(new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN)); } catch (error) { // Access token invalid, proceed to check refresh token + console.log(error); } - } - else if (refresh_token) { + } else if (refresh_token) { try { - await jwt.verify(refresh_token, JWT_SECRET) - access_token = await user.generateAccessToken() + await jwt.verify(refresh_token, JWT_SECRET); + access_token = await user.generateAccessToken(); return res .status(HTTP_STATUS.OK) .cookie('access_token', access_token, cookieOptions) - .json( - new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN) - ) + .json(new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN)); } catch (error) { // Access token invalid, proceed to check refresh token that is in db + console.log(error); } } - const user = await User.findById(_id) + const user = await User.findById(_id); if (!user) { - return res.status(HTTP_STATUS.NOT_FOUND).json( - new ApiResponse(HTTP_STATUS.NOT_FOUND, '', RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS) - ) + return res + .status(HTTP_STATUS.NOT_FOUND) + .json(new ApiResponse(HTTP_STATUS.NOT_FOUND, '', RESPONSE_MESSAGES.USERS.USER_NOT_EXISTS)); } - const { refreshToken } = user + const { refreshToken } = user; if (!refreshToken) { - return res.status(HTTP_STATUS.UNAUTHORIZED).json( - new ApiResponse(HTTP_STATUS.UNAUTHORIZED, '', RESPONSE_MESSAGES.USERS.INVALID_TOKEN) - ) + return res + .status(HTTP_STATUS.UNAUTHORIZED) + .json(new ApiResponse(HTTP_STATUS.UNAUTHORIZED, '', RESPONSE_MESSAGES.USERS.INVALID_TOKEN)); } try { - await jwt.verify(refreshToken, JWT_SECRET) - access_token = await user.generateAccessToken() - refresh_token = await user.generateRefreshToken() + await jwt.verify(refreshToken, JWT_SECRET); + access_token = await user.generateAccessToken(); + refresh_token = await user.generateRefreshToken(); - user.refreshToken = refresh_token - await user.save() + user.refreshToken = refresh_token; + await user.save(); return res .status(HTTP_STATUS.OK) .cookie('access_token', access_token, cookieOptions) .cookie('refresh_token', refresh_token, cookieOptions) - .json( - new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN) - ) + .json(new ApiResponse(HTTP_STATUS.OK, access_token, RESPONSE_MESSAGES.USERS.VALID_TOKEN)); } catch (error) { - return res.status(HTTP_STATUS.UNAUTHORIZED).json( - new ApiResponse(HTTP_STATUS.UNAUTHORIZED, '', RESPONSE_MESSAGES.USERS.INVALID_TOKEN) - ) + return res + .status(HTTP_STATUS.UNAUTHORIZED) + .json( + new ApiResponse( + HTTP_STATUS.UNAUTHORIZED, + error.message, + RESPONSE_MESSAGES.USERS.INVALID_TOKEN + ) + ); } -}); \ No newline at end of file +}); diff --git a/backend/controllers/posts-controller.js b/backend/controllers/posts-controller.js index cd8d9dfe..6de2f83c 100644 --- a/backend/controllers/posts-controller.js +++ b/backend/controllers/posts-controller.js @@ -1,10 +1,6 @@ import Post from '../models/post.js'; import User from '../models/user.js'; -import { - deleteDataFromCache, - retrieveDataFromCache, - storeDataInCache, -} from '../utils/cache-posts.js'; +import { deleteDataFromCache, storeDataInCache } from '../utils/cache-posts.js'; import { HTTP_STATUS, REDIS_KEYS, RESPONSE_MESSAGES, validCategories } from '../utils/constants.js'; export const createPostHandler = async (req, res) => { try { diff --git a/backend/controllers/user-controller.js b/backend/controllers/user-controller.js index e449c388..dd159131 100644 --- a/backend/controllers/user-controller.js +++ b/backend/controllers/user-controller.js @@ -6,7 +6,7 @@ export const getAllUserHandler = async (req, res) => { const users = await User.find().select('_id name email'); return res.status(HTTP_STATUS.OK).json({ users }); } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message }); + res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: error.message }); } }; @@ -31,7 +31,7 @@ export const changeUserRoleHandler = async (req, res) => { } catch (error) { res .status(HTTP_STATUS.INTERNAL_SERVER_ERROR) - .json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR }); + .json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR, error: error }); } }; @@ -47,6 +47,6 @@ export const deleteUserHandler = async (req, res) => { } catch (error) { res .status(HTTP_STATUS.INTERNAL_SERVER_ERROR) - .json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR }); + .json({ message: RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR, error: error }); } }; diff --git a/backend/eslint.config.js b/backend/eslint.config.js index 719b0297..98e6a9bb 100644 --- a/backend/eslint.config.js +++ b/backend/eslint.config.js @@ -1,4 +1,7 @@ import globals from 'globals'; import pluginJs from '@eslint/js'; -export default [{ languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended]; +export default [ + { languageOptions: { globals: { ...globals.node } } }, + pluginJs.configs.recommended, +]; diff --git a/backend/middlewares/auth-middleware.js b/backend/middlewares/auth-middleware.js index 6bc82e51..39c8d8bd 100644 --- a/backend/middlewares/auth-middleware.js +++ b/backend/middlewares/auth-middleware.js @@ -1,18 +1,18 @@ import { JWT_SECRET } from '../config/utils.js'; -import { ApiError } from '../utils/api-error.js' +import { ApiError } from '../utils/api-error.js'; import { HTTP_STATUS, RESPONSE_MESSAGES } from '../utils/constants.js'; import jwt from 'jsonwebtoken'; export const authMiddleware = async (req, res, next) => { - const token = req.cookies?.access_token + const token = req.cookies?.access_token; if (!token) { - return next(new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.USERS.RE_LOGIN)) + return next(new ApiError(HTTP_STATUS.BAD_REQUEST, RESPONSE_MESSAGES.USERS.RE_LOGIN)); } if (token) { await jwt.verify(token, JWT_SECRET, (error, payload) => { if (error) { - return new ApiError(HTTP_STATUS.FORBIDDEN, RESPONSE_MESSAGES.USERS.INVALID_TOKEN) + return new ApiError(HTTP_STATUS.FORBIDDEN, RESPONSE_MESSAGES.USERS.INVALID_TOKEN); } req.user = payload; next(); @@ -23,7 +23,7 @@ export const authMiddleware = async (req, res, next) => { export const isAdminMiddleware = async (req, res, next) => { const role = req.user.role; if (role !== 'ADMIN') { - return new ApiError(HTTP_STATUS.UNAUTHORIZED, RESPONSE_MESSAGES.USERS.UNAUTHORIZED_USER) + return new ApiError(HTTP_STATUS.UNAUTHORIZED, RESPONSE_MESSAGES.USERS.UNAUTHORIZED_USER); } next(); }; diff --git a/backend/middlewares/error-middleware.js b/backend/middlewares/error-middleware.js index f1c672ba..e4d0b6d6 100644 --- a/backend/middlewares/error-middleware.js +++ b/backend/middlewares/error-middleware.js @@ -1,13 +1,13 @@ -import { HTTP_STATUS, RESPONSE_MESSAGES } from "../utils/constants.js"; +import { HTTP_STATUS, RESPONSE_MESSAGES } from '../utils/constants.js'; const errorMiddleware = (err, req, res, next) => { - console.error(err.stack); - - res.status(err.status || HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ - status: err.status || HTTP_STATUS.INTERNAL_SERVER_ERROR, - message: err.message || RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR, - errors: err.errors || [], - }); + console.error(err.stack); + res.status(err.status || HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ + status: err.status || HTTP_STATUS.INTERNAL_SERVER_ERROR, + message: err.message || RESPONSE_MESSAGES.COMMON.INTERNAL_SERVER_ERROR, + errors: err.errors || [], + }); + next(); }; -export default errorMiddleware \ No newline at end of file +export default errorMiddleware; diff --git a/backend/middlewares/post-middleware.js b/backend/middlewares/post-middleware.js index 83378264..4a7dd72d 100644 --- a/backend/middlewares/post-middleware.js +++ b/backend/middlewares/post-middleware.js @@ -18,6 +18,6 @@ export const isAuthorMiddleware = async (req, res, next) => { } next(); } catch (error) { - res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message }); + res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ message: error.message }); } }; diff --git a/backend/models/post.js b/backend/models/post.js index efe645da..1a3ab4db 100644 --- a/backend/models/post.js +++ b/backend/models/post.js @@ -1,4 +1,4 @@ -import mongoose, { Schema, model } from 'mongoose'; +import { Schema, model } from 'mongoose'; const postSchema = new Schema({ authorName: String, diff --git a/backend/models/user.js b/backend/models/user.js index bba31df7..618696e7 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -1,98 +1,115 @@ import { Schema, model } from 'mongoose'; -import JWT from 'jsonwebtoken' -import bcrypt from 'bcryptjs' -import crypto from 'crypto' +import JWT from 'jsonwebtoken'; +import bcrypt from 'bcryptjs'; +import crypto from 'crypto'; import { ACCESS_TOKEN_EXPIRES_IN, JWT_SECRET, REFRESH_TOKEN_EXPIRES_IN } from '../config/utils.js'; -const userSchema = new Schema({ - userName: { - type: String, - required: [true, 'Username is required'], - lowercase: true, - unique: true, - trim: true, - index: true +const userSchema = new Schema( + { + userName: { + type: String, + required: [true, 'Username is required'], + lowercase: true, + unique: true, + trim: true, + index: true, + }, + fullName: { + type: String, + required: [true, 'Name is required'], + minLength: [3, 'Name must be at least 3 character'], + maxLength: [15, 'Name should be less than 15 character'], + trim: true, + }, + email: { + type: String, + unique: true, + required: [true, 'Email is required'], + trim: true, + match: [ + /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, + 'Please Enter a valid email address', + ], + }, + password: { + type: String, + required: [true, 'Password is required'], + minLength: [8, 'Password must be at least 8 character '], + match: [ + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/, + 'Password must be contains at least one uppercase and one lowercase and one digit and one special character', + ], + select: false, + }, + avatar: { + type: String, + required: false, + }, + role: { + type: String, + default: 'USER', + enum: ['USER', 'ADMIN'], + }, + posts: [ + { + type: Schema.Types.ObjectId, + ref: 'Post', + }, + ], + refreshToken: String, + forgotPasswordToken: String, + forgotPasswordExpiry: Date, }, - fullName: { - type: String, - required: [true, 'Name is required'], - minLength: [3, 'Name must be at least 3 character'], - maxLength: [15, 'Name should be less than 15 character'], - trim: true - }, - email: { - type: String, - unique: true, - required: [true, 'Email is required'], - trim: true, - match: [/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, 'Please Enter a valid email address'] - }, - password: { - type: String, - required: [true, 'Password is required'], - minLength: [8, 'Password must be at least 8 character '], - match: [/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/, 'Password must be contains at least one uppercase and one lowercase and one digit and one special character'], - select: false - }, - avatar: { - type: String, - required: false, - }, - role: { - type: String, - default: 'USER', - enum: ['USER', 'ADMIN'] - }, - posts: [ - { - type: Schema.Types.ObjectId, - ref: 'Post' - } - ], - refreshToken: String, - forgotPasswordToken: String, - forgotPasswordExpiry: Date -}, { timestamps: true }); + { timestamps: true } +); userSchema.pre('save', async function (next) { if (!this.isModified('password')) { return next(); } this.password = await bcrypt.hash(this.password, 10); -}) +}); userSchema.methods = { isPasswordCorrect: async function (password) { return await bcrypt.compare(password, this.password); }, generateAccessToken: async function () { - return JWT.sign({ - _id: this._id, - username: this.userName, - email: this.email, - role: this.role - }, JWT_SECRET, { - expiresIn: ACCESS_TOKEN_EXPIRES_IN - }) + return JWT.sign( + { + _id: this._id, + username: this.userName, + email: this.email, + role: this.role, + }, + JWT_SECRET, + { + expiresIn: ACCESS_TOKEN_EXPIRES_IN, + } + ); }, generateRefreshToken: async function () { - return JWT.sign({ - _id: this._id, - username: this.userName, - email: this.email, - role: this.role - }, JWT_SECRET, { - expiresIn: REFRESH_TOKEN_EXPIRES_IN - }) + return JWT.sign( + { + _id: this._id, + username: this.userName, + email: this.email, + role: this.role, + }, + JWT_SECRET, + { + expiresIn: REFRESH_TOKEN_EXPIRES_IN, + } + ); }, generateResetToken: async function () { - const resetToken = crypto.randomBytes(20).toString('hex') - this.forgotPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex') - this.forgotPasswordExpiry = Date.now() + 15 * 60 * 1000 + const resetToken = crypto.randomBytes(20).toString('hex'); + this.forgotPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex'); + this.forgotPasswordExpiry = Date.now() + 15 * 60 * 1000; return resetToken; - } -} + }, +}; const User = model('User', userSchema); -export default User \ No newline at end of file +export default User; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 0fb7cc3b..c11b3394 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -3,28 +3,12 @@ import { authMiddleware } from '../middlewares/auth-middleware.js'; import { signUpWithEmail, signInWithEmailOrUsername, - openGoogleAuthWindow, - signUpWithGoogle, - signInWithGoogle, - openGithubAuthWindow, - signUpWithGithub, - signInWithGithub, signOutUser, isLoggedIn, } from '../controllers/auth-controller.js'; const router = Router(); -//GOOGLE STRATEGY -router.get('/google', openGoogleAuthWindow); -router.get('/google/signup/callback', signUpWithGoogle); -router.get('/google/signin/callback', signInWithGoogle); - -//GITHUB STRATEGY -router.get('/github', openGithubAuthWindow); -router.get('/github/signup/callback', signUpWithGithub); -router.get('/github/signin/callback', signInWithGithub); - //REGULAR EMAIL PASSWORD STRATEGY router.post('/email-password/signup', signUpWithEmail); router.post('/email-password/signin', signInWithEmailOrUsername); @@ -33,6 +17,6 @@ router.post('/email-password/signin', signInWithEmailOrUsername); router.post('/signout', authMiddleware, signOutUser); //CHECK USER STATUS -router.get('/check/:_id', isLoggedIn) +router.get('/check/:_id', isLoggedIn); export default router; diff --git a/backend/server.js b/backend/server.js index 41d25b3e..0e6f4e75 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,12 +1,12 @@ -import app from "./app.js"; -import connectDB from './config/db.js' -import { PORT } from "./config/utils.js"; -import { connectToRedis } from "./services/redis.js"; +import app from './app.js'; +import connectDB from './config/db.js'; +import { PORT } from './config/utils.js'; +import { connectToRedis } from './services/redis.js'; -const port = PORT || 8080 +const port = PORT || 8080; // Connect to redis -connectToRedis() +connectToRedis(); //Connect to Mongodb connectDB() @@ -16,5 +16,5 @@ connectDB() }); }) .catch((error) => { - console.log("MongoDB connection failed:", error); - }) \ No newline at end of file + console.log('MongoDB connection failed:', error); + }); diff --git a/backend/tests/integration/controllers/posts-controller.test.js b/backend/tests/integration/controllers/posts-controller.test.js index fb0dcbe3..cb747db0 100644 --- a/backend/tests/integration/controllers/posts-controller.test.js +++ b/backend/tests/integration/controllers/posts-controller.test.js @@ -4,6 +4,7 @@ import Post from '../../../models/post.js'; import server from '../../../server.js'; import { validCategories, HTTP_STATUS, RESPONSE_MESSAGES } from '../../../utils/constants.js'; import { createPostObject } from '../../utils/helper-objects.js'; +import { expect, jest, it, afterAll, describe } from '@jest/globals'; afterAll(async () => { await mongoose.disconnect(); diff --git a/backend/tests/unit/controllers/posts-controller.test.js b/backend/tests/unit/controllers/posts-controller.test.js index f8bcec30..a315ee36 100644 --- a/backend/tests/unit/controllers/posts-controller.test.js +++ b/backend/tests/unit/controllers/posts-controller.test.js @@ -9,6 +9,7 @@ import { updatePostHandler, } from '../../../controllers/posts-controller.js'; import Post from '../../../models/post.js'; +import { expect, jest, it, describe } from '@jest/globals'; import { validCategories, HTTP_STATUS, RESPONSE_MESSAGES } from '../../../utils/constants.js'; import { createPostObject, createRequestObject, res } from '../../utils/helper-objects.js'; diff --git a/backend/tests/utils/helper-objects.js b/backend/tests/utils/helper-objects.js index 6c3c9176..551a8368 100644 --- a/backend/tests/utils/helper-objects.js +++ b/backend/tests/utils/helper-objects.js @@ -1,5 +1,5 @@ import { validCategories } from '../../utils/constants'; - +import { jest } from '@jest/globals'; export const res = { json: jest.fn(), status: jest.fn().mockReturnThis(), diff --git a/backend/utils/api-error.js b/backend/utils/api-error.js index b37b34ca..260f5b0a 100644 --- a/backend/utils/api-error.js +++ b/backend/utils/api-error.js @@ -1,25 +1,20 @@ -import { RESPONSE_MESSAGES } from "./constants.js"; +import { RESPONSE_MESSAGES } from './constants.js'; class ApiError extends Error { - constructor( - status, - message = RESPONSE_MESSAGES.COMMON.SOMETHING_WRONG, - errors = [], - stack = "" - ) { - super(message); - this.status = status; - this.data = null; - this.message = message; - this.success = false; - this.errors = errors; + constructor(status, message = RESPONSE_MESSAGES.COMMON.SOMETHING_WRONG, errors = [], stack = '') { + super(message); + this.status = status; + this.data = null; + this.message = message; + this.success = false; + this.errors = errors; - if (stack) { - this.stack = stack; - } else { - Error.captureStackTrace(this, this.constructor); - } + if (stack) { + this.stack = stack; + } else { + Error.captureStackTrace(this, this.constructor); } + } } -export { ApiError }; \ No newline at end of file +export { ApiError }; diff --git a/backend/utils/api-response.js b/backend/utils/api-response.js index 17da85c3..3308bac8 100644 --- a/backend/utils/api-response.js +++ b/backend/utils/api-response.js @@ -1,12 +1,12 @@ -import { HTTP_STATUS } from "./constants.js"; +import { HTTP_STATUS } from './constants.js'; class ApiResponse { - constructor(status, data, message = "Success") { - this.status = status; - this.data = data; - this.message = message; - this.success = status < HTTP_STATUS.BAD_REQUEST; - } + constructor(status, data, message = 'Success') { + this.status = status; + this.data = data; + this.message = message; + this.success = status < HTTP_STATUS.BAD_REQUEST; + } } -export { ApiResponse } \ No newline at end of file +export { ApiResponse }; diff --git a/backend/utils/async-handler.js b/backend/utils/async-handler.js index 13d5629c..6fe0277a 100644 --- a/backend/utils/async-handler.js +++ b/backend/utils/async-handler.js @@ -1,5 +1,5 @@ export const asyncHandler = (func) => { - return (req, res, next) => { - Promise.resolve(func(req, res, next)).catch((err) => next(err)); - }; -}; \ No newline at end of file + return (req, res, next) => { + Promise.resolve(func(req, res, next)).catch((err) => next(err)); + }; +}; diff --git a/backend/utils/constants.js b/backend/utils/constants.js index a6d18e0d..8a4270b2 100644 --- a/backend/utils/constants.js +++ b/backend/utils/constants.js @@ -21,7 +21,7 @@ export const RESPONSE_MESSAGES = { COMMON: { INTERNAL_SERVER_ERROR: 'Internal Server Error', REQUIRED_FIELDS: 'All fields are required.', - SOMETHING_WRONG: 'Something went wrong' + SOMETHING_WRONG: 'Something went wrong', }, POSTS: { CREATED: 'Post created successfully', diff --git a/backend/utils/cookie_options.js b/backend/utils/cookie_options.js index 2c6658fd..d8779b67 100644 --- a/backend/utils/cookie_options.js +++ b/backend/utils/cookie_options.js @@ -2,7 +2,7 @@ import { ACCESS_COOKIE_MAXAGE, NODE_ENV } from '../config/utils.js'; export const cookieOptions = { httpOnly: true, - sameSite: NODE_ENV === "Development" ? "lax" : "none", - secure: NODE_ENV === "Development" ? false : true, + sameSite: NODE_ENV === 'Development' ? 'lax' : 'none', + secure: NODE_ENV === 'Development' ? false : true, maxAge: ACCESS_COOKIE_MAXAGE, }; diff --git a/backend/vercel.json b/backend/vercel.json index f3984b6a..b633a242 100644 --- a/backend/vercel.json +++ b/backend/vercel.json @@ -12,4 +12,4 @@ "dest": "server.js" } ] -} \ No newline at end of file +} diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 6389574b..74848a50 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -6,14 +6,12 @@ module.exports = { 'plugin:@typescript-eslint/recommended-type-checked', 'plugin:react-hooks/recommended', 'plugin:@typescript-eslint/stylistic-type-checked', - 'plugin:prettier/recommended' + 'plugin:prettier/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs', 'node_modules/'], parser: '@typescript-eslint/parser', - plugins: [ - 'react-refresh' - ], + plugins: ['react-refresh'], rules: { - 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, }; diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 92e5c35e..fc012a59 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,10 +1,15 @@ -import globals from "globals"; -import tseslint from "typescript-eslint"; -import pluginReactConfig from "eslint-plugin-react/configs/recommended.js"; - +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js'; export default [ - {languageOptions: { globals: globals.browser }}, + { languageOptions: { globals: globals.browser } }, ...tseslint.configs.recommended, pluginReactConfig, -]; \ No newline at end of file + { + rules: { + 'react/react-in-jsx-scope': 'off' , + 'react/no-unescaped-entities': 'off' + } + } +]; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 93f4edb0..acde5d8c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -30,10 +30,10 @@ function App() { } /> } /> - }> + }> } /> - }> + }> } /> } /> diff --git a/frontend/src/__tests__/App.test.tsx b/frontend/src/__tests__/App.test.tsx index 3f68c171..578870f8 100644 --- a/frontend/src/__tests__/App.test.tsx +++ b/frontend/src/__tests__/App.test.tsx @@ -5,4 +5,4 @@ describe('App', () => { it('should work as expected', () => { render(); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/__tests__/integration/home.test.tsx b/frontend/src/__tests__/integration/home.test.tsx index 743ab92f..53259976 100644 --- a/frontend/src/__tests__/integration/home.test.tsx +++ b/frontend/src/__tests__/integration/home.test.tsx @@ -5,7 +5,7 @@ import { BrowserRouter } from 'react-router-dom'; const mockedUseNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ - ...(jest.requireActual('react-router-dom') as Record), + ...(jest.requireActual('react-router-dom') as Record), useNavigate: () => mockedUseNavigate, })); @@ -129,4 +129,4 @@ describe('Integration Test: Home Route', () => { await userEvent.click(img); expect(mockedUseNavigate).toHaveBeenCalledTimes(1); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/__tests__/mocks/handeler-mock.ts b/frontend/src/__tests__/mocks/handeler-mock.ts index ba0a2e5c..e89146f3 100644 --- a/frontend/src/__tests__/mocks/handeler-mock.ts +++ b/frontend/src/__tests__/mocks/handeler-mock.ts @@ -1,10 +1,10 @@ // src/mocks/handlers.js -import { http, HttpResponse } from 'msw' - +import { http, HttpResponse } from 'msw'; + export const handlers = [ - http.post("http://localhost:5000/api/auth/email-password/signup", () => { - return new HttpResponse(null, { - status: 200, - }) - }) -] \ No newline at end of file + http.post('http://localhost:5000/api/auth/email-password/signup', () => { + return new HttpResponse(null, { + status: 200, + }); + }), +]; diff --git a/frontend/src/__tests__/mocks/node-mock.ts b/frontend/src/__tests__/mocks/node-mock.ts index 9aba7b75..3b07bb36 100644 --- a/frontend/src/__tests__/mocks/node-mock.ts +++ b/frontend/src/__tests__/mocks/node-mock.ts @@ -1,5 +1,5 @@ // src/mocks/node.js -import { setupServer } from 'msw/node' -import { handlers } from './handeler-mock' - -export const server = setupServer(...handlers) \ No newline at end of file +import { setupServer } from 'msw/node'; +import { handlers } from './handeler-mock'; + +export const server = setupServer(...handlers); diff --git a/frontend/src/__tests__/unit-test/signup/signup.test.tsx b/frontend/src/__tests__/unit-test/signup/signup.test.tsx index 629de8e0..32d8b677 100644 --- a/frontend/src/__tests__/unit-test/signup/signup.test.tsx +++ b/frontend/src/__tests__/unit-test/signup/signup.test.tsx @@ -22,24 +22,17 @@ vi.mock('react-router-dom', async () => { }); describe('Unit Tests : Signup Component', async () => { - test('Signup : Failure - Invalid Email Address', async () => { const userActions = userEvent.setup(); - const { - - emailInput, - signupbuttonText, - } = await formSetup(); - + const { emailInput, signupbuttonText } = await formSetup(); + await userActions.type(emailInput, 'abc@'); await userActions.click(signupbuttonText); - await waitFor(()=>{ + await waitFor(() => { //should need to trigger the default HTML validation error message - expect(emailInput.validity.typeMismatch).toBe(true) - }) - - - }) + expect(emailInput.validity.typeMismatch).toBe(true); + }); + }); test('Signup : Failure - Confirm Password is not same as Password', async () => { const userActions = userEvent.setup(); const { @@ -52,15 +45,15 @@ describe('Unit Tests : Signup Component', async () => { } = await formSetup(); await userActions.type(usernameInput, 'aryastark'); await userActions.type(emailInput, 'arya@gmail.com'); - await userActions.type(passwordInput,"12345678") - await userActions.type(confirmpasswordInput,"1234") + await userActions.type(passwordInput, '12345678'); + await userActions.type(confirmpasswordInput, '1234'); await userActions.click(signupbuttonText); - await waitFor(()=>{ - expect(form.getByText(INVALID_CONFIRMPWD_ERRORMESSAGE)).toBeInTheDocument() - }) - }) + await waitFor(() => { + expect(form.getByText(INVALID_CONFIRMPWD_ERRORMESSAGE)).toBeInTheDocument(); + }); + }); - test('Signup : Failure - Invalid Username', async() => { + test('Signup : Failure - Invalid Username', async () => { const userActions = userEvent.setup(); const { form, @@ -72,31 +65,25 @@ describe('Unit Tests : Signup Component', async () => { } = await formSetup(); await userActions.type(usernameInput, 'ary'); await userActions.type(emailInput, 'arya@gmail.com'); - await userActions.type(passwordInput,"12345678") - await userActions.type(confirmpasswordInput,"12345678") + await userActions.type(passwordInput, '12345678'); + await userActions.type(confirmpasswordInput, '12345678'); await userActions.click(signupbuttonText); - await waitFor(()=>{ - expect(form.getByText(INVALID_USERNAME_ERRORMESSAGE)).toBeInTheDocument() - }) - - }) - + await waitFor(() => { + expect(form.getByText(INVALID_USERNAME_ERRORMESSAGE)).toBeInTheDocument(); + }); + }); test('Signup : Failure - Form is submitted without any values', async () => { const userActions = userEvent.setup(); - const { - form, - signupbuttonText, - } = await formSetup(); + const { form, signupbuttonText } = await formSetup(); await userActions.click(signupbuttonText); - await waitFor(()=>{ - expect(form.getByText(USERNAME_EMPTY_ERRORMESSAGE)).toBeInTheDocument() - expect(form.getByText(EMAIL_EMPTY_ERRORMESSAGE)).toBeInTheDocument() - expect(form.getByText(PASSWORD_EMPTY_ERRORMESSAGE)).toBeInTheDocument() - expect(form.getByText(CONFIRMPASSWORD_EMPTY_ERRORMESSAGE)).toBeInTheDocument() - - }) - }) + await waitFor(() => { + expect(form.getByText(USERNAME_EMPTY_ERRORMESSAGE)).toBeInTheDocument(); + expect(form.getByText(EMAIL_EMPTY_ERRORMESSAGE)).toBeInTheDocument(); + expect(form.getByText(PASSWORD_EMPTY_ERRORMESSAGE)).toBeInTheDocument(); + expect(form.getByText(CONFIRMPASSWORD_EMPTY_ERRORMESSAGE)).toBeInTheDocument(); + }); + }); test('should display invalid error message when providing invalid inputs during signup', async () => { const userActions = userEvent.setup(); @@ -123,14 +110,8 @@ describe('Unit Tests : Signup Component', async () => { test('should call the signup api when all the input values are valid and should redirect to home page', async () => { const userActions = userEvent.setup(); - const { - - usernameInput, - emailInput, - passwordInput, - confirmpasswordInput, - signupbuttonText, - } = await formSetup(); + const { usernameInput, emailInput, passwordInput, confirmpasswordInput, signupbuttonText } = + await formSetup(); await userActions.type(usernameInput, 'aryastark'); await userActions.type(emailInput, 'arya@gmail.com'); await userActions.type(passwordInput, '123456789'); diff --git a/frontend/src/assets/svg/eye-off.svg b/frontend/src/assets/svg/eye-off.svg new file mode 100644 index 00000000..7e2c293a --- /dev/null +++ b/frontend/src/assets/svg/eye-off.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/svg/eye.svg b/frontend/src/assets/svg/eye.svg new file mode 100644 index 00000000..6a9e0fd1 --- /dev/null +++ b/frontend/src/assets/svg/eye.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/category-pill.tsx b/frontend/src/components/category-pill.tsx index bb392bbd..1e8545f0 100644 --- a/frontend/src/components/category-pill.tsx +++ b/frontend/src/components/category-pill.tsx @@ -20,7 +20,7 @@ export default function CategoryPill({ category, disabled, selected = false }: C return ( diff --git a/frontend/src/components/featured-post-card.tsx b/frontend/src/components/featured-post-card.tsx index 8fb1147a..374b804d 100644 --- a/frontend/src/components/featured-post-card.tsx +++ b/frontend/src/components/featured-post-card.tsx @@ -13,7 +13,7 @@ export default function FeaturedPostCard({ const slug = createSlug(post.title); return (
navigate(`/details-page/${slug}/${post._id}`, { state: { post } })} data-testid={testId} > diff --git a/frontend/src/components/require-auth.tsx b/frontend/src/components/require-auth.tsx index c18ed705..3dae12ef 100644 --- a/frontend/src/components/require-auth.tsx +++ b/frontend/src/components/require-auth.tsx @@ -1,17 +1,23 @@ -import { Navigate, Outlet } from 'react-router-dom' +import { Navigate, Outlet } from 'react-router-dom'; import Loader from './skeletons/loader'; import useAuthData from '@/hooks/useAuthData'; function RequireAuth({ allowedRole }: { allowedRole: string[] }) { - const { role, token, loading } = useAuthData(); + const { role, token, loading } = useAuthData(); - if (loading) { - return <>; // Render a loading indicator - } + if (loading) { + return ( + <> + + + ); // Render a loading indicator + } - return token && allowedRole.find((myRole) => myRole === role) ? ( - - ) : + return token && allowedRole.find((myRole) => myRole === role) ? ( + + ) : ( + + ); } -export default RequireAuth +export default RequireAuth; diff --git a/frontend/src/components/skeletons/loader.tsx b/frontend/src/components/skeletons/loader.tsx index b45f4e74..641d42cb 100644 --- a/frontend/src/components/skeletons/loader.tsx +++ b/frontend/src/components/skeletons/loader.tsx @@ -1,13 +1,25 @@ function Loader() { - return ( -
- - Loading... -
- ) + return ( +
+ + Loading... +
+ ); } -export default Loader +export default Loader; diff --git a/frontend/src/components/skeletons/post-card-skeleton.tsx b/frontend/src/components/skeletons/post-card-skeleton.tsx index 46ea7a88..5d098bbb 100644 --- a/frontend/src/components/skeletons/post-card-skeleton.tsx +++ b/frontend/src/components/skeletons/post-card-skeleton.tsx @@ -6,15 +6,15 @@ export const PostCardSkeleton = () => {
- - - -
+ + + +
diff --git a/frontend/src/components/theme-toggle-button.tsx b/frontend/src/components/theme-toggle-button.tsx index 26eee51a..a9c5a505 100644 --- a/frontend/src/components/theme-toggle-button.tsx +++ b/frontend/src/components/theme-toggle-button.tsx @@ -8,7 +8,7 @@ function ThemeToggle() { setIsDarkTheme((prevTheme) => (prevTheme === null ? true : !prevTheme)); }; useLayoutEffect(() => { - const storedTheme = useThemeClass() + const storedTheme = useThemeClass(); setIsDarkTheme(storedTheme === 'dark'); }, []); diff --git a/frontend/src/components/unprotected-route.tsx b/frontend/src/components/unprotected-route.tsx index ebcc7588..e620100c 100644 --- a/frontend/src/components/unprotected-route.tsx +++ b/frontend/src/components/unprotected-route.tsx @@ -2,9 +2,9 @@ import { Navigate, Outlet } from 'react-router-dom'; import useAuthData from '@/hooks/useAuthData'; function UnprotectedRoute() { - const { token } = useAuthData(); + const { token } = useAuthData(); - return token ? : ; + return token ? : ; } -export default UnprotectedRoute +export default UnprotectedRoute; diff --git a/frontend/src/constants/images.ts b/frontend/src/constants/images.ts index 9a512ce8..0fc3331d 100644 --- a/frontend/src/constants/images.ts +++ b/frontend/src/constants/images.ts @@ -7,18 +7,18 @@ export const imageUrls: string[] = [ 'https://i.ibb.co/KLwfZzG/Maldives-1024x767.webp', 'https://i.ibb.co/qxFMj1H/sunset-horizon-clean-sky-nature.webp', ]; -export const USERNAME_EMPTY_ERRORMESSAGE = "Username is required" -export const EMAIL_EMPTY_ERRORMESSAGE = "Email is required" -export const PASSWORD_EMPTY_ERRORMESSAGE = "Password is required" -export const CONFIRMPASSWORD_EMPTY_ERRORMESSAGE = "Confirm Password is required" +export const USERNAME_EMPTY_ERRORMESSAGE = 'Username is required'; +export const EMAIL_EMPTY_ERRORMESSAGE = 'Email is required'; +export const PASSWORD_EMPTY_ERRORMESSAGE = 'Password is required'; +export const CONFIRMPASSWORD_EMPTY_ERRORMESSAGE = 'Confirm Password is required'; -export const USERNAME_PLACEHOLDER = "Username" -export const EMAILINPUT_PLACEHOLDER = "Email" -export const PASSWORDINPUT_PLACEHOLDER = "Password" -export const CONFIRMPASSWORD_PLACEHOLDER = "Confirm Password" -export const SIGNUPBUTTON_TEXT = "Sign Up" +export const USERNAME_PLACEHOLDER = 'Username'; +export const EMAILINPUT_PLACEHOLDER = 'Email'; +export const PASSWORDINPUT_PLACEHOLDER = 'Password'; +export const CONFIRMPASSWORD_PLACEHOLDER = 'Confirm Password'; +export const SIGNUPBUTTON_TEXT = 'Sign Up'; -export const INVALID_USERNAME_ERRORMESSAGE = "Username must be at least 5 characters long" -export const INVALID_EMAIL_ERRORMESSAGE = "Invalid email address" -export const INVALID_PWD_ERRORMESSAGE = "Password must be at least 8 characters long" -export const INVALID_CONFIRMPWD_ERRORMESSAGE = "Passwords do not match" \ No newline at end of file +export const INVALID_USERNAME_ERRORMESSAGE = 'Username must be at least 5 characters long'; +export const INVALID_EMAIL_ERRORMESSAGE = 'Invalid email address'; +export const INVALID_PWD_ERRORMESSAGE = 'Password must be at least 8 characters long'; +export const INVALID_CONFIRMPWD_ERRORMESSAGE = 'Passwords do not match'; diff --git a/frontend/src/layouts/footer-layout.tsx b/frontend/src/layouts/footer-layout.tsx index 5f3aab9c..9176eb9e 100644 --- a/frontend/src/layouts/footer-layout.tsx +++ b/frontend/src/layouts/footer-layout.tsx @@ -5,7 +5,7 @@ function footer() { return (