diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0e088fac26bb629ca97757939afee5482636b0da --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Ludvig Damberg (ludda756) +Ludvig Hillert (ludhi842) + +We've created a social media applicaiton that is reddit-like. This is meant to have similar functionality to reddit, built with the MERN stack together with some usefull libraries, managed through NodeJS. Ofcourse this wont be an equal to reddit but the purpose is to create something in acceptable quality with similar basic functionality. + +During the project we have updated through this gitlab, ofcourse. But also through github since we both think its smoother and are more "used" and comfortable using it. So to get a little better detail over some of the commits/pushes and commenting, please check out the github-repo which will be linked below. + +Github-repo: https://github.com/ludvigdamberg/TDDD27 + diff --git a/backend/index.js b/backend/index.js index e383d9b561edf2ae80ae298f531cba2425d6b3ec..9f83ea5a06d27b55c6da9fc0cfd4f1f4dbc81eed 100644 --- a/backend/index.js +++ b/backend/index.js @@ -3,7 +3,6 @@ const express = require('express') const cors = require('cors') require('dotenv').config() const Routes = require('./routes/routes') -const { errorHandler, notFound } = require('./middlewares/errorMiddleware') const checkUserExists = require('./middlewares/MulterMiddleware') @@ -14,8 +13,6 @@ app.use(express.json()) app.use(express.static("public")) app.use(cors()) app.use(Routes) -app.use(notFound) -app.use(errorHandler) const PORT = process.env.PORT || 5000 diff --git a/backend/middlewares/auth.js b/backend/middlewares/auth.js deleted file mode 100644 index 3483aa10dc30314d0488b560104fb8ba59066621..0000000000000000000000000000000000000000 --- a/backend/middlewares/auth.js +++ /dev/null @@ -1,29 +0,0 @@ -/*const jwt = require('jsonwebtoken'); -const config = require('config'); -require('dotenv').config() - - -const auth = (req, res, next) => { - // Get token from header - const token = req.headers['Authorization']; - console.log(token) - - // Check if not token - if (!token) { - return res.status(401).json({ msg: 'No token, authorization denied' }); - } - - try { - // Verify token - const decoded = jwt.verify(token, process.env.JWT_SECRET); - - // Add user from payload - req.user = decoded.user; - next(); - } catch (err) { - res.status(401).json({ msg: 'Token is not valid' }); - } - } - - module.exports = auth; -*/ \ No newline at end of file diff --git a/backend/middlewares/errorMiddleware.js b/backend/middlewares/errorMiddleware.js deleted file mode 100644 index 10d13d21679d49edb96a5da8b99b6e58e9079963..0000000000000000000000000000000000000000 --- a/backend/middlewares/errorMiddleware.js +++ /dev/null @@ -1,16 +0,0 @@ -const notFound = (req,res,next) => { - const error = new Error(`Not found ${req.originalUrl}`) - res.status(400) - next(error) -} - -const errorHandler = (err,req,res,next) => { - const statusCode = res.statusCode === 200 ? 500 : res.statusCode - res.status(statusCode) - res.json({ - message:err.message, - stack: process.env.NODE.ENV === "production" ? null : err.stack, - }) -} - -module.exports = {notFound,errorHandler} \ No newline at end of file diff --git a/backend/models/user.js b/backend/models/user.js index 27cf264fc9dae46767301da28fb18bfbdfd21e4c..6890f95e1950185aad0c632e95e9a3438acc463a 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -1,7 +1,5 @@ const mongoose = require('mongoose') -const jwt = require('jsonwebtoken') require('dotenv').config() -const passwordComplexity = require('joi-password-complexity') const bcrypt = require('bcrypt') const userSchema = new mongoose.Schema({ diff --git a/backend/public/uploads/043edabc-bff2-4ccf-b2bd-401e86ccc6a5_.jpg b/backend/public/uploads/043edabc-bff2-4ccf-b2bd-401e86ccc6a5_.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d62454433216ead19366d7a5e46c7d8cd8601be4 Binary files /dev/null and b/backend/public/uploads/043edabc-bff2-4ccf-b2bd-401e86ccc6a5_.jpg differ diff --git a/backend/routes/routes.js b/backend/routes/routes.js index f9882542649654e2f5b729c0defd8e67235956a0..a6b818641e526bd2cb08c33a2908dbe2599e3a76 100644 --- a/backend/routes/routes.js +++ b/backend/routes/routes.js @@ -7,7 +7,6 @@ const multer = require('multer') const generateToken = require('../utils/generateToken') const asyncHandler = require('express-async-handler') const router = Router() -const auth = require('../middlewares/auth') const jwt = require('jsonwebtoken'); const fs = require('fs') const commentModel = require('../models/comment') @@ -17,7 +16,7 @@ require('dotenv').config() -//Log in +//Logga in router.post('/login', async (req, res) => { const { email, password } = req.body @@ -40,7 +39,8 @@ router.post('/login', async (req, res) => { } }) -//Register +//Registrering + router.post("/register", uploadMiddleware.single("photo"), asyncHandler(async (req, res) => { const email = req.body.email @@ -72,13 +72,11 @@ router.post("/register", uploadMiddleware.single("photo"), asyncHandler(async (r } })) -//Create post +//Skapa inlägg router.post("/post", uploadMiddleware.single("photo"), (req, res) => { const photo = req.file.filename - - const post = new postModel( { name: req.body.name, @@ -96,7 +94,7 @@ router.post("/post", uploadMiddleware.single("photo"), (req, res) => { }) -//Get posts +//Hämta inlägg router.get('/posts', async (req, res) => { const loads = req.headers.loads @@ -109,7 +107,7 @@ router.get('/posts', async (req, res) => { } else if (req.headers.search.length > 0) { - const search = req.headers.search; // Get the search query from the request query parameters + const search = req.headers.search; console.log(search) const posts = await postModel.find({ @@ -127,17 +125,18 @@ router.get('/posts', async (req, res) => { }) + +//Hämta resusltat i databasen utefter en sökning + router.get('/search', async (req, res) => { - const search = req.headers.search; // Get the search query from the request query parameters + const search = req.headers.search; - console.log(search) const posts = await postModel.find({ $or: [ { name: { $regex: searchString, $options: "i" } }, { recipe: { $regex: searchString, $options: "i" } } - // add as many components as needed ], }) @@ -147,7 +146,7 @@ router.get('/search', async (req, res) => { }) -//get profile posts +//Hämta profilinlägg router.get('/profilePosts', async (req, res) => { const userId = req.headers.author @@ -156,23 +155,21 @@ router.get('/profilePosts', async (req, res) => { .populate('author') .exec() .then(posts => { - res.json(posts) + res.send(posts) }) }) -//fetch profile +//Hämta specifik profil router.get('/profile', (req, res) => { - // Get the user's token from local storage + + const token = req.headers.authorization.split(' ')[1]; - // Use jwt.verify to decode the token and get the user ID jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { if (err) { - // If the token is invalid, send an error response return res.status(401).json({ message: 'Invalid token' }); } const userId = decoded.userId; - // If the token is valid, use the user ID to fetch the user data from the database userModel.findById(userId) @@ -185,6 +182,8 @@ router.get('/profile', (req, res) => { }); }); + +//Ta bort ett inlägg och radera bild ur databasen router.post('/deletePost', async (req, res) => { @@ -202,7 +201,7 @@ router.post('/deletePost', async (req, res) => { }) -// adding user id to upvote list +// Upvote, lägger till en användares id i en array router.put('/upvote', async (req, res) => { console.log(req.body) @@ -232,17 +231,7 @@ router.put('/upvote', async (req, res) => { }) -router.get("/api/posts/search", (req, res) => { - const search = req.headers.search; // Get the search query from the request query parameters - postModel.find({ $text: { $search: search } }, (err, posts) => { - if (err) { - console.log(err); - return res.status(500).send("Internal server error"); - } - return res.json(posts); // Send the matching posts back to the frontend - }); -}); - +//kommentera ett inlägg router.post("/comment", (req, res) => { console.log(req.body) @@ -259,6 +248,7 @@ router.post("/comment", (req, res) => { }) +//spara kommentaren i inläggsmodellen router.put('/savecomment', async (req, res) => { console.log(req.body) @@ -283,7 +273,7 @@ router.put('/savecomment', async (req, res) => { } }) - +//hämta kommentarer för ett specifikt inlägg router.get('/getcomments', async (req, res) => { @@ -313,5 +303,4 @@ router.get('/getcomments', async (req, res) => { - module.exports = router \ No newline at end of file diff --git a/frontend/src/components/Posts.js b/frontend/src/components/Posts.js index e398d82c65ebbd2b5b9d3c4eff514961399a14aa..a5d1f5c060fb1dfb5f00c0d4101a76a94754b348 100644 --- a/frontend/src/components/Posts.js +++ b/frontend/src/components/Posts.js @@ -10,14 +10,14 @@ import buttons from '../styles/buttons.module.css' const Posts = () => { const [posts, setPosts] = useState([]) - const [loading, setLoading] = useState(false) + const [loading, setLoading] = useState(true) const [drinks, setDrinks] = useState([]) const [loads, setLoads] = useState(1) const [search, setSearch] = useState("") const [searchChange, setSearchChange] = useState() const [profileData, setProfileData] = useState() const [comment, setComment] = useState("") - const [commentData, setCommentData] = useState() + const [commentData, setCommentData] = useState([]) const [showComments, setShowComments] = useState(false) const get_profile = () => { @@ -34,18 +34,18 @@ const Posts = () => { setProfileData(res.data) }) - setLoading(false) + setTimeout(() => { + setLoading(false) + }, 1000) } const loadPosts = () => { - setLoading(true) console.log(loads) axios.get("http://localhost:5000/posts", { headers: { loads: loads, search: search } }) .then((res) => { setPosts(res.data) - setLoading(false) }) setLoads(loads + 1) } @@ -101,6 +101,7 @@ const Posts = () => { console.log(id) setLoading(true) + axios.get("http://localhost:5000/getcomments", { headers: { postId: id } }) .then((res) => { @@ -109,23 +110,17 @@ const Posts = () => { }) - - setLoading(false) + setTimeout(() => { + setLoading(false) + }, 1000) + } - if (loading) { - return ( - <div> - <Loading /> - </div> - ) - } else - console.log(posts) + return ( - <div> <input className={styles.searchbar} type="text" @@ -183,6 +178,8 @@ const Posts = () => { <button className={buttons.button3} onClick={() => setShowComments(false)}>hide comments</button> {loading ? (<></>) : commentData.map((comment) => { + if (post.comments.includes(comment._id)) { + return( <> <div className={styles.comment_header}> @@ -194,7 +191,7 @@ const Posts = () => { </div> </> - )})} + )}})} </div> diff --git a/frontend/src/components/ProfilePosts.js b/frontend/src/components/ProfilePosts.js index 862823ae234d73275447161b48c45b9f786546bc..f075b19a48a890eb81eeedbf202c0a74965f9c2a 100644 --- a/frontend/src/components/ProfilePosts.js +++ b/frontend/src/components/ProfilePosts.js @@ -29,10 +29,10 @@ const ProfilePosts = () => { ) .then((res) => { setPosts(res.data); - }).catch(err => console.log(err)) - - setLoading(false) + setTimeout(() => { + setLoading(false) + }, 1000) } const deletePost = async (image, id) => { @@ -48,26 +48,26 @@ const ProfilePosts = () => { } useEffect(() => { - setLoading(true) loadPosts() + }, []) if (loading) { return ( <div> - <Loading /> + <></> </div> ) } else return ( <div className={styles.container}> - <div>{posts.map((post) => { + <div>{posts.map((post,index) => { return ( - <div className={styles.wrapper}> - <div className={styles.posts_container} key={post._id}> - - <img className={styles.post_img_container2} src={`http://localhost:5000/uploads/${post.photo}`} /> - + <div className={styles.wrapper} key={post._id}> + <div className={styles.posts_container} > + + <img className={styles.post_img_container2} src={`http://localhost:5000/uploads/${post.photo}`} /> + <div> <h1>{post.name}</h1> <div > diff --git a/frontend/src/pages/Feed.js b/frontend/src/pages/Feed.js index 974dd143ae604a496c027439a6337ed4b10fc7df..ab72a0f34a1f70f08057552d8ef706a782fc0649 100644 --- a/frontend/src/pages/Feed.js +++ b/frontend/src/pages/Feed.js @@ -8,6 +8,7 @@ import { FaArrowLeft } from 'react-icons/fa'; import { Link } from 'react-router-dom' import axios from 'axios' import { useNavigate } from 'react-router-dom' +import Loading from '../components/Loading' const Feed = () => { const [profileData, setProfileData] = useState() @@ -21,7 +22,7 @@ const Feed = () => { localStorage.removeItem("token") setIsLoggedIn(false) navigate('/') - } + } const fetchProfile = async () => { @@ -35,27 +36,30 @@ const Feed = () => { } }).then((res) => { setProfileData(res.data); - setLoading(false) }).catch(err => console.log(err)) + setTimeout(() => { + setLoading(false) + }, 1000) + } const checkLoggedIn = async () => { const token = localStorage.getItem('token'); if (!token) { - logout() - return; + logout() + return; } const response = await axios.get('http://localhost:5000/profile', { - headers: { Authorization: `Bearer ${token}` }, + headers: { Authorization: `Bearer ${token}` }, }).then((res) => { - setIsLoggedIn(true); + setIsLoggedIn(true); }).catch(err => { - localStorage.removeItem("token") - logout() + localStorage.removeItem("token") + logout() }) - } + } useEffect(() => { @@ -73,10 +77,14 @@ const Feed = () => { } } - if (loading == false) { - + if (loading === true) { return ( + <Loading /> + ) + } + else { + return ( <div className={styles.feed}> <div className={styles.header}> @@ -99,9 +107,9 @@ const Feed = () => { </div> ) - } else { - return (<div>loading</div>) + } + } export default Feed \ No newline at end of file diff --git a/frontend/src/pages/Profile.js b/frontend/src/pages/Profile.js index 182b3a83fbb06e9b8507802ece405b17ffdd63aa..cad8709ed0379eca3ed4453578019eaa15055f8f 100644 --- a/frontend/src/pages/Profile.js +++ b/frontend/src/pages/Profile.js @@ -9,14 +9,17 @@ import Loading from '../components/Loading'; import { FaArrowLeft } from 'react-icons/fa'; import buttons from '../styles/buttons.module.css' import jwt_decode from 'jwt-decode'; + + const Profile = () => { const [profileData, setProfileData] = useState() - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); const navigate = useNavigate(); - const [posts, setPosts] = useState(null) + const [posts, setPosts] = useState() + let upvotes = 0 const loadPosts = async () => { @@ -28,16 +31,20 @@ const Profile = () => { await axios.get('http://localhost:5000/profilePosts', { headers: { author: `${author}` } }, ) - .then((res) => { - setPosts(res.data); + .then((res) => { + setPosts(res.data); + + }).catch(err => console.log(err)) + setTimeout(() => { + setLoading(false) + }, 1000) + + } + - }).catch(err => console.log(err)) - setLoading(false) -} - const checkLoggedIn = async () => { const token = localStorage.getItem('token'); @@ -57,10 +64,8 @@ const Profile = () => { const fetchProfile = async () => { - setLoading(true) const token = localStorage.getItem('token'); - console.log(token) axios.get('http://localhost:5000/profile', { headers: { @@ -71,7 +76,6 @@ const Profile = () => { }).catch(err => console.log(err)) - setLoading(false) } const logout = () => { @@ -87,15 +91,28 @@ const Profile = () => { }, []) - if (!profileData) { + const count_upvotes = () => { + + posts.map((post) => { + upvotes += post.upvotes.length + console.log(upvotes) + }) + + } + + + + if (loading === true) { return ( <Loading /> ) - } else if (profileData) { - console.log(profileData) + } else if (loading === false) { + count_upvotes() return ( + <> + <div className={styles.header}> <h1>Profile Page</h1> @@ -124,7 +141,7 @@ const Profile = () => { <div> <h3>Posts</h3></div> </div> <div className={styles.info_slot}> - <div> <h1>0</h1></div> + <div> <h1>{upvotes}</h1></div> <div> <h3>Upvotes</h3></div> </div> </div> diff --git a/frontend/src/styles/loading.module.css b/frontend/src/styles/loading.module.css index d623e08f87617833de79c7c46d1a949e12d67416..8c2eaeaeefde61bd50fea59971ccedcb3b01934a 100644 --- a/frontend/src/styles/loading.module.css +++ b/frontend/src/styles/loading.module.css @@ -3,6 +3,7 @@ height: 100vh; position: fixed; background-color: rgba(0, 0, 0, 0.342); + } .loadingspinner { --square: 26px;