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
13 changes: 13 additions & 0 deletions middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import User from "../models/User.js";

const authenticateUser = async (req, res, next) => {
const user = await User.findOne({ accessToken: req.header("Authorization") });
if (user) {
req.user = user;
next();
} else {
res.status(401).json({ error: "Authentication failed" });
}
};

export default authenticateUser;
28 changes: 28 additions & 0 deletions models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
import crypto from "crypto";

const userSchema = new mongoose.Schema({
name: {
type: String,
unique: true,
required: true,
},
email: {
type: String,
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
});

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

export default User;
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"bcryptjs": "^3.0.3",
"cors": "^2.8.5",
"express": "^4.17.3",
"dotenv": "^17.2.3",
"express": "^4.22.1",
"jsonwebtoken": "^9.0.3",
"mongoose": "^9.1.5",
"nodemon": "^3.0.1"
}
}
51 changes: 51 additions & 0 deletions routes/userRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import express from "express";
import jwt from "jsonwebtoken";
import User from "../models/User.js";
import bcrypt from "bcryptjs";

const router = express.Router();

// Register new user
router.post("/users", async (req, res) => {
try {
// Get data from request body
const { name, email, password } = req.body;

// Does the email already exist?
const existingUser = await User.findOne({ email });
if (existingUser) {
// if the email already exists, return error messages
return res.status(400).json({ error: "That email already exists" });
}

// Create a new user with a hashed password
const user = new User({ name, email, password: bcrypt.hashSync(password) });

await user.save();

// Return user ID and token on successful registration
res.status(201).json({ id: user._id, accessToken: user.accessToken });
} catch (err) {
res
.status(400)
.json({ message: "Could not create user", errors: err.errors });
}
});

// When user Log in, send email and password, receive accessToken.
router.post("/sessions", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });

// Check if the user exists AND if the password is correct
if (user && bcrypt.compareSync(req.body.password, user.password)) {
res.json({ userId: user._id, accessToken: user.accessToken });
} else {
res.status(401).json({ message: "Invalid email or password" });
}
} catch (err) {
res.status(500).json({ error: "Login failed" });
}
});

export default router;
211 changes: 195 additions & 16 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,201 @@
import cors from "cors"
import express from "express"
import cors from "cors"; // Allows requests from other domains (frontend)
import express from "express"; // Web server framework
import mongoose from "mongoose"; // Database connection to MongoDB
import authenticateUser from "./middleware/auth.js"; // Middleware to protect routes
import userRoutes from "./routes/userRoutes.js"; // Routes for register/login

// Defines the port the app will run on. Defaults to 8080, but can be overridden
// when starting the server. Example command to overwrite PORT env variable value:
// PORT=9000 npm start
const port = process.env.PORT || 8080
const app = express()
// Load environment variables from the .env file
import dotenv from "dotenv";
dotenv.config();

// Add middlewares to enable cors and json body parsing
app.use(cors())
app.use(express.json())
//DATABASE CONNECTION
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/HappyThoughts";
mongoose.connect(mongoUrl);

// Start defining your routes here
// Log when we have connected to the database
mongoose.connection.on("connected", () => {
console.log(" Connected to MongoDB");
});

// Log if something goes wrong with the database connection
mongoose.connection.on("error", (err) => {
console.log("MongoDB connection error:", err);
});
mongoose.Promise = Promise;

// THOUGHT-MODELL
// Defines what a "thought" looks like in the database
const Thought = mongoose.model("Thought", {
message: {
type: String,
required: true,
minlength: 3,
maxlength: 140,
},
hearts: {
type: Number,
default: 0,
},
createdAt: {
type: Date,
default: () => new Date(),
},
});

// SERVER-SETUP
const port = process.env.PORT || 8080;
const app = express();

// Middleware
app.use(cors());
app.use(express.json());

// ROUTES
// API-dokumentation
app.get("/", (req, res) => {
res.send("Hello Technigo!")
})
res.json({
message: "Welcome to Happy Thoughts API",
endpoints: [
{ method: "GET", path: "/", description: "API documentation" },
{ method: "GET", path: "/thoughts", description: "Get all thoughts" },
{
method: "GET",
path: "/thoughts/random",
description: "Get a random thought",
},
{
method: "GET",
path: "/thoughts/:id",
description: "Get a specific thought by ID",
},
{
method: "POST",
path: "/thoughts",
description: "Create a new thought (requires authentication)",
},
{
method: "DELETE",
path: "/thoughts/:id",
description: "Delete a thought (requires authentication)",
},
{
method: "PATCH",
path: "/thoughts/:id",
description: "Add a heart to a thought (requires authentication)",
},
{
method: "PATCH",
path: "/thoughts/:id/update",
description: "Update message (requires authentication)",
},
],
});
});

// Get all thoughts
app.get("/thoughts", async (req, res) => {
try {
const results = await Thought.find();
res.json(results);
} catch (err) {
res.status(500).json({ error: "Could not fetch thoughts" });
}
});

// Get a random thought
app.get("/thoughts/random", async (req, res) => {
try {
const thoughts = await Thought.find();
if (thoughts.length === 0) {
return res.status(404).json({ error: "No thoughts found" });
}
const randomIndex = Math.floor(Math.random() * thoughts.length);
res.json(thoughts[randomIndex]);
} catch (err) {
res.status(500).json({ error: "Could not get random thought" });
}
});

// Get a specific thought by ID
app.get("/thoughts/:id", async (req, res) => {
try {
const thought = await Thought.findById(req.params.id);
if (thought) {
res.json(thought);
} else {
res.status(404).json({ error: "Thought do not exist" });
}
} catch (error) {
res.status(400).json({ error: "Invalid thought ID" });
}
});

// Create a new thought – PROTECTED (requires login)
// authenticateUser runs FIRST and checks that the user has a valid token
app.post("/thoughts", authenticateUser, async (req, res) => {
try {
const newThought = new Thought({
message: req.body.message,
hearts: 0,
});
const savedThought = await newThought.save();
res.status(201).json(savedThought);
} catch (err) {
res.status(400).json({ error: err.message });
}
});

// Delete thought – PROTECTED (requires login)
app.delete("/thoughts/:id", authenticateUser, async (req, res) => {
try {
const deleted = await Thought.findByIdAndDelete(req.params.id);

if (!deleted) {
return res.status(404).json({ error: "Thought not found" });
}
res.status(200).json({ message: "Deleted", id: req.params.id });
} catch (err) {
res.status(400).json({ error: "Invalid ID" });
}
});

// Add a heart to a thought – PROTECTED (requires login)
app.patch("/thoughts/:id", authenticateUser, async (req, res) => {
try {
const updatedThought = await Thought.findByIdAndUpdate(
req.params.id,
{ $inc: { hearts: 1 } },
{ new: true },
);
if (!updatedThought) {
return res.status(404).json({ error: "Thought not found" });
}
res.status(200).json(updatedThought);
} catch {
res.status(400).json({ error: "Unable to add heart to thought" });
}
});

// User updates a thought – PROTECTED (requires login)
app.patch("/thoughts/:id/update", authenticateUser, async (req, res) => {
try {
const updatedThought = await Thought.findByIdAndUpdate(
req.params.id,
{ message: req.body.message },
{ new: true },
);
if (!updatedThought) {
return res.status(404).json({ error: "Thought not found" });
}
res.status(200).json(updatedThought);
} catch {
res.status(400).json({ error: "Unable to update message" });
}
});

app.use(userRoutes);

// Start the server
// START SERVERN
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
})
console.log(`Server running on http://localhost:${port}`);
});