From 67dbe9fb98c3578925eb9813290bafb5c1a9f0cc Mon Sep 17 00:00:00 2001 From: gf-rog Date: Fri, 26 Apr 2024 10:37:26 +0200 Subject: [PATCH 01/26] Move uuid from devDependencies to dependencies --- backend/package-lock.json | 6 ++---- backend/package.json | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 8d1a9ba..0de50ca 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -21,6 +21,7 @@ "neo4j-driver": "^5.13.0", "socket.io": "^4.7.2", "unidecode": "^0.1.8", + "uuid": "^9.0.1", "vite": "^5.2.6", "zod": "^3.22.4" }, @@ -36,7 +37,6 @@ "tsc-watch": "^6.0.4", "tsx": "^4.7.1", "typescript": "^5.2.2", - "uuid": "^9.0.1", "vitest": "^0.34.6" } }, @@ -3960,7 +3960,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -6988,8 +6987,7 @@ "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-to-istanbul": { "version": "9.1.3", diff --git a/backend/package.json b/backend/package.json index 1582f93..b29df43 100644 --- a/backend/package.json +++ b/backend/package.json @@ -27,6 +27,7 @@ "neo4j-driver": "^5.13.0", "socket.io": "^4.7.2", "unidecode": "^0.1.8", + "uuid": "^9.0.1", "vite": "^5.2.6", "zod": "^3.22.4" }, @@ -42,7 +43,6 @@ "tsc-watch": "^6.0.4", "tsx": "^4.7.1", "typescript": "^5.2.2", - "uuid": "^9.0.1", "vitest": "^0.34.6" } } From dfe2ab38b4173ede6925fa91de5c25352ab65abc Mon Sep 17 00:00:00 2001 From: gf-rog Date: Sat, 27 Apr 2024 11:18:19 +0200 Subject: [PATCH 02/26] Remove data folder --- backend/src/{data/users.ts => userData.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/src/{data/users.ts => userData.ts} (100%) diff --git a/backend/src/data/users.ts b/backend/src/userData.ts similarity index 100% rename from backend/src/data/users.ts rename to backend/src/userData.ts From 55946716be56c2aa9614cf80e828517e1002f0f8 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Sat, 27 Apr 2024 11:18:35 +0200 Subject: [PATCH 03/26] Rename db.ts to importDb.ts --- backend/src/httpServer.ts | 2 +- backend/src/{db.ts => importDb.ts} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename backend/src/{db.ts => importDb.ts} (98%) diff --git a/backend/src/httpServer.ts b/backend/src/httpServer.ts index 543d8ab..37a72ab 100644 --- a/backend/src/httpServer.ts +++ b/backend/src/httpServer.ts @@ -3,7 +3,7 @@ import servers from "./server.js"; import usersRouter from "./routes/usersRoute.js"; import authRouter from "./routes/authRoute.js"; import chatRouter from "./routes/chatRoute.js"; -import { cleanUpData, importInitialData } from "./db.js"; +import { cleanUpData, importInitialData } from "./importDb.js"; const { app } = servers; diff --git a/backend/src/db.ts b/backend/src/importDb.ts similarity index 98% rename from backend/src/db.ts rename to backend/src/importDb.ts index 841a062..f588437 100644 --- a/backend/src/db.ts +++ b/backend/src/importDb.ts @@ -1,6 +1,6 @@ import driver from "./driver/driver.js"; -import userData from "./data/users.js"; +import userData from "./userData.js"; import { registerUser, registerUserSchema } from "./users.js"; import { addFriend } from "./userFriends.js"; From 12034e6910f5ebc0d971507463e6f33e69e2350b Mon Sep 17 00:00:00 2001 From: gf-rog Date: Sat, 27 Apr 2024 11:23:00 +0200 Subject: [PATCH 04/26] Move driver.ts to /src --- backend/src/{driver => }/driver.ts | 0 backend/src/importDb.ts | 2 +- backend/src/routes/authRoute.ts | 2 +- backend/src/routes/userFriendsRoute.ts | 2 +- backend/src/routes/usersRoute.ts | 2 +- backend/src/socketServer.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename backend/src/{driver => }/driver.ts (100%) diff --git a/backend/src/driver/driver.ts b/backend/src/driver.ts similarity index 100% rename from backend/src/driver/driver.ts rename to backend/src/driver.ts diff --git a/backend/src/importDb.ts b/backend/src/importDb.ts index f588437..7f08d6b 100644 --- a/backend/src/importDb.ts +++ b/backend/src/importDb.ts @@ -1,4 +1,4 @@ -import driver from "./driver/driver.js"; +import driver from "./driver.js"; import userData from "./userData.js"; import { registerUser, registerUserSchema } from "./users.js"; diff --git a/backend/src/routes/authRoute.ts b/backend/src/routes/authRoute.ts index 9af001d..5becbdc 100644 --- a/backend/src/routes/authRoute.ts +++ b/backend/src/routes/authRoute.ts @@ -2,7 +2,7 @@ import { Router, Request } from "express"; import bcrypt from "bcrypt"; -import driver from "../driver/driver.js"; +import driver from "../driver.js"; import { JWTRequest, authenticateToken, diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index a7f6dc5..f24188b 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { Session } from "neo4j-driver"; -import driver from "../driver/driver.js"; +import driver from "../driver.js"; import User from "../models/User.js"; import { OkErrorResponse, diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index b51db37..23b882f 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import driver from "../driver/driver.js"; +import driver from "../driver.js"; import { JWTRequest, authenticateToken, getToken } from "../misc/jwt.js"; import { AuthOkErrorResponse, diff --git a/backend/src/socketServer.ts b/backend/src/socketServer.ts index 5b42ef8..730bc91 100644 --- a/backend/src/socketServer.ts +++ b/backend/src/socketServer.ts @@ -1,6 +1,6 @@ import servers from "./server.js"; import dotenv from "dotenv"; -import driver from "./driver/driver.js"; +import driver from "./driver.js"; import { Socket } from "socket.io"; import { connectToSocket, From 1f8b978aa65b1b9163ec3f287e08a9394df12fe5 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Sat, 27 Apr 2024 11:58:20 +0200 Subject: [PATCH 05/26] Move server setup code to correct places --- backend/src/httpServer.ts | 35 +++++++++++++++++++----- backend/src/index.ts | 16 +++++++++++ backend/src/server.ts | 53 ------------------------------------- backend/src/socketServer.ts | 17 +++++++----- 4 files changed, 55 insertions(+), 66 deletions(-) delete mode 100644 backend/src/server.ts diff --git a/backend/src/httpServer.ts b/backend/src/httpServer.ts index 37a72ab..c0eb744 100644 --- a/backend/src/httpServer.ts +++ b/backend/src/httpServer.ts @@ -1,17 +1,38 @@ -import dotenv from "dotenv"; -import servers from "./server.js"; +import express from "express"; +import cors from "cors"; +import cookieParser from "cookie-parser"; + +import { createServer } from "node:http"; + import usersRouter from "./routes/usersRoute.js"; import authRouter from "./routes/authRoute.js"; import chatRouter from "./routes/chatRoute.js"; -import { cleanUpData, importInitialData } from "./importDb.js"; -const { app } = servers; +const app = express(); +const port: number = 5000; + +const corsOptions = { + origin: ["http://localhost:5000", "http://localhost:5173"], + optionsSuccessStatus: 200, +}; -dotenv.config(); +app.use(cors(corsOptions)); +app.use((_req, res, next) => { + res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE"); + res.header("Access-Control-Allow-Headers", "Content-Type"); + next(); +}); +app.use(cookieParser()); +app.use(express.json({ limit: "10mb" })); -cleanUpData(); -importInitialData().then((res) => console.log(res)); +const expressServer = createServer(app); + +expressServer.listen(port, () => { + console.log(`HTTP server running on port ${port}`); +}); app.use("/users", usersRouter); app.use("/auth", authRouter); app.use("/chat", chatRouter); + +export {expressServer} diff --git a/backend/src/index.ts b/backend/src/index.ts index 1ecbafb..3be6b55 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,3 +1,19 @@ +import "dotenv/config" + import "./httpServer.js"; import "./socketServer.js"; import "./kcAdminClient.js"; + +import { cleanUpData, importInitialData } from "./importDb.js"; +import { connect } from "mongoose"; + +cleanUpData(); +importInitialData().then((res) => console.log(res)); + +try { + const uri = process.env.MONGODB_URI || "mongodb://localhost:27017"; + await connect(`${uri}/chats`); + console.log("Chat database started"); +} catch (err) { + console.error(err); +} diff --git a/backend/src/server.ts b/backend/src/server.ts deleted file mode 100644 index 633268e..0000000 --- a/backend/src/server.ts +++ /dev/null @@ -1,53 +0,0 @@ -import express, { Express } from "express"; -import dotenv from "dotenv"; -import cors from "cors"; -import { Server as SocketServer } from "socket.io"; -import { createServer } from "http"; -import { connect } from "mongoose"; -import cookieParser from "cookie-parser"; -import ServerToClientEvents from "./events/ServerToClientEvents.js"; -import ClientToServerEvents from "./events/ClientToServerEvents.js"; - -dotenv.config(); - -const corsOptions = { - origin: ["http://localhost:5000", "http://localhost:5173"], - optionsSuccessStatus: 200, -}; - -const app: Express = express(); -const port: number = 5000; - -app.use(cors(corsOptions)); -app.use((_req, res, next) => { - res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE"); - res.header("Access-Control-Allow-Headers", "Content-Type"); - next(); -}); -app.use(cookieParser()); -app.use(express.json({ limit: "10mb" })); - -const expressServer = createServer(app); - -const io = new SocketServer( - expressServer, - { - cors: { origin: ["http://localhost:5173"] }, - }, -); - -(async () => { - try { - const uri = process.env.MONGODB_URI || "mongodb://localhost:27017"; - await connect(`${uri}/chats`); - - expressServer.listen(port, () => { - console.log("Chat database started"); - console.log(`HTTP server running on port ${port}`); - }); - } catch (err) { - console.error(err); - } -})(); - -export default { expressServer, io, app }; diff --git a/backend/src/socketServer.ts b/backend/src/socketServer.ts index 730bc91..e961126 100644 --- a/backend/src/socketServer.ts +++ b/backend/src/socketServer.ts @@ -1,5 +1,3 @@ -import servers from "./server.js"; -import dotenv from "dotenv"; import driver from "./driver.js"; import { Socket } from "socket.io"; import { @@ -16,10 +14,17 @@ import { joinMeeting, } from "./meetings.js"; import { addMessageToDb } from "./messages.js"; - -const { io } = servers; - -dotenv.config(); +import { Server as SocketServer } from "socket.io"; +import ClientToServerEvents from "./events/ClientToServerEvents.js"; +import ServerToClientEvents from "./events/ServerToClientEvents.js"; +import { expressServer } from "./httpServer.js"; + +const io = new SocketServer( + expressServer, + { + cors: { origin: ["http://localhost:5173"] }, + }, +); const meetings: Record = {}; From 084b29fe081fe8c103d64d65ef3bbbce7c7fa519 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 2 May 2024 09:21:50 +0200 Subject: [PATCH 06/26] Apply formatting --- backend/src/driver.ts | 4 ++-- backend/src/httpServer.ts | 2 +- backend/src/index.ts | 2 +- backend/src/models/ChangePasswordReq.ts | 15 +++++++++------ backend/src/models/Response.ts | 4 ++-- backend/src/models/routes/Search.ts | 14 ++++++++------ backend/src/routes/usersRoute.ts | 7 ++++--- frontend/src/components/Profile.tsx | 3 ++- frontend/src/components/Search.tsx | 4 ++-- 9 files changed, 31 insertions(+), 24 deletions(-) diff --git a/backend/src/driver.ts b/backend/src/driver.ts index a670730..b580763 100644 --- a/backend/src/driver.ts +++ b/backend/src/driver.ts @@ -4,11 +4,11 @@ const username = process.env.NEO4J_USERNAME; const password = process.env.NEO4J_PASSWORD; if (!username) { - throw new Error("NEO4J_USERNAME environment variable not provided!") + throw new Error("NEO4J_USERNAME environment variable not provided!"); } if (!password) { - throw new Error("NEO4J_PASSWORD environment variable not provided!") + throw new Error("NEO4J_PASSWORD environment variable not provided!"); } const driver = neo4j.driver( diff --git a/backend/src/httpServer.ts b/backend/src/httpServer.ts index c0eb744..c9b69a9 100644 --- a/backend/src/httpServer.ts +++ b/backend/src/httpServer.ts @@ -35,4 +35,4 @@ app.use("/users", usersRouter); app.use("/auth", authRouter); app.use("/chat", chatRouter); -export {expressServer} +export { expressServer }; diff --git a/backend/src/index.ts b/backend/src/index.ts index 3be6b55..92d2982 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,4 +1,4 @@ -import "dotenv/config" +import "dotenv/config"; import "./httpServer.js"; import "./socketServer.js"; diff --git a/backend/src/models/ChangePasswordReq.ts b/backend/src/models/ChangePasswordReq.ts index 3310774..04665b5 100644 --- a/backend/src/models/ChangePasswordReq.ts +++ b/backend/src/models/ChangePasswordReq.ts @@ -1,11 +1,13 @@ import { z } from "zod"; import { userPasswordSchema } from "./User.js"; -type ChangePasswordReq = { - old_password: string; - new_password: string; - repeat_password: string; -} | {} +type ChangePasswordReq = + | { + old_password: string; + new_password: string; + repeat_password: string; + } + | {}; export const changePasswordReqSchema: z.ZodType = z .object({ @@ -16,6 +18,7 @@ export const changePasswordReqSchema: z.ZodType = z .refine((data) => data.new_password === data.repeat_password, { message: "Passwords don't match", path: ["repeat_password"], - }).or(z.object({})) + }) + .or(z.object({})); export default ChangePasswordReq; diff --git a/backend/src/models/Response.ts b/backend/src/models/Response.ts index 8a8291a..d0f80c3 100644 --- a/backend/src/models/Response.ts +++ b/backend/src/models/Response.ts @@ -9,8 +9,8 @@ export interface CustomResponse extends Response { } export type Errors = { - [key: string]: Errors | string -} + [key: string]: Errors | string; +}; export interface ErrorResponse { status: "error"; diff --git a/backend/src/models/routes/Search.ts b/backend/src/models/routes/Search.ts index a694989..38b51b7 100644 --- a/backend/src/models/routes/Search.ts +++ b/backend/src/models/routes/Search.ts @@ -1,5 +1,5 @@ import { ZodType, z } from "zod"; -import Page, { pageSchema } from "./Page.js" +import Page, { pageSchema } from "./Page.js"; import { userCountrySchema } from "../User.js"; interface Search extends Page { @@ -8,10 +8,12 @@ interface Search extends Page { userId?: string; } -export const searchSchema = z.object({ - q: z.string().min(0).max(64), - country: z.union([userCountrySchema, z.literal("")]), - userId: z.optional(z.string().uuid()) -}).merge(pageSchema) satisfies ZodType +export const searchSchema = z + .object({ + q: z.string().min(0).max(64), + country: z.union([userCountrySchema, z.literal("")]), + userId: z.optional(z.string().uuid()), + }) + .merge(pageSchema) satisfies ZodType; export default Search; diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index 23b882f..5f64334 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -264,14 +264,15 @@ usersRouter.post( session, userId, parsedPasswords, - req.token + req.token, ); if (!changePasswordResult.success) { - const {userExists, isUserIssued, passwordCorrect} = changePasswordResult; + const { userExists, isUserIssued, passwordCorrect } = + changePasswordResult; if (!userExists) { - return userNotFoundRes(res) + return userNotFoundRes(res); } if (isUserIssued) { diff --git a/frontend/src/components/Profile.tsx b/frontend/src/components/Profile.tsx index 833ed21..271eb0f 100644 --- a/frontend/src/components/Profile.tsx +++ b/frontend/src/components/Profile.tsx @@ -14,7 +14,8 @@ function Profile(props: ProfilePageFormProps) { const { user, handleEditClick, deleteUser } = props; - const countryName = countriesData.find((v) => v.Code == user.country)?.Country + const countryName = countriesData.find((v) => v.Code == user.country) + ?.Country; return (
diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 4d54801..5cfca76 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -12,7 +12,7 @@ interface searchProps { const Search = (props: searchProps) => { const navigate = useNavigate(); - const {user} = useUser(); + const { user } = useUser(); // Logic const [searchQuery, setSearchQuery] = useState(""); @@ -53,7 +53,7 @@ const Search = (props: searchProps) => { const urlSearchParams = new URLSearchParams({ q: searchQuery, country: countryQuery, - userId: user?.id || "" + userId: user?.id || "", }); props.handler(`/users/search?${urlSearchParams}`); From f96b203e222996005ea366a97c857b504ac978e0 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Tue, 14 May 2024 16:25:42 +0200 Subject: [PATCH 07/26] Don't restart backend if tests changed --- backend/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/tsconfig.json b/backend/tsconfig.json index d6fe990..121aafe 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -108,5 +108,6 @@ }, "ts-node": { "esm": true - } + }, + "exclude": ["test"] } From a041f3a35e668dbb7e04f69c342b541e95e029a8 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Tue, 14 May 2024 17:02:06 +0200 Subject: [PATCH 08/26] Add token parameter --- backend/src/misc/fetchData.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backend/src/misc/fetchData.ts b/backend/src/misc/fetchData.ts index 4937b1a..ea2bad3 100644 --- a/backend/src/misc/fetchData.ts +++ b/backend/src/misc/fetchData.ts @@ -1,4 +1,16 @@ -export const fetchData = async (url: string, method: string, options = {}) => { +export const fetchData = async ( + url: string, + method: string, + options: RequestInit = {}, + token?: string, +) => { + if (token) { + options.headers = { + ...options.headers, + "Authorization": `Bearer ${token}` + } + } + try { const response = await fetch(url, { ...options, method }); const data = await response.json(); From 4b269234365597535288965e7f185fca2711d40b Mon Sep 17 00:00:00 2001 From: gf-rog Date: Tue, 14 May 2024 17:02:45 +0200 Subject: [PATCH 09/26] Add authorization to user CRUD --- backend/src/routes/usersRoute.ts | 84 +++++++------ backend/test/userCRUD.test.ts | 201 +++++++++++++------------------ 2 files changed, 131 insertions(+), 154 deletions(-) diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index 5f64334..a5af236 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -218,31 +218,35 @@ usersRouter.post("/", async (req: Request, res: UserErrorResponse) => { } }); -usersRouter.put("/:userId", async (req: Request, res: OkErrorResponse) => { - const userParse = updateUserSchema.safeParse(req.body); - if (!userParse.success) { - const errors = formatError(userParse.error); - return res.status(400).json({ status: "error", errors }); - } +usersRouter.put( + "/:userId", + authenticateToken, + async (req: Request, res: OkErrorResponse) => { + const userParse = updateUserSchema.safeParse(req.body); + if (!userParse.success) { + const errors = formatError(userParse.error); + return res.status(400).json({ status: "error", errors }); + } - const parsedUser: UpdateUser = userParse.data; - const userId = req.params.userId; + const parsedUser: UpdateUser = userParse.data; + const userId = req.params.userId; - const session = driver.session(); - try { - const newUser = await updateUser(session, userId, parsedUser); - if (!newUser) { - return userNotFoundRes(res); - } + const session = driver.session(); + try { + const newUser = await updateUser(session, userId, parsedUser); + if (!newUser) { + return userNotFoundRes(res); + } - return res.json({ status: "ok" }); - } catch (err) { - console.log("Error:", err); - return res.status(404).json({ status: "error", errors: err as Errors }); - } finally { - await session.close(); - } -}); + return res.json({ status: "ok" }); + } catch (err) { + console.log("Error:", err); + return res.status(404).json({ status: "error", errors: err as Errors }); + } finally { + await session.close(); + } + }, +); usersRouter.post( "/:userId/change-password", @@ -296,23 +300,27 @@ usersRouter.post( }, ); -usersRouter.delete("/:userId", async (req: Request, res: OkErrorResponse) => { - const userId = req.params.userId; +usersRouter.delete( + "/:userId", + authenticateToken, + async (req: Request, res: OkErrorResponse) => { + const userId = req.params.userId; - const session = driver.session(); - try { - const isDeleted = await deleteUser(session, userId); - if (!isDeleted) { - return userNotFoundRes(res); - } + const session = driver.session(); + try { + const isDeleted = await deleteUser(session, userId); + if (!isDeleted) { + return userNotFoundRes(res); + } - return res.json({ status: "ok" }); - } catch (err) { - console.log("Error:", err); - return res.status(404).json({ status: "error", errors: err as Errors }); - } finally { - await session.close(); - } -}); + return res.json({ status: "ok" }); + } catch (err) { + console.log("Error:", err); + return res.status(404).json({ status: "error", errors: err as Errors }); + } finally { + await session.close(); + } + }, +); export default usersRouter; diff --git a/backend/test/userCRUD.test.ts b/backend/test/userCRUD.test.ts index ea26cad..e55f386 100644 --- a/backend/test/userCRUD.test.ts +++ b/backend/test/userCRUD.test.ts @@ -1,7 +1,7 @@ import { expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; -let userId: number; +let userId: string; let userData = { first_name: "John", last_name: "Smith", @@ -23,6 +23,58 @@ const login = async (mail: string, password: string) => { token = response.token; }; +const updateUser = async (userId: string, token?: string) => { + const response = await fetchData( + `http://localhost:5000/users/${userId}`, + "PUT", + { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }, + token, + ); + + return response; +}; + +const changePassword = async ( + userId: string, + new_password: string, + repeat_password: string, + token?: string, +) => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/change-password`, + "POST", + { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + old_password: userData.password, + new_password, + repeat_password, + }), + }, + token, + ); + + return response; +}; + +const deleteUser = async (userId: string, token?: string) => { + const response = await fetchData( + `http://localhost:5000/users/${userId}`, + "DELETE", + {}, + token, + ); + + return response; +}; + test("Get all users", async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); const { status, users } = response; @@ -47,6 +99,7 @@ test("Create user", async () => { expect(user.mail).toBe(userData.mail); userId = user.id; + await login(userData.mail, userData.password); }); test("Create user with existing mail", async () => { @@ -142,34 +195,20 @@ test("Get user with incorrect ID", async () => { expect(errors.id).toBe("not found"); }); +test("Update user without token", async () => { + const { status } = await updateUser(userId); + expect(status).toBe("unauthorized"); +}); + test("Update user by ID", async () => { userData.profile_picture = "https://example.com/new_john_smith.jpg"; - const response = await fetchData( - `http://localhost:5000/users/${userId}`, - "PUT", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }, - ); - - const { status } = response; - + const { status } = await updateUser(userId, token); expect(status).toBe("ok"); }); test("Update user with incorrect ID", async () => { - const response = await fetchData(`http://localhost:5000/users/0`, "PUT", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }); - - const { status, errors } = response; + const { status, errors } = await updateUser("0", token); expect(status).toBe("error"); expect(errors.id).toBe("not found"); @@ -178,18 +217,7 @@ test("Update user with incorrect ID", async () => { test("Update user with short first name", async () => { userData.first_name = "j"; - const response = await fetchData( - `http://localhost:5000/users/${userId}`, - "PUT", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }, - ); - - const { status, errors } = response; + const { status, errors } = await updateUser(userId, token); expect(status).toBe("error"); expect(errors.first_name).toBe("String must contain at least 2 character(s)"); @@ -198,116 +226,57 @@ test("Update user with short first name", async () => { test("Update user with short last name", async () => { userData.last_name = "s"; - const response = await fetchData( - `http://localhost:5000/users/${userId}`, - "PUT", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }, - ); - - const { status, errors } = response; + const { status, errors } = await updateUser(userId, token); expect(status).toBe("error"); expect(errors.last_name).toBe("String must contain at least 2 character(s)"); }); test("Update user with short password", async () => { - await login(userData.mail, userData.password); - - const response = await fetchData( - `http://localhost:5000/users/${userId}/change-password`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token: token, - old_password: userData.password, - new_password: "1234", - repeat_password: "1234", - }), - }, + const { status, errors } = await changePassword( + userId, + "1234", + "1234", + token, ); - const { status, errors } = response; - expect(status).toBe("error"); expect(errors).toBeDefined(); expect(errors.old_password).toBe("incorrect"); }); test("Update user with same password", async () => { - await login(userData.mail, userData.password); - - const response = await fetchData( - `http://localhost:5000/users/${userId}/change-password`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token: token, - old_password: userData.password, - new_password: "12345678", - repeat_password: "12345678", - }), - }, + const { status } = await changePassword( + userId, + "12345678", + "12345678", + token, ); - - const { status } = response; - expect(status).toBe("ok"); }); test("Update user with correct password", async () => { - await login(userData.mail, userData.password); - - const response = await fetchData( - `http://localhost:5000/users/${userId}/change-password`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token: token, - old_password: userData.password, - new_password: "123456789", - repeat_password: "123456789", - }), - }, + const { status } = await changePassword( + userId, + "123456789", + "123456789", + token, ); - - const { status } = response; - expect(status).toBe("ok"); }); -test("Delete user by ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}`, - "DELETE", - {}, - ); - - const { status } = response; +test("Delete user without token", async () => { + const { status } = await deleteUser(userId); + expect(status).toBe("unauthorized"); +}); +test("Delete user by ID", async () => { + const { status } = await deleteUser(userId, token); expect(status).toBe("ok"); }); test("Delete user with incorrect ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/🐍`, - "DELETE", - {}, - ); - + const response = await deleteUser("🐍", token); const { status, errors } = response; expect(status).toBe("error"); From b8f852b66a3064337a6b1a3f57888a469c816e42 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 10:16:52 +0200 Subject: [PATCH 10/26] Use token introspection endpoint instead of userinfo --- backend/src/kcAdminClient.ts | 3 +++ backend/src/misc/jwt.ts | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/kcAdminClient.ts b/backend/src/kcAdminClient.ts index 89eb6a5..d8e99a9 100644 --- a/backend/src/kcAdminClient.ts +++ b/backend/src/kcAdminClient.ts @@ -2,6 +2,9 @@ import KeycloakAdminClient from "@keycloak/keycloak-admin-client"; export const keycloakUri = process.env.KEYCLOAK_URI || "http://localhost:3000"; export const keycloakIssuer = process.env.KEYCLOAK_ISSUER || keycloakUri; +export const keycloakCredentials = Buffer.from( + `mercury-backend:${process.env.CLIENT_SECRET}`, +).toString("base64"); const kcAdminClient = new KeycloakAdminClient({ baseUrl: keycloakUri, diff --git a/backend/src/misc/jwt.ts b/backend/src/misc/jwt.ts index d4eb8a8..ac13b4a 100644 --- a/backend/src/misc/jwt.ts +++ b/backend/src/misc/jwt.ts @@ -4,7 +4,11 @@ import { AuthResponse, CustomResponse } from "../models/Response.js"; import DecodedData from "../models/DecodedData.js"; import Issuer from "../models/Issuer.js"; import TokenPayload from "../models/TokenPayload.js"; -import { keycloakIssuer, keycloakUri } from "../kcAdminClient.js"; +import { + keycloakCredentials, + keycloakIssuer, + keycloakUri, +} from "../kcAdminClient.js"; export interface JWTRequest extends Request { token?: TokenPayload; @@ -37,12 +41,14 @@ function tokenIssuerToName(issuer: string): Issuer | "unknown" { export async function verifyKeycloakToken(tokenStr: string): Promise { const response = await fetch( - `${keycloakUri}/realms/mercury/protocol/openid-connect/userinfo`, + `${keycloakUri}/realms/mercury/protocol/openid-connect/token/introspect`, { - method: "GET", + method: "POST", headers: { - "Authorization": `Bearer ${tokenStr}`, + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": `Basic ${keycloakCredentials}`, }, + body: `token=${tokenStr}`, }, ); From 40203cb7595e439e2bc78aa69bc229856f09258d Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 10:18:02 +0200 Subject: [PATCH 11/26] Print response if request failed --- keycloak/login-user.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/keycloak/login-user.sh b/keycloak/login-user.sh index d6f1bc0..28f7cb2 100755 --- a/keycloak/login-user.sh +++ b/keycloak/login-user.sh @@ -1,17 +1,23 @@ -request=\ +request_body=\ "grant_type=password"\ "&client_id=mercury-testing"\ "&client_secret=5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag"\ "&username=reptilian@meta.com"\ "&password=metaverse4ever" -access_token=$(curl \ +response=$(curl \ -sS \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ - -d "$request" \ - localhost:3000/realms/mercury/protocol/openid-connect/token \ -| jq -r .access_token) + -d "$request_body" \ + localhost:3000/realms/mercury/protocol/openid-connect/token) + +access_token=$(echo "$response" | jq -r .access_token) echo "Access token:" echo "$access_token" + +if [ "$access_token" = "null" ]; then + echo "Response:" + echo "$response" +fi From fb92fdc7339986efe83f1e59f00aabebdf7c5f67 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 11:28:47 +0200 Subject: [PATCH 12/26] Add authorization to user friend requests --- backend/src/routes/userFriendsRoute.ts | 5 + backend/test/userFriendRequests.test.ts | 279 +++++++++++++----------- 2 files changed, 154 insertions(+), 130 deletions(-) diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index f24188b..a9d3a5c 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -22,6 +22,7 @@ import { userNotFoundRes } from "./usersRoute.js"; import { Errors } from "../models/Response.js"; import Page, { pageSchema } from "../models/routes/Page.js"; import { formatError } from "../misc/formatError.js"; +import { authenticateToken } from "../misc/jwt.js"; const friendsRouter = Router(); @@ -159,6 +160,7 @@ friendsRouter.get( friendsRouter.post( "/:userId1/send-friend-request/:userId2", + authenticateToken, async (req: Request, res: OkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; @@ -193,6 +195,7 @@ friendsRouter.post( friendsRouter.post( "/:userId1/accept-friend-request/:userId2", + authenticateToken, async (req: Request, res: OkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; @@ -244,6 +247,7 @@ friendsRouter.post( friendsRouter.post( "/:userId1/decline-friend-request/:userId2", + authenticateToken, async (req: Request, res: OkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; @@ -291,6 +295,7 @@ friendsRouter.post( friendsRouter.delete( "/:userId1/delete-friend/:userId2", + authenticateToken, async (req: Request, res: OkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; diff --git a/backend/test/userFriendRequests.test.ts b/backend/test/userFriendRequests.test.ts index bb538be..7a1c1d2 100644 --- a/backend/test/userFriendRequests.test.ts +++ b/backend/test/userFriendRequests.test.ts @@ -7,18 +7,124 @@ let maxUsers: number = 1; let userId: string = ""; let userId2: string = ""; +const userMail1 = "bconford2@wikimedia.org"; +const userPassword1 = "heuristic"; + +const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; + const getUsers = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); - userId = response.users.find( - (user: User) => user.mail === "bconford2@wikimedia.org", - ).id; - userId2 = response.users.find( - (user: User) => user.mail === "cruckman3@archive.org", - ).id; + userId = response.users.find((user: User) => user.mail === userMail1).id; + userId2 = response.users.find((user: User) => user.mail === userMail2).id; +}; + +const getKeycloakToken = async ( + mail: string, + password: string, +): Promise => { + const urlParams = new URLSearchParams({ + grant_type: "password", + client_id: "mercury-testing", + client_secret: "5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag", + username: mail, + password: password, + }); + + const response = await fetchData( + `http://localhost:3000/realms/mercury/protocol/openid-connect/token`, + "POST", + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: urlParams, + }, + ); + + return response.access_token; }; await getUsers(); +const token1 = await getKeycloakToken(userMail1, userPassword1); +const token2 = await getKeycloakToken(userMail2, userPassword2); + +const sendFriendRequest = async ( + userId1: string, + userId2: string, + token?: string, +) => { + const response = await fetchData( + `http://localhost:5000/users/${userId1}/send-friend-request/${userId2}`, + "POST", + { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }, + token, + ); + + return response; +}; + +const declineFriendRequest = async ( + userId1: string, + userId2: string, + token?: string, +) => { + const response = await fetchData( + `http://localhost:5000/users/${userId1}/decline-friend-request/${userId2}`, + "POST", + { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }, + token, + ); + + return response; +}; + +const acceptFriendRequest = async ( + userId1: string, + userId2: string, + token?: string, +) => { + const response = await fetchData( + `http://localhost:5000/users/${userId1}/accept-friend-request/${userId2}`, + "POST", + { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }, + token, + ); + + return response; +}; + +const deleteFriend = async ( + userId1: string, + userId2: string, + token?: string, +) => { + const response = await fetchData( + `http://localhost:5000/users/${userId1}/delete-friend/${userId2}`, + "DELETE", + {}, + token, + ); + + return response; +}; + test("Check current requests", async () => { const response = await fetchData( `http://localhost:5000/users/${userId}/friend-requests?page=${page}&maxUsers=${maxUsers}`, @@ -117,122 +223,59 @@ test("maxUsers equals 0", async () => { expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); }); -test("Send invite", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/send-friend-request/${userId2}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status } = response; +test("Send friend request without token", async () => { + const { status } = await sendFriendRequest(userId, userId2); + expect(status).toBe("unauthorized"); +}); +test("Send friend request", async () => { + const { status } = await sendFriendRequest(userId, userId2, token1); expect(status).toBe("ok"); }); -test("Send invite with incorrect id", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/send-friend-request/0`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status, errors } = response; +test("Send friend request with incorrect id", async () => { + const { status, errors } = await sendFriendRequest(userId, "0", token1); expect(status).toBe("error"); expect(errors).toBeDefined(); expect(errors.userId2).toBe("not found"); }); -test("Decline friend request", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/decline-friend-request/${userId2}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status } = response; +test("Decline friend request without token", async () => { + const { status } = await declineFriendRequest(userId2, userId); + expect(status).toBe("unauthorized"); +}); +test("Decline friend request", async () => { + const { status } = await declineFriendRequest(userId2, userId, token2); expect(status).toBe("ok"); }); test("Decline friend request with incorrect id", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/decline-friend-request/0`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status, errors } = response; + const { status, errors } = await declineFriendRequest(userId, "0", token2); expect(status).toBe("error"); expect(errors).toBeDefined(); expect(errors.userId2).toBe("not found"); }); -test("Accept not invited friend request", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/accept-friend-request/${userId2}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); +test("Accept friend request without token", async () => { + const { status } = await acceptFriendRequest(userId2, userId); + expect(status).toBe("unauthorized"); +}); - const { status, errors } = response; +test("Accept friend request when not invited", async () => { + const { status, errors } = await acceptFriendRequest(userId2, userId, token2); expect(status).toBe("error"); expect(errors).toBeDefined(); expect(errors.userId1).toBe("not invited"); }); -test("Accept invite", async () => { - await fetchData( - `http://localhost:5000/users/${userId}/send-friend-request/${userId2}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const response = await fetchData( - `http://localhost:5000/users/${userId2}/accept-friend-request/${userId}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status } = response; +test("Accept friend request", async () => { + await sendFriendRequest(userId, userId2, token1); + const { status } = await acceptFriendRequest(userId2, userId, token2); expect(status).toBe("ok"); const friendsResponse = await fetchData( @@ -248,18 +291,8 @@ test("Accept invite", async () => { expect(friend).toBeDefined(); }); -test("Accept invite with incorrect id", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/accept-friend-request/0`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - +test("Accept friend request with incorrect id", async () => { + const response = await acceptFriendRequest(userId, "0", token1); const { status, errors } = response; expect(status).toBe("error"); @@ -268,43 +301,29 @@ test("Accept invite with incorrect id", async () => { }); test("Decline friend request when not invited", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId2}/decline-friend-request/${userId}`, - "POST", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, + const { status, errors } = await declineFriendRequest( + userId2, + userId, + token2, ); - const { status, errors } = response; - expect(status).toBe("error"); expect(errors).toBeDefined(); expect(errors.userId1).toBe("not invited"); }); -test("Delete friend", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId2}/delete-friend/${userId}`, - "DELETE", - {}, - ); - - const { status } = response; +test("Delete friend without token", async () => { + const { status } = await deleteFriend(userId2, userId); + expect(status).toBe("unauthorized"); +}); +test("Delete friend", async () => { + const { status } = await deleteFriend(userId2, userId, token2); expect(status).toBe("ok"); }); test("Delete friend with incorrect id", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/delete-friend/0`, - "DELETE", - {}, - ); - + const response = await deleteFriend(userId, "0", token1); const { status, errors } = response; expect(status).toBe("error"); From e26477e964da843351aa5ae110cb6d2b0687cc2c Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 11:49:22 +0200 Subject: [PATCH 13/26] Add authorization to chat requests --- backend/src/routes/chatRoute.ts | 3 +- backend/test/chat.test.ts | 65 ++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/backend/src/routes/chatRoute.ts b/backend/src/routes/chatRoute.ts index c853838..8c70c08 100644 --- a/backend/src/routes/chatRoute.ts +++ b/backend/src/routes/chatRoute.ts @@ -1,10 +1,11 @@ import { Router } from "express"; import Message from "../models/Message.js"; import MessageModel from "../mongoDB/MessageModel.js"; +import { authenticateToken } from "../misc/jwt.js"; const chatRouter = Router(); -chatRouter.get("/:user1Id/:user2Id", async (req, res) => { +chatRouter.get("/:user1Id/:user2Id", authenticateToken, async (req, res) => { try { const { user1Id, user2Id } = req.params; const messageRequest = await MessageModel.find({ diff --git a/backend/test/chat.test.ts b/backend/test/chat.test.ts index c81ba53..d9b829c 100644 --- a/backend/test/chat.test.ts +++ b/backend/test/chat.test.ts @@ -5,17 +5,57 @@ import User from "../src/models/User.js"; let userId1: string = ""; let userId2: string = ""; +const userMail1 = "bconford2@wikimedia.org"; +const userPassword1 = "heuristic"; + +const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; + const getUsers = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); - userId1 = response.users.find( - (user: User) => user.mail === "bconford2@wikimedia.org", - ).id; - userId2 = response.users.find( - (user: User) => user.mail === "cruckman3@archive.org", - ).id; + userId1 = response.users.find((user: User) => user.mail === userMail1).id; + userId2 = response.users.find((user: User) => user.mail === userMail2).id; +}; + +const getKeycloakToken = async ( + mail: string, + password: string, +): Promise => { + const urlParams = new URLSearchParams({ + grant_type: "password", + client_id: "mercury-testing", + client_secret: "5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag", + username: mail, + password: password, + }); + + const response = await fetchData( + `http://localhost:3000/realms/mercury/protocol/openid-connect/token`, + "POST", + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: urlParams, + }, + ); + + return response.access_token; }; await getUsers(); +const token = await getKeycloakToken(userMail1, userPassword1); + +const getChat = async (userId1: string, userId2: string, token?: string) => { + const response = await fetchData( + `http://localhost:5000/chat/${userId1}/${userId2}`, + "GET", + {}, + token, + ); + + return response; +}; test("Get chat without ids", async () => { const response = await fetch(`http://localhost:5000/chat`); @@ -25,14 +65,13 @@ test("Get chat without ids", async () => { expect(data).toBe("text/html; charset=utf-8"); }); -test("Get chat with ids", async () => { - const response = await fetchData( - `http://localhost:5000/chat/${userId1}/${userId2}`, - "GET", - {}, - ); +test("Get chat without token", async () => { + const { status } = await getChat(userId1, userId2); + expect(status).toBe("unauthorized"); +}); - const { status, messages } = response; +test("Get chat with ids", async () => { + const { status, messages } = await getChat(userId1, userId2, token); expect(status).toBe("ok"); expect(messages).toBeDefined(); From 28dfced686bae9dd47ab16a5986461da989a1054 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 11:51:23 +0200 Subject: [PATCH 14/26] Remove unused meeting endpoints --- backend/src/routes/usersRoute.ts | 49 ------------------------- backend/test/userMeetings.test.ts | 59 ------------------------------- 2 files changed, 108 deletions(-) delete mode 100644 backend/test/userMeetings.test.ts diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index a5af236..c6d30b9 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -136,55 +136,6 @@ usersRouter.get("/:userId", async (req: Request, res: UserErrorResponse) => { } }); -usersRouter.get("/meetings/:userId", async (req: Request, res) => { - try { - const session = driver.session(); - const userId = req.params.userId; - - const user = await getDbUser(session, { id: userId }); - if (!user) { - await session.close(); - return res; - } - - const meetingsRequest = await session.run( - `MATCH (u1:User {id: $userId})-[m:MEETING]-(u2:User) RETURN m, u2`, - { userId }, - ); - await session.close(); - try { - const meetings = meetingsRequest.records.map((meeting) => { - const { meetingId, waiting } = meeting.get(0).properties; - const { id, first_name, last_name } = meeting.get(1).properties; - return { meetingId, id, first_name, last_name, waiting }; - }); - return res.json({ status: "ok", meetings }); - } catch (_err) { - return res.json({ status: "ok", meetings: [] }); - } - } catch (err) { - console.log("Error:", err); - return res.status(404).json({ status: "error", errors: err as object }); - } -}); - -usersRouter.put("/meetings/:meetingId", async (req: Request, res) => { - try { - const session = driver.session(); - const meetingId = req.params.meetingId; - - await session.run( - `MATCH (u1:User)-[m:MEETING]-(u2:User) WHERE m.meetingId=$meetingId SET m.waiting = true`, - { meetingId }, - ); - await session.close(); - return res.json({ status: "ok" }); - } catch (err) { - console.log("Error:", err); - return res.status(404).json({ status: "error", errors: err as object }); - } -}); - usersRouter.post("/", async (req: Request, res: UserErrorResponse) => { const userParse = registerUserSchema.safeParse(req.body); if (!userParse.success) { diff --git a/backend/test/userMeetings.test.ts b/backend/test/userMeetings.test.ts deleted file mode 100644 index 88531a1..0000000 --- a/backend/test/userMeetings.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { expect, test } from "vitest"; -import { fetchData } from "../src/misc/fetchData.js"; -import User from "../src/models/User.js"; - -let userId: string = ""; - -const getFirstUser = async () => { - const response = await fetchData(`http://localhost:5000/users`, "GET", {}); - userId = response.users.find( - (user: User) => user.mail === "bconford2@wikimedia.org", - ).id; -}; - -await getFirstUser(); - -test("Get users meetings", async () => { - const response = await fetchData( - `http://localhost:5000/users/meetings/${userId}`, - "GET", - {}, - ); - - const { status, meetings } = response; - - expect(status).toBe("ok"); - expect(meetings).toBeDefined(); - expect(meetings.length).toBe(0); -}); - -test("Get users meetings without id", async () => { - const response = await fetchData( - `http://localhost:5000/users/meetings`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); -}); - -test("Update meeting", async () => { - const response = await fetchData( - `http://localhost:5000/users/meetings/0`, - "PUT", - { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({}), - }, - ); - - const { status } = response; - - expect(status).toBe("ok"); -}); From b630f275a45cf74a1ea9962ed67f9fc888983869 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 12:12:29 +0200 Subject: [PATCH 15/26] Add token parameter to frontend fetchData --- frontend/src/services/data.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/services/data.ts b/frontend/src/services/data.ts index 113b147..bafd292 100644 --- a/frontend/src/services/data.ts +++ b/frontend/src/services/data.ts @@ -1,7 +1,19 @@ class DataService { private url: string = "http://localhost:5000"; - async fetchData(endpoint: string, method: string, options = {}) { + async fetchData( + endpoint: string, + method: string, + options: RequestInit = {}, + token?: string, + ) { + if (token) { + options.headers = { + ...options.headers, + "Authorization": `Bearer ${token}`, + }; + } + try { const response = await fetch(this.url + endpoint, { ...options, method }); const data = await response.json(); From 32f42294ae42ca67f02a6dcc551ec39e4735f78b Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 12:44:55 +0200 Subject: [PATCH 16/26] Fix frontend authorization --- frontend/src/components/ChatBox.tsx | 5 ++++- frontend/src/components/FoundUser.tsx | 4 ++++ frontend/src/helpers/KeycloakUserProvider.tsx | 4 ++-- frontend/src/helpers/RestUserProvider.tsx | 20 +++++++++++-------- frontend/src/pages/FriendsPage.tsx | 8 +++++--- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 15f3a4e..6779628 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -6,6 +6,7 @@ import User from "../models/User"; import notificationSoundUrl from "../assets/notification.mp3"; import Message, { MessageProps } from "./Message"; import dataService from "../services/data"; +import { useUser } from "../helpers/UserContext"; const notificationSound = new Audio(notificationSoundUrl); @@ -16,6 +17,8 @@ interface ChatBoxProps { } function ChatBox({ user, socket, friendId }: ChatBoxProps) { + const { token } = useUser(); + const messages = useRef([]); const handleScroll = (ref: HTMLDivElement | null) => { @@ -116,7 +119,7 @@ function ChatBox({ user, socket, friendId }: ChatBoxProps) { const messageResponse = await dataService.fetchData( `/chat/${user.id}/${friendId}`, "GET", - {}, + {}, token ); await addMessages(messageResponse.messages); diff --git a/frontend/src/components/FoundUser.tsx b/frontend/src/components/FoundUser.tsx index 2206027..629d5b8 100644 --- a/frontend/src/components/FoundUser.tsx +++ b/frontend/src/components/FoundUser.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import User from "../models/User"; import dataService from "../services/data"; import countriesData from "../assets/countries.json"; +import { useUser } from "../helpers/UserContext"; interface FoundUserProps { user: User; @@ -13,6 +14,7 @@ interface FoundUserProps { function FoundUser(props: FoundUserProps) { const [requestSent, setRequestSent] = useState(false); const { user, isFriend } = props; + const { token } = useUser(); const countryName = countriesData.find((v) => v.Code == user.country) ?.Country; @@ -44,6 +46,8 @@ function FoundUser(props: FoundUserProps) { await dataService.fetchData( `/users/${props.currentId}/send-friend-request/${props.user.id}`, "POST", + {}, + token, ); setRequestSent(true); } catch (error) { diff --git a/frontend/src/helpers/KeycloakUserProvider.tsx b/frontend/src/helpers/KeycloakUserProvider.tsx index b85e4d5..b552c26 100644 --- a/frontend/src/helpers/KeycloakUserProvider.tsx +++ b/frontend/src/helpers/KeycloakUserProvider.tsx @@ -202,7 +202,7 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { const response = await dataService.fetchData(`/users/${user.id}`, "PUT", { headers: { "Content-Type": "application/json" }, body: JSON.stringify(user), - }); + }, token); if (response.status === "ok") { setUserLoggedIn(user); @@ -217,7 +217,7 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return true; const user = userState.user!; - const response = await dataService.fetchData(`/users/${user.id}`, "DELETE"); + const response = await dataService.fetchData(`/users/${user.id}`, "DELETE", {}, token); if (response.status === "ok") { setUserAnonymous(); diff --git a/frontend/src/helpers/RestUserProvider.tsx b/frontend/src/helpers/RestUserProvider.tsx index 6900095..151a0fb 100644 --- a/frontend/src/helpers/RestUserProvider.tsx +++ b/frontend/src/helpers/RestUserProvider.tsx @@ -17,7 +17,9 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { () => (userState.status == "logged_in" ? userState.user : null), [userState], ); - const [token, setToken] = useState(null); + const [decodedToken, setDecodedToken] = useState(); + const [token, setToken] = useState(); + const [socket, setSocket] = useState(null); const [friends, setFriends] = useState>({}); @@ -48,7 +50,8 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { const decodedToken = decodeToken(tokenStr); if (decodedToken && !isExpired(tokenStr)) { - setToken(decodedToken); + setToken(token) + setDecodedToken(decodedToken); return true; } } @@ -103,7 +106,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { } sessionStorage.setItem("token", response.token); - setToken(decodeToken(response.token)); + setDecodedToken(decodeToken(response.token) ?? undefined); }; const logout = async () => { @@ -154,7 +157,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { const response = await dataService.fetchData(`/users/${user.id}`, "PUT", { headers: { "Content-Type": "application/json" }, body: JSON.stringify(user), - }); + }, token); if (response.status === "ok") { return true; @@ -168,7 +171,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return true; const user = userState.user!; - const response = await dataService.fetchData(`/users/${user.id}`, "DELETE"); + const response = await dataService.fetchData(`/users/${user.id}`, "DELETE", {}, token); if (response.status === "ok") { setUserAnonymous(); @@ -227,8 +230,8 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { useEffect(() => { const handleToken = async () => { - if (token) { - const newUserId = (token as any).userId; + if (decodedToken) { + const newUserId = (decodedToken as any).userId; const response = await dataService.fetchData( `/users/${newUserId}`, "GET", @@ -242,7 +245,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { }; handleToken(); - }, [token]); + }, [decodedToken]); if (firstRefresh.current) { firstRefresh.current = false; @@ -259,6 +262,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { provider, user, userState, + token, socket, redirectToLogin, login, diff --git a/frontend/src/pages/FriendsPage.tsx b/frontend/src/pages/FriendsPage.tsx index a270db6..ebac202 100644 --- a/frontend/src/pages/FriendsPage.tsx +++ b/frontend/src/pages/FriendsPage.tsx @@ -15,10 +15,12 @@ import { useProtected } from "../helpers/Protected"; import FoundUser from "../components/FoundUser"; import Friend from "../components/Friend"; import PaginatorV2 from "../components/PaginatorV2"; +import { useUser } from "../helpers/UserContext"; function FriendsPage() { const navigate = useNavigate(); const { user } = useProtected(); + const { token } = useUser(); const { meeting, createMeeting, joinMeeting } = useMeeting(); const [friendsRequests, setFriendsRequests] = useState([]); @@ -53,7 +55,7 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/accept-friend-request/${currentId}`, - "POST", + "POST", {}, token ); setRefresh(() => !refresh); @@ -64,7 +66,7 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/decline-friend-request/${friend.id}`, - "POST", + "POST", {}, token ); setRefresh(() => !refresh); @@ -75,7 +77,7 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/delete-friend/${friend.id}`, - "DELETE", + "DELETE", {}, token ); setRefresh(() => !refresh); From 454873756de493c6f8d13bd303142efa1eba74f0 Mon Sep 17 00:00:00 2001 From: Piotr Maszczak Date: Wed, 15 May 2024 13:32:44 +0200 Subject: [PATCH 17/26] Refactor tests for more readability --- backend/test/chat.test.ts | 35 +- backend/test/userCRUD.test.ts | 334 ++++++++-------- backend/test/userFriendRequests.test.ts | 330 ++++++++-------- backend/test/userFriendSuggestions.test.ts | 230 +++++------ backend/test/userFriends.test.ts | 256 ++++++------ backend/test/userSearch.test.ts | 434 +++++++++++---------- 6 files changed, 836 insertions(+), 783 deletions(-) diff --git a/backend/test/chat.test.ts b/backend/test/chat.test.ts index d9b829c..1911dae 100644 --- a/backend/test/chat.test.ts +++ b/backend/test/chat.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -9,7 +9,6 @@ const userMail1 = "bconford2@wikimedia.org"; const userPassword1 = "heuristic"; const userMail2 = "cruckman3@archive.org"; -const userPassword2 = "coreCar0l;"; const getUsers = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); @@ -57,23 +56,25 @@ const getChat = async (userId1: string, userId2: string, token?: string) => { return response; }; -test("Get chat without ids", async () => { - const response = await fetch(`http://localhost:5000/chat`); - const data = response.headers.get("content-type"); +describe("Get chat messages", () => { + test("without IDs", async () => { + const response = await fetch(`http://localhost:5000/chat`); + const data = response.headers.get("content-type"); - expect(response.status).toBe(404); - expect(data).toBe("text/html; charset=utf-8"); -}); + expect(response.status).toBe(404); + expect(data).toBe("text/html; charset=utf-8"); + }); -test("Get chat without token", async () => { - const { status } = await getChat(userId1, userId2); - expect(status).toBe("unauthorized"); -}); + test("without token", async () => { + const { status } = await getChat(userId1, userId2); + expect(status).toBe("unauthorized"); + }); -test("Get chat with ids", async () => { - const { status, messages } = await getChat(userId1, userId2, token); + test("correct", async () => { + const { status, messages } = await getChat(userId1, userId2, token); - expect(status).toBe("ok"); - expect(messages).toBeDefined(); - expect(messages.length).toBe(0); + expect(status).toBe("ok"); + expect(messages).toBeDefined(); + expect(messages.length).toBe(0); + }); }); diff --git a/backend/test/userCRUD.test.ts b/backend/test/userCRUD.test.ts index e55f386..8812719 100644 --- a/backend/test/userCRUD.test.ts +++ b/backend/test/userCRUD.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; let userId: string; @@ -75,210 +75,232 @@ const deleteUser = async (userId: string, token?: string) => { return response; }; -test("Get all users", async () => { - const response = await fetchData(`http://localhost:5000/users`, "GET", {}); - const { status, users } = response; +describe("Create user", () => { + test("correct", async () => { + const response = await fetchData(`http://localhost:5000/users`, "POST", { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); - expect(status).toBe("ok"); - expect(users.length).toBe(27); -}); + const { status, user } = response; -test("Create user", async () => { - const response = await fetchData(`http://localhost:5000/users`, "POST", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), - }); + expect(status).toBe("ok"); + expect(user.first_name).toBe(userData.first_name); + expect(user.last_name).toBe(userData.last_name); + expect(user.mail).toBe(userData.mail); - const { status, user } = response; + userId = user.id; + await login(userData.mail, userData.password); + }); - expect(status).toBe("ok"); - expect(user.first_name).toBe(userData.first_name); - expect(user.last_name).toBe(userData.last_name); - expect(user.mail).toBe(userData.mail); + test("existing mail", async () => { + userData.mail = "shudghton1@geocities.com"; - userId = user.id; - await login(userData.mail, userData.password); -}); + const response = await fetchData(`http://localhost:5000/users`, "POST", { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); -test("Create user with existing mail", async () => { - userData.mail = "shudghton1@geocities.com"; + const { status, errors } = response; - const response = await fetchData(`http://localhost:5000/users`, "POST", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), + expect(status).toBe("error"); + expect(errors.id).toBe("already exists"); }); - const { status, errors } = response; + test("too short first name", async () => { + userData.first_name = "j"; - expect(status).toBe("error"); - expect(errors.id).toBe("already exists"); -}); + const response = await fetchData(`http://localhost:5000/users`, "POST", { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); -test("Create user with short first name", async () => { - userData.first_name = "j"; + const { status, errors } = response; - const response = await fetchData(`http://localhost:5000/users`, "POST", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), + expect(status).toBe("error"); + expect(errors.first_name).toBe( + "String must contain at least 2 character(s)", + ); }); - const { status, errors } = response; + test("too short last name", async () => { + userData.last_name = "s"; - expect(status).toBe("error"); - expect(errors.first_name).toBe("String must contain at least 2 character(s)"); -}); + const response = await fetchData(`http://localhost:5000/users`, "POST", { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); -test("Create user with short last name", async () => { - userData.last_name = "s"; + const { status, errors } = response; - const response = await fetchData(`http://localhost:5000/users`, "POST", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), + expect(status).toBe("error"); + expect(errors.last_name).toBe( + "String must contain at least 2 character(s)", + ); }); - const { status, errors } = response; + test("too short password", async () => { + userData.password = "1234"; - expect(status).toBe("error"); - expect(errors.last_name).toBe("String must contain at least 2 character(s)"); -}); + const response = await fetchData(`http://localhost:5000/users`, "POST", { + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(userData), + }); -test("Create user with short password", async () => { - userData.password = "1234"; + const { status, errors } = response; - const response = await fetchData(`http://localhost:5000/users`, "POST", { - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userData), + expect(status).toBe("error"); + expect(errors.password).toBe("String must contain at least 8 character(s)"); }); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.password).toBe("String must contain at least 8 character(s)"); }); -test("Get user by ID", async () => { - userData.mail = "john_smith@example.com"; - userData.first_name = "John"; - userData.last_name = "Smith"; - userData.password = "12345678"; +describe("Get user", () => { + test("incorrect ID", async () => { + const response = await fetchData( + `http://localhost:5000/users/🐈`, + "GET", + {}, + ); - const response = await fetchData( - `http://localhost:5000/users/${userId}`, - "GET", - {}, - ); - const { status, user } = response; + const { status, errors } = response; - expect(status).toBe("ok"); - expect(user.id).toBe(userId); - expect(user.first_name).toBe(userData.first_name); - expect(user.last_name).toBe(userData.last_name); - expect(user.mail).toBe(userData.mail); -}); + expect(status).toBe("error"); + expect(errors.id).toBe("not found"); + }); -test("Get user with incorrect ID", async () => { - const response = await fetchData(`http://localhost:5000/users/🐈`, "GET", {}); + test("all users", async () => { + const response = await fetchData(`http://localhost:5000/users`, "GET", {}); + const { status, users } = response; - const { status, errors } = response; + expect(status).toBe("ok"); + expect(users.length).toBe(28); + }); - expect(status).toBe("error"); - expect(errors.id).toBe("not found"); + test("correct", async () => { + userData.mail = "john_smith@example.com"; + userData.first_name = "John"; + userData.last_name = "Smith"; + userData.password = "12345678"; + + const response = await fetchData( + `http://localhost:5000/users/${userId}`, + "GET", + {}, + ); + const { status, user } = response; + + expect(status).toBe("ok"); + expect(user.id).toBe(userId); + expect(user.first_name).toBe(userData.first_name); + expect(user.last_name).toBe(userData.last_name); + expect(user.mail).toBe(userData.mail); + }); }); -test("Update user without token", async () => { - const { status } = await updateUser(userId); - expect(status).toBe("unauthorized"); -}); +describe("Update user", () => { + test("without token", async () => { + const { status } = await updateUser(userId); + expect(status).toBe("unauthorized"); + }); -test("Update user by ID", async () => { - userData.profile_picture = "https://example.com/new_john_smith.jpg"; + test("incorrect ID", async () => { + const { status, errors } = await updateUser("0", token); - const { status } = await updateUser(userId, token); - expect(status).toBe("ok"); -}); + expect(status).toBe("error"); + expect(errors.id).toBe("not found"); + }); -test("Update user with incorrect ID", async () => { - const { status, errors } = await updateUser("0", token); + test("correct", async () => { + userData.profile_picture = "https://example.com/new_john_smith.jpg"; - expect(status).toBe("error"); - expect(errors.id).toBe("not found"); -}); + const { status } = await updateUser(userId, token); + expect(status).toBe("ok"); + }); -test("Update user with short first name", async () => { - userData.first_name = "j"; + test("too short first name", async () => { + userData.first_name = "j"; - const { status, errors } = await updateUser(userId, token); + const { status, errors } = await updateUser(userId, token); - expect(status).toBe("error"); - expect(errors.first_name).toBe("String must contain at least 2 character(s)"); -}); + expect(status).toBe("error"); + expect(errors.first_name).toBe( + "String must contain at least 2 character(s)", + ); + }); -test("Update user with short last name", async () => { - userData.last_name = "s"; + test("too short last name", async () => { + userData.last_name = "s"; - const { status, errors } = await updateUser(userId, token); + const { status, errors } = await updateUser(userId, token); - expect(status).toBe("error"); - expect(errors.last_name).toBe("String must contain at least 2 character(s)"); + expect(status).toBe("error"); + expect(errors.last_name).toBe( + "String must contain at least 2 character(s)", + ); + }); }); -test("Update user with short password", async () => { - const { status, errors } = await changePassword( - userId, - "1234", - "1234", - token, - ); - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.old_password).toBe("incorrect"); -}); +describe("Change password", () => { + test("too short", async () => { + const { status, errors } = await changePassword( + userId, + "1234", + "1234", + token, + ); + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.old_password).toBe("incorrect"); + }); -test("Update user with same password", async () => { - const { status } = await changePassword( - userId, - "12345678", - "12345678", - token, - ); - expect(status).toBe("ok"); -}); + test("identical", async () => { + const { status } = await changePassword( + userId, + "12345678", + "12345678", + token, + ); + expect(status).toBe("ok"); + }); -test("Update user with correct password", async () => { - const { status } = await changePassword( - userId, - "123456789", - "123456789", - token, - ); - expect(status).toBe("ok"); + test("correct", async () => { + const { status } = await changePassword( + userId, + "123456789", + "123456789", + token, + ); + expect(status).toBe("ok"); + }); }); -test("Delete user without token", async () => { - const { status } = await deleteUser(userId); - expect(status).toBe("unauthorized"); -}); +describe("Delete user", () => { + test("without token", async () => { + const { status } = await deleteUser(userId); + expect(status).toBe("unauthorized"); + }); -test("Delete user by ID", async () => { - const { status } = await deleteUser(userId, token); - expect(status).toBe("ok"); -}); + test("incorrect ID", async () => { + const response = await deleteUser("🐍", token); + const { status, errors } = response; -test("Delete user with incorrect ID", async () => { - const response = await deleteUser("🐍", token); - const { status, errors } = response; + expect(status).toBe("error"); + expect(errors.id).toBe("not found"); + }); - expect(status).toBe("error"); - expect(errors.id).toBe("not found"); + test("correct", async () => { + const { status } = await deleteUser(userId, token); + expect(status).toBe("ok"); + }); }); diff --git a/backend/test/userFriendRequests.test.ts b/backend/test/userFriendRequests.test.ts index 7a1c1d2..d0e7a63 100644 --- a/backend/test/userFriendRequests.test.ts +++ b/backend/test/userFriendRequests.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -125,208 +125,224 @@ const deleteFriend = async ( return response; }; -test("Check current requests", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); +describe("Get current requests", () => { + test("correct", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); - const { status, pageCount, friendRequests } = response; + const { status, pageCount, friendRequests } = response; - expect(status).toBe("ok"); - expect(pageCount).toBe(0); - expect(friendRequests.length).toBe(0); -}); + expect(status).toBe("ok"); + expect(pageCount).toBe(0); + expect(friendRequests.length).toBe(0); + }); -test("Check current requests with incorrect ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/0/friend-requests?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); + test("incorrect ID", async () => { + const response = await fetchData( + `http://localhost:5000/users/0/friend-requests?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); - const { status, errors } = response; + const { status, errors } = response; - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.id).toBe("not found"); + }); }); -test("Missing page", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests?maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; +describe("Pagination parameters", () => { + test("missing page", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests?maxUsers=${maxUsers}`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.page).toBe("Invalid input"); -}); + const { status, errors } = response; -test("Missing maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests?page=${page}`, - "GET", - {}, - ); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.page).toBe("Invalid input"); + }); - const { status, errors } = response; + test("missing maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests?page=${page}`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Invalid input"); -}); + const { status, errors } = response; -test("Missing page and maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests`, - "GET", - {}, - ); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Invalid input"); + }); - const { status, errors } = response; + test("missing page and maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); - expect(errors.maxUsers).toBe("Invalid input"); -}); + const { status, errors } = response; -test("maxUsers as a text", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests?page=text?page=${page}&maxUsers=text`, - "GET", - {}, - ); + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + expect(errors.maxUsers).toBe("Invalid input"); + }); - const { status, errors } = response; + test("maxUsers as a text", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests?page=text?page=${page}&maxUsers=text`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Expected number, received nan"); -}); + const { status, errors } = response; -test("maxUsers equals 0", async () => { - maxUsers = 0; - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-requests?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Expected number, received nan"); + }); - const { status, errors } = response; + test("maxUsers equals 0", async () => { + maxUsers = 0; + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-requests?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); -}); + const { status, errors } = response; -test("Send friend request without token", async () => { - const { status } = await sendFriendRequest(userId, userId2); - expect(status).toBe("unauthorized"); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); + }); }); -test("Send friend request", async () => { - const { status } = await sendFriendRequest(userId, userId2, token1); - expect(status).toBe("ok"); -}); +describe("Send friend request", () => { + test("without token", async () => { + const { status } = await sendFriendRequest(userId, userId2); + expect(status).toBe("unauthorized"); + }); -test("Send friend request with incorrect id", async () => { - const { status, errors } = await sendFriendRequest(userId, "0", token1); + test("correct", async () => { + const { status } = await sendFriendRequest(userId, userId2, token1); + expect(status).toBe("ok"); + }); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId2).toBe("not found"); -}); + test("incorrect id", async () => { + const { status, errors } = await sendFriendRequest(userId, "0", token1); -test("Decline friend request without token", async () => { - const { status } = await declineFriendRequest(userId2, userId); - expect(status).toBe("unauthorized"); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId2).toBe("not found"); + }); }); -test("Decline friend request", async () => { - const { status } = await declineFriendRequest(userId2, userId, token2); - expect(status).toBe("ok"); -}); +describe("Decline friend request", () => { + test("without token", async () => { + const { status } = await declineFriendRequest(userId2, userId); + expect(status).toBe("unauthorized"); + }); -test("Decline friend request with incorrect id", async () => { - const { status, errors } = await declineFriendRequest(userId, "0", token2); + test("correct", async () => { + const { status } = await declineFriendRequest(userId2, userId, token2); + expect(status).toBe("ok"); + }); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId2).toBe("not found"); -}); + test("incorrect id", async () => { + const { status, errors } = await declineFriendRequest(userId, "0", token2); -test("Accept friend request without token", async () => { - const { status } = await acceptFriendRequest(userId2, userId); - expect(status).toBe("unauthorized"); -}); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId2).toBe("not found"); + }); -test("Accept friend request when not invited", async () => { - const { status, errors } = await acceptFriendRequest(userId2, userId, token2); + test("not invited", async () => { + const { status, errors } = await declineFriendRequest( + userId2, + userId, + token2, + ); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId1).toBe("not invited"); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId1).toBe("not invited"); + }); }); -test("Accept friend request", async () => { - await sendFriendRequest(userId, userId2, token1); +describe("Accept friend request", () => { + test("without token", async () => { + const { status } = await acceptFriendRequest(userId2, userId); + expect(status).toBe("unauthorized"); + }); - const { status } = await acceptFriendRequest(userId2, userId, token2); - expect(status).toBe("ok"); + test("incorrect id", async () => { + const response = await acceptFriendRequest(userId, "0", token1); + const { status, errors } = response; - const friendsResponse = await fetchData( - `http://localhost:5000/users/${userId2}/friends?page=1&maxUsers=10`, - "GET", - {}, - ); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId2).toBe("not found"); + }); - const friend = friendsResponse.friends.find( - (user: User) => user.id == userId, - ); + test("not invited", async () => { + const { status, errors } = await acceptFriendRequest( + userId2, + userId, + token2, + ); - expect(friend).toBeDefined(); -}); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId1).toBe("not invited"); + }); -test("Accept friend request with incorrect id", async () => { - const response = await acceptFriendRequest(userId, "0", token1); - const { status, errors } = response; + test("correct", async () => { + await sendFriendRequest(userId, userId2, token1); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId2).toBe("not found"); -}); + const { status } = await acceptFriendRequest(userId2, userId, token2); + expect(status).toBe("ok"); -test("Decline friend request when not invited", async () => { - const { status, errors } = await declineFriendRequest( - userId2, - userId, - token2, - ); + const friendsResponse = await fetchData( + `http://localhost:5000/users/${userId2}/friends?page=1&maxUsers=10`, + "GET", + {}, + ); - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId1).toBe("not invited"); -}); + const friend = friendsResponse.friends.find( + (user: User) => user.id == userId, + ); -test("Delete friend without token", async () => { - const { status } = await deleteFriend(userId2, userId); - expect(status).toBe("unauthorized"); + expect(friend).toBeDefined(); + }); }); -test("Delete friend", async () => { - const { status } = await deleteFriend(userId2, userId, token2); - expect(status).toBe("ok"); -}); +describe("Delete friend", () => { + test("without token", async () => { + const { status } = await deleteFriend(userId2, userId); + expect(status).toBe("unauthorized"); + }); -test("Delete friend with incorrect id", async () => { - const response = await deleteFriend(userId, "0", token1); - const { status, errors } = response; + test("incorrect id", async () => { + const response = await deleteFriend(userId, "0", token1); + const { status, errors } = response; - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.userId2).toBe("not found"); + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.userId2).toBe("not found"); + }); + + test("correct", async () => { + const { status } = await deleteFriend(userId2, userId, token2); + expect(status).toBe("ok"); + }); }); diff --git a/backend/test/userFriendSuggestions.test.ts b/backend/test/userFriendSuggestions.test.ts index 3053b5d..ffa9370 100644 --- a/backend/test/userFriendSuggestions.test.ts +++ b/backend/test/userFriendSuggestions.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -15,118 +15,122 @@ const getFirstUser = async () => { await getFirstUser(); -test("Get friend suggestions", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, pageCount, friendSuggestions } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(3); - expect(friendSuggestions).toBeDefined(); - expect(friendSuggestions.length).toBe(3); -}); - -test("Get friend suggestions with incorrect ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/0/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); -}); - -test("Missing page", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.page).toBe("Invalid input"); -}); - -test("Missing maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Invalid input"); +describe("Get friend suggestions", () => { + test("incorrect ID", async () => { + const response = await fetchData( + `http://localhost:5000/users/0/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.id).toBe("not found"); + }); + + test("correct", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, pageCount, friendSuggestions } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(3); + expect(friendSuggestions).toBeDefined(); + expect(friendSuggestions.length).toBe(3); + }); + + test("first user", async () => { + page = 1; + maxUsers = 1; + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, pageCount, friendSuggestions } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(13); + expect(friendSuggestions).toBeDefined(); + expect(friendSuggestions.length).toBe(1); + }); }); -test("Missing page and maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("maxUsers as a text", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?page=text?page=${page}&maxUsers=text`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Expected number, received nan"); -}); - -test("First user", async () => { - page = 1; - maxUsers = 1; - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, pageCount, friendSuggestions } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(13); - expect(friendSuggestions).toBeDefined(); - expect(friendSuggestions.length).toBe(1); -}); - -test("maxUsers equals 0", async () => { - maxUsers = 0; - const response = await fetchData( - `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); +describe("Pagination parameters", () => { + test("missing page", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.page).toBe("Invalid input"); + }); + + test("missing maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("missing page and maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("maxUsers as a text", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?page=text?page=${page}&maxUsers=text`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Expected number, received nan"); + }); + + test("maxUsers equals 0", async () => { + maxUsers = 0; + const response = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); + }); }); diff --git a/backend/test/userFriends.test.ts b/backend/test/userFriends.test.ts index 1f7f1e0..17653c4 100644 --- a/backend/test/userFriends.test.ts +++ b/backend/test/userFriends.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -15,131 +15,135 @@ const getFirstUser = async () => { await getFirstUser(); -test("Get friends", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, pageCount, friends } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(1); - expect(friends.length).toBe(9); -}); - -test("Get friends with incorrect ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); -}); - -test("Missing page", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); -}); - -test("Page as a text", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=text?maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Expected number, received nan"); -}); - -test("Missing maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=${page}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("maxUsers as a text", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=text?page=${page}&maxUsers=text`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Expected number, received nan"); +describe("Get friends", () => { + test("incorrect ID", async () => { + const response = await fetchData( + `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.id).toBe("not found"); + }); + + test("correct", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, pageCount, friends } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(1); + expect(friends.length).toBe(9); + }); + + test("first user", async () => { + page = 1; + maxUsers = 1; + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, pageCount, friends } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(9); + expect(friends.length).toBe(1); + expect(friends[0].country).toBe("CN"); + expect(friends[0].mail).toBe("tshillitoe@state.gov"); + expect(friends[0].first_name).toBe("Trever"); + expect(friends[0].last_name).toBe("Shillito"); + }); }); -test("Missing page and maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("First user", async () => { - page = 1; - maxUsers = 1; - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, pageCount, friends } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(9); - expect(friends.length).toBe(1); - expect(friends[0].country).toBe("CN"); - expect(friends[0].mail).toBe("tshillitoe@state.gov"); - expect(friends[0].first_name).toBe("Trever"); - expect(friends[0].last_name).toBe("Shillito"); -}); - -test("maxUsers equals 0", async () => { - maxUsers = 0; - const response = await fetchData( - `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); +describe("Pagination parameters", () => { + test("missing page", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + }); + + test("page as a text", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=text?maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Expected number, received nan"); + }); + + test("missing maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=${page}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("maxUsers as a text", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=text?page=${page}&maxUsers=text`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Expected number, received nan"); + }); + + test("missing page and maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("maxUsers equals 0", async () => { + maxUsers = 0; + const response = await fetchData( + `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Number must be greater than or equal to 1"); + }); }); diff --git a/backend/test/userSearch.test.ts b/backend/test/userSearch.test.ts index 2fcca02..d71f060 100644 --- a/backend/test/userSearch.test.ts +++ b/backend/test/userSearch.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -7,221 +7,227 @@ let maxUsers: number = 32; let query: string = "a"; let country: string = "PL"; -test("Search all users from Poland", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, - "GET", - {}, - ); - - const { status, pageCount, users } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(1); - expect(users).toBeDefined(); - expect(users.length).toBe(4); -}); - -test("Missing country", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&q=`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.country).toBe("Invalid input"); -}); - -test("Missing page", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?maxUsers=${maxUsers}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); -}); - -test("Missing maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("Missing page and maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/search`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.page).toBe("Invalid input"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("maxUsers as a text", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=text?page=${page}&maxUsers=text`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.maxUsers).toBe("Expected number, received nan"); -}); - -test("First user from Brazil", async () => { - country = "BR"; - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, - "GET", - {}, - ); - - const { status, pageCount, users } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(1); - expect(users).toBeDefined(); - expect(users.length).toBe(1); - expect(users[0].country).toBe("BR"); -}); - -test("Not found users", async () => { - country = "GR"; - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, - "GET", - {}, - ); - - const { status, pageCount, users } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(1); - expect(users).toBeDefined(); - expect(users.length).toBe(1); -}); - -test("Search with polish characters", async () => { - query = "Małysz"; - country = "PL"; - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=${query}`, - "GET", - {}, - ); - - const { status, pageCount, users } = response; - - expect(status).toBe("ok"); - expect(pageCount).toBe(1); - expect(users).toBeDefined(); - expect(users.length).toBe(4); - - const malysz = users.find((user: User) => user.mail == "adasko@malysz.pl"); - - expect(malysz).toBeDefined(); +describe("Search users", () => { + test("all users from Poland", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, + "GET", + {}, + ); + + const { status, pageCount, users } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(1); + expect(users).toBeDefined(); + expect(users.length).toBe(4); + }); + + test("first user from Brazil", async () => { + country = "BR"; + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, + "GET", + {}, + ); + + const { status, pageCount, users } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(1); + expect(users).toBeDefined(); + expect(users.length).toBe(1); + expect(users[0].country).toBe("BR"); + }); + + test("with polish characters", async () => { + query = "Małysz"; + country = "PL"; + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=${query}`, + "GET", + {}, + ); + + const { status, pageCount, users } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(1); + expect(users).toBeDefined(); + expect(users.length).toBe(4); + + const malysz = users.find((user: User) => user.mail == "adasko@malysz.pl"); + + expect(malysz).toBeDefined(); + }); + + test("not found", async () => { + country = "GR"; + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, + "GET", + {}, + ); + + const { status, pageCount, users } = response; + + expect(status).toBe("ok"); + expect(pageCount).toBe(1); + expect(users).toBeDefined(); + expect(users.length).toBe(1); + }); }); -test("Repeated page", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&page=${page}&q=`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.page).toBe("Invalid input"); +describe("Search parameters", () => { + test("missing country", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&q=`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.country).toBe("Invalid input"); + }); + + test("empty query", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, + "GET", + {}, + ); + + const { status, users } = response; + + expect(status).toBe("ok"); + expect(users).toBeDefined(); + }); + + test("missing query", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.q).toBe("Required"); + }); }); -test("Repeated maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&maxUsers=${maxUsers}&q=`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("MaxUsers above 32", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=33&country=${country}&q=`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.maxUsers).toBe("Number must be less than or equal to 32"); -}); - -test("Repeated page and maxUsers", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&page=${page}&maxUsers=${maxUsers}&q=`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.page).toBe("Invalid input"); - expect(errors.maxUsers).toBe("Invalid input"); -}); - -test("Empty query", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}&q=`, - "GET", - {}, - ); - - const { status, users } = response; - - expect(status).toBe("ok"); - expect(users).toBeDefined(); -}); - -test("Missing query", async () => { - const response = await fetchData( - `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&country=${country}`, - "GET", - {}, - ); - - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.q).toBe("Required"); +describe("Pagination parameters", () => { + test("missing page", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + }); + + test("missing maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("missing page and maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/search`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.page).toBe("Invalid input"); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("maxUsers as a text", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=text?page=${page}&maxUsers=text`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors.maxUsers).toBe("Expected number, received nan"); + }); + + test("repeated page", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&page=${page}&q=`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.page).toBe("Invalid input"); + }); + + test("repeated maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&maxUsers=${maxUsers}&q=`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Invalid input"); + }); + + test("maxUsers above 32", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=33&country=${country}&q=`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.maxUsers).toBe("Number must be less than or equal to 32"); + }); + + test("repeated page and maxUsers", async () => { + const response = await fetchData( + `http://localhost:5000/users/search?page=${page}&maxUsers=${maxUsers}&page=${page}&maxUsers=${maxUsers}&q=`, + "GET", + {}, + ); + + const { status, errors } = response; + + expect(status).toBe("error"); + expect(errors).toBeDefined(); + expect(errors.page).toBe("Invalid input"); + expect(errors.maxUsers).toBe("Invalid input"); + }); }); From 165317da9360172b97a6ee6a35ad22370b4817c2 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Wed, 15 May 2024 14:30:26 +0200 Subject: [PATCH 18/26] Add authorization to user friends --- backend/src/routes/userFriendsRoute.ts | 1 + backend/test/userFriends.test.ts | 50 +++++++++++++++++++ frontend/src/helpers/KeycloakUserProvider.tsx | 22 ++++++-- frontend/src/helpers/RestUserProvider.tsx | 24 ++++++--- frontend/src/pages/FriendsPage.tsx | 1 + 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index a9d3a5c..323aa49 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -28,6 +28,7 @@ const friendsRouter = Router(); friendsRouter.get( "/:userId/friends", + authenticateToken, async (req: Request, res: FriendsPageErrorResponse) => { const userId = req.params.userId; diff --git a/backend/test/userFriends.test.ts b/backend/test/userFriends.test.ts index 17653c4..0e736a6 100644 --- a/backend/test/userFriends.test.ts +++ b/backend/test/userFriends.test.ts @@ -6,6 +6,9 @@ let page: number = 1; let maxUsers: number = 10; let userId: string = ""; +const userMail = "bconford2@wikimedia.org"; +const userPassword = "heuristic"; + const getFirstUser = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); userId = response.users.find( @@ -13,14 +16,53 @@ const getFirstUser = async () => { ).id; }; +const getKeycloakToken = async ( + mail: string, + password: string, +): Promise => { + const urlParams = new URLSearchParams({ + grant_type: "password", + client_id: "mercury-testing", + client_secret: "5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag", + username: mail, + password: password, + }); + + const response = await fetchData( + `http://localhost:3000/realms/mercury/protocol/openid-connect/token`, + "POST", + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: urlParams, + }, + ); + + return response.access_token; +}; + await getFirstUser(); +const token = await getKeycloakToken(userMail, userPassword); describe("Get friends", () => { + test("without token", async () => { + const response = await fetchData( + `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + ); + + const { status } = response + expect(status).toBe("unauthorized"); + }); + test("incorrect ID", async () => { const response = await fetchData( `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, errors } = response; @@ -35,6 +77,7 @@ describe("Get friends", () => { `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, pageCount, friends } = response; @@ -51,6 +94,7 @@ describe("Get friends", () => { `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, pageCount, friends } = response; @@ -71,6 +115,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, errors } = response; @@ -84,6 +129,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=text?maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, errors } = response; @@ -97,6 +143,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=${page}`, "GET", {}, + token ); const { status, errors } = response; @@ -110,6 +157,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=text?page=${page}&maxUsers=text`, "GET", {}, + token ); const { status, errors } = response; @@ -123,6 +171,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends`, "GET", {}, + token ); const { status, errors } = response; @@ -138,6 +187,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token ); const { status, errors } = response; diff --git a/frontend/src/helpers/KeycloakUserProvider.tsx b/frontend/src/helpers/KeycloakUserProvider.tsx index b552c26..6701f22 100644 --- a/frontend/src/helpers/KeycloakUserProvider.tsx +++ b/frontend/src/helpers/KeycloakUserProvider.tsx @@ -32,6 +32,8 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { const friendsResponse = await dataService.fetchData( `/users/${userId}/friends?${searchParams}`, "GET", + {}, + token, ); if (friendsResponse.status != "ok") { console.error("Couldn't fetch friends: ", friendsResponse); @@ -199,10 +201,15 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return false; const user = { ...userState.user, ...updateUser }; - const response = await dataService.fetchData(`/users/${user.id}`, "PUT", { - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(user), - }, token); + const response = await dataService.fetchData( + `/users/${user.id}`, + "PUT", + { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(user), + }, + token, + ); if (response.status === "ok") { setUserLoggedIn(user); @@ -217,7 +224,12 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return true; const user = userState.user!; - const response = await dataService.fetchData(`/users/${user.id}`, "DELETE", {}, token); + const response = await dataService.fetchData( + `/users/${user.id}`, + "DELETE", + {}, + token, + ); if (response.status === "ok") { setUserAnonymous(); diff --git a/frontend/src/helpers/RestUserProvider.tsx b/frontend/src/helpers/RestUserProvider.tsx index 151a0fb..8dc5810 100644 --- a/frontend/src/helpers/RestUserProvider.tsx +++ b/frontend/src/helpers/RestUserProvider.tsx @@ -50,7 +50,7 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { const decodedToken = decodeToken(tokenStr); if (decodedToken && !isExpired(tokenStr)) { - setToken(token) + setToken(token); setDecodedToken(decodedToken); return true; } @@ -154,10 +154,15 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return false; const user = { ...userState.user, ...updateUser }; - const response = await dataService.fetchData(`/users/${user.id}`, "PUT", { - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(user), - }, token); + const response = await dataService.fetchData( + `/users/${user.id}`, + "PUT", + { + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(user), + }, + token, + ); if (response.status === "ok") { return true; @@ -171,7 +176,12 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { if (userState.status != "logged_in") return true; const user = userState.user!; - const response = await dataService.fetchData(`/users/${user.id}`, "DELETE", {}, token); + const response = await dataService.fetchData( + `/users/${user.id}`, + "DELETE", + {}, + token, + ); if (response.status === "ok") { setUserAnonymous(); @@ -193,6 +203,8 @@ function RestUserProvider({ children }: { children: React.ReactNode }) { const friendsResponse = await dataService.fetchData( `/users/${userId}/friends?${searchParams}`, "GET", + {}, + token, ); if (friendsResponse.status != "ok") { console.error("Couldn't fetch friends: ", friendsResponse); diff --git a/frontend/src/pages/FriendsPage.tsx b/frontend/src/pages/FriendsPage.tsx index ebac202..7f201bd 100644 --- a/frontend/src/pages/FriendsPage.tsx +++ b/frontend/src/pages/FriendsPage.tsx @@ -44,6 +44,7 @@ function FriendsPage() { `/users/${user.id}/friend-requests?page=1&maxUsers=32`, "GET", {}, + token ); setFriendsRequests(friendsRequestsResponse.friendRequests); } From fa21c2752de480e862feb7a9b62040e9e8a86542 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 13:37:13 +0200 Subject: [PATCH 19/26] Fix endpoints not checking the token user ID --- backend/src/models/Response.ts | 6 ++ backend/src/routes/chatRoute.ts | 78 +++++++++++------ backend/src/routes/userFriendsRoute.ts | 83 ++++++++++++++---- backend/src/routes/usersRoute.ts | 46 +++++++--- backend/src/types/userResponse.ts | 7 +- backend/src/users.ts | 36 ++++++++ backend/test/chat.test.ts | 11 ++- backend/test/userCRUD.test.ts | 86 ++++++++++++++----- backend/test/userFriendRequests.test.ts | 23 ++++- backend/test/userFriends.test.ts | 62 +++++++------ frontend/src/components/PaginatorV2.tsx | 5 +- frontend/src/helpers/KeycloakUserProvider.tsx | 18 ++-- frontend/src/pages/FriendsPage.tsx | 16 +++- 13 files changed, 358 insertions(+), 119 deletions(-) diff --git a/backend/src/models/Response.ts b/backend/src/models/Response.ts index d0f80c3..7c6f2be 100644 --- a/backend/src/models/Response.ts +++ b/backend/src/models/Response.ts @@ -1,3 +1,4 @@ +import Message from "./Message.js"; import User from "./User.js"; import { Response } from "express"; @@ -60,6 +61,11 @@ export interface UsersSearchResponse { users: User[]; } +export interface MessagesResponse { + status: "ok"; + messages: Message[]; +} + export interface JWTResponse extends OkResponse { token: string; } diff --git a/backend/src/routes/chatRoute.ts b/backend/src/routes/chatRoute.ts index 8c70c08..fa6d483 100644 --- a/backend/src/routes/chatRoute.ts +++ b/backend/src/routes/chatRoute.ts @@ -1,36 +1,58 @@ import { Router } from "express"; import Message from "../models/Message.js"; import MessageModel from "../mongoDB/MessageModel.js"; -import { authenticateToken } from "../misc/jwt.js"; +import { JWTRequest, authenticateToken } from "../misc/jwt.js"; +import { AuthMessagesErrorResponse } from "../types/userResponse.js"; +import { Errors } from "../models/Response.js"; +import { getTokenDbUser } from "../users.js"; +import driver from "../driver.js"; +import { userNotFoundRes } from "./usersRoute.js"; const chatRouter = Router(); -chatRouter.get("/:user1Id/:user2Id", authenticateToken, async (req, res) => { - try { - const { user1Id, user2Id } = req.params; - const messageRequest = await MessageModel.find({ - $or: [ - { fromUserId: user1Id, toUserId: user2Id }, - { fromUserId: user2Id, toUserId: user1Id }, - ], - }).sort({ created_date: "asc" }); - - const messages = messageRequest.map((m: Message) => { - const { sentDate, fromUserId, toUserId, content } = m; - return { - type: fromUserId === user1Id ? "sent" : "received", - sentDate, - fromUserId, - toUserId, - content, - }; - }); - - return res.json({ status: "ok", messages }); - } catch (err) { - console.log("Error:", err); - return res.status(404).json({ status: "error", errors: err as object }); - } -}); +chatRouter.get( + "/:user1Id/:user2Id", + authenticateToken, + async (req: JWTRequest, res: AuthMessagesErrorResponse) => { + const session = driver.session(); + try { + const { user1Id, user2Id } = req.params; + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != user1Id) { + return res.status(403).json({ status: "forbidden" }); + } + + const messageRequest = await MessageModel.find({ + $or: [ + { fromUserId: user1Id, toUserId: user2Id }, + { fromUserId: user2Id, toUserId: user1Id }, + ], + }).sort({ created_date: "asc" }); + + const messages = messageRequest.map((m: Message) => { + const { sentDate, fromUserId, toUserId, content } = m; + return { + type: fromUserId === user1Id ? "sent" : "received", + sentDate, + fromUserId, + toUserId, + content, + } as Message; + }); + + return res.json({ status: "ok", messages }); + } catch (err) { + console.log("Error:", err); + return res.status(404).json({ status: "error", errors: err as Errors }); + } finally { + session.close(); + } + }, +); export default chatRouter; diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index 323aa49..c3fc337 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -7,6 +7,7 @@ import { FriendsPageErrorResponse, FriendRequestsPageErrorResponse, FriendSuggestionsPageErrorResponse, + AuthOkErrorResponse, } from "../types/userResponse.js"; import { deleteFriend } from "../userFriends.js"; import { declineFriendRequest } from "../userFriends.js"; @@ -22,27 +23,37 @@ import { userNotFoundRes } from "./usersRoute.js"; import { Errors } from "../models/Response.js"; import Page, { pageSchema } from "../models/routes/Page.js"; import { formatError } from "../misc/formatError.js"; -import { authenticateToken } from "../misc/jwt.js"; +import { JWTRequest, authenticateToken } from "../misc/jwt.js"; +import { getDbUser, getTokenDbUser } from "../users.js"; const friendsRouter = Router(); friendsRouter.get( "/:userId/friends", authenticateToken, - async (req: Request, res: FriendsPageErrorResponse) => { - const userId = req.params.userId; + async (req: JWTRequest, res: FriendsPageErrorResponse) => { + const session = driver.session(); + try { + const userId = req.params.userId; + const user = await getTokenDbUser(session, req.token!); - const pageParse = pageSchema.safeParse(req.query); - if (!pageParse.success) { - const errors = formatError(pageParse.error); - return res.status(400).json({ status: "error", errors }); - } + if (!user) { + return userNotFoundRes(res); + } - const { page, maxUsers }: Page = pageParse.data; - const maxUsersBig = BigInt(maxUsers); + if (user.id != userId) { + return res.status(403).json({ status: "forbidden" }); + } + + const pageParse = pageSchema.safeParse(req.query); + if (!pageParse.success) { + const errors = formatError(pageParse.error); + return res.status(400).json({ status: "error", errors }); + } + + const { page, maxUsers }: Page = pageParse.data; + const maxUsersBig = BigInt(maxUsers); - const session = driver.session(); - try { const friends = await getFriends(session, userId, page - 1, maxUsers); if (friends === null) { console.log(friends); @@ -162,12 +173,22 @@ friendsRouter.get( friendsRouter.post( "/:userId1/send-friend-request/:userId2", authenticateToken, - async (req: Request, res: OkErrorResponse) => { + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; const userId2 = req.params.userId2; try { + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId1) { + return res.status(403).json({ status: "forbidden" }); + } + const requestResult = await sendFriendRequest(session, userId1, userId2); if (!requestResult.success) { const { firstUserExists, secondUserExists } = requestResult; @@ -197,12 +218,22 @@ friendsRouter.post( friendsRouter.post( "/:userId1/accept-friend-request/:userId2", authenticateToken, - async (req: Request, res: OkErrorResponse) => { + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; const userId2 = req.params.userId2; try { + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId1) { + return res.status(403).json({ status: "forbidden" }); + } + const requestResult = await acceptFriendRequest( session, userId1, @@ -249,12 +280,22 @@ friendsRouter.post( friendsRouter.post( "/:userId1/decline-friend-request/:userId2", authenticateToken, - async (req: Request, res: OkErrorResponse) => { + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; const userId2 = req.params.userId2; try { + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId1) { + return res.status(403).json({ status: "forbidden" }); + } + const requestResult = await declineFriendRequest( session, userId1, @@ -297,12 +338,22 @@ friendsRouter.post( friendsRouter.delete( "/:userId1/delete-friend/:userId2", authenticateToken, - async (req: Request, res: OkErrorResponse) => { + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); const userId1 = req.params.userId1; const userId2 = req.params.userId2; try { + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId1) { + return res.status(403).json({ status: "forbidden" }); + } + const requestResult = await deleteFriend(session, userId1, userId2); if (!requestResult.success) { const { firstUserExists, secondUserExists, wasFriend } = requestResult; diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index c6d30b9..0980a84 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -25,6 +25,7 @@ import { RegisterUser, updateUserSchema, UpdateUser, + getTokenDbUser, } from "../users.js"; import DbUser from "../models/DbUser.js"; import { changePasswordReqSchema } from "../models/ChangePasswordReq.js"; @@ -172,18 +173,28 @@ usersRouter.post("/", async (req: Request, res: UserErrorResponse) => { usersRouter.put( "/:userId", authenticateToken, - async (req: Request, res: OkErrorResponse) => { - const userParse = updateUserSchema.safeParse(req.body); - if (!userParse.success) { - const errors = formatError(userParse.error); - return res.status(400).json({ status: "error", errors }); - } - - const parsedUser: UpdateUser = userParse.data; - const userId = req.params.userId; - + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); try { + const userId = req.params.userId; + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId) { + return res.status(403).json({ status: "forbidden" }); + } + + const userParse = updateUserSchema.safeParse(req.body); + if (!userParse.success) { + const errors = formatError(userParse.error); + return res.status(400).json({ status: "error", errors }); + } + + const parsedUser: UpdateUser = userParse.data; + const newUser = await updateUser(session, userId, parsedUser); if (!newUser) { return userNotFoundRes(res); @@ -254,11 +265,20 @@ usersRouter.post( usersRouter.delete( "/:userId", authenticateToken, - async (req: Request, res: OkErrorResponse) => { - const userId = req.params.userId; - + async (req: JWTRequest, res: AuthOkErrorResponse) => { const session = driver.session(); try { + const userId = req.params.userId; + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId) { + return res.status(403).json({ status: "forbidden" }); + } + const isDeleted = await deleteUser(session, userId); if (!isDeleted) { return userNotFoundRes(res); diff --git a/backend/src/types/userResponse.ts b/backend/src/types/userResponse.ts index 155d087..733ec92 100644 --- a/backend/src/types/userResponse.ts +++ b/backend/src/types/userResponse.ts @@ -10,6 +10,7 @@ import { FriendsPageResponse, FriendRequestsPageResponse, FriendSuggestionsPageResponse, + MessagesResponse, } from "../models/Response.js"; export type UsersErrorResponse = CustomResponse; @@ -28,8 +29,12 @@ export type FriendSuggestionsPageErrorResponse = CustomResponse< FriendSuggestionsPageResponse | ErrorResponse >; export type FriendsPageErrorResponse = CustomResponse< - FriendsPageResponse | ErrorResponse + AuthResponse | FriendsPageResponse | ErrorResponse >; export type UsersSearchErrorResponse = CustomResponse< UsersSearchResponse | ErrorResponse >; + +export type AuthMessagesErrorResponse = CustomResponse< + AuthResponse | MessagesResponse | ErrorResponse +>; diff --git a/backend/src/users.ts b/backend/src/users.ts index 9e5674a..a9ae1d5 100644 --- a/backend/src/users.ts +++ b/backend/src/users.ts @@ -14,6 +14,7 @@ import { ZodType } from "zod"; import ChangePasswordReq from "./models/ChangePasswordReq.js"; import jwt from "jsonwebtoken"; import TokenPayload from "./models/TokenPayload.js"; +import { issuers } from "./misc/jwt.js"; export const filterUser = (user: DbUser): User => { if ("password" in user) { @@ -206,6 +207,41 @@ export async function getDbUser( return user; } +type SubProps = { + sub?: string; + props?: Partial; +}; + +function tokenPayloadToSubProps(tokenPayload: TokenPayload): SubProps { + const issuer = tokenPayload.iss; + + if (issuer == issuers.mercury) { + const sub = tokenPayload.sub; + const props = { issuer: "mercury", issuer_id: sub }; + return { sub, props }; + } else if (issuer == issuers.rest) { + const sub = tokenPayload.userId; + const props = { id: sub }; + return { sub, props }; + } + + return {}; +} + +export async function getTokenDbUser( + session: Session, + tokenPayload: TokenPayload, +): Promise { + let { sub, props } = tokenPayloadToSubProps(tokenPayload); + + if (!sub || !props) { + return null; + } + + const user = await getDbUser(session, props); + return user; +} + export async function getAllUsers(session: Session) { const usersRequest = await session.run(`MATCH (u:User) RETURN u`); const users = usersRequest.records.map((r) => diff --git a/backend/test/chat.test.ts b/backend/test/chat.test.ts index 1911dae..ac6b5c3 100644 --- a/backend/test/chat.test.ts +++ b/backend/test/chat.test.ts @@ -9,6 +9,7 @@ const userMail1 = "bconford2@wikimedia.org"; const userPassword1 = "heuristic"; const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; const getUsers = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); @@ -43,7 +44,8 @@ const getKeycloakToken = async ( }; await getUsers(); -const token = await getKeycloakToken(userMail1, userPassword1); +const token1 = await getKeycloakToken(userMail1, userPassword1); +const token2 = await getKeycloakToken(userMail2, userPassword2); const getChat = async (userId1: string, userId2: string, token?: string) => { const response = await fetchData( @@ -70,8 +72,13 @@ describe("Get chat messages", () => { expect(status).toBe("unauthorized"); }); + test("with incorrect token", async () => { + const { status } = await getChat(userId1, userId2, token2); + expect(status).toBe("forbidden"); + }); + test("correct", async () => { - const { status, messages } = await getChat(userId1, userId2, token); + const { status, messages } = await getChat(userId1, userId2, token1); expect(status).toBe("ok"); expect(messages).toBeDefined(); diff --git a/backend/test/userCRUD.test.ts b/backend/test/userCRUD.test.ts index 8812719..7e4881b 100644 --- a/backend/test/userCRUD.test.ts +++ b/backend/test/userCRUD.test.ts @@ -10,19 +10,51 @@ let userData = { mail: "john_smith@example.com", password: "12345678", }; -let token: string; + +const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; + +const getKeycloakToken = async ( + mail: string, + password: string, +): Promise => { + const urlParams = new URLSearchParams({ + grant_type: "password", + client_id: "mercury-testing", + client_secret: "5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag", + username: mail, + password: password, + }); + + const response = await fetchData( + `http://localhost:3000/realms/mercury/protocol/openid-connect/token`, + "POST", + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: urlParams, + }, + ); + + return response.access_token; +}; const login = async (mail: string, password: string) => { const response = await fetchData(`http://localhost:5000/auth/login`, "POST", { headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ mail: mail, password: password }), + body: JSON.stringify({ mail, password }), }); - token = response.token; + return response.token; }; +// set after creating user +let token1 = ""; +const token2 = await getKeycloakToken(userMail2, userPassword2); + const updateUser = async (userId: string, token?: string) => { const response = await fetchData( `http://localhost:5000/users/${userId}`, @@ -92,7 +124,7 @@ describe("Create user", () => { expect(user.mail).toBe(userData.mail); userId = user.id; - await login(userData.mail, userData.password); + token1 = await login(userData.mail, userData.password); }); test("existing mail", async () => { @@ -213,24 +245,29 @@ describe("Update user", () => { expect(status).toBe("unauthorized"); }); - test("incorrect ID", async () => { - const { status, errors } = await updateUser("0", token); - - expect(status).toBe("error"); - expect(errors.id).toBe("not found"); + test("with incorrect token", async () => { + const { status } = await updateUser(userId, token2); + expect(status).toBe("forbidden"); }); + // test("incorrect ID", async () => { + // const { status, errors } = await updateUser("0", token1); + + // expect(status).toBe("error"); + // expect(errors.id).toBe("not found"); + // }); + test("correct", async () => { userData.profile_picture = "https://example.com/new_john_smith.jpg"; - const { status } = await updateUser(userId, token); + const { status } = await updateUser(userId, token1); expect(status).toBe("ok"); }); test("too short first name", async () => { userData.first_name = "j"; - const { status, errors } = await updateUser(userId, token); + const { status, errors } = await updateUser(userId, token1); expect(status).toBe("error"); expect(errors.first_name).toBe( @@ -241,7 +278,7 @@ describe("Update user", () => { test("too short last name", async () => { userData.last_name = "s"; - const { status, errors } = await updateUser(userId, token); + const { status, errors } = await updateUser(userId, token1); expect(status).toBe("error"); expect(errors.last_name).toBe( @@ -256,7 +293,7 @@ describe("Change password", () => { userId, "1234", "1234", - token, + token1, ); expect(status).toBe("error"); @@ -269,7 +306,7 @@ describe("Change password", () => { userId, "12345678", "12345678", - token, + token1, ); expect(status).toBe("ok"); }); @@ -279,7 +316,7 @@ describe("Change password", () => { userId, "123456789", "123456789", - token, + token1, ); expect(status).toBe("ok"); }); @@ -291,16 +328,21 @@ describe("Delete user", () => { expect(status).toBe("unauthorized"); }); - test("incorrect ID", async () => { - const response = await deleteUser("🐍", token); - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors.id).toBe("not found"); + test("with incorrect token", async () => { + const { status } = await deleteUser(userId, token2); + expect(status).toBe("forbidden"); }); + // test("incorrect ID", async () => { + // const response = await deleteUser("🐍", token1); + // const { status, errors } = response; + + // expect(status).toBe("error"); + // expect(errors.id).toBe("not found"); + // }); + test("correct", async () => { - const { status } = await deleteUser(userId, token); + const { status } = await deleteUser(userId, token1); expect(status).toBe("ok"); }); }); diff --git a/backend/test/userFriendRequests.test.ts b/backend/test/userFriendRequests.test.ts index d0e7a63..debf39e 100644 --- a/backend/test/userFriendRequests.test.ts +++ b/backend/test/userFriendRequests.test.ts @@ -233,6 +233,11 @@ describe("Send friend request", () => { expect(status).toBe("unauthorized"); }); + test("with incorrect token", async () => { + const { status } = await sendFriendRequest(userId, userId2, token2); + expect(status).toBe("forbidden"); + }); + test("correct", async () => { const { status } = await sendFriendRequest(userId, userId2, token1); expect(status).toBe("ok"); @@ -253,13 +258,18 @@ describe("Decline friend request", () => { expect(status).toBe("unauthorized"); }); + test("with incorrect token", async () => { + const { status } = await declineFriendRequest(userId2, userId, token1); + expect(status).toBe("forbidden"); + }); + test("correct", async () => { const { status } = await declineFriendRequest(userId2, userId, token2); expect(status).toBe("ok"); }); test("incorrect id", async () => { - const { status, errors } = await declineFriendRequest(userId, "0", token2); + const { status, errors } = await declineFriendRequest(userId2, "0", token2); expect(status).toBe("error"); expect(errors).toBeDefined(); @@ -285,6 +295,11 @@ describe("Accept friend request", () => { expect(status).toBe("unauthorized"); }); + test("with incorrect token", async () => { + const { status } = await acceptFriendRequest(userId2, userId, token1); + expect(status).toBe("forbidden"); + }); + test("incorrect id", async () => { const response = await acceptFriendRequest(userId, "0", token1); const { status, errors } = response; @@ -316,6 +331,7 @@ describe("Accept friend request", () => { `http://localhost:5000/users/${userId2}/friends?page=1&maxUsers=10`, "GET", {}, + token2, ); const friend = friendsResponse.friends.find( @@ -332,6 +348,11 @@ describe("Delete friend", () => { expect(status).toBe("unauthorized"); }); + test("with incorrect token", async () => { + const { status } = await deleteFriend(userId2, userId, token1); + expect(status).toBe("forbidden"); + }); + test("incorrect id", async () => { const response = await deleteFriend(userId, "0", token1); const { status, errors } = response; diff --git a/backend/test/userFriends.test.ts b/backend/test/userFriends.test.ts index 0e736a6..6181d13 100644 --- a/backend/test/userFriends.test.ts +++ b/backend/test/userFriends.test.ts @@ -6,14 +6,15 @@ let page: number = 1; let maxUsers: number = 10; let userId: string = ""; -const userMail = "bconford2@wikimedia.org"; -const userPassword = "heuristic"; +const userMail1 = "bconford2@wikimedia.org"; +const userPassword1 = "heuristic"; + +const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; const getFirstUser = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); - userId = response.users.find( - (user: User) => user.mail === "bconford2@wikimedia.org", - ).id; + userId = response.users.find((user: User) => user.mail === userMail1).id; }; const getKeycloakToken = async ( @@ -43,41 +44,54 @@ const getKeycloakToken = async ( }; await getFirstUser(); -const token = await getKeycloakToken(userMail, userPassword); +const token1 = await getKeycloakToken(userMail1, userPassword1); +const token2 = await getKeycloakToken(userMail2, userPassword2); describe("Get friends", () => { test("without token", async () => { const response = await fetchData( - `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, + `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, ); - const { status } = response + const { status } = response; expect(status).toBe("unauthorized"); }); - test("incorrect ID", async () => { + test("with incorrect token", async () => { const response = await fetchData( - `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, + `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, - token + token2, ); - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); + const { status } = response; + expect(status).toBe("forbidden"); }); + // test("incorrect ID", async () => { + // const response = await fetchData( + // `http://localhost:5000/users/0/friends?page=${page}&maxUsers=${maxUsers}`, + // "GET", + // {}, + // token1 + // ); + + // const { status, errors } = response; + + // expect(status).toBe("error"); + // expect(errors).toBeDefined(); + // expect(errors.id).toBe("not found"); + // }); + test("correct", async () => { const response = await fetchData( `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, - token + token1, ); const { status, pageCount, friends } = response; @@ -94,7 +108,7 @@ describe("Get friends", () => { `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, - token + token1, ); const { status, pageCount, friends } = response; @@ -115,7 +129,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?maxUsers=${maxUsers}`, "GET", {}, - token + token1, ); const { status, errors } = response; @@ -129,7 +143,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=text?maxUsers=${maxUsers}`, "GET", {}, - token + token1, ); const { status, errors } = response; @@ -143,7 +157,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=${page}`, "GET", {}, - token + token1, ); const { status, errors } = response; @@ -157,7 +171,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=text?page=${page}&maxUsers=text`, "GET", {}, - token + token1, ); const { status, errors } = response; @@ -171,7 +185,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends`, "GET", {}, - token + token1, ); const { status, errors } = response; @@ -187,7 +201,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friends?page=${page}&maxUsers=${maxUsers}`, "GET", {}, - token + token1, ); const { status, errors } = response; diff --git a/frontend/src/components/PaginatorV2.tsx b/frontend/src/components/PaginatorV2.tsx index 54a51d4..e8ad192 100644 --- a/frontend/src/components/PaginatorV2.tsx +++ b/frontend/src/components/PaginatorV2.tsx @@ -6,6 +6,7 @@ import dataService from "../services/data"; interface PaginatorProps { endpoint: string; + token?: string; itemsPerPage: number; getItems: (response: any) => User[]; renderItem: (user: User) => React.ReactNode; @@ -14,6 +15,8 @@ interface PaginatorProps { } function PaginatorV2(props: PaginatorProps) { + const { token } = props; + const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [error, setError] = useState(""); @@ -38,7 +41,7 @@ function PaginatorV2(props: PaginatorProps) { console.log(url); await dataService - .fetchData(url, "GET") + .fetchData(url, "GET", {}, token) .then((response) => { const users = props.getItems(response); diff --git a/frontend/src/helpers/KeycloakUserProvider.tsx b/frontend/src/helpers/KeycloakUserProvider.tsx index 6701f22..c3140df 100644 --- a/frontend/src/helpers/KeycloakUserProvider.tsx +++ b/frontend/src/helpers/KeycloakUserProvider.tsx @@ -24,6 +24,7 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { const fetchFriendsPage = async ( userId: number, page: number, + token: string, ): Promise => { const searchParams = new URLSearchParams({ page: page.toString(), @@ -43,13 +44,16 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { return friendsResponse.friends; }; - const fetchFriends = async (userId: number): Promise => { + const fetchFriends = async ( + userId: number, + token: string, + ): Promise => { let friends = []; let pageEmpty = false; let page = 1; while (!pageEmpty) { - const friendsPage = await fetchFriendsPage(userId, page); + const friendsPage = await fetchFriendsPage(userId, page, token); if (friendsPage === null) { return null; } @@ -69,7 +73,7 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { return Object.fromEntries(friends.map((f) => [f.id, f])); }; - const updateUserData = async () => { + const updateUserData = async (token: string) => { const keycloak = keycloakRef.current!; const tokenDecoded: any = await keycloak.loadUserInfo(); @@ -86,7 +90,7 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { } const userId = response.user.id; - const newFriendsArray = (await fetchFriends(userId)) || []; + const newFriendsArray = (await fetchFriends(userId, token)) || []; const newFriends = friendsToObject(newFriendsArray); setFriends(newFriends); setUserLoggedIn(response.user); @@ -102,12 +106,12 @@ function KeycloakUserProvider({ children }: { children: React.ReactNode }) { realm: "mercury", clientId: "mercury-client", }); + keycloakRef.current = keycloak; + keycloak.onAuthSuccess = () => { - updateUserData(); setToken(keycloak.token); + updateUserData(keycloak.token!); }; - - keycloakRef.current = keycloak; }); useEffect(() => { diff --git a/frontend/src/pages/FriendsPage.tsx b/frontend/src/pages/FriendsPage.tsx index 7f201bd..cd194c8 100644 --- a/frontend/src/pages/FriendsPage.tsx +++ b/frontend/src/pages/FriendsPage.tsx @@ -44,7 +44,7 @@ function FriendsPage() { `/users/${user.id}/friend-requests?page=1&maxUsers=32`, "GET", {}, - token + token, ); setFriendsRequests(friendsRequestsResponse.friendRequests); } @@ -56,7 +56,9 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/accept-friend-request/${currentId}`, - "POST", {}, token + "POST", + {}, + token, ); setRefresh(() => !refresh); @@ -67,7 +69,9 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/decline-friend-request/${friend.id}`, - "POST", {}, token + "POST", + {}, + token, ); setRefresh(() => !refresh); @@ -78,7 +82,9 @@ function FriendsPage() { if (user) { await dataService.fetchData( `/users/${user.id}/delete-friend/${friend.id}`, - "DELETE", {}, token + "DELETE", + {}, + token, ); setRefresh(() => !refresh); @@ -113,6 +119,7 @@ function FriendsPage() { {user && ( Date: Thu, 16 May 2024 15:40:00 +0200 Subject: [PATCH 20/26] Add authorization to user friend suggestions --- backend/src/routes/userFriendsRoute.ts | 31 ++++++--- backend/src/types/userResponse.ts | 2 +- backend/test/userFriendSuggestions.test.ts | 79 +++++++++++++++++++--- 3 files changed, 93 insertions(+), 19 deletions(-) diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index c3fc337..ada077f 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -125,20 +125,31 @@ friendsRouter.get( friendsRouter.get( "/:userId/friend-suggestions", - async (req: Request, res: FriendSuggestionsPageErrorResponse) => { + authenticateToken, + async (req: JWTRequest, res: FriendSuggestionsPageErrorResponse) => { const userId = req.params.userId; - const pageParse = pageSchema.safeParse(req.query); - if (!pageParse.success) { - const errors = formatError(pageParse.error); - return res.status(400).json({ status: "error", errors }); - } - - const { page, maxUsers }: Page = pageParse.data; - const maxUsersBig = BigInt(maxUsers); - const session = driver.session(); try { + const user = await getTokenDbUser(session, req.token!); + + if (!user) { + return userNotFoundRes(res); + } + + if (user.id != userId) { + return res.status(403).json({ status: "forbidden" }); + } + + const pageParse = pageSchema.safeParse(req.query); + if (!pageParse.success) { + const errors = formatError(pageParse.error); + return res.status(400).json({ status: "error", errors }); + } + + const { page, maxUsers }: Page = pageParse.data; + const maxUsersBig = BigInt(maxUsers); + const friendSuggestions = await getFriendSuggestions( session, userId, diff --git a/backend/src/types/userResponse.ts b/backend/src/types/userResponse.ts index 733ec92..b4f4afa 100644 --- a/backend/src/types/userResponse.ts +++ b/backend/src/types/userResponse.ts @@ -26,7 +26,7 @@ export type FriendRequestsPageErrorResponse = CustomResponse< FriendRequestsPageResponse | ErrorResponse >; export type FriendSuggestionsPageErrorResponse = CustomResponse< - FriendSuggestionsPageResponse | ErrorResponse + AuthResponse | FriendSuggestionsPageResponse | ErrorResponse >; export type FriendsPageErrorResponse = CustomResponse< AuthResponse | FriendsPageResponse | ErrorResponse diff --git a/backend/test/userFriendSuggestions.test.ts b/backend/test/userFriendSuggestions.test.ts index ffa9370..f79713f 100644 --- a/backend/test/userFriendSuggestions.test.ts +++ b/backend/test/userFriendSuggestions.test.ts @@ -6,6 +6,12 @@ let userId: number; let page: number = 3; let maxUsers: number = 5; +const userMail1 = "bconford2@wikimedia.org"; +const userPassword1 = "heuristic"; + +const userMail2 = "cruckman3@archive.org"; +const userPassword2 = "coreCar0l;"; + const getFirstUser = async () => { const response = await fetchData(`http://localhost:5000/users`, "GET", {}); userId = response.users.find( @@ -13,28 +19,79 @@ const getFirstUser = async () => { ).id; }; +const getKeycloakToken = async ( + mail: string, + password: string, +): Promise => { + const urlParams = new URLSearchParams({ + grant_type: "password", + client_id: "mercury-testing", + client_secret: "5mwGU0Efyh3cT2WVX7ffA8UAWEAmrBag", + username: mail, + password: password, + }); + + const response = await fetchData( + `http://localhost:3000/realms/mercury/protocol/openid-connect/token`, + "POST", + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: urlParams, + }, + ); + + return response.access_token; +}; + await getFirstUser(); +const token1 = await getKeycloakToken(userMail1, userPassword1); +const token2 = await getKeycloakToken(userMail2, userPassword2); describe("Get friend suggestions", () => { - test("incorrect ID", async () => { - const response = await fetchData( - `http://localhost:5000/users/0/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + test("without token", async () => { + const { status } = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions` + + `?page=${page}&maxUsers=${maxUsers}`, "GET", {}, ); + expect(status).toBe("unauthorized"); + }); - const { status, errors } = response; - - expect(status).toBe("error"); - expect(errors).toBeDefined(); - expect(errors.id).toBe("not found"); + test("with incorrect token", async () => { + const { status } = await fetchData( + `http://localhost:5000/users/${userId}/friend-suggestions` + + `?page=${page}&maxUsers=${maxUsers}`, + "GET", + {}, + token2 + ); + expect(status).toBe("forbidden"); }); + // test("incorrect ID", async () => { + // const response = await fetchData( + // `http://localhost:5000/users/0/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, + // "GET", + // {}, + // token1 + // ); + + // const { status, errors } = response; + + // expect(status).toBe("error"); + // expect(errors).toBeDefined(); + // expect(errors.id).toBe("not found"); + // }); + test("correct", async () => { const response = await fetchData( `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token1, ); const { status, pageCount, friendSuggestions } = response; @@ -52,6 +109,7 @@ describe("Get friend suggestions", () => { `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token1, ); const { status, pageCount, friendSuggestions } = response; @@ -69,6 +127,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friend-suggestions?maxUsers=${maxUsers}`, "GET", {}, + token1, ); const { status, errors } = response; @@ -83,6 +142,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}`, "GET", {}, + token1, ); const { status, errors } = response; @@ -97,6 +157,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friend-suggestions`, "GET", {}, + token1, ); const { status, errors } = response; @@ -111,6 +172,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friend-suggestions?page=text?page=${page}&maxUsers=text`, "GET", {}, + token1, ); const { status, errors } = response; @@ -125,6 +187,7 @@ describe("Pagination parameters", () => { `http://localhost:5000/users/${userId}/friend-suggestions?page=${page}&maxUsers=${maxUsers}`, "GET", {}, + token1, ); const { status, errors } = response; From 5b4c87b1cdb415327a18dcba175e007ba3159149 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 15:49:19 +0200 Subject: [PATCH 21/26] Fix issuers not exported --- backend/src/misc/jwt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/misc/jwt.ts b/backend/src/misc/jwt.ts index ac13b4a..9031f43 100644 --- a/backend/src/misc/jwt.ts +++ b/backend/src/misc/jwt.ts @@ -15,7 +15,7 @@ export interface JWTRequest extends Request { tokenStr?: string; } -const issuers: Record = { +export const issuers: Record = { mercury: `${keycloakIssuer}/realms/mercury`, rest: "http://localhost:5000", }; From 2ccd6d96ec1b5d5aa939a14178956f208711907e Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 15:51:52 +0200 Subject: [PATCH 22/26] Add @ianvs/prettier-plugin-sort-imports --- .prettierrc | 3 +- package-lock.json | 687 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + 3 files changed, 692 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index aa22ba8..a9fe69d 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,5 +8,6 @@ "endOfLine": "lf", "semi": true, "bracketSpacing": true, - "quoteProps": "preserve" + "quoteProps": "preserve", + plugins: ["@ianvs/prettier-plugin-sort-imports"] } diff --git a/package-lock.json b/package-lock.json index 3969da7..b964b7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,626 @@ "version": "1.0.0", "dependencies": { "prettier": "^3.1.0" + }, + "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.2.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ianvs/prettier-plugin-sort-imports": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.2.1.tgz", + "integrity": "sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.0", + "@babel/generator": "^7.23.6", + "@babel/parser": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", + "semver": "^7.5.2" + }, + "peerDependencies": { + "@vue/compiler-sfc": "2.7.x || 3.x", + "prettier": "2 || 3" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001620", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", + "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/electron-to-chromium": { + "version": "1.4.772", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.772.tgz", + "integrity": "sha512-jFfEbxR/abTTJA3ci+2ok1NTuOBBtB4jH+UT6PUmRN+DY3WSD4FFRsgoVQ+QNIJ0T7wrXwzsWCI2WKC46b++2A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, "node_modules/prettier": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", @@ -24,6 +642,75 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } } diff --git a/package.json b/package.json index 340b801..4c83d21 100644 --- a/package.json +++ b/package.json @@ -26,5 +26,8 @@ ], "dependencies": { "prettier": "^3.1.0" + }, + "devDependencies": { + "@ianvs/prettier-plugin-sort-imports": "^4.2.1" } } From c25cce2b5ae64dc03d0c527cf6ca8e89bb0734ba Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 15:54:33 +0200 Subject: [PATCH 23/26] Ignore non-code folders when formatting code --- .prettierignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 642271f..90dab77 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,5 @@ node_modules/ -coverage/ \ No newline at end of file +coverage/ +frontend/.vite/ +keycloak/ +db/ From 8ce797c3b718f2b13c1a4f620715f16a0b799ea6 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 16:03:48 +0200 Subject: [PATCH 24/26] Add blank lines between import groups --- .prettierrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierrc b/.prettierrc index a9fe69d..b200fb8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,5 +9,6 @@ "semi": true, "bracketSpacing": true, "quoteProps": "preserve", - plugins: ["@ianvs/prettier-plugin-sort-imports"] + "plugins": ["@ianvs/prettier-plugin-sort-imports"], + "importOrder": ["", "", "", "", "^[.]"] } From 4428e0587975c490ac20422d086573da232f94fc Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 14:38:43 +0000 Subject: [PATCH 25/26] Apply formatting --- backend/src/httpServer.ts | 10 ++--- backend/src/importDb.ts | 3 +- backend/src/index.ts | 4 +- backend/src/meetings.ts | 1 + backend/src/misc/fetchData.ts | 4 +- backend/src/misc/formatError.ts | 1 + backend/src/misc/jwt.ts | 11 ++--- backend/src/models/ChangePasswordReq.ts | 1 + backend/src/models/NativeUser.ts | 2 +- backend/src/models/Response.ts | 4 +- backend/src/models/User.ts | 2 +- backend/src/models/routes/Page.ts | 2 +- backend/src/models/routes/Search.ts | 5 ++- backend/src/mongoDB/MessageModel.ts | 1 + backend/src/routes/authRoute.ts | 12 +++--- backend/src/routes/chatRoute.ts | 7 ++-- backend/src/routes/userFriendsRoute.ts | 41 ++++++++++--------- backend/src/routes/usersRoute.ts | 37 +++++++++-------- backend/src/socketServer.ts | 26 ++++++------ backend/src/types/authResponse.ts | 4 +- backend/src/types/userResponse.ts | 12 +++--- backend/src/userFriends.ts | 1 + backend/src/users.ts | 21 +++++----- backend/test/chat.test.ts | 1 + backend/test/unit.test.ts | 19 ++++----- backend/test/userCRUD.test.ts | 1 + backend/test/userFriendRequests.test.ts | 1 + backend/test/userFriendSuggestions.test.ts | 3 +- backend/test/userFriends.test.ts | 1 + backend/test/userSearch.test.ts | 1 + frontend/src/components/Banner.tsx | 2 +- frontend/src/components/ChatBox.tsx | 10 ++--- frontend/src/components/EditDetails.tsx | 9 ++-- frontend/src/components/EditPassword.tsx | 9 ++-- frontend/src/components/EditPhoto.tsx | 3 +- frontend/src/components/FoundUser.tsx | 5 ++- frontend/src/components/Friend.tsx | 7 ++-- frontend/src/components/FriendRequest.tsx | 1 + frontend/src/components/LoginBox.tsx | 4 +- frontend/src/components/Modal.tsx | 2 +- frontend/src/components/Navbar.tsx | 12 +++--- frontend/src/components/PaginatorV2.tsx | 1 + frontend/src/components/Profile.tsx | 3 +- frontend/src/components/RegisterBox.tsx | 13 +++--- frontend/src/components/Search.tsx | 1 + frontend/src/components/Transition.tsx | 2 +- frontend/src/helpers/KeycloakUserProvider.tsx | 13 +++--- frontend/src/helpers/MeetingProvider.tsx | 1 + frontend/src/helpers/Protected.tsx | 3 +- frontend/src/helpers/RestUserProvider.tsx | 13 +++--- frontend/src/helpers/UserContext.tsx | 1 + frontend/src/layout/Reasons.tsx | 6 +-- frontend/src/layout/Technologies.tsx | 1 + frontend/src/layout/WelcomeMessage.tsx | 3 +- frontend/src/main.tsx | 4 +- frontend/src/models/RegisterUserSchema.ts | 3 +- frontend/src/pages/EditDataPage.tsx | 7 ++-- frontend/src/pages/FriendsPage.tsx | 17 ++++---- frontend/src/pages/HomePage.tsx | 4 +- frontend/src/pages/LoginPage.tsx | 5 ++- frontend/src/pages/MessagingPage.tsx | 6 +-- frontend/src/pages/PageNotFound.tsx | 1 + frontend/src/pages/ProfilePage.tsx | 5 ++- frontend/src/pages/RegisterPage.tsx | 5 ++- frontend/src/pages/SearchPage.tsx | 14 +++---- frontend/src/pages/VideoCallPage.tsx | 21 +++++----- frontend/src/redux/store.ts | 1 + frontend/vite.config.ts | 2 +- 68 files changed, 250 insertions(+), 209 deletions(-) diff --git a/backend/src/httpServer.ts b/backend/src/httpServer.ts index c9b69a9..258b25f 100644 --- a/backend/src/httpServer.ts +++ b/backend/src/httpServer.ts @@ -1,12 +1,12 @@ -import express from "express"; -import cors from "cors"; -import cookieParser from "cookie-parser"; - import { createServer } from "node:http"; -import usersRouter from "./routes/usersRoute.js"; +import cookieParser from "cookie-parser"; +import cors from "cors"; +import express from "express"; + import authRouter from "./routes/authRoute.js"; import chatRouter from "./routes/chatRoute.js"; +import usersRouter from "./routes/usersRoute.js"; const app = express(); const port: number = 5000; diff --git a/backend/src/importDb.ts b/backend/src/importDb.ts index 7f08d6b..28c09b3 100644 --- a/backend/src/importDb.ts +++ b/backend/src/importDb.ts @@ -1,8 +1,7 @@ import driver from "./driver.js"; - import userData from "./userData.js"; -import { registerUser, registerUserSchema } from "./users.js"; import { addFriend } from "./userFriends.js"; +import { registerUser, registerUserSchema } from "./users.js"; export async function isDatabaseEmpty() { const session = driver.session(); diff --git a/backend/src/index.ts b/backend/src/index.ts index 92d2982..ef86d87 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,12 +1,12 @@ import "dotenv/config"; - import "./httpServer.js"; import "./socketServer.js"; import "./kcAdminClient.js"; -import { cleanUpData, importInitialData } from "./importDb.js"; import { connect } from "mongoose"; +import { cleanUpData, importInitialData } from "./importDb.js"; + cleanUpData(); importInitialData().then((res) => console.log(res)); diff --git a/backend/src/meetings.ts b/backend/src/meetings.ts index 317ef34..ec346ab 100644 --- a/backend/src/meetings.ts +++ b/backend/src/meetings.ts @@ -1,5 +1,6 @@ import { Session } from "neo4j-driver"; import { v4 as uuidv4 } from "uuid"; + import Meeting from "./models/Meeting.js"; export async function isInMeeting(session: Session, userId: string) { diff --git a/backend/src/misc/fetchData.ts b/backend/src/misc/fetchData.ts index ea2bad3..4b2d161 100644 --- a/backend/src/misc/fetchData.ts +++ b/backend/src/misc/fetchData.ts @@ -7,8 +7,8 @@ export const fetchData = async ( if (token) { options.headers = { ...options.headers, - "Authorization": `Bearer ${token}` - } + "Authorization": `Bearer ${token}`, + }; } try { diff --git a/backend/src/misc/formatError.ts b/backend/src/misc/formatError.ts index c00c1c0..3682afc 100644 --- a/backend/src/misc/formatError.ts +++ b/backend/src/misc/formatError.ts @@ -1,4 +1,5 @@ import { ZodError } from "zod"; + import { Errors } from "../models/Response.js"; export function formatError(error: ZodError): Errors { diff --git a/backend/src/misc/jwt.ts b/backend/src/misc/jwt.ts index 9031f43..2c388ab 100644 --- a/backend/src/misc/jwt.ts +++ b/backend/src/misc/jwt.ts @@ -1,14 +1,15 @@ -import { Request, Response, NextFunction } from "express"; +import { NextFunction, Request, Response } from "express"; import jwt from "jsonwebtoken"; -import { AuthResponse, CustomResponse } from "../models/Response.js"; -import DecodedData from "../models/DecodedData.js"; -import Issuer from "../models/Issuer.js"; -import TokenPayload from "../models/TokenPayload.js"; + import { keycloakCredentials, keycloakIssuer, keycloakUri, } from "../kcAdminClient.js"; +import DecodedData from "../models/DecodedData.js"; +import Issuer from "../models/Issuer.js"; +import { AuthResponse, CustomResponse } from "../models/Response.js"; +import TokenPayload from "../models/TokenPayload.js"; export interface JWTRequest extends Request { token?: TokenPayload; diff --git a/backend/src/models/ChangePasswordReq.ts b/backend/src/models/ChangePasswordReq.ts index 04665b5..a55534e 100644 --- a/backend/src/models/ChangePasswordReq.ts +++ b/backend/src/models/ChangePasswordReq.ts @@ -1,4 +1,5 @@ import { z } from "zod"; + import { userPasswordSchema } from "./User.js"; type ChangePasswordReq = diff --git a/backend/src/models/NativeUser.ts b/backend/src/models/NativeUser.ts index 471733c..3730d88 100644 --- a/backend/src/models/NativeUser.ts +++ b/backend/src/models/NativeUser.ts @@ -1,4 +1,4 @@ -import { ZodType, z } from "zod"; +import { z, ZodType } from "zod"; type NativeUser = { password: string; diff --git a/backend/src/models/Response.ts b/backend/src/models/Response.ts index 7c6f2be..3600577 100644 --- a/backend/src/models/Response.ts +++ b/backend/src/models/Response.ts @@ -1,8 +1,8 @@ +import { Response } from "express"; + import Message from "./Message.js"; import User from "./User.js"; -import { Response } from "express"; - type Send = (body?: J) => T; export interface CustomResponse extends Response { diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index 836a8ad..5d12d17 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -1,4 +1,4 @@ -import { ZodType, z } from "zod"; +import { z, ZodType } from "zod"; export default interface User { id: string; diff --git a/backend/src/models/routes/Page.ts b/backend/src/models/routes/Page.ts index 259afee..b436bbe 100644 --- a/backend/src/models/routes/Page.ts +++ b/backend/src/models/routes/Page.ts @@ -1,4 +1,4 @@ -import { ZodType, z } from "zod"; +import { z, ZodType } from "zod"; interface Page { page: string | number; diff --git a/backend/src/models/routes/Search.ts b/backend/src/models/routes/Search.ts index 38b51b7..476ff38 100644 --- a/backend/src/models/routes/Search.ts +++ b/backend/src/models/routes/Search.ts @@ -1,6 +1,7 @@ -import { ZodType, z } from "zod"; -import Page, { pageSchema } from "./Page.js"; +import { z, ZodType } from "zod"; + import { userCountrySchema } from "../User.js"; +import Page, { pageSchema } from "./Page.js"; interface Search extends Page { q: string; diff --git a/backend/src/mongoDB/MessageModel.ts b/backend/src/mongoDB/MessageModel.ts index 816a37c..6cdce3b 100644 --- a/backend/src/mongoDB/MessageModel.ts +++ b/backend/src/mongoDB/MessageModel.ts @@ -1,4 +1,5 @@ import { model, Schema } from "mongoose"; + import Message from "../models/Message.js"; const chatSchema = new Schema({ diff --git a/backend/src/routes/authRoute.ts b/backend/src/routes/authRoute.ts index 5becbdc..e6ea581 100644 --- a/backend/src/routes/authRoute.ts +++ b/backend/src/routes/authRoute.ts @@ -1,21 +1,19 @@ -import { Router, Request } from "express"; - import bcrypt from "bcrypt"; +import { Request, Router } from "express"; +import jwt, { JwtPayload } from "jsonwebtoken"; import driver from "../driver.js"; +import { leaveMeeting } from "../meetings.js"; import { - JWTRequest, authenticateToken, generateAccessToken, generateRefreshToken, + JWTRequest, } from "../misc/jwt.js"; - -import jwt, { JwtPayload } from "jsonwebtoken"; +import { Errors } from "../models/Response.js"; import { TokenErrorResponse } from "../types/authResponse.js"; import { OkErrorResponse } from "../types/userResponse.js"; -import { leaveMeeting } from "../meetings.js"; import { getDbUser } from "../users.js"; -import { Errors } from "../models/Response.js"; const authRouter = Router(); diff --git a/backend/src/routes/chatRoute.ts b/backend/src/routes/chatRoute.ts index fa6d483..dfb8d6b 100644 --- a/backend/src/routes/chatRoute.ts +++ b/backend/src/routes/chatRoute.ts @@ -1,11 +1,12 @@ import { Router } from "express"; + +import driver from "../driver.js"; +import { authenticateToken, JWTRequest } from "../misc/jwt.js"; import Message from "../models/Message.js"; +import { Errors } from "../models/Response.js"; import MessageModel from "../mongoDB/MessageModel.js"; -import { JWTRequest, authenticateToken } from "../misc/jwt.js"; import { AuthMessagesErrorResponse } from "../types/userResponse.js"; -import { Errors } from "../models/Response.js"; import { getTokenDbUser } from "../users.js"; -import driver from "../driver.js"; import { userNotFoundRes } from "./usersRoute.js"; const chatRouter = Router(); diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index ada077f..e3bb044 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -1,30 +1,33 @@ -import { Router, Request, Response } from "express"; +import { Request, Response, Router } from "express"; import { Session } from "neo4j-driver"; + import driver from "../driver.js"; +import { formatError } from "../misc/formatError.js"; +import { authenticateToken, JWTRequest } from "../misc/jwt.js"; +import { Errors } from "../models/Response.js"; +import Page, { pageSchema } from "../models/routes/Page.js"; import User from "../models/User.js"; import { - OkErrorResponse, - FriendsPageErrorResponse, + AuthOkErrorResponse, FriendRequestsPageErrorResponse, + FriendsPageErrorResponse, FriendSuggestionsPageErrorResponse, - AuthOkErrorResponse, + OkErrorResponse, } from "../types/userResponse.js"; -import { deleteFriend } from "../userFriends.js"; -import { declineFriendRequest } from "../userFriends.js"; -import { acceptFriendRequest } from "../userFriends.js"; -import { sendFriendRequest } from "../userFriends.js"; -import { getFriendSuggestionsCount } from "../userFriends.js"; -import { getFriendSuggestions } from "../userFriends.js"; -import { getFriendRequestsCount } from "../userFriends.js"; -import { getFriendRequests } from "../userFriends.js"; -import { getFriendsCount } from "../userFriends.js"; -import { getFriends } from "../userFriends.js"; -import { userNotFoundRes } from "./usersRoute.js"; -import { Errors } from "../models/Response.js"; -import Page, { pageSchema } from "../models/routes/Page.js"; -import { formatError } from "../misc/formatError.js"; -import { JWTRequest, authenticateToken } from "../misc/jwt.js"; +import { + acceptFriendRequest, + declineFriendRequest, + deleteFriend, + getFriendRequests, + getFriendRequestsCount, + getFriends, + getFriendsCount, + getFriendSuggestions, + getFriendSuggestionsCount, + sendFriendRequest, +} from "../userFriends.js"; import { getDbUser, getTokenDbUser } from "../users.js"; +import { userNotFoundRes } from "./usersRoute.js"; const friendsRouter = Router(); diff --git a/backend/src/routes/usersRoute.ts b/backend/src/routes/usersRoute.ts index 0980a84..2c0ac79 100644 --- a/backend/src/routes/usersRoute.ts +++ b/backend/src/routes/usersRoute.ts @@ -1,6 +1,12 @@ -import { Router, Request, Response } from "express"; +import { Request, Response, Router } from "express"; + import driver from "../driver.js"; -import { JWTRequest, authenticateToken, getToken } from "../misc/jwt.js"; +import { formatError } from "../misc/formatError.js"; +import { authenticateToken, getToken, JWTRequest } from "../misc/jwt.js"; +import { changePasswordReqSchema } from "../models/ChangePasswordReq.js"; +import DbUser from "../models/DbUser.js"; +import { Errors } from "../models/Response.js"; +import { searchSchema } from "../models/routes/Search.js"; import { AuthOkErrorResponse, OkErrorResponse, @@ -8,30 +14,25 @@ import { UsersErrorResponse, UsersSearchErrorResponse, } from "../types/userResponse.js"; -import usersFriendsRoute from "./userFriendsRoute.js"; import { - getAllUsers, - searchUser as searchUsers, - getUser as getUser, + changePassword, createUser, - updateUser, deleteUser, - UserCreateResult, - registerUser, + getAllUsers, getDbUser, - changePassword, + getTokenDbUser, + getUser, getUsersCount, - registerUserSchema, + registerUser, RegisterUser, - updateUserSchema, + registerUserSchema, + searchUser as searchUsers, + updateUser, UpdateUser, - getTokenDbUser, + updateUserSchema, + UserCreateResult, } from "../users.js"; -import DbUser from "../models/DbUser.js"; -import { changePasswordReqSchema } from "../models/ChangePasswordReq.js"; -import { formatError } from "../misc/formatError.js"; -import { Errors } from "../models/Response.js"; -import { searchSchema } from "../models/routes/Search.js"; +import usersFriendsRoute from "./userFriendsRoute.js"; const usersRouter = Router(); diff --git a/backend/src/socketServer.ts b/backend/src/socketServer.ts index e961126..94454e0 100644 --- a/backend/src/socketServer.ts +++ b/backend/src/socketServer.ts @@ -1,23 +1,23 @@ +import { Socket, Server as SocketServer } from "socket.io"; + import driver from "./driver.js"; -import { Socket } from "socket.io"; -import { - connectToSocket, - disconnectFromSocket, - getAllSockets, -} from "./sockets.js"; -import { isFriend } from "./userFriends.js"; -import Meeting from "./models/Meeting.js"; +import ClientToServerEvents from "./events/ClientToServerEvents.js"; +import ServerToClientEvents from "./events/ServerToClientEvents.js"; +import { expressServer } from "./httpServer.js"; import { createMeeting, - leaveMeeting, isInMeeting, joinMeeting, + leaveMeeting, } from "./meetings.js"; import { addMessageToDb } from "./messages.js"; -import { Server as SocketServer } from "socket.io"; -import ClientToServerEvents from "./events/ClientToServerEvents.js"; -import ServerToClientEvents from "./events/ServerToClientEvents.js"; -import { expressServer } from "./httpServer.js"; +import Meeting from "./models/Meeting.js"; +import { + connectToSocket, + disconnectFromSocket, + getAllSockets, +} from "./sockets.js"; +import { isFriend } from "./userFriends.js"; const io = new SocketServer( expressServer, diff --git a/backend/src/types/authResponse.ts b/backend/src/types/authResponse.ts index 4e7899e..4d514f1 100644 --- a/backend/src/types/authResponse.ts +++ b/backend/src/types/authResponse.ts @@ -1,8 +1,8 @@ import { - CustomResponse, - JWTResponse, AuthResponse, + CustomResponse, ErrorResponse, + JWTResponse, } from "../models/Response.js"; export type TokenErrorResponse = CustomResponse< diff --git a/backend/src/types/userResponse.ts b/backend/src/types/userResponse.ts index b4f4afa..7ff0126 100644 --- a/backend/src/types/userResponse.ts +++ b/backend/src/types/userResponse.ts @@ -1,16 +1,16 @@ import { + AuthResponse, CustomResponse, ErrorResponse, + FriendRequestsPageResponse, + FriendsPageResponse, + FriendsResponse, + FriendSuggestionsPageResponse, + MessagesResponse, OkResponse, UserResponse, UsersResponse, - FriendsResponse, UsersSearchResponse, - AuthResponse, - FriendsPageResponse, - FriendRequestsPageResponse, - FriendSuggestionsPageResponse, - MessagesResponse, } from "../models/Response.js"; export type UsersErrorResponse = CustomResponse; diff --git a/backend/src/userFriends.ts b/backend/src/userFriends.ts index 30ae7f2..dbeac05 100644 --- a/backend/src/userFriends.ts +++ b/backend/src/userFriends.ts @@ -1,4 +1,5 @@ import neo4j, { Session } from "neo4j-driver"; + import User from "./models/User.js"; import { filterUser, getUser } from "./users.js"; diff --git a/backend/src/users.ts b/backend/src/users.ts index a9ae1d5..e242672 100644 --- a/backend/src/users.ts +++ b/backend/src/users.ts @@ -1,20 +1,21 @@ +import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation.js"; +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; import neo4j, { Session } from "neo4j-driver"; import { v4 as uuidv4 } from "uuid"; -import bcrypt from "bcrypt"; -import User, { userSchema } from "./models/User.js"; +import { ZodType } from "zod"; + +import kcAdminClient from "./kcAdminClient.js"; +import { Either } from "./misc/Either.js"; +import { issuers } from "./misc/jwt.js"; import removeKeys from "./misc/removeKeys.js"; import wordToVec from "./misc/wordToVec.js"; +import ChangePasswordReq from "./models/ChangePasswordReq.js"; import DbUser from "./models/DbUser.js"; -import kcAdminClient from "./kcAdminClient.js"; -import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation.js"; -import { Either } from "./misc/Either.js"; -import NativeUser, { nativeUserSchema } from "./models/NativeUser.js"; import ExternalUser from "./models/ExternalUser.js"; -import { ZodType } from "zod"; -import ChangePasswordReq from "./models/ChangePasswordReq.js"; -import jwt from "jsonwebtoken"; +import NativeUser, { nativeUserSchema } from "./models/NativeUser.js"; import TokenPayload from "./models/TokenPayload.js"; -import { issuers } from "./misc/jwt.js"; +import User, { userSchema } from "./models/User.js"; export const filterUser = (user: DbUser): User => { if ("password" in user) { diff --git a/backend/test/chat.test.ts b/backend/test/chat.test.ts index ac6b5c3..2234f7e 100644 --- a/backend/test/chat.test.ts +++ b/backend/test/chat.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; diff --git a/backend/test/unit.test.ts b/backend/test/unit.test.ts index 031aba3..da34acb 100644 --- a/backend/test/unit.test.ts +++ b/backend/test/unit.test.ts @@ -1,20 +1,19 @@ import { expect, test } from "vitest"; -import { - letterToKb, - wordDifference, + +import removeKeys from "../src/misc/removeKeys.js"; +import wordToVec, { cosineSimilarity, - sortLetters, + dot, keepLetters, + l2Norm, lerp, - wordVecInterp, + letterToKb, + sortLetters, sum, + wordDifference, + wordVecInterp, zip, - l2Norm, - dot, } from "../src/misc/wordToVec.js"; -import removeKeys from "../src/misc/removeKeys.js"; - -import wordToVec from "../src/misc/wordToVec.js"; test("Letter to Kb", async () => { expect(letterToKb("Q")).toStrictEqual(-1); diff --git a/backend/test/userCRUD.test.ts b/backend/test/userCRUD.test.ts index 7e4881b..0ee89d4 100644 --- a/backend/test/userCRUD.test.ts +++ b/backend/test/userCRUD.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; let userId: string; diff --git a/backend/test/userFriendRequests.test.ts b/backend/test/userFriendRequests.test.ts index debf39e..38b2f37 100644 --- a/backend/test/userFriendRequests.test.ts +++ b/backend/test/userFriendRequests.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; diff --git a/backend/test/userFriendSuggestions.test.ts b/backend/test/userFriendSuggestions.test.ts index f79713f..25e7b66 100644 --- a/backend/test/userFriendSuggestions.test.ts +++ b/backend/test/userFriendSuggestions.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; @@ -66,7 +67,7 @@ describe("Get friend suggestions", () => { `?page=${page}&maxUsers=${maxUsers}`, "GET", {}, - token2 + token2, ); expect(status).toBe("forbidden"); }); diff --git a/backend/test/userFriends.test.ts b/backend/test/userFriends.test.ts index 6181d13..3e801df 100644 --- a/backend/test/userFriends.test.ts +++ b/backend/test/userFriends.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; diff --git a/backend/test/userSearch.test.ts b/backend/test/userSearch.test.ts index d71f060..4c80798 100644 --- a/backend/test/userSearch.test.ts +++ b/backend/test/userSearch.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; + import { fetchData } from "../src/misc/fetchData.js"; import User from "../src/models/User.js"; diff --git a/frontend/src/components/Banner.tsx b/frontend/src/components/Banner.tsx index a8871ee..2c06d0e 100644 --- a/frontend/src/components/Banner.tsx +++ b/frontend/src/components/Banner.tsx @@ -1,5 +1,5 @@ -import { Link } from "react-router-dom"; import LogoSVG from "/logo.svg"; +import { Link } from "react-router-dom"; function Banner() { return ( diff --git a/frontend/src/components/ChatBox.tsx b/frontend/src/components/ChatBox.tsx index 6779628..fd9cfe8 100644 --- a/frontend/src/components/ChatBox.tsx +++ b/frontend/src/components/ChatBox.tsx @@ -1,12 +1,11 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; - import { Socket } from "socket.io-client"; -import User from "../models/User"; import notificationSoundUrl from "../assets/notification.mp3"; -import Message, { MessageProps } from "./Message"; -import dataService from "../services/data"; import { useUser } from "../helpers/UserContext"; +import User from "../models/User"; +import dataService from "../services/data"; +import Message, { MessageProps } from "./Message"; const notificationSound = new Audio(notificationSoundUrl); @@ -119,7 +118,8 @@ function ChatBox({ user, socket, friendId }: ChatBoxProps) { const messageResponse = await dataService.fetchData( `/chat/${user.id}/${friendId}`, "GET", - {}, token + {}, + token, ); await addMessages(messageResponse.messages); diff --git a/frontend/src/components/EditDetails.tsx b/frontend/src/components/EditDetails.tsx index bb04e32..1a7ba5c 100644 --- a/frontend/src/components/EditDetails.tsx +++ b/frontend/src/components/EditDetails.tsx @@ -1,10 +1,11 @@ -import { useForm } from "react-hook-form"; -import User, { FrontendUser } from "../models/User"; import { zodResolver } from "@hookform/resolvers/zod"; -import { userEditDetails } from "../models/RegisterUserSchema"; import { ChangeEvent, useState } from "react"; -import countriesData from "../assets/countries.json"; +import { useForm } from "react-hook-form"; import Select from "react-select"; + +import countriesData from "../assets/countries.json"; +import { userEditDetails } from "../models/RegisterUserSchema"; +import User, { FrontendUser } from "../models/User"; import Popup from "./Popup"; export interface EditDetails { diff --git a/frontend/src/components/EditPassword.tsx b/frontend/src/components/EditPassword.tsx index 48e8bc5..32b76b1 100644 --- a/frontend/src/components/EditPassword.tsx +++ b/frontend/src/components/EditPassword.tsx @@ -1,10 +1,11 @@ -import { ChangeEvent, FormEvent, useState } from "react"; -import User from "../models/User"; -import { changePasswordSchema } from "../models/RegisterUserSchema"; -import { useNavigate } from "react-router-dom"; import { zodResolver } from "@hookform/resolvers/zod"; +import { ChangeEvent, FormEvent, useState } from "react"; import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; + import { PasswordForm } from "../models/PasswordForm"; +import { changePasswordSchema } from "../models/RegisterUserSchema"; +import User from "../models/User"; export interface EditDetails { provider: string; diff --git a/frontend/src/components/EditPhoto.tsx b/frontend/src/components/EditPhoto.tsx index 3519026..7d5f934 100644 --- a/frontend/src/components/EditPhoto.tsx +++ b/frontend/src/components/EditPhoto.tsx @@ -1,5 +1,6 @@ -import User from "../models/User"; import { useState } from "react"; + +import User from "../models/User"; import Popup from "./Popup"; export interface EditDetails { diff --git a/frontend/src/components/FoundUser.tsx b/frontend/src/components/FoundUser.tsx index 629d5b8..f6a05f2 100644 --- a/frontend/src/components/FoundUser.tsx +++ b/frontend/src/components/FoundUser.tsx @@ -1,8 +1,9 @@ import { useState } from "react"; -import User from "../models/User"; -import dataService from "../services/data"; + import countriesData from "../assets/countries.json"; import { useUser } from "../helpers/UserContext"; +import User from "../models/User"; +import dataService from "../services/data"; interface FoundUserProps { user: User; diff --git a/frontend/src/components/Friend.tsx b/frontend/src/components/Friend.tsx index 806aef3..3b4e79a 100644 --- a/frontend/src/components/Friend.tsx +++ b/frontend/src/components/Friend.tsx @@ -1,12 +1,13 @@ import { + faCommentAlt, faUserMinus, faVideo, - faCommentAlt, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import User from "../models/User"; -import { useNavigate } from "react-router-dom"; import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import User from "../models/User"; import Modal from "./Modal"; export interface FriendProps { diff --git a/frontend/src/components/FriendRequest.tsx b/frontend/src/components/FriendRequest.tsx index e4691ce..ccee85f 100644 --- a/frontend/src/components/FriendRequest.tsx +++ b/frontend/src/components/FriendRequest.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; + import User from "../models/User"; import Modal from "./Modal"; diff --git a/frontend/src/components/LoginBox.tsx b/frontend/src/components/LoginBox.tsx index 7d99c0c..32c44f8 100644 --- a/frontend/src/components/LoginBox.tsx +++ b/frontend/src/components/LoginBox.tsx @@ -1,6 +1,6 @@ -import { useNavigate } from "react-router-dom"; import { useEffect, useRef, useState } from "react"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; + import { useUser } from "../helpers/UserContext"; function LoginBox() { diff --git a/frontend/src/components/Modal.tsx b/frontend/src/components/Modal.tsx index 1b8355b..cb0b91a 100644 --- a/frontend/src/components/Modal.tsx +++ b/frontend/src/components/Modal.tsx @@ -1,4 +1,4 @@ -import { motion, AnimatePresence } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; interface ModalInterface { text: string; diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index a3a4784..3e05238 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,14 +1,16 @@ -import { Link } from "react-router-dom"; -import LogoSVG from "/logo.svg"; -import { useUser } from "../helpers/UserContext"; -import { useState } from "react"; import { faMagnifyingGlass, + faRightFromBracket, faUser, faUsers, - faRightFromBracket, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import LogoSVG from "/logo.svg"; +import { useState } from "react"; +import { Link } from "react-router-dom"; + +import { useUser } from "../helpers/UserContext"; + ; export interface NavbarProps { diff --git a/frontend/src/components/PaginatorV2.tsx b/frontend/src/components/PaginatorV2.tsx index e8ad192..fce6dc8 100644 --- a/frontend/src/components/PaginatorV2.tsx +++ b/frontend/src/components/PaginatorV2.tsx @@ -1,6 +1,7 @@ import { faArrowLeft, faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useEffect, useState } from "react"; + import User from "../models/User"; import dataService from "../services/data"; diff --git a/frontend/src/components/Profile.tsx b/frontend/src/components/Profile.tsx index 271eb0f..a3bf9ef 100644 --- a/frontend/src/components/Profile.tsx +++ b/frontend/src/components/Profile.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; + +import countriesData from "../assets/countries.json"; import User from "../models/User"; import Modal from "./Modal"; -import countriesData from "../assets/countries.json"; export interface ProfilePageFormProps { user: User; diff --git a/frontend/src/components/RegisterBox.tsx b/frontend/src/components/RegisterBox.tsx index ddcf6cb..791e7e9 100644 --- a/frontend/src/components/RegisterBox.tsx +++ b/frontend/src/components/RegisterBox.tsx @@ -1,13 +1,14 @@ +import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; -import { Link } from "react-router-dom"; import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { userRegisterSchema } from "../models/RegisterUserSchema"; -import { FrontendUser } from "../models/User"; -import * as userPlaceholder from "../assets/user-placeholder.jpg"; -import { useUser } from "../helpers/UserContext"; +import { Link } from "react-router-dom"; import Select from "react-select"; + import countriesData from "../assets/countries.json"; +import * as userPlaceholder from "../assets/user-placeholder.jpg"; +import { useUser } from "../helpers/UserContext"; +import { userRegisterSchema } from "../models/RegisterUserSchema"; +import { FrontendUser } from "../models/User"; function RegisterBox() { const { diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index d3dc77f..a255e56 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import Select from "react-select"; + import countriesData from "../assets/countries.json"; import { useUser } from "../helpers/UserContext"; diff --git a/frontend/src/components/Transition.tsx b/frontend/src/components/Transition.tsx index 500e054..3912f1d 100644 --- a/frontend/src/components/Transition.tsx +++ b/frontend/src/components/Transition.tsx @@ -1,5 +1,5 @@ -import React, { useEffect } from "react"; import { motion, useAnimation } from "framer-motion"; +import React, { useEffect } from "react"; interface TransitionProps { startAnimation: boolean; diff --git a/frontend/src/helpers/KeycloakUserProvider.tsx b/frontend/src/helpers/KeycloakUserProvider.tsx index c3140df..8f159eb 100644 --- a/frontend/src/helpers/KeycloakUserProvider.tsx +++ b/frontend/src/helpers/KeycloakUserProvider.tsx @@ -1,11 +1,12 @@ -import React, { useState, useEffect, useRef, useMemo } from "react"; -import dataService from "../services/data"; -import User, { FrontendUser } from "../models/User"; -import { Socket, io } from "socket.io-client"; -import UserContext from "./UserContext"; -import UserState from "../models/UserState"; import Keycloak from "keycloak-js"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; +import { io, Socket } from "socket.io-client"; + +import User, { FrontendUser } from "../models/User"; +import UserState from "../models/UserState"; +import dataService from "../services/data"; +import UserContext from "./UserContext"; function KeycloakUserProvider({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); diff --git a/frontend/src/helpers/MeetingProvider.tsx b/frontend/src/helpers/MeetingProvider.tsx index 78d548f..83010de 100644 --- a/frontend/src/helpers/MeetingProvider.tsx +++ b/frontend/src/helpers/MeetingProvider.tsx @@ -1,4 +1,5 @@ import { createContext, useContext, useState } from "react"; + import Meeting from "../models/Meeting"; import { useUser } from "./UserContext"; diff --git a/frontend/src/helpers/Protected.tsx b/frontend/src/helpers/Protected.tsx index 09f6d06..55c39d6 100644 --- a/frontend/src/helpers/Protected.tsx +++ b/frontend/src/helpers/Protected.tsx @@ -1,6 +1,7 @@ import { createContext, useContext, useEffect } from "react"; -import { useUser } from "./UserContext"; + import User from "../models/User"; +import { useUser } from "./UserContext"; export interface ProtectedContextValue { user: User; diff --git a/frontend/src/helpers/RestUserProvider.tsx b/frontend/src/helpers/RestUserProvider.tsx index 8dc5810..22a08b1 100644 --- a/frontend/src/helpers/RestUserProvider.tsx +++ b/frontend/src/helpers/RestUserProvider.tsx @@ -1,12 +1,13 @@ -import React, { useState, useEffect, useRef, useMemo } from "react"; -import { isExpired, decodeToken } from "react-jwt"; import Cookies from "js-cookie"; -import dataService from "../services/data"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { decodeToken, isExpired } from "react-jwt"; +import { useNavigate } from "react-router-dom"; +import { io, Socket } from "socket.io-client"; + import User, { FrontendUser } from "../models/User"; -import { Socket, io } from "socket.io-client"; -import UserContext from "./UserContext"; import UserState from "../models/UserState"; -import { useNavigate } from "react-router-dom"; +import dataService from "../services/data"; +import UserContext from "./UserContext"; function RestUserProvider({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); diff --git a/frontend/src/helpers/UserContext.tsx b/frontend/src/helpers/UserContext.tsx index 058f2c5..2c8eafe 100644 --- a/frontend/src/helpers/UserContext.tsx +++ b/frontend/src/helpers/UserContext.tsx @@ -1,5 +1,6 @@ import { createContext, useContext } from "react"; import { Socket } from "socket.io-client"; + import User, { FrontendUser } from "../models/User"; import UserState from "../models/UserState"; diff --git a/frontend/src/layout/Reasons.tsx b/frontend/src/layout/Reasons.tsx index ccbf5d0..212f79f 100644 --- a/frontend/src/layout/Reasons.tsx +++ b/frontend/src/layout/Reasons.tsx @@ -1,11 +1,11 @@ import { - faShield, faDesktop, - faUsers, faGlobe, + faShield, + faUsers, } from "@fortawesome/free-solid-svg-icons"; - import { motion } from "framer-motion"; + import ReasonCard from "../components/ReasonCard"; export default function Reasons() { diff --git a/frontend/src/layout/Technologies.tsx b/frontend/src/layout/Technologies.tsx index 1e4a610..967da0d 100644 --- a/frontend/src/layout/Technologies.tsx +++ b/frontend/src/layout/Technologies.tsx @@ -1,4 +1,5 @@ import { AnimatePresence, motion } from "framer-motion"; + import technologies from "../helpers/TechnologiesContent"; export default function Technologies() { diff --git a/frontend/src/layout/WelcomeMessage.tsx b/frontend/src/layout/WelcomeMessage.tsx index 678c5e7..7b43c47 100644 --- a/frontend/src/layout/WelcomeMessage.tsx +++ b/frontend/src/layout/WelcomeMessage.tsx @@ -1,7 +1,8 @@ -import { useUser } from "../helpers/UserContext"; import LogoSVG from "/logo.svg"; import { Link } from "react-router-dom"; +import { useUser } from "../helpers/UserContext"; + const scrollToTop = (): void => { window.scrollTo(0, 0); }; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 90575be..a67238e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -15,11 +15,11 @@ import VideoCallPage from "./pages/VideoCallPage.tsx"; import "./styles/styles.scss"; -import { store } from "./redux/store.ts"; +import KeycloakUserProvider from "./helpers/KeycloakUserProvider.tsx"; import MeetingProvider from "./helpers/MeetingProvider.tsx"; import Protected from "./helpers/Protected.tsx"; -import KeycloakUserProvider from "./helpers/KeycloakUserProvider.tsx"; import EditDataPage from "./pages/EditDataPage.tsx"; +import { store } from "./redux/store.ts"; const body = document.getElementsByTagName("body")[0]!; body.className = "bg-my-darker text-my-light"; diff --git a/frontend/src/models/RegisterUserSchema.ts b/frontend/src/models/RegisterUserSchema.ts index bc52389..3abc33c 100644 --- a/frontend/src/models/RegisterUserSchema.ts +++ b/frontend/src/models/RegisterUserSchema.ts @@ -1,6 +1,7 @@ import * as z from "zod"; -import { FrontendUser } from "./User"; + import { PasswordForm } from "./PasswordForm"; +import { FrontendUser } from "./User"; export const userRegisterSchema: z.ZodType> = z.object({ first_name: z diff --git a/frontend/src/pages/EditDataPage.tsx b/frontend/src/pages/EditDataPage.tsx index d364d1c..390cc08 100644 --- a/frontend/src/pages/EditDataPage.tsx +++ b/frontend/src/pages/EditDataPage.tsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; + +import EditDetails from "../components/EditDetails"; +import EditPassword from "../components/EditPassword"; +import EditPhoto from "../components/EditPhoto"; import Footer from "../components/Footer"; import Navbar from "../components/Navbar"; import Transition from "../components/Transition"; -import EditDetails from "../components/EditDetails"; -import EditPhoto from "../components/EditPhoto"; -import EditPassword from "../components/EditPassword"; import { useUser } from "../helpers/UserContext"; function EditDataPage() { diff --git a/frontend/src/pages/FriendsPage.tsx b/frontend/src/pages/FriendsPage.tsx index cd194c8..044847c 100644 --- a/frontend/src/pages/FriendsPage.tsx +++ b/frontend/src/pages/FriendsPage.tsx @@ -1,21 +1,20 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { useMeeting } from "../helpers/MeetingProvider"; - import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; import Footer from "../components/Footer"; +import FoundUser from "../components/FoundUser"; +import Friend from "../components/Friend"; import FriendRequest from "../components/FriendRequest"; import Navbar from "../components/Navbar"; -import User from "../models/User"; -import dataService from "../services/data"; +import PaginatorV2 from "../components/PaginatorV2"; import Transition from "../components/Transition"; +import { useMeeting } from "../helpers/MeetingProvider"; import { useProtected } from "../helpers/Protected"; -import FoundUser from "../components/FoundUser"; -import Friend from "../components/Friend"; -import PaginatorV2 from "../components/PaginatorV2"; import { useUser } from "../helpers/UserContext"; +import User from "../models/User"; +import dataService from "../services/data"; function FriendsPage() { const navigate = useNavigate(); diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 1a507b3..4f66069 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -1,9 +1,9 @@ import Footer from "../components/Footer"; +import CallToAction from "../layout/CallToAction"; import PhotoAndText from "../layout/PhotoAndText"; import Reasons from "../layout/Reasons"; -import WelcomeMessage from "../layout/WelcomeMessage"; -import CallToAction from "../layout/CallToAction"; import Technologies from "../layout/Technologies"; +import WelcomeMessage from "../layout/WelcomeMessage"; function HomePage() { return ( diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index a2432ce..4d0270c 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -1,7 +1,8 @@ +import { useEffect, useState } from "react"; + +import Banner from "../components/Banner"; import Footer from "../components/Footer"; import LoginBox from "../components/LoginBox"; -import Banner from "../components/Banner"; -import { useEffect, useState } from "react"; import Transition from "../components/Transition"; function LoginPage() { diff --git a/frontend/src/pages/MessagingPage.tsx b/frontend/src/pages/MessagingPage.tsx index 70984f5..f1f17a3 100644 --- a/frontend/src/pages/MessagingPage.tsx +++ b/frontend/src/pages/MessagingPage.tsx @@ -1,11 +1,11 @@ import { useEffect, useRef } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import Navbar from "../components/Navbar"; -import Footer from "../components/Footer"; import ChatBox from "../components/ChatBox"; -import { useUser } from "../helpers/UserContext"; +import Footer from "../components/Footer"; +import Navbar from "../components/Navbar"; import { useProtected } from "../helpers/Protected"; +import { useUser } from "../helpers/UserContext"; function MessagingPage() { const navigate = useNavigate(); diff --git a/frontend/src/pages/PageNotFound.tsx b/frontend/src/pages/PageNotFound.tsx index a4ff74c..8bd1ae8 100644 --- a/frontend/src/pages/PageNotFound.tsx +++ b/frontend/src/pages/PageNotFound.tsx @@ -1,4 +1,5 @@ import { Link } from "react-router-dom"; + import Banner from "../components/Banner"; function PageNotFound() { diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 88a0b2a..4236f2b 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -1,12 +1,13 @@ -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; + import Footer from "../components/Footer"; import Navbar from "../components/Navbar"; -import { useUser } from "../helpers/UserContext"; import Profile from "../components/Profile"; import Transition from "../components/Transition"; import { useMeeting } from "../helpers/MeetingProvider"; import { useProtected } from "../helpers/Protected"; +import { useUser } from "../helpers/UserContext"; function ProfilePage() { const navigate = useNavigate(); diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx index d935783..8a615da 100644 --- a/frontend/src/pages/RegisterPage.tsx +++ b/frontend/src/pages/RegisterPage.tsx @@ -1,7 +1,8 @@ +import { useEffect, useState } from "react"; + +import Banner from "../components/Banner"; import Footer from "../components/Footer"; import RegisterBox from "../components/RegisterBox"; -import Banner from "../components/Banner"; -import { useEffect, useState } from "react"; import Transition from "../components/Transition"; function RegisterPage() { diff --git a/frontend/src/pages/SearchPage.tsx b/frontend/src/pages/SearchPage.tsx index 30ee10a..e21d416 100644 --- a/frontend/src/pages/SearchPage.tsx +++ b/frontend/src/pages/SearchPage.tsx @@ -1,14 +1,14 @@ -import Navbar from "../components/Navbar"; -import Footer from "../components/Footer"; import { useEffect, useState } from "react"; -import FoundUser from "../components/FoundUser"; -import { useUser } from "../helpers/UserContext"; -import User from "../models/User"; -import Transition from "../components/Transition"; -import Search from "../components/Search"; +import Footer from "../components/Footer"; +import FoundUser from "../components/FoundUser"; +import Navbar from "../components/Navbar"; import PaginatorV2 from "../components/PaginatorV2"; +import Search from "../components/Search"; +import Transition from "../components/Transition"; import { useProtected } from "../helpers/Protected"; +import { useUser } from "../helpers/UserContext"; +import User from "../models/User"; function SearchPage() { // Logic diff --git a/frontend/src/pages/VideoCallPage.tsx b/frontend/src/pages/VideoCallPage.tsx index bd93d07..59b455e 100644 --- a/frontend/src/pages/VideoCallPage.tsx +++ b/frontend/src/pages/VideoCallPage.tsx @@ -1,21 +1,22 @@ -import Navbar from "../components/Navbar"; -import Footer from "../components/Footer"; -import { useUser } from "../helpers/UserContext"; -import { useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { Socket } from "socket.io-client"; -import stunServers from "../stun/stunServers"; -import Meeting from "../models/Meeting"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { - faPhoneSlash, faMicrophone, faMicrophoneSlash, + faPhoneSlash, faVideo, faVideoSlash, } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useRef, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Socket } from "socket.io-client"; + +import Footer from "../components/Footer"; +import Navbar from "../components/Navbar"; import { useMeeting } from "../helpers/MeetingProvider"; import { useProtected } from "../helpers/Protected"; +import { useUser } from "../helpers/UserContext"; +import Meeting from "../models/Meeting"; +import stunServers from "../stun/stunServers"; function VideoCallPage() { const { user } = useProtected(); diff --git a/frontend/src/redux/store.ts b/frontend/src/redux/store.ts index 30ecd7c..f8e22d7 100644 --- a/frontend/src/redux/store.ts +++ b/frontend/src/redux/store.ts @@ -1,4 +1,5 @@ import { combineReducers, createStore } from "redux"; + import friendsReducer from "./reducers/friendsReducer"; const rootReducer = combineReducers({ diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 9cc50ea..1ff0da0 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ From 2b329098b55ad719cf8a6ea375b3b09bbad41700 Mon Sep 17 00:00:00 2001 From: gf-rog Date: Thu, 16 May 2024 16:41:35 +0200 Subject: [PATCH 26/26] Remove debug console.log --- backend/src/routes/userFriendsRoute.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/routes/userFriendsRoute.ts b/backend/src/routes/userFriendsRoute.ts index e3bb044..656cadc 100644 --- a/backend/src/routes/userFriendsRoute.ts +++ b/backend/src/routes/userFriendsRoute.ts @@ -59,7 +59,6 @@ friendsRouter.get( const friends = await getFriends(session, userId, page - 1, maxUsers); if (friends === null) { - console.log(friends); return userNotFoundRes(res); } @@ -71,7 +70,6 @@ friendsRouter.get( const pageCount = Number( (friendsCount.toBigInt() + maxUsersBig - 1n) / maxUsersBig, ); - console.log(pageCount); return res.json({ status: "ok", pageCount, friends }); } catch (err) { console.log("Error:", err);