Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
33 changes: 33 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import dotenv from "dotenv";

import thoughtRoutes from "./routes/thoughts.js";
import userRoutes from "./routes/auth.js";

dotenv.config();

const app = express();

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

// === Connect to MongoDB ===
const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts";
mongoose.connect(mongoUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
mongoose.Promise = Promise;

// === Routes ===
app.get("/", (req, res) => {
res.send("Welcome to the Happy Thoughts API!");
});

app.use("/thoughts", thoughtRoutes);
app.use("/users", userRoutes);

export default app;
19 changes: 19 additions & 0 deletions middlewares/authenticateUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { User } from "../models/User.js";

const authenticateUser = async (req, res, next) => {
const accessToken = req.header("Authorization");

try {
const user = await User.findOne({ accessToken });
if (user) {
req.user = user; // Lägg till användaren på request-objektet
next(); // Gå vidare till nästa steg (ex. route handler)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stick to english comments, easier for the reviewer to understand

} else {
res.status(401).json({ message: "Access token is invalid" });
}
} catch (error) {
res.status(500).json({ message: "Server error during authentication" });
}
};

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

const thoughtSchema = new mongoose.Schema(
{
message: {
type: String,
required: [true, "Message is required"],
minlength: [5, "Message must be at least 5 characters"],
maxlength: [140, "Message cannot exceed 140 characters"],
trim: true,
},
hearts: {
type: Number,
default: 0,
},
},
{
timestamps: true,
},
{
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
Comment on lines +22 to +24

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great linking here to the other collection

},
}
);

const Thought = mongoose.model("Thought", thoughtSchema);

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

const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
minlength: 3,
maxlength: 30,
},
password: {
type: String,
required: true,
minlength: 5,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a maxLength here as well?

},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
});

export const User = mongoose.model("User", userSchema);
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"@babel/core": "^7.27.4",
"@babel/node": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"bcrypt": "^6.0.0",
"bcrypt-nodejs": "^0.0.3",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.17.3",
"nodemon": "^3.0.1"
"express-list-endpoints": "^7.1.1",
"mongoose": "^8.15.1",
"nodemon": "^3.1.10"
}
}
58 changes: 58 additions & 0 deletions routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import express from "express";
import bcrypt from "bcrypt";
import crypto from "crypto";

import { User } from "../models/User.js";

const router = express.Router();

router.post("/", async (req, res) => {
console.log("Incoming body:", req.body);
const { username, password } = req.body;

try {
const salt = bcrypt.genSaltSync();
const hashedPassword = bcrypt.hashSync(password, salt);
const user = new User({
username,
password: hashedPassword,
accessToken: crypto.randomBytes(128).toString("hex"),
});

await user.save();

res.status(201).json({
userId: user._id,
username: user.username,
accessToken: user.accessToken,
});
} catch (err) {
console.error("Signup error:", err); // ← Lägg till denna rad

if (err.code === 11000) {
res.status(400).json({ message: "Username already exists" });
} else {
res.status(400).json({ message: "Invalid request", error: err });
}
}
});

router.post("/sessions", async (req, res) => {
const { username, password } = req.body;

try {
const user = await User.findOne({ username });
if (user && bcrypt.compareSync(password, user.password)) {
res.json({
userId: user._id,
accessToken: user.accessToken,
});
} else {
res.status(401).json({ error: "Username or password is incorrect" });
}
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
});

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

import authenticateUser from "../middlewares/authenticateUser.js";
import HappyThought from "../models/Thought.js";

const router = express.Router();

// get last 20 posts
router.get("/", async (req, res) => {
try {
const thoughts = await HappyThought.find()
.sort({ createdAt: -1 })
.limit(20);
res.status(200).json(thoughts);
} catch (error) {
res.status(500).json({ error: "Could not fetch thoughts" });
}
});

// get single post
router.get("/:id", async (req, res) => {
try {
const thought = await HappyThought.findById(req.params.id);
if (thought) {
res.json(thought);
} else {
res.status(404).json({ error: "Thought not found" });
}
} catch (error) {
res.status(400).json({ error: "Invalid ID format" });
}
});
Comment on lines +20 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used in the frontend?? It's a neat solution, just asking what the use of getting a single thought is here!


// add new post
router.post("/", authenticateUser, async (req, res) => {
const { message } = req.body;

try {
const newThought = new HappyThought({
message,
createdBy: req.user._id, // lägg till användaren som skapar
});

const savedThought = await newThought.save();
res.status(201).json(savedThought);
} catch (error) {
res.status(400).json({ error: "Invalid input", details: error.errors });
}
});
Comment on lines +34 to +49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly add input validation here as well?


// update existing post
router.put("/:id", async (req, res) => {
try {
const updatedThought = await HappyThought.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);

if (updatedThought) {
res.status(200).json(updatedThought);
} else {
res.status(404).json({ error: "Thought not found" });
}
} catch (error) {
res.status(400).json({ error: "Invalid update", details: error.errors });
}
});

// delete post
router.delete("/:id", async (req, res) => {
try {
const deleted = await HappyThought.findByIdAndDelete(req.params.id);
if (deleted) {
res.status(200).json({ success: true });
} else {
res.status(404).json({ error: "Thought not found" });
}
} catch (error) {
res.status(400).json({ error: "Invalid ID format" });
}
});

// like post
router.post("/:id/like", async (req, res) => {
try {
const thought = await HappyThought.findByIdAndUpdate(
req.params.id,
{ $inc: { hearts: 1 } },
{ new: true }
);

if (thought) {
res.status(200).json(thought);
} else {
res.status(404).json({ error: "Thought not found" });
}
} catch (error) {
res.status(400).json({ error: "Invalid ID format" });
}
});

export default router;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future developments - maybe consider adding some rate limits on creation of posts? That way, a user can't flood the feed. :)

25 changes: 5 additions & 20 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import cors from "cors"
import express from "express"
import app from "./app.js";

// 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()
const port = process.env.PORT || 8080;

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

// Start defining your routes here
app.get("/", (req, res) => {
res.send("Hello Technigo!")
})

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