-
Notifications
You must be signed in to change notification settings - Fork 31
Happy Thoughts API - Linda L #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 8 commits
0ba2f4d
b1bcc0f
6f2a10b
e53c0f4
cee1c0d
de74bae
6ebd8ca
e84f46b
bd710c1
785382b
635984b
bd46f58
e93c0ed
b548f79
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; |
| 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) | ||
| } else { | ||
| res.status(401).json({ message: "Access token is invalid" }); | ||
| } | ||
| } catch (error) { | ||
| res.status(500).json({ message: "Server error during authentication" }); | ||
| } | ||
| }; | ||
|
|
||
| export default authenticateUser; | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| 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, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| 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; |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. :) |
||
| 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}`); | ||
| }); |
There was a problem hiding this comment.
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