From 2c136ba3742ec8d96224e6c15ab724bfdf255996 Mon Sep 17 00:00:00 2001 From: Shubham-Lal Date: Sun, 4 Aug 2024 13:27:59 +0530 Subject: [PATCH] modified card ui --- client/src/App.css | 1 - client/src/App.tsx | 30 +-- client/src/ProtectedRoute.tsx | 8 +- client/src/components/button/toggle-theme.tsx | 8 +- client/src/components/card/claim-username.tsx | 51 ++--- .../components/card/closed-debate-card.css | 27 +-- .../components/card/closed-debate-card.tsx | 9 +- client/src/components/card/debate-bar.tsx | 6 +- .../src/components/card/open-debate-card.css | 49 +--- .../src/components/card/open-debate-card.tsx | 12 +- .../components/modal/auth/forgot-password.tsx | 50 ++-- client/src/components/modal/auth/index.css | 1 - client/src/components/modal/auth/index.tsx | 34 +-- .../src/components/modal/auth/login-tab.tsx | 54 ++--- .../components/modal/auth/set-password.tsx | 56 ++--- .../src/components/modal/auth/signup-tab.tsx | 113 ++++----- client/src/components/sidebar/explore.css | 8 +- client/src/components/sidebar/explore.tsx | 30 +-- .../src/components/sidebar/left-sidebar.css | 1 - .../src/components/sidebar/left-sidebar.tsx | 20 +- client/src/components/sidebar/profile.tsx | 42 ++-- .../src/components/sidebar/right-sidebar.tsx | 2 +- client/src/data/categories-data.ts | 2 +- client/src/data/regex.ts | 6 +- client/src/globals.css | 1 - client/src/hooks/useFormatNumber.ts | 22 +- client/src/main.tsx | 4 +- client/src/pages/auth/index.tsx | 40 ++-- client/src/pages/create-debate/editor.tsx | 8 +- client/src/pages/create-debate/index.tsx | 24 +- client/src/pages/home/index.tsx | 4 +- client/src/pages/hot-topics/index.tsx | 2 +- client/src/pages/open-topics/index.tsx | 2 +- client/src/pages/search/index.tsx | 21 +- client/src/pages/search/style.css | 2 +- client/src/store/useAuthStore.ts | 4 +- client/src/store/useNavStore.ts | 16 +- client/src/utils/handleAutoLogin.ts | 34 +-- client/vite.config.ts | 3 +- server/controllers/auth.js | 216 +++++++++--------- server/db/README.md | 4 +- server/db/index.js | 6 +- server/middleware/verifyToken.js | 22 +- server/routes/auth.js | 78 +++---- server/server.js | 4 +- server/utils/error.js | 14 +- server/utils/mail.js | 8 +- 47 files changed, 551 insertions(+), 608 deletions(-) diff --git a/client/src/App.css b/client/src/App.css index 038d7e7..bbb390e 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -61,7 +61,6 @@ display: flex; flex-direction: column; gap: 30px; - user-select: none; } .column-debates { diff --git a/client/src/App.tsx b/client/src/App.tsx index 86edd17..fcc6ecd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -19,29 +19,29 @@ import NotificationPage from "./pages/notifications" import { FaChevronLeft, FaChevronRight } from "react-icons/fa" export default function App() { - const { setRoute, setUser, setIsAuthenticated, authTab, setAuthTab } = useAuthStore(); - const { expand, sidebar, setSidebar } = useNavStore(); + const { setRoute, setUser, setIsAuthenticated, authTab, setAuthTab } = useAuthStore() + const { expand, sidebar, setSidebar } = useNavStore() - const mainRef = useRef(null); - const lastScrollTop = useRef(0); - const [isScrollingUp, setIsScrollingUp] = useState(true); + const mainRef = useRef(null) + const lastScrollTop = useRef(0) + const [isScrollingUp, setIsScrollingUp] = useState(true) useEffect(() => { - document.body.setAttribute('data-theme', localStorage.getItem('theme') === Theme.Light ? Theme.Light : Theme.Dark); + document.body.setAttribute('data-theme', localStorage.getItem('theme') === Theme.Light ? Theme.Light : Theme.Dark) - handleAutoLogin(setRoute, setUser, setIsAuthenticated, setAuthTab); + handleAutoLogin(setRoute, setUser, setIsAuthenticated, setAuthTab) const handleScroll = () => { - const st = mainRef.current?.scrollTop ?? 0; - setIsScrollingUp(st <= lastScrollTop.current); - lastScrollTop.current = Math.max(st, 0); - }; + const st = mainRef.current?.scrollTop ?? 0 + setIsScrollingUp(st <= lastScrollTop.current) + lastScrollTop.current = Math.max(st, 0) + } - const mainElement = mainRef.current; - mainElement?.addEventListener('scroll', handleScroll, { passive: true }); - return () => mainElement?.removeEventListener('scroll', handleScroll); + const mainElement = mainRef.current + mainElement?.addEventListener('scroll', handleScroll, { passive: true }) + return () => mainElement?.removeEventListener('scroll', handleScroll) // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, []) return (
diff --git a/client/src/ProtectedRoute.tsx b/client/src/ProtectedRoute.tsx index 47800a8..e1a0694 100644 --- a/client/src/ProtectedRoute.tsx +++ b/client/src/ProtectedRoute.tsx @@ -7,14 +7,14 @@ interface ProtectedRouteProps { } export const ProtectedRoute: React.FC = ({ children }) => { - const { isAuthenticated } = useAuthStore(); + const { isAuthenticated } = useAuthStore() if (isAuthenticated === AuthStatus.Authenticating) { return } else if (isAuthenticated === AuthStatus.Failed) { - return ; + return } - return <>{children}; -}; \ No newline at end of file + return <>{children} +} \ No newline at end of file diff --git a/client/src/components/button/toggle-theme.tsx b/client/src/components/button/toggle-theme.tsx index ed45e86..c42a498 100644 --- a/client/src/components/button/toggle-theme.tsx +++ b/client/src/components/button/toggle-theme.tsx @@ -2,12 +2,12 @@ import "./toggle-theme.css" import { Theme, useNavStore } from "../../store/useNavStore" const ToggleTheme = () => { - const { theme, setTheme } = useNavStore(); + const { theme, setTheme } = useNavStore() const handleToggleTheme = () => { - const newTheme = theme === Theme.Light ? Theme.Dark : Theme.Light; - document.querySelector("body")?.setAttribute('data-theme', newTheme); - setTheme(newTheme); + const newTheme = theme === Theme.Light ? Theme.Dark : Theme.Light + document.querySelector("body")?.setAttribute('data-theme', newTheme) + setTheme(newTheme) } return ( diff --git a/client/src/components/card/claim-username.tsx b/client/src/components/card/claim-username.tsx index 2b512c5..198021e 100644 --- a/client/src/components/card/claim-username.tsx +++ b/client/src/components/card/claim-username.tsx @@ -8,38 +8,36 @@ import { useNavStore } from "../../store/useNavStore" import { usernameRegex } from "../../data/regex" const ClaimUsername = () => { - const { setAuthTab } = useAuthStore(); - const { sidebar } = useNavStore(); + const { setAuthTab } = useAuthStore() + const { sidebar } = useNavStore() - const [isSubmitted, setIsSubmitted] = useState(false); - const [username, setUsername] = useState(localStorage.getItem("username") || ""); - const [loading, setLoading] = useState(false); - const [message, setMessage] = useState({ type: '', content: '' }); + const [isSubmitted, setIsSubmitted] = useState(false) + const [username, setUsername] = useState(localStorage.getItem("username") || "") + const [loading, setLoading] = useState(false) + const [message, setMessage] = useState({ type: '', content: '' }) const handleKeyPress = useCallback((e: React.KeyboardEvent) => { - if (e.key === ' ') e.preventDefault(); - }, []); + if (e.key === ' ') e.preventDefault() + }, []) const handleUsernameChange = (e: React.ChangeEvent) => { - const inputUsername = e.target.value; - setUsername(inputUsername); + const inputUsername = e.target.value + setUsername(inputUsername) if (inputUsername) { if (!usernameRegex.test(inputUsername)) { - setMessage({ type: 'error', content: 'Username can only contain alphanumeric characters, underscores (_) and hyphens (-). No spaces or other special characters are allowed.' }); - } else setMessage({ type: '', content: '' }); + setMessage({ type: 'error', content: 'Username can only contain alphanumeric characters, underscores (_) and hyphens (-). No spaces or other special characters are allowed.' }) + } else setMessage({ type: '', content: '' }) } - }; + } const handleUsernameSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setIsSubmitted(true); - setTimeout(() => setIsSubmitted(false), 500); + e.preventDefault() + setIsSubmitted(true) + setTimeout(() => setIsSubmitted(false), 500) if (username && message.type !== 'error') { - setLoading(true); - - localStorage.setItem("username", username); + setLoading(true) await fetch(`${import.meta.env.VITE_SERVER_URL}/api/auth/check-username`, { method: 'POST', @@ -49,15 +47,16 @@ const ClaimUsername = () => { .then(res => res.json()) .then(response => { if (response.success) { - setAuthTab(AuthTab.Signup); - setMessage({ type: 'success', content: response.message }); - toast.success(`${response.message} Register your account to claim username`); - } else setMessage({ type: 'error', content: response.message }); + localStorage.setItem('username', username) + setAuthTab(AuthTab.Signup) + setMessage({ type: 'success', content: response.message }) + toast.success('Register your account to claim username') + } else setMessage({ type: 'error', content: response.message }) }) .catch(() => setMessage({ type: 'error', content: 'Something went wrong. Try again later!' })) - .finally(() => setLoading(false)); + .finally(() => setLoading(false)) } - }; + } return (
@@ -76,7 +75,7 @@ const ClaimUsername = () => { />
+

No debates found

) : (
diff --git a/client/src/pages/search/style.css b/client/src/pages/search/style.css index f9fc185..65e7a34 100644 --- a/client/src/pages/search/style.css +++ b/client/src/pages/search/style.css @@ -2,7 +2,7 @@ padding: 20px; display: flex; flex-direction: column; - gap: 10px; + gap: 20px; } #search #explore { diff --git a/client/src/store/useAuthStore.ts b/client/src/store/useAuthStore.ts index 495db03..dafe0b2 100644 --- a/client/src/store/useAuthStore.ts +++ b/client/src/store/useAuthStore.ts @@ -48,7 +48,7 @@ export const useAuthStore = create((set) => ({ avatar: null }, setUser: (data: User) => set({ user: data }) -})); +})) interface TempStore { tempUser: User @@ -74,4 +74,4 @@ export const useTempStore = create((set) => ({ avatar: null } }) -})); \ No newline at end of file +})) \ No newline at end of file diff --git a/client/src/store/useNavStore.ts b/client/src/store/useNavStore.ts index 86a5cee..fec2780 100644 --- a/client/src/store/useNavStore.ts +++ b/client/src/store/useNavStore.ts @@ -15,21 +15,21 @@ interface NavStore { } export const useNavStore = create((set) => { - const savedTheme = (localStorage.getItem('theme') as Theme) || Theme.Dark; - const savedSidebar = localStorage.getItem('sidebar') === 'true' || false; + const savedTheme = (localStorage.getItem('theme') as Theme) || Theme.Dark + const savedSidebar = localStorage.getItem('sidebar') === 'true' || false return { theme: savedTheme, setTheme: (theme_data: Theme) => { - set({ theme: theme_data }); - localStorage.setItem('theme', theme_data); + set({ theme: theme_data }) + localStorage.setItem('theme', theme_data) }, expand: false, setExpand: (toggle: boolean) => set({ expand: toggle }), sidebar: savedSidebar, setSidebar: (toggle: boolean) => { - set({ sidebar: toggle }); - localStorage.setItem('sidebar', toggle.toString()); + set({ sidebar: toggle }) + localStorage.setItem('sidebar', toggle.toString()) } - }; -}); \ No newline at end of file + } +}) \ No newline at end of file diff --git a/client/src/utils/handleAutoLogin.ts b/client/src/utils/handleAutoLogin.ts index e77a4ec..88ea90f 100644 --- a/client/src/utils/handleAutoLogin.ts +++ b/client/src/utils/handleAutoLogin.ts @@ -1,20 +1,20 @@ import { User, AuthStatus, AuthTab } from "../store/useAuthStore" -type SetRoute = (navigate: string) => void; -type SetUser = (user: User) => void; -type SetIsAuthenticated = (authenticated: AuthStatus) => void; -type SetAuthTab = (tab: AuthTab) => void; +type SetRoute = (navigate: string) => void +type SetUser = (user: User) => void +type SetIsAuthenticated = (authenticated: AuthStatus) => void +type SetAuthTab = (tab: AuthTab) => void const handleAutoLogin = (setRoute: SetRoute, setUser: SetUser, setIsAuthenticated: SetIsAuthenticated, setAuthTab: SetAuthTab) => { - const token = localStorage.getItem('token'); - setRoute(location.pathname); + const token = localStorage.getItem('token') + setRoute(location.pathname) if (!token) { - return setIsAuthenticated(AuthStatus.Failed); + return setIsAuthenticated(AuthStatus.Failed) } else { - const headers = new Headers(); - headers.append('Authorization', `Bearer ${token}`); + const headers = new Headers() + headers.append('Authorization', `Bearer ${token}`) fetch(`${import.meta.env.VITE_SERVER_URL}/api/auth/auto-login`, { method: 'GET', @@ -23,19 +23,19 @@ const handleAutoLogin = (setRoute: SetRoute, setUser: SetUser, setIsAuthenticate .then(res => res.json()) .then(response => { if (response.success) { - setUser(response.data.user); - setIsAuthenticated(AuthStatus.Authenticated); - setAuthTab(AuthTab.Closed); + setUser(response.data.user) + setIsAuthenticated(AuthStatus.Authenticated) + setAuthTab(AuthTab.Closed) } else { - setIsAuthenticated(AuthStatus.Failed); - localStorage.removeItem('token'); + setIsAuthenticated(AuthStatus.Failed) + localStorage.removeItem('token') } }) .catch(() => { - setIsAuthenticated(AuthStatus.Failed); - localStorage.removeItem('token'); - }); + setIsAuthenticated(AuthStatus.Failed) + localStorage.removeItem('token') + }) } } diff --git a/client/vite.config.ts b/client/vite.config.ts index 9078ea5..714d296 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,6 @@ import { defineConfig } from "vite" import react from "@vitejs/plugin-react-swc" -// https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}) \ No newline at end of file diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 7d3e779..43d7c37 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -1,81 +1,81 @@ -const { ErrorHandler, catchError } = require('../utils/error'); -const { sign } = require('jsonwebtoken'); -const { hash, compare } = require('bcrypt'); -const { randomBytes } = require('node:crypto'); -const { sendMail } = require('../utils/mail'); +const { ErrorHandler, catchError } = require('../utils/error') +const { sign } = require('jsonwebtoken') +const { hash, compare } = require('bcrypt') +const { randomBytes } = require('node:crypto') +const { sendMail } = require('../utils/mail') exports.handleGoogleAuth = async function (fastify, request, reply) { try { - const { token } = await fastify.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request, reply); - const userinfo = await fastify.googleOAuth2.userinfo(token.access_token); + const { token } = await fastify.googleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request, reply) + const userinfo = await fastify.googleOAuth2.userinfo(token.access_token) - const cacheKey = `user:${userinfo.email}`; - let user = null; + const cacheKey = `user:${userinfo.email}` + let user = null await fastify.cache.get(cacheKey, async (err, cachedUser) => { - if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache'); + if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache') - if (cachedUser) user = cachedUser.item; + if (cachedUser) user = cachedUser.item else { - const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE email=?', [userinfo.email]); + const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE email=?', [userinfo.email]) if (db_user.length > 0) { await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => { - if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache'); - }); - user = db_user[0]; + if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache') + }) + user = db_user[0] } } - }); + }) if (user) { const token = await new Promise((resolve, reject) => { sign({ userId: user.username }, process.env.JWT_SECRET, { expiresIn: '12h' }, (err, token) => { - if (err) reject(err); + if (err) reject(err) else resolve(token) - }); - }); - reply.redirect(`${process.env.FRONTEND_URL}/auth?type=login&token=${token}`); + }) + }) + reply.redirect(`${process.env.FRONTEND_URL}/auth?type=login&token=${token}`) } else { - reply.redirect(`${process.env.FRONTEND_URL}/auth?type=signup&user=${encodeURIComponent(JSON.stringify(userinfo))}`); + reply.redirect(`${process.env.FRONTEND_URL}/auth?type=signup&user=${encodeURIComponent(JSON.stringify(userinfo))}`) } } catch (error) { - reply.redirect(`${process.env.FRONTEND_URL}/auth?type=login&error=${encodeURIComponent(error.message)}`); + reply.redirect(`${process.env.FRONTEND_URL}/auth?type=login&error=${encodeURIComponent(error.message)}`) } -}; +} exports.register = async function (fastify, request, reply) { try { - const { email, username, first_name, last_name } = request.body; + const { email, username, first_name, last_name } = request.body - let avatar = null; - if (request.body.avatar) avatar = request.body.avatar; - else if (request.file) avatar = null; + let avatar = null + if (request.body.avatar) avatar = request.body.avatar + else if (request.file) avatar = null - const [existingUser] = await fastify.mysql.query('SELECT * FROM users WHERE username=? OR email=?', [username, email]); + const [existingUser] = await fastify.mysql.query('SELECT * FROM users WHERE username=? OR email=?', [username, email]) if (existingUser.length > 0) { if (existingUser[0].username === username) { - throw new ErrorHandler(400, false, 'Username already taken'); + throw new ErrorHandler(400, false, 'Username already taken') } else if (existingUser[0].email === email) { - throw new ErrorHandler(400, false, 'Email already exists'); + throw new ErrorHandler(400, false, 'Email already exists') } } - const token = randomBytes(32).toString('hex'); - const tokenExpiry = new Date(Date.now() + 3600000); + const token = randomBytes(32).toString('hex') + const tokenExpiry = new Date(Date.now() + 3600000) - const tempPassword = await hash(randomBytes(32).toString('hex'), 10); + const tempPassword = await hash(randomBytes(32).toString('hex'), 10) const [result] = await fastify.mysql.query( 'INSERT INTO users (email, username, first_name, last_name, avatar, password) VALUES (?, ?, ?, ?, ?, ?)', [email, username, first_name, last_name, avatar, `temp:${tempPassword}`] - ); + ) if (result.affectedRows > 0) { await fastify.mysql.query( 'INSERT INTO reset (username, token, expiry) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE token=?, expiry=?', [username, token, tokenExpiry, token, tokenExpiry] - ); + ) await sendMail(fastify.mailer, { to: email, @@ -87,48 +87,48 @@ exports.register = async function (fastify, request, reply) { reply.status(200).send({ success: true, message: 'Please check your email to activate your account.' - }); + }) }) - .catch(errors => { throw new ErrorHandler(400, false, 'Failed to send activate mail') }); + .catch(errors => { throw new ErrorHandler(400, false, 'Failed to send activate mail') }) } else { - throw new ErrorHandler(400, false, 'Failed to create account'); + throw new ErrorHandler(400, false, 'Failed to create account') } } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } -}; +} exports.login = async function (fastify, request, reply) { try { - const { id, password } = request.body; - const cacheKey = `user:${id}`; - let user = null; + const { id, password } = request.body + const cacheKey = `user:${id}` + let user = null await fastify.cache.get(cacheKey, async (err, cachedUser) => { - if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache'); + if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache') - if (cachedUser) user = cachedUser.item; + if (cachedUser) user = cachedUser.item else { - const [db_user] = await fastify.mysql.query(`SELECT * FROM users WHERE ${id.includes('@') ? 'email=?' : 'username=?'}`, [id, id]); - if (!db_user.length) throw new ErrorHandler(400, false, "Account doesn't exist"); + const [db_user] = await fastify.mysql.query(`SELECT * FROM users WHERE ${id.includes('@') ? 'email=?' : 'username=?'}`, [id, id]) + if (!db_user.length) throw new ErrorHandler(400, false, "Account doesn't exist") await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => { - if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache'); - }); + if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache') + }) - user = db_user[0]; + user = db_user[0] } - }); + }) - const isPasswordValid = await compare(password, user.password); - if (!isPasswordValid) throw new ErrorHandler(400, false, 'Incorrect password'); + const isPasswordValid = await compare(password, user.password) + if (!isPasswordValid) throw new ErrorHandler(400, false, 'Incorrect password') const token = await new Promise((resolve, reject) => { sign({ userId: user.username }, process.env.JWT_SECRET, { expiresIn: '12h' }, (err, token) => { - if (err) reject(err); - else resolve(token); - }); - }); + if (err) reject(err) + else resolve(token) + }) + }) return reply.code(200).send({ success: true, @@ -137,32 +137,32 @@ exports.login = async function (fastify, request, reply) { user: { ...(({ password, reset_token, reset_token_expiry, ...rest }) => rest)(user) }, token } - }); + }) } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } -}; +} exports.autoLogin = async function (fastify, request, reply) { try { - const id = request.user.userId; - const cacheKey = `user:${id}`; - let user = null; + const id = request.user.userId + const cacheKey = `user:${id}` + let user = null await fastify.cache.get(cacheKey, async (err, cachedUser) => { - if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache'); + if (err) throw new ErrorHandler(400, false, 'Failed to get data from cache') - if (cachedUser) user = cachedUser.item; + if (cachedUser) user = cachedUser.item else { - const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [id]); - if (!db_user.length) throw new ErrorHandler(400, false, 'Invalid credentials'); + const [db_user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [id]) + if (!db_user.length) throw new ErrorHandler(400, false, 'Invalid credentials') await fastify.cache.set(cacheKey, db_user[0], 432000, (err) => { - if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache'); - }); - user = db_user[0]; + if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache') + }) + user = db_user[0] } - }); + }) return reply.code(200).send({ success: true, @@ -170,42 +170,42 @@ exports.autoLogin = async function (fastify, request, reply) { data: { user: { ...(({ password, reset_token, reset_token_expiry, ...rest }) => rest)(user) } } - }); + }) } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } } exports.checkUsername = async function (fastify, request, reply) { try { - const { username } = request.body; + const { username } = request.body - const [user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [username]); - if (user.length) throw new ErrorHandler(400, false, 'Username already taken'); + const [user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [username]) + if (user.length) throw new ErrorHandler(400, false, 'Username already taken') return reply.code(200).send({ success: true, - message: 'Username available', - }); + message: 'Username available' + }) } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } } exports.recoverAccount = async function (fastify, request, reply) { try { - const { email, username } = request.body; + const { email, username } = request.body - const [user] = await fastify.mysql.query('SELECT * FROM users WHERE email=? OR username=?', [email, username]); - if (!user.length) throw new ErrorHandler(400, false, "Account doesn't exist"); + const [user] = await fastify.mysql.query('SELECT * FROM users WHERE email=? OR username=?', [email, username]) + if (!user.length) throw new ErrorHandler(400, false, "Account doesn't exist") - const token = randomBytes(32).toString('hex'); - const tokenExpiry = new Date(Date.now() + 3600000); + const token = randomBytes(32).toString('hex') + const tokenExpiry = new Date(Date.now() + 3600000) await fastify.mysql.query( 'INSERT INTO reset (username, token, expiry) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE token=?, expiry=?', [user[0].username, token, tokenExpiry, token, tokenExpiry] - ); + ) await sendMail(fastify.mailer, { to: user[0].email, @@ -217,44 +217,44 @@ exports.recoverAccount = async function (fastify, request, reply) { reply.status(200).send({ success: true, message: `Reset password link sent to your email${username ? (': ' + user[0].email) : ''}` - }); + }) }) - .catch(errors => { throw new ErrorHandler(400, false, 'Failed to send reset password mail') }); + .catch(errors => { throw new ErrorHandler(400, false, 'Failed to send reset password mail') }) } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } -}; +} exports.resetPassword = async (fastify, request, reply) => { try { - const { token, password } = request.body; + const { token, password } = request.body - const [reset] = await fastify.mysql.query('SELECT * FROM reset WHERE token=? AND expiry>?', [token, new Date()]); - if (!reset.length) throw new ErrorHandler(400, false, "Invalid or expired token"); + const [reset] = await fastify.mysql.query('SELECT * FROM reset WHERE token=? AND expiry>?', [token, new Date()]) + if (!reset.length) throw new ErrorHandler(400, false, 'Invalid or expired token') - const hashedPassword = await hash(password, 10); + const hashedPassword = await hash(password, 10) - const [result] = await fastify.mysql.query('UPDATE users SET password=? WHERE username=?', [hashedPassword, reset[0].username]); + const [result] = await fastify.mysql.query('UPDATE users SET password=? WHERE username=?', [hashedPassword, reset[0].username]) if (result.affectedRows > 0) { - await fastify.mysql.query('DELETE FROM reset WHERE username=?', [reset[0].username]); + await fastify.mysql.query('DELETE FROM reset WHERE username=?', [reset[0].username]) - const [updatedUser] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [reset[0].username]); - if (!updatedUser.length) throw new ErrorHandler(400, false, "Account doesn't exists"); + const [updatedUser] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [reset[0].username]) + if (!updatedUser.length) throw new ErrorHandler(400, false, "Account doesn't exists") - const cacheKeys = [`user:${updatedUser[0].username}`, `user:${updatedUser[0].email}`]; + const cacheKeys = [`user:${updatedUser[0].username}`, `user:${updatedUser[0].email}`] await Promise.all(cacheKeys.map(cacheKey => fastify.cache.set(cacheKey, updatedUser[0], 432000, (err) => { - if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache'); + if (err) throw new ErrorHandler(400, false, 'Failed to set data in cache') }) - )); + )) const token = await new Promise((resolve, reject) => { sign({ userId: updatedUser[0].username }, process.env.JWT_SECRET, { expiresIn: '12h' }, (err, token) => { - if (err) reject(err); - else resolve(token); - }); - }); + if (err) reject(err) + else resolve(token) + }) + }) reply.status(200).send({ success: true, @@ -263,11 +263,11 @@ exports.resetPassword = async (fastify, request, reply) => { user: { ...(({ password, ...rest }) => rest)(updatedUser[0]) }, token } - }); + }) } else { - throw new ErrorHandler(400, false, 'Failed to reset password'); + throw new ErrorHandler(400, false, 'Failed to reset password') } } catch (err) { - return catchError(reply, err); + return catchError(reply, err) } -}; \ No newline at end of file +} \ No newline at end of file diff --git a/server/db/README.md b/server/db/README.md index 52f1261..aa3afdf 100644 --- a/server/db/README.md +++ b/server/db/README.md @@ -8,7 +8,7 @@ CREATE TABLE users ( last_name VARCHAR(30) NOT NULL, avatar VARCHAR(255), PRIMARY KEY (username) -); +) ``` ## Table `reset` @@ -19,5 +19,5 @@ CREATE TABLE reset ( expiry DATETIME NOT NULL, PRIMARY KEY (username), FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE -); +) ``` \ No newline at end of file diff --git a/server/db/index.js b/server/db/index.js index 6276706..7526419 100644 --- a/server/db/index.js +++ b/server/db/index.js @@ -1,4 +1,4 @@ -const { createPool } = require('mysql2/promise'); +const { createPool } = require('mysql2/promise') const pool = createPool({ host: process.env.DB_HOST, @@ -12,6 +12,6 @@ const pool = createPool({ queueLimit: 0, enableKeepAlive: true, keepAliveInitialDelay: 0 -}); +}) -module.exports = pool; \ No newline at end of file +module.exports = pool \ No newline at end of file diff --git a/server/middleware/verifyToken.js b/server/middleware/verifyToken.js index 0b8ce6b..d0a2085 100644 --- a/server/middleware/verifyToken.js +++ b/server/middleware/verifyToken.js @@ -1,23 +1,23 @@ -const { verify } = require('jsonwebtoken'); +const { verify } = require('jsonwebtoken') async function verifyToken(request, reply) { - const authHeader = request.headers.authorization; + const authHeader = request.headers.authorization if (!authHeader) { - throw new Error('Authorization header is missing'); + throw new Error('Authorization header is missing') } - const token = authHeader.split(' ')[1]; + const token = authHeader.split(' ')[1] try { const data = await new Promise((resolve, reject) => { verify(token, process.env.JWT_SECRET, (err, payload) => { - if (err) reject(err); - else resolve(payload); - }); - }); - request.user = data; + if (err) reject(err) + else resolve(payload) + }) + }) + request.user = data } catch (err) { - throw new Error('Invalid token'); + throw new Error('Invalid token') } } -module.exports = verifyToken; \ No newline at end of file +module.exports = verifyToken \ No newline at end of file diff --git a/server/routes/auth.js b/server/routes/auth.js index a35383d..97551d9 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -1,8 +1,8 @@ -const { handleGoogleAuth, register, login, autoLogin, checkUsername, recoverAccount, resetPassword } = require('../controllers/auth'); -const verifyToken = require('../middleware/verifyToken'); +const { handleGoogleAuth, register, login, autoLogin, checkUsername, recoverAccount, resetPassword } = require('../controllers/auth') +const verifyToken = require('../middleware/verifyToken') module.exports = async function (fastify, opts) { - const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+(com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|in|space)))$/; + const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+(com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|in|space)))$/ const registerSchema = { consumes: ['multipart/form-data'], @@ -16,7 +16,7 @@ module.exports = async function (fastify, opts) { }, required: ['email', 'username', 'first_name', 'last_name'] } - }; + } const loginSchema = { body: { @@ -27,7 +27,7 @@ module.exports = async function (fastify, opts) { }, required: ['id', 'password'] } - }; + } const usernameSchema = { body: { @@ -37,7 +37,7 @@ module.exports = async function (fastify, opts) { }, required: ['username'] } - }; + } const recoverSchema = { body: { @@ -51,7 +51,7 @@ module.exports = async function (fastify, opts) { { required: ['username'] } ] } - }; + } const resetSchema = { body: { @@ -62,11 +62,11 @@ module.exports = async function (fastify, opts) { }, required: ['token', 'password'] } - }; + } fastify.get('/google/callback', async (request, reply) => { - return handleGoogleAuth(fastify, request, reply); - }); + return handleGoogleAuth(fastify, request, reply) + }) fastify.post('/register', { schema: registerSchema, @@ -78,12 +78,12 @@ module.exports = async function (fastify, opts) { return { field: error.params.missingProperty || error.instancePath.substring(1), message: error.message - }; - }); - return reply.code(400).send({ success: false, message: 'Validation failed', errors }); + } + }) + return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return register(fastify, request, reply); - }); + return register(fastify, request, reply) + }) fastify.post('/login', { schema: loginSchema, @@ -94,26 +94,26 @@ module.exports = async function (fastify, opts) { return { field: error.params.missingProperty || error.instancePath.substring(1), message: error.message - }; - }); - return reply.code(400).send({ success: false, message: 'Validation failed', errors }); + } + }) + return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return login(fastify, request, reply); - }); + return login(fastify, request, reply) + }) fastify.get('/auto-login', { preHandler: verifyToken }, async (request, reply) => { try { - return autoLogin(fastify, request, reply); + return autoLogin(fastify, request, reply) } catch (error) { if (error.message === 'Authorization header is missing' || error.message === 'Invalid token') { - return reply.code(401).send({ success: false, message: error.message }); + return reply.code(401).send({ success: false, message: error.message }) } else { - return reply.code(500).send({ success: false, message: 'Internal Server Error' }); + return reply.code(500).send({ success: false, message: 'Internal Server Error' }) } } - }); + }) fastify.post('/check-username', { schema: usernameSchema, @@ -124,12 +124,12 @@ module.exports = async function (fastify, opts) { return { field: error.params.missingProperty || error.instancePath.substring(1), message: error.message - }; - }); - return reply.code(400).send({ success: false, message: 'Validation failed', errors }); + } + }) + return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return checkUsername(fastify, request, reply); - }); + return checkUsername(fastify, request, reply) + }) fastify.post('/recover-account', { schema: recoverSchema, @@ -140,12 +140,12 @@ module.exports = async function (fastify, opts) { return { field: error.params.missingProperty || error.instancePath.substring(1), message: error.message - }; - }); - return reply.code(400).send({ success: false, message: 'Validation failed', errors }); + } + }) + return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return recoverAccount(fastify, request, reply); - }); + return recoverAccount(fastify, request, reply) + }) fastify.post('/reset-password', { schema: resetSchema, @@ -156,10 +156,10 @@ module.exports = async function (fastify, opts) { return { field: error.params.missingProperty || error.instancePath.substring(1), message: error.message - }; - }); - return reply.code(400).send({ success: false, message: 'Validation failed', errors }); + } + }) + return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return resetPassword(fastify, request, reply); - }); + return resetPassword(fastify, request, reply) + }) } \ No newline at end of file diff --git a/server/server.js b/server/server.js index 8e86814..340b32e 100644 --- a/server/server.js +++ b/server/server.js @@ -11,7 +11,7 @@ const fastify = require('fastify')({ bodyLimit: 7 * 1024 * 1024 }) -fastify.register(require('@fastify/cors'), { origin: "*" }) +fastify.register(require('@fastify/cors'), { origin: '*' }) fastify.register(require('@fastify/multipart')) const multer = require('fastify-multer') @@ -20,7 +20,7 @@ fastify.decorate('upload', upload) fastify.decorate('mysql', require('./db')) -fastify.register(require('@fastify/caching')); +fastify.register(require('@fastify/caching')) fastify.register(require('@fastify/oauth2'), { name: 'googleOAuth2', diff --git a/server/utils/error.js b/server/utils/error.js index 323f2e9..84e316a 100644 --- a/server/utils/error.js +++ b/server/utils/error.js @@ -1,9 +1,9 @@ class ErrorHandler extends Error { constructor(statusCode, success, message) { - super(message); - this.statusCode = statusCode; - this.success = success; - Error.captureStackTrace(this, this.constructor); + super(message) + this.statusCode = statusCode + this.success = success + Error.captureStackTrace(this, this.constructor) } } @@ -12,13 +12,13 @@ function catchError(reply, err) { return reply.code(err.statusCode).send({ success: err.success, message: err.message - }); + }) } else { return reply.code(500).send({ success: false, message: 'Internal Server Error' - }); + }) } } -module.exports = { ErrorHandler, catchError }; \ No newline at end of file +module.exports = { ErrorHandler, catchError } \ No newline at end of file diff --git a/server/utils/mail.js b/server/utils/mail.js index f120ce4..f714a93 100644 --- a/server/utils/mail.js +++ b/server/utils/mail.js @@ -1,10 +1,10 @@ const sendMail = (mailer, mailOptions) => { return new Promise((resolve, reject) => { mailer.sendMail(mailOptions, (error, info) => { - if (error) return reject(error); - resolve(info); - }); - }); + if (error) return reject(error) + resolve(info) + }) + }) } module.exports = { sendMail } \ No newline at end of file