diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 0f42389..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index e033760..d28864a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # customs ignore +.DS_Store # Logs logs diff --git a/client/src/lib/index.js b/client/src/lib/index.js deleted file mode 100644 index 856f2b6..0000000 --- a/client/src/lib/index.js +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/client/src/routes/(app)/+layout.svelte b/client/src/routes/(app)/+layout.svelte index 5da07dd..d93db1a 100644 --- a/client/src/routes/(app)/+layout.svelte +++ b/client/src/routes/(app)/+layout.svelte @@ -1,11 +1,12 @@
-
- + +
+ + +
+ + + + + -
+ + + + + + -
- {$user.firstname} {$user.lastname} +
+ {#if loading} +
Loading...
+ {:else} + {$user.first_name} {$user.last_name} User avatar -
- {#if $page.url.pathname != "/home"} - - - - {/if} - - {#if $page.url.pathname != "/settings"} - - - - {/if} -
-
-
+ {/if} + + - + \ No newline at end of file + diff --git a/client/src/routes/(app)/home/+page.svelte b/client/src/routes/(app)/home/+page.svelte index 4e64034..6b0ec76 100644 --- a/client/src/routes/(app)/home/+page.svelte +++ b/client/src/routes/(app)/home/+page.svelte @@ -5,6 +5,7 @@ import { goto } from "$app/navigation"; let backendUrl; + let loading = true; onMount(async () => { backendUrl = `http://${window.location.hostname}:3001/`; @@ -12,24 +13,18 @@ }); async function fetchPosts() { + loading = true; try { - const response = await fetch(`${backendUrl}getPosts`); + const response = await fetch(`${backendUrl}getPosts`, { + method: "GET", + credentials: "include", + }); if (!response.ok) { throw new Error("Network response was not ok " + response.statusText); } const data = await response.json(); - - data.posts.forEach((post) => { - post.comments = []; - post.likes = 0; - }); - posts.set( - data.posts.map((post) => ({ - ...post, - comments: [], - likes: 0, - })) - ); + posts.set(data.posts); + loading = false; } catch (error) { console.error( "There has been a problem with your fetch operation:", @@ -38,43 +33,80 @@ } } - let openedCommentsPostId = 0; + let openedCommentsId = 0; let newComment = ""; - function addComment(postId) { - const postsData = get(posts); - const postIndex = postsData.findIndex((post) => post.id === postId); - if (postIndex !== -1) { - const updatedPost = { ...postsData[postIndex] }; - updatedPost.comments.push({ - commenter: `${get(user).firstname} ${get(user).lastname}`, - comment: newComment, + async function addComment(post_id) { + try { + const response = await fetch(`${backendUrl}addComment`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ post_id: post_id, content: newComment }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok " + response.statusText); + } + const data = await response.json(); + + posts.update((postsArray) => { + const updatedPosts = postsArray.map((post) => { + if (post.post_id === post_id) { + post.comments.push({ + comment_id: data.comment_id, + user_id: get(user).user_id, + content: newComment, + created_at: new Date().toISOString(), + first_name: get(user).first_name, + last_name: get(user).last_name, + }); + } + return post; + }); + return updatedPosts; }); - postsData[postIndex] = updatedPost; - posts.set(postsData); newComment = ""; + } catch (error) { + console.error("There has been a problem with your comment", error); } } - function addLike(postId) { - fetch(`${backendUrl}likePost`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ postId }), - }) - .then(() => { - const postsData = get(posts); - const postIndex = postsData.findIndex((post) => post.id === postId); - if (postIndex !== -1) { - postsData[postIndex].likes += 1; - posts.set(postsData); - } - }) - .catch((err) => { - console.log("Error", err); + async function addLike(post_id) { + try { + const response = await fetch(`${backendUrl}likePost`, { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ post_id: post_id }), + }); + + if (!response.ok) { + throw new Error("Network response was not ok " + response.statusText); + } + const data = await response.json(); + console.log(data); + + posts.update((postsArray) => { + const updatedPosts = postsArray.map((post) => { + if (post.post_id === post_id) { + if(data.message === "Like removed successfully") + post.like_count = Number(post.like_count) - 1; + else if(data.message === "Like created successfully"){ + post.like_count = Number(post.like_count) + 1; + } + } + return post; + }); + return updatedPosts; }); + } catch (error) { + console.error("There has been a problem with your like", error); + } } @@ -82,61 +114,87 @@

News Feed

- Write a Blog +
- -
    - {#each $posts as post (post.id)} -
  • -
    - Author avatar -
    -

    {post.title}

    - by {post.firstname} {post.lastname} + {#if loading} +
    +

    Loading...

    +
    + {:else} +
      + {#each $posts as post (post.post_id)} +
    • +
      + Author avatar +
      +

      {post.title}

      + by {post.first_name} {post.last_name} +
      -
    -

    {@html post.content}

    - - - {#if openedCommentsPostId === post.id} -
      - {#each post.comments as comment} -
    • - {comment.commenter}: {@html comment.comment} -
    • - {/each} -
    - +

    {@html post.content}

    + - {/if} -
  • - {/each} -
+ {#if openedCommentsId === post.post_id} +
    + {#each post.comments as comment} +
  • + {comment.first_name} + {comment.last_name}: {@html comment.content} +
  • + {/each} +
+ + + {/if} + + {/each} + + {/if}
+ + diff --git a/client/src/routes/(app)/settings/+page.svelte b/client/src/routes/(app)/settings/+page.svelte index 5c909b4..cc6cf2a 100644 --- a/client/src/routes/(app)/settings/+page.svelte +++ b/client/src/routes/(app)/settings/+page.svelte @@ -4,6 +4,12 @@ import { goto } from "$app/navigation"; let backendUrl; + let showAlert = false; + let validationError = ""; + let userForm; + let file; + + $: userForm = $user; onMount(() => { backendUrl = `http://${window.location.hostname}:3001/`; @@ -29,52 +35,14 @@ } } - let showAlert = false; - let validationError = ""; - - let userForm = $user; - let file; - - function updateUser(newData) { - user.update((u) => { - return { ...u, ...newData }; - }); - } - function handleFileChange(event) { file = event.target.files[0]; } - async function saveChanges() { + async function handleSaveChanges() { try { - // First, send JSON data - const response = await fetch(`${backendUrl}updateUser`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userForm), - }); - - if (!response.ok) { - throw new Error("Failed to save changes"); - } - - // Then, send the image as FormData if it exists - if (file) { - const formData = new FormData(); - formData.append("avatar", file, file.name); - - const imgResponse = await fetch(`${backendUrl}updateUserAvatar`, { - method: "POST", - body: formData, - }); - - if (!imgResponse.ok) { - throw new Error("Failed to upload image"); - } - } - + await saveUserData(); + if (file) await uploadAvatar(); console.log("Changes and image upload successful:", userForm); updateUser(userForm); } catch (err) { @@ -83,6 +51,37 @@ validationError = err.message || "Something went wrong"; } } + + async function saveUserData() { + const response = await fetch(`${backendUrl}updateUser`, { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(userForm) + }); + if (!response.ok) throw new Error("Failed to save changes"); + } + + async function uploadAvatar() { + const formData = new FormData(); + formData.append("avatar", file, file.name); + const imgResponse = await fetch(`${backendUrl}updateUserAvatar`, { + method: "POST", + credentials: "include", + body: formData + }); + if (!imgResponse.ok) throw new Error("Failed to upload image"); + } + + async function deleteUser() { + const response = await fetch(`${backendUrl}user`, { + method: "DELETE", + credentials: "include", + }); + if (!response.ok) throw new Error("Failed to delete user"); + + goto("/"); + }
@@ -90,7 +89,7 @@

Settings

-
+
@@ -110,7 +109,7 @@ @@ -134,6 +133,7 @@ @@ -152,6 +152,20 @@ /> + {#if showAlert} + + {/if} +
Logout -
- {#if showAlert} - - {/if} + +
+ +

Danger Zone

+
\ No newline at end of file diff --git a/client/src/routes/(app)/write/+page.svelte b/client/src/routes/(app)/write/+page.svelte index a37d541..fa6b5d5 100644 --- a/client/src/routes/(app)/write/+page.svelte +++ b/client/src/routes/(app)/write/+page.svelte @@ -1,13 +1,66 @@ + function Upload() { + try { + fetch(`${backendUrl}write`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({title:Post.Title, content:Post.Content}) + }); + } catch(err) { + console.error(err); + } + + Reset(); + } + + function Reset(){ + Post.Title = ""; + Post.Content = ""; + } +
-
\ No newline at end of file +
+

Writing Section

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ diff --git a/client/src/routes/login/+page.svelte b/client/src/routes/login/+page.svelte index c62e244..4d73651 100644 --- a/client/src/routes/login/+page.svelte +++ b/client/src/routes/login/+page.svelte @@ -3,8 +3,6 @@ import { user } from "../../store/store.js"; import { onMount } from "svelte"; - let email = ""; - let password = ""; let showAlert = false; let validationError = ""; let backendUrl; @@ -23,7 +21,7 @@ headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ email, password }), + body: JSON.stringify({ email: $user.email, password: $user.password }), }); if (!response.ok) { validationError = "Invalid login credentials"; @@ -42,10 +40,10 @@ function validateFields() { validationError = ""; - if (!email || !password) { + if (!$user.email || !$user.password) { validationError = "All fields must be filled out."; showAlert = true; - } else if (!/\S+@\S+\.\S+/.test(email)) { + } else if (!/\S+@\S+\.\S+/.test($user.email)) { validationError = "Invalid email format."; showAlert = true; }else{ @@ -85,7 +83,7 @@ >Email @@ -98,7 +96,7 @@ >Password
@@ -200,6 +200,7 @@ > @@ -214,6 +215,7 @@ diff --git a/client/src/store/store.js b/client/src/store/store.js index 271ab93..06f158e 100644 --- a/client/src/store/store.js +++ b/client/src/store/store.js @@ -1,14 +1,35 @@ import { writable } from 'svelte/store'; export let user = writable({ - id: null, - firstname: "", - lastname: "", + user_id: null, + first_name: "", + last_name: "", email: "", password: "", - avatar_path: "", + avatar: "", }); export let posts = writable([ - - ]); \ No newline at end of file + { + "id": 0, + "user_id": 0, + "post_id": 0, + "first_name": "", + "last_name": "", + "avatar": "", + "title": "", + "content": "", + "timestamp": "", + "comments": [ + { + "comment_id": 0, + "user_id": 0, + "content": "", + "created_at": "", + "first_name": "", + "last_name": "", + }, + ], + "like_count": 0, + } +]); \ No newline at end of file diff --git a/src/app.js b/src/app.js index 87fd3c0..b3e47d4 100644 --- a/src/app.js +++ b/src/app.js @@ -4,11 +4,12 @@ const path = require("path"); var cookieParser = require("cookie-parser"); const multer = require("multer"); const { connectDatabase } = require("./database/connectionconfigDb"); +const api = require("./api"); +const { notFound, errorHandler } = require("./middlewares/errors.middleware"); const app = express(); -const api = require("./api"); -const { notFound, errorHandler } = require("./middlewares/errors.middleware"); +// ----------------- MIDDLEWARES ----------------- // if (process.env.NODE_ENV === "production") { app.use(express.static("client/public")); @@ -19,10 +20,10 @@ if (process.env.NODE_ENV === "production") { const storage = multer.diskStorage({ destination: function (req, file, cb) { - cb(null, "src/uploads/"); // Destination folder + cb(null, "src/uploads/"); }, filename: function (req, file, cb) { - cb(null, Date.now() + "-" + file.originalname); // Naming file + cb(null, Date.now() + "-" + file.originalname); }, }); @@ -35,44 +36,39 @@ app.use(express.json()); const corsOptions = { credentials: true, origin: (origin, callback) => { - const allowedOrigin = `${origin}`; - callback(null, allowedOrigin); - } + const allowedOrigin = `${origin}`; + callback(null, allowedOrigin); + }, }; app.use(cors(corsOptions)); +// ----------------- USERS ----------------- // + app.post("/login", async (req, res) => { try { const { email, password } = req.body; if (!email || !password) { res.status(400).json({ message: "Invalid Request" }); - // stop the execution if the username or password is missing return; } - // connect to the database - database = await connectDatabase(); - // Verify the user and password are correct from the post request - // verify the user exists in the request - database - .query( - `SELECT id,email,firstname,lastname,avatar_path FROM users WHERE email='${email}' AND password='${password}';` - ) - .then((result) => { - // If the user is found, return the user information - if (result.rows.length > 0) { - // save the user in the session - res.cookie("user", JSON.stringify(result.rows), { - maxAge: 3600000 * 24, - httpOnly: false, // The cookie is accessible via JavaScript - secure: false, // The cookie will be transmitted over HTTP - }); - res.status(200).json(result.rows); - } else { - res.status(404).json({ message: "User not found" }); - } + const database = await connectDatabase(); + + const result = await database.query( + `SELECT user_id,email,first_name,last_name,avatar FROM users WHERE email='${email}' AND password='${password}';` + ); + + if (result.rows.length > 0) { + res.cookie("user", JSON.stringify(result.rows), { + maxAge: 3600000 * 24, + httpOnly: false, + secure: false, }); + res.status(200).json(result.rows); + } else { + res.status(404).json({ message: "User not found" }); + } } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); @@ -81,28 +77,21 @@ app.post("/login", async (req, res) => { app.post("/signup", upload.single("avatar"), async (req, res) => { try { - // Extract user details from the post request - const { password, email, firstname, lastname } = req.body; + const { password, email, first_name, last_name } = req.body; - // Verify the user and password are correct from the post request - if (!password || !email || !firstname || !lastname) { + if (!password || !email || !first_name || !last_name) { res.status(400).json({ message: "Invalid Request" }); - // stop the execution if the username or password is missing return; } - // verify if the email has a good syntax const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if (!emailRegex.test(email)) { res.status(400).json({ message: "Invalid email syntax" }); - // stop the execution if the email is invalid return; } - // connect to the database const database = await connectDatabase(); - // Verify the user isn't already signup const emailCheckResult = await database.query( `SELECT email FROM users WHERE email = '${email}';` ); @@ -111,17 +100,95 @@ app.post("/signup", upload.single("avatar"), async (req, res) => { return res.status(400).json({ message: "Email already used" }); } - // Get the file path after uploading const avatarPath = path.basename(req.file.path); console.log(avatarPath); + const result = await database.query( + `INSERT INTO users (password, email, first_name, last_name, avatar) VALUES ('${password}', '${email}', '${first_name}', '${last_name}','${avatarPath}') RETURNING *;` + ); + + if (result.rows.length > 0) { + res.cookie("user", JSON.stringify(result.rows[0]), { + maxAge: 3600000 * 24, + httpOnly: false, + secure: false, + }); + res + .status(200) + .json({ message: "User created successfully", user: result.rows[0] }); + } else { + res.status(500).json({ message: "Error creating user" }); + } + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}); + +app.get("/avatar/:id", async (req, res) => { + try { + const { id } = req.params; + + res.sendFile(path.join(__dirname, `./uploads/${id}`)); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}); + +app.get("/currentuser", async (req, res) => { + const userCookie = req.cookies.user; + + if (userCookie) { try { - // insert the user into the database + const user = JSON.parse(userCookie); + res.status(200).json({ user: user }); + } catch (err) { + console.error("Error parsing user data", err); + res.status(400).json({ message: "Bad Request - Invalid Cookie Data" }); + } + } else { + res.status(401).json({ message: "Unauthorized" }); + } +}); + +app.post("/updateUser", async (req, res) => { + try { + const userCookie = req.cookies.user; + + if (userCookie) { + const { password, email, first_name, last_name } = req.body; + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; + + if (!user_id || !password || !email || !first_name || !last_name) { + res.status(400).json({ message: "Invalid Request" }); + return; + } + + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + if (!emailRegex.test(email)) { + res.status(400).json({ message: "Invalid email syntax" }); + return; + } + + const database = await connectDatabase(); + + const emailCheckResult = await database.query( + `SELECT email FROM users WHERE email = '${email}';` + ); + + if (emailCheckResult.rows.length > 0) { + return res.status(400).json({ message: "Email already used" }); + } + + const avatarPath = path.basename(req.file.path); + console.log(avatarPath); + const result = await database.query( - `INSERT INTO users (password, email, firstname, lastname, avatar_path) VALUES ('${password}', '${email}', '${firstname}', '${lastname}','${avatarPath}') RETURNING *;` + `UPDATE users SET password = '${password}', email = '${email}', first_name = '${first_name}', last_name = '${last_name}', avatar = '${avatarPath}' WHERE user_id = '${user_id}' RETURNING *;` ); - // check if the user was created if (result.rows.length > 0) { res.cookie("user", JSON.stringify(result.rows[0]), { maxAge: 3600000 * 24, @@ -130,13 +197,12 @@ app.post("/signup", upload.single("avatar"), async (req, res) => { }); res .status(200) - .json({ message: "User created successfully", user: result.rows[0] }); + .json({ message: "User updated successfully", user: result.rows[0] }); } else { - res.status(500).json({ message: "Error creating user" }); + res.status(500).json({ message: "Error updating user" }); } - } catch (error) { - console.error(error); - res.status(500).json({ message: "Database error" }); + } else { + res.status(401).json({ message: "Unauthorized" }); } } catch (error) { console.error(error); @@ -144,109 +210,155 @@ app.post("/signup", upload.single("avatar"), async (req, res) => { } }); -app.post("/addPost", async (req, res) => { +app.post("/updateUserAvatar", upload.single("avatar"), async (req, res) => { try { const userCookie = req.cookies.user; - // check if the user is logged in + if (userCookie) { - // Extract user details from the post request - const { title, content } = req.body; - const user_id = userCookie[0].id; - - // connect to the database - database = await connectDatabase(); - // insert the post into the database - const result = await database - .query( - `INSERT INTO posts (user_id, title, content) VALUES ('${user_id}', '${title}', '${content}') RETURNING *;` - ) - .then((result) => { - // check if the user was created - if (result.rows.length > 0) { - res - .status(200) - .json({ - message: "Post created successfully", - post: result.rows[0], - }); - } else { - res.status(500).json({ message: "Error creating post" }); - } + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; + + if (!user_id) { + res.status(400).json({ message: "Invalid Request" }); + return; + } + + const avatarPath = path.basename(req.file.path); + console.log(avatarPath); + + const database = await connectDatabase(); + + const result = await database.query( + `UPDATE users SET avatar = '${avatarPath}' WHERE user_id = '${user_id}' RETURNING *;` + ); + + if (result.rows.length > 0) { + res.cookie("user", JSON.stringify(result.rows[0]), { + maxAge: 3600000 * 24, + httpOnly: false, + secure: false, }); - } else - res - .status(401) - .json({ message: "You must be logged in to create a post" }); + res + .status(200) + .json({ message: "User updated successfully", user: result.rows[0] }); + } else { + res.status(500).json({ message: "Error updating user" }); + } + } else { + res.status(401).json({ message: "Unauthorized" }); + } } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); -app.post("/addComment", async (req, res) => { +app.delete("/user", async (req, res) => { try { const userCookie = req.cookies.user; - // check if the user is logged in + if (userCookie) { - // Extract user details from the post request - const { post_id, content } = req.body; - const user_id = userCookie[0].id; + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; - // Verify the user and password are correct from the post request - if (!post_id || !content) { + if (!user_id) { res.status(400).json({ message: "Invalid Request" }); - // stop the execution if the username or password is missing return; } - // connect to the database - database = await connectDatabase(); - // insert the comment into the database - const result = await database - .query( - `INSERT INTO comments (user_id, post_id, content) VALUES ('${user_id}', '${post_id}', '${content}') RETURNING *;` - ) - .then((result) => { - // check if the user was created - if (result.rows.length > 0) { - res.status(200).json({ - message: "Comment created successfully", - comment: result.rows[0], - }); - } else { - res.status(500).json({ message: "Error creating comment" }); - } - }); - } else - res - .status(401) - .json({ message: "You must be logged in to create a comment" }); + const database = await connectDatabase(); + + const result = await database.query( + `DELETE FROM users WHERE user_id = '${user_id}' RETURNING *;` + ); + + if (result.rows.length > 0) { + res.clearCookie("user"); + res.status(200).json({ message: "User deleted successfully", user: result.rows[0] }); + } + } else { + res.status(401).json({ message: "Unauthorized" }); + } } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); + +app.get("/logout", (req, res) => { + res.clearCookie("user"); + res.status(200).json({ message: "Logged out successfully" }); +}); + +// ----------------- POSTS ----------------- // + app.get("/getPosts", async (req, res) => { try { - // connect to the database - database = await connectDatabase(); - - const query = ` - SELECT posts.id as id, - users.firstname as firstName, - users.lastname as lastName, - users.id as user_id, - users.avatar_path as avatar_path, - posts.content, - posts.title, - posts.DATE as timestamp - FROM posts - INNER JOIN users on posts.user_id = users.id - ORDER BY DATE DESC; - `; - - const result = await database.query(query).then((result) => { + const userCookie = req.cookies.user; + + if (userCookie) { + const database = await connectDatabase(); + + const query = ` + WITH + CommentData AS ( + SELECT + comments.post_id, + COALESCE( + JSON_AGG( + JSON_BUILD_OBJECT( + 'comment_id', comments.comment_id, + 'user_id', comments.user_id, + 'first_name', users.first_name, + 'last_name', users.last_name, + 'content', comments.content, + 'created_at', comments.created_at + ) + ) FILTER (WHERE comments.comment_id IS NOT NULL), + '[]' + ) AS comments + FROM + comments + LEFT JOIN + users ON comments.user_id = users.user_id + GROUP BY + comments.post_id + ), + LikeData AS ( + SELECT + post_id, + COUNT(user_id) AS like_count + FROM + likes + GROUP BY + post_id + ) + SELECT + posts.post_id, + posts.user_id, + users.first_name, + users.last_name, + users.avatar, + posts.title, + posts.content, + posts.created_at, + COALESCE(CommentData.comments, '[]') AS comments, + COALESCE(LikeData.like_count, 0) AS like_count + FROM + posts + INNER JOIN + users ON posts.user_id = users.user_id + LEFT JOIN + CommentData ON posts.post_id = CommentData.post_id + LEFT JOIN + LikeData ON posts.post_id = LikeData.post_id + ORDER BY + posts.created_at DESC; + `; + + const result = await database.query(query); + if (result.rows.length > 0) { res.status(200).json({ message: "Posts retrieved successfully", @@ -255,111 +367,139 @@ app.get("/getPosts", async (req, res) => { } else { res.status(404).json({ message: "No posts found" }); } - }); + } } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); -app.get("/getComments", async (req, res) => { +app.post("/write", async (req, res) => { try { - // get the parameters from the request - const { post_id } = req.query; - - // connect to the database - database = await connectDatabase(); - // get the comments of the post from the database - const result = await database - .query(`SELECT * FROM comments WHERE post_id='${post_id}';`) - .then((result) => { - // check if the user was created - if (result.rows.length > 0) { - res.status(200).json({ - message: "Comments retrieved successfully", - comments: result.rows, - }); - } else { - res.status(500).json({ message: "Error retrieving comments" }); - } - }); + const userCookie = req.cookies.user; + + if (userCookie) { + const { title, content } = req.body; + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; + + if (!user_id || !title || !content) { + res.status(400).json({ message: "Invalid Request" }); + return; + } + + const database = await connectDatabase(); + + const query = ` + INSERT INTO posts (user_id, title, content) + VALUES ($1, $2, $3) + RETURNING *; + `; + + const values = [user_id, title, content]; + + const result = await database.query(query, values); + + if (result.rows.length > 0) { + res.status(200).json({ + message: "Post created successfully", + post: result.rows[0], + }); + } else { + res.status(500).json({ message: "Error creating post" }); + } + } else { + res + .status(401) + .json({ message: "You must be logged in to create a post" }); + } } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }); -app.post("/likePost", async (req, res) => { +app.post("/addComment", async (req, res) => { try { const userCookie = req.cookies.user; - // Check if the user is logged in + if (userCookie) { - // Extract user details from the post request - const { user_id, post_id } = req.body; + const { post_id, content } = req.body; + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; - // Verify the user and post IDs are provided - if (!user_id || !post_id) { - return res.status(400).json({ message: "Invalid Request" }); + if (!post_id || !content) { + res.status(400).json({ message: "Invalid Request" }); + return; } - // connect to the database - database = await connectDatabase(); - // Insert the like into the database without using parameterized query + + const database = await connectDatabase(); const result = await database.query( - `INSERT INTO Likes (user_id, post_id) VALUES ('${user_id}', '${post_id}') RETURNING *;` + `INSERT INTO comments (user_id, post_id, content) VALUES ($1, $2, $3) RETURNING *;`, + [user_id, post_id, content] ); - // Check if the like was created if (result.rows.length > 0) { - return res - .status(200) - .json({ message: "Like created successfully", like: result.rows[0] }); + res.status(200).json({ + message: "Comment created successfully", + comment: result.rows[0], + }); } else { - return res.status(500).json({ message: "Error creating like" }); + res.status(500).json({ message: "Error creating comment" }); } } else { - return res + res .status(401) - .json({ message: "You must be logged in to create a like" }); + .json({ message: "You must be logged in to create a comment" }); } } catch (error) { console.error(error); - - // Check if error is due to a unique constraint violation - if (error.code === "23505") { - return res.status(400).json({ message: "User already liked this post" }); - } - - return res.status(500).json({ message: "Internal Server Error" }); + res.status(500).json({ message: "Internal Server Error" }); } }); app.post("/likePost", async (req, res) => { try { const userCookie = req.cookies.user; - // Check if the user is logged in + if (userCookie) { - // Extract user details from the post request const { post_id } = req.body; - const user_id = userCookie[0].id; + const user = JSON.parse(userCookie)[0]; + const user_id = user.user_id; - // Verify the user and post IDs are provided if (!post_id) { return res.status(400).json({ message: "Invalid Request" }); } - // connect to the database - database = await connectDatabase(); - // Insert the like into the database without using parameterized query - const result = await database.query( - `INSERT INTO Likes (user_id, post_id) VALUES ('${user_id}', '${post_id}') RETURNING *;` - ); - // Check if the like was created - if (result.rows.length > 0) { - return res - .status(200) - .json({ message: "Like created successfully", like: result.rows[0] }); + const database = await connectDatabase(); + const checkQuery = + "SELECT * FROM likes WHERE user_id = $1 AND post_id = $2;"; + const checkResult = await database.query(checkQuery, [user_id, post_id]); + + if (checkResult.rows.length > 0) { + const deleteQuery = + "DELETE FROM likes WHERE user_id = $1 AND post_id = $2 RETURNING *;"; + const deleteResult = await database.query(deleteQuery, [ + user_id, + post_id, + ]); + + return res.status(200).json({ + message: "Like removed successfully", + like: deleteResult.rows[0], + }); } else { - return res.status(500).json({ message: "Error creating like" }); + const insertQuery = + "INSERT INTO likes (user_id, post_id) VALUES ($1, $2) RETURNING *;"; + const insertResult = await database.query(insertQuery, [ + user_id, + post_id, + ]); + + return res.status(200).json({ + message: "Like created successfully", + like: insertResult.rows[0], + }); } } else { return res @@ -368,55 +508,10 @@ app.post("/likePost", async (req, res) => { } } catch (error) { console.error(error); - - // Check if error is due to a unique constraint violation - if (error.code === "23505") { - return res.status(400).json({ message: "User already liked this post" }); - } - return res.status(500).json({ message: "Internal Server Error" }); } }); -app.get("/logout", (req, res) => { - // Clear the user cookie; the name 'user' should match the name used when the cookie was set in the login route. - res.clearCookie("user"); - // Sending a successful response. In a real-world scenario, additional cleanup or checks might be necessary. - res.status(200).json({ message: "Logged out successfully" }); -}); - -app.get("/avatar/:id", async (req, res) => { - try { - const { id } = req.params; - - res.sendFile(path.join(__dirname, `./uploads/${id}`)); - - } catch (error) { - console.error(error); - res.status(500).json({ message: "Internal Server Error" }); - } -}); - -app.get("/currentuser", (req, res) => { - // Attempt to retrieve the user data from the cookie instead of the session. - // This is insecure because user data is exposed, and cookies can be manipulated on the client-side. - const userCookie = req.cookies.user; - - if (userCookie) { - let user; - try { - user = JSON.parse(userCookie); - res.status(200).json({ user: user }); - } catch (err) { - console.error("Error parsing user data", err); - res.status(400).json({ message: "Bad Request - Invalid Cookie Data" }); - } - } else { - // No cookie means that the user is not authenticated. - res.status(401).json({ message: "Unauthorized" }); - } -}); - app.use("/api/v1", api); app.use(notFound); app.use(errorHandler); diff --git a/src/database/fixtures.js b/src/database/fixtures.js index 753d400..24149ce 100644 --- a/src/database/fixtures.js +++ b/src/database/fixtures.js @@ -1,80 +1,79 @@ -require('dotenv').config(); -const {connectDatabase} = require('./connectionconfigDb'); +require("dotenv").config(); +const { connectDatabase } = require("./connectionconfigDb"); const seedDatabase = async () => { - const client = await connectDatabase(); + const client = await connectDatabase(); - const insertUsers = ` - INSERT INTO users (password, email, firstname, lastname, avatar_path) - VALUES - ('password1', 'john.doe@example.com', 'John', 'Doe', 'eren_avatar.jpg'), - ('password2', 'jane.doe@example.com', 'Jane', 'Doe', 'eren_avatar.jpg'), - ('password3', 'will.smith@example.com', 'Will', 'Smith', 'eren_avatar.jpg'), - ('password4', 'sarah.connor@example.com', 'Sarah', 'Connor', 'eren_avatar.jpg'), - ('password5', 'mary.jane@example.com', 'Mary', 'Jane', 'eren_avatar.jpg'), - ('password6', 'tony.stark@example.com', 'Tony', 'Stark', 'eren_avatar.jpg'), - ('password7', 'peter.parker@example.com', 'Peter', 'Parker', 'eren_avatar.jpg'), - ('password8', 'bruce.wayne@example.com', 'Bruce', 'Wayne', 'eren_avatar.jpg'); - `; - await client.query(insertUsers); + const insertUsers = ` + INSERT INTO users (password, email, first_name, last_name, avatar) + VALUES + ('password1', 'john.doe@example.com', 'John', 'Doe', 'eren_avatar.jpg'), + ('password2', 'jane.doe@example.com', 'Jane', 'Doe', 'eren_avatar.jpg'), + ('password3', 'will.smith@example.com', 'Will', 'Smith', 'eren_avatar.jpg'), + ('password4', 'sarah.connor@example.com', 'Sarah', 'Connor', 'eren_avatar.jpg'), + ('password5', 'mary.jane@example.com', 'Mary', 'Jane', 'eren_avatar.jpg'), + ('password6', 'tony.stark@example.com', 'Tony', 'Stark', 'eren_avatar.jpg'), + ('password7', 'peter.parker@example.com', 'Peter', 'Parker', 'eren_avatar.jpg'), + ('password8', 'bruce.wayne@example.com', 'Bruce', 'Wayne', 'eren_avatar.jpg'); +`; + await client.query(insertUsers); - const insertPosts = ` - INSERT INTO posts (user_id, title, content, DATE) - VALUES - (1, 'My First Post', 'This is my first post content', '2023-10-01 08:00:00'), - (1, 'My Second Post', 'This is my second post content', '2023-10-02 09:00:00'), - (2, 'Jane''s Thoughts', 'Random musings', '2023-10-03 10:00:00'), - (3, 'Tech Tips', 'Some useful tech tips', '2023-10-04 11:00:00'), - (4, 'Nature Love', 'Why we should love nature', '2023-10-05 12:00:00'), - (5, 'The Importance of Sleep', 'Sleep is crucial for health', '2023-10-06 13:00:00'), - (6, 'My New Invention', 'I just invented something cool', '2023-10-07 14:00:00'), - (7, 'My Photography Journey', 'How I got into photography', '2023-10-08 15:00:00'), - (8, 'Batman vs Superman', 'Who would win?', '2023-10-09 16:00:00'); - `; - await client.query(insertPosts); + const insertPosts = ` + INSERT INTO posts (user_id, title, content, created_at) + VALUES + (1, 'My First Post', 'This is my first post content', '2023-10-01 08:00:00'), + (1, 'My Second Post', 'This is my second post content', '2023-10-02 09:00:00'), + (2, 'Jane''s Thoughts', 'Random musings', '2023-10-03 10:00:00'), + (3, 'Tech Tips', 'Some useful tech tips', '2023-10-04 11:00:00'), + (4, 'Nature Love', 'Why we should love nature', '2023-10-05 12:00:00'), + (5, 'The Importance of Sleep', 'Sleep is crucial for health', '2023-10-06 13:00:00'), + (6, 'My New Invention', 'I just invented something cool', '2023-10-07 14:00:00'), + (7, 'My Photography Journey', 'How I got into photography', '2023-10-08 15:00:00'), + (8, 'Batman vs Superman', 'Who would win?', '2023-10-09 16:00:00'); +`; + await client.query(insertPosts); - const insertComments = ` - INSERT INTO comments (user_id, post_id, content) - VALUES - (1, 1, 'Great post!'), - (2, 1, 'Thanks for sharing'), - (3, 2, 'Very insightful'), - (4, 3, 'I learned a lot'), - (1, 4, 'Love this!'), - (2, 5, 'Nature is wonderful'), - (3, 6, 'Tell us more about your invention'), - (4, 6, 'That sounds amazing'), - (5, 7, 'Photography is an art'), - (6, 8, 'Team Batman all the way'), - (7, 8, 'Superman would definitely win'); - `; - await client.query(insertComments); + const insertComments = ` + INSERT INTO comments (user_id, post_id, content) + VALUES + (1, 1, 'Great post!'), + (2, 1, 'Thanks for sharing'), + (3, 2, 'Very insightful'), + (4, 3, 'I learned a lot'), + (1, 4, 'Love this!'), + (2, 5, 'Nature is wonderful'), + (3, 6, 'Tell us more about your invention'), + (4, 6, 'That sounds amazing'), + (5, 7, 'Photography is an art'), + (6, 8, 'Team Batman all the way'), + (7, 8, 'Superman would definitely win'); +`; + await client.query(insertComments); - const insertLikes = ` - INSERT INTO likes (post_id, user_id) - VALUES - (1, 1), - (1, 2), - (2, 3), - (3, 4), - (2, 1), - (3, 2), - (4, 1), - (5, 2), - (6, 4), - (4, 3), - (7, 5), - (8, 6), - (5, 5), - (6, 7), - (6, 8), - (7, 7); - `; - await client.query(insertLikes); + const insertLikes = ` + INSERT INTO likes (post_id, user_id) + VALUES + (1, 1), + (1, 2), + (2, 3), + (3, 4), + (2, 1), + (4, 1), + (5, 2), + (6, 4), + (4, 3), + (7, 5), + (8, 6), + (5, 5), + (6, 7), + (6, 8), + (7, 7); +`; + await client.query(insertLikes); - console.log('Random data done ✅'); + console.log("Random data done ✅"); - await client.end(); + await client.end(); }; -seedDatabase().catch(err => console.error('Error seeding database:', err)); +seedDatabase().catch((err) => console.error("Error seeding database:", err)); diff --git a/src/database/resetDb.js b/src/database/resetDb.js index a3e2a1d..51e313c 100644 --- a/src/database/resetDb.js +++ b/src/database/resetDb.js @@ -2,18 +2,15 @@ const { Client } = require('pg'); require('dotenv').config(); const resetDatabase = async () => { - // Create a client for database deletion const rootClient = new Client({ host: process.env.HOST_DATABASE, port: process.env.PORT_DATABASE, user: process.env.USER_DATABASE, password: process.env.PASSWORD_DATABASE, - // Connect to the main database when deleting the hackme database database: process.env.BIG_DATABASE }); await rootClient.connect(); - // Drop the hackme database await rootClient.query(`DROP DATABASE IF EXISTS hackme;`) .then(() => console.log('Database deleted successfully')) .catch(err => console.error('Error deleting database:', err)); diff --git a/src/database/setupDb.js b/src/database/setupDb.js index cb62491..562759b 100644 --- a/src/database/setupDb.js +++ b/src/database/setupDb.js @@ -26,46 +26,46 @@ const createDatabase = async () => { // Create a client for the new database const client = await connectDatabase(); - // drop all tables - // await client.query('DROP TABLE users, posts, comments;'); - // Create tables - const createUsersTable = ` CREATE TABLE users ( - id SERIAL PRIMARY KEY, + const createUserTable = `CREATE TABLE users ( + user_id SERIAL PRIMARY KEY, password VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, - firstname VARCHAR(255) NOT NULL, - lastname VARCHAR(255) NOT NULL, - avatar_path VARCHAR(255) NOT NULL + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + avatar VARCHAR(255) NOT NULL );`; - const createPostsTable = `CREATE TABLE posts ( - id SERIAL PRIMARY KEY, + const createPostTable = `CREATE TABLE posts ( + post_id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id), - DATE TIMESTAMP DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE );`; - const createCommentsTable = `CREATE TABLE comments ( - id SERIAL PRIMARY KEY, + const createCommentTable = `CREATE TABLE comments ( + comment_id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, post_id INTEGER NOT NULL, content TEXT NOT NULL, - FOREIGN KEY (user_id) REFERENCES users(id), - FOREIGN KEY (post_id) REFERENCES posts(id), - DATE TIMESTAMP DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE );`; - const createLikes = `CREATE TABLE Likes ( - id SERIAL PRIMARY KEY, - post_id INTEGER REFERENCES posts(id), - user_id INTEGER REFERENCES users(id), - UNIQUE(user_id, post_id) + const createLikeTable = ` + CREATE TABLE likes ( + like_id SERIAL PRIMARY KEY, + post_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + UNIQUE (user_id, post_id) );`; - await client.query(createUsersTable); - await client.query(createPostsTable); - await client.query(createCommentsTable); - await client.query(createLikes); + await client.query(createUserTable); + await client.query(createPostTable); + await client.query(createCommentTable); + await client.query(createLikeTable); await client.end().then(() => console.log('Database created successfully')); };