Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions backend/middleware/authenticate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

/* Request kommer in
[ Middleware ] ← kollar token, är du inloggad?
Ja → fortsätt till routen
Nej → skicka tillbaka 401 Unauthorized */

import jwt from 'jsonwebtoken';

const authenticate = (req, res, next) => {

const token = req.headers.authorization?.split(' ')[1];

if (!token){
return res.status(400).json({ message: "No token"});
}

try{
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.userId = decoded.userId;
next();
} catch (err) {
return res.status(401).json({ message: "invalid token"})
}
}

export default authenticate;
23 changes: 23 additions & 0 deletions backend/models/Thought.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import mongoose from "mongoose";

const userMessage = new mongoose.Schema({

message: {
type: String,
required: true,
},
hearts: {
type: Number,
default: 0,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId, //mongoDB id
ref: "User"
}

}, { timestamps: true }
);

const Message = mongoose.model("Message", userMessage)

export default Message
21 changes: 21 additions & 0 deletions backend/models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import mongoose from "mongoose";

const userSchema = new mongoose.Schema({

email: {
type: String,
required: true,
unique: true,
lowercase: true,
},
password: {
type: String,
required: true,
}
},
{ timestamps: true}
);

const User = mongoose.model("User", userSchema)

export default User
44 changes: 44 additions & 0 deletions backend/my-happy-thoughts-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
my-happy-thoughts-api
/* global use, db */
// MongoDB Playground
// Use Ctrl+Space inside a snippet or a string literal to trigger completions.

const database = 'NEW_DATABASE_NAME';
const collection = 'NEW_COLLECTION_NAME';

// Create a new database.
use(database);

// Create a new collection.
db.createCollection(collection);

// The prototype form to create a collection:
/* db.createCollection( <name>,
{
capped: <boolean>,
autoIndexId: <boolean>,
size: <number>,
max: <number>,
storageEngine: <document>,
validator: <document>,
validationLevel: <string>,
validationAction: <string>,
indexOptionDefaults: <document>,
viewOn: <string>,
pipeline: <pipeline>,
collation: <document>,
writeConcern: <document>,
timeseries: { // Added in MongoDB 5.0
timeField: <string>, // required for time series collections
metaField: <string>,
granularity: <string>,
bucketMaxSpanSeconds: <number>, // Added in MongoDB 6.3
bucketRoundingSeconds: <number>, // Added in MongoDB 6.3
},
expireAfterSeconds: <number>,
clusteredIndex: <document>, // Added in MongoDB 5.3
}
)*/

// More information on the `createCollection` command can be found at:
// https://www.mongodb.com/docs/manual/reference/method/db.createCollection/
12 changes: 9 additions & 3 deletions package.json → backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@
"name": "project-api",
"version": "1.0.0",
"description": "Project API",
"type": "module",
"scripts": {
"start": "babel-node server.js",
"dev": "nodemon server.js --exec babel-node"
"start": "node server.js",
"dev": "nodemon server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"express": "^4.17.3",
"dotenv": "^17.3.1",
"express": "^4.22.1",
"express-list-endpoints": "^7.1.1",
"jsonwebtoken": "^9.0.3",
"mongoose": "^9.1.5",
"nodemon": "^3.0.1"
}
}
File renamed without changes.
64 changes: 64 additions & 0 deletions backend/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import express from "express";
import bcrypt from "bcrypt";
import User from "../models/User.js";
import jwt from "jsonwebtoken";

const router = express.Router();

router.post("/register", async (req, res) => {

const { email, password} = req.body;
if (!email || !password) {
return res.status(400).json({ message: "Both email and password are required"});
}

const existingUser = await User.findOne({ email });
if (existingUser){
return res.status(400).json({ message: "Email is already used"});
}

const hadhedPassword = await bcrypt.hash(password, 10);

const newUser = new User({
email: email,
password: hadhedPassword,
});

await newUser.save();

res.status(201).json({ message: "New User was created"});
});

router.post("/login", async (req, res) => {

const { email, password } = req.body;

if (!email || !password) {
return res.status(400).json({ message: "Both email and password are required"});
}

const user = await User.findOne({ email });

if (!user){
return res.status(400).json({ message: "Wrong email or password"});
}

const isMatch = await bcrypt.compare(password, user.password);

if (!isMatch){
return res.status(400).json({ message: "Wrong email or password"});
}

const token = jwt.sign(
{ userId: user._id }, // payload
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);

res.status(200).json({
message: "Logged In",
token: token
});
});

export default router
106 changes: 106 additions & 0 deletions backend/routes/thoughts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import express from "express";
import Thought from "../models/Thought.js";
import authenticate from "../middleware/authenticate.js";

const router = express.Router()

router.get("/thoughts", async (req, res) => {
try{
const thoughts = await Thought.find()
res.status(200).json(thoughts);
} catch (err){
res.status(400).json({ message: "Could not get thoughts"})
}
})

router.get("/thoughts/:id", async (req, res) => {

try {
const thoughts = await Thought.findById(req.params.id)

if (!thoughts){
res.status(404).json({ error: "Thought id does not exist"})
}
res.status(200).json(thoughtsId);
} catch (err) {
res.status(404).json({ error: "Could not get thoughts"})
}
})

router.post("/thoughts", authenticate, async (req, res) => {

try {
const newThought = new Thought({
message: req.body.message, //comes from req.body
createdBy: req.userId
})
await newThought.save()
res.status(201).json(newThought)

} catch (err){
res.status(400).json({ error: "Could not save thought"})
}
})

router.put("/thoughts/:id", authenticate, async (req, res) => {
try {
const thought = await Thought.findById(req.params.id); //find exsiting thought

if (!thought){
return res.status(404).json({ error: "Thought not found" })
}

if (thought.createdBy.toString() !== req.userId){ //to string needed, for mongo its an objekt not a string
return res.status(403).json({ error: "Not allowed"})
}

thought.message = req.body.message;
await thought.save();
res.status(200).json(thought)

} catch {
res.status(400).json({ error: "Could not update thought"})
}
})

router.delete("/thoughts/:id", authenticate, async (req, res) => {

try{
const thought = await Thought.findById(req.params.id);

if (!thought){
return res.status(404).json({ error: "Thought not found"})
}

if (thought.createdBy.toString() !== req.userId){
return res.status(403).json({ error: "The thought does not exist"})
}
await Thought.findByIdAndDelete(req.params.id);
res.status(200).json({ message: "Deleted thought"})
} catch {
res.status(400).json({ message: "Could not delete thought"})
} //same as put but delete in the end
})

router.post("/thoughts/:id/like", async (req, res ) => {

try {
const thought = await Thought.findByIdAndUpdate(

req.params.id,
{ $inc: { hearts: 1 } },
{ new: true }
);

if (!thought){
return res.status(404).json({ error: "Thought not found"});
}

res.status(200).json(thought)
} catch {
res.status(400).json({ error: "Could not like thought"})
}

});

export default router;
43 changes: 43 additions & 0 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import cors from "cors"
import express from "express"
import listEndpoints from "express-list-endpoints" //kolla upp denna
import mongoose from "mongoose"
import authRoutes from "./routes/auth.js";
import thoughtRoutes from "./routes/thoughts.js";
import dotenv from "dotenv";

dotenv.config()

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost:27017/project-thoughts"

const port = process.env.PORT || 8080
const app = express()

// Add middlewares to enable cors and json body parsing
app.use(cors())
app.use(express.json())

app.use("/api", authRoutes);
app.use("/api", thoughtRoutes);

app.get("/", (req, res) => {
res.json(listEndpoints(app))
});

// Start the server
mongoose.connect(mongoUrl)
.then(() => {
console.log("✅ Succé! Vi har kontakt med MongoDB Atlas.");
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
})
.catch((err) => {
console.error("❌ Aj då, kunde inte ansluta till databasen:", err);
});






1 change: 1 addition & 0 deletions frontend/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_URL=https://js-project-api-i97g.onrender.com
25 changes: 25 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.env
node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Loading