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
12 changes: 6 additions & 6 deletions data.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[
{
{
"_id": "682bab8c12155b00101732ce",
"message": "Berlin baby",
"hearts": 37,
"createdAt": "2025-05-19T22:07:08.999Z",
"__v": 0
},
{
"_id": "682e53cc4fddf50010bbe739",
"_id": "682e53cc4fddf50010bbe739",
"message": "My family!",
"hearts": 0,
"createdAt": "2025-05-22T22:29:32.232Z",
Expand All @@ -25,7 +25,7 @@
"message": "Newly washed bedlinen, kids that sleeps through the night.. FINGERS CROSSED 🤞🏼\n",
"hearts": 6,
"createdAt": "2025-05-21T21:42:23.862Z",
"__v": 0
"__v": 0
},
{
"_id": "682e45804fddf50010bbe736",
Expand Down Expand Up @@ -53,7 +53,7 @@
"message": "A god joke: \nWhy did the scarecrow win an award?\nBecause he was outstanding in his field!",
"hearts": 12,
"createdAt": "2025-05-20T20:54:51.082Z",
"__v": 0
"__v": 0
},
{
"_id": "682cebbe17487d0010a298b5",
Expand All @@ -74,7 +74,7 @@
"message": "Summer is coming...",
"hearts": 2,
"createdAt": "2025-05-20T15:03:22.379Z",
"__v": 0
"__v": 0
},
{
"_id": "682c706c951f7a0017130024",
Expand Down Expand Up @@ -118,4 +118,4 @@
"createdAt": "2025-05-19T22:07:08.999Z",
"__v": 0
}
]
]
4 changes: 4 additions & 0 deletions 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",
"bcrypt": "^6.0.0",
"cors": "^2.8.5",
"dotenv": "^17.2.0",
"express": "^4.17.3",
"express-list-endpoints": "^7.1.1",
"mongoose": "^8.16.4",
"nodemon": "^3.0.1"
}
}
244 changes: 228 additions & 16 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,234 @@
import cors from "cors"
import express from "express"
import express from "express";
import listEndpoints from "express-list-endpoints";
import cors from "cors";
import mongoose from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";

// 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()
// Setup
const port = process.env.PORT || 8080;
const app = express();
const mongoURL = process.env.mongoURL || "mongodb://127.0.0.1/happy-thoughts";
mongoose.connect(mongoURL);
mongoose.Promise = Promise;

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

// Start defining your routes here
// Schemas
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: [true, "Email is required"],
unique: true,
match: [/.+@.+\..+/, "Invalid email format"],

Choose a reason for hiding this comment

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

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

const ThoughtSchema = new mongoose.Schema({
message: {
type: String,
required: [true, "Message is required"],
minlength: 5,
maxlength: 140,
},
hearts: {
type: Number,
default: 0,
},
createdAt: {
type: Date,
default: Date.now,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
default: null,
Comment on lines +52 to +55

Choose a reason for hiding this comment

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

},
});

const User = mongoose.model("User", UserSchema);
const Thought = mongoose.model("Thought", ThoughtSchema);

// Auth middleware
const authenticateUser = async (req, res, next) => {
const accessToken = req.header("Authorization");
try {
const user = await User.findOne({ accessToken });
if (user) {
req.user = user;
next();
} else {
res.status(401).json({ error: "Please log in to access this resource" });
}
} catch {
res.status(401).json({ error: "Invalid request" });
}
};

// Routes
app.get("/", (req, res) => {
res.send("Hello Technigo!")
})
res.json({
message: "Welcome to Oscar's Thoughts API!",
endpoints: listEndpoints(app),
});
});

app.get("/thoughts", async (req, res) => {
const { page = 1, limit = 5 } = req.query;
try {
const totalThoughts = await Thought.countDocuments();
const thoughts = await Thought.find()
.sort({ createdAt: -1 })
.skip((page - 1) * limit)
.limit(Number(limit));
res.json({
page: Number(page),
totalThoughts,
totalPages: Math.ceil(totalThoughts / limit),
results: thoughts,
});
} catch {
res.status(500).json({ error: "Could not fetch thoughts" });
}
});

app.get("/thoughts/:id", async (req, res) => {
try {
const thought = await Thought.findById(req.params.id);
if (!thought) return res.status(404).json({ error: "Thought not found" });
res.json(thought);
} catch {
res.status(400).json({ error: "Invalid ID" });
}
});

app.post("/thoughts", async (req, res) => {
const { message } = req.body;
const accessToken = req.header("Authorization");

try {
let createdBy = null;
if (accessToken) {
const user = await User.findOne({ accessToken });
if (user) {
createdBy = user._id;
}
}

const newThought = new Thought({ message, createdBy });
const savedThought = await newThought.save();
res.status(201).json(savedThought);
} catch (err) {
res.status(400).json({ error: err.message });
}
});

app.post("/thoughts/:id/like", async (req, res) => {
try {
const updated = await Thought.findByIdAndUpdate(
req.params.id,
{ $inc: { hearts: 1 } },
{ new: true }
);
if (!updated) return res.status(404).json({ error: "Thought not found" });
res.status(200).json(updated);
} catch {
res.status(400).json({ error: "Invalid ID" });
}
});

app.patch("/thoughts/:id", authenticateUser, 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 ||
thought.createdBy.toString() !== req.user._id.toString()
) {
return res
.status(403)
.json({ error: "Not allowed to edit this thought" });
}
thought.message = req.body.message;
await thought.save();
res.json(thought);
} catch (err) {
res.status(400).json({ error: err.message });
}
});

app.delete("/thoughts/:id", authenticateUser, 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 ||
thought.createdBy.toString() !== req.user._id.toString()
) {
return res
.status(403)
.json({ error: "Not allowed to delete this thought" });
}
await thought.deleteOne();
res.status(204).end();
} catch {
res.status(400).json({ error: "Invalid ID" });
}
});

app.post("/register", async (req, res) => {
const { email, password } = req.body;
try {
if (!email || !password)
return res.status(400).json({ error: "All fields are required" });

const existing = await User.findOne({ email });
if (existing)
return res
.status(400)
.json({ error: "That email address already exists" });

const hashed = bcrypt.hashSync(password, bcrypt.genSaltSync());
const newUser = await new User({ email, password: hashed }).save();

res.status(201).json({
email: newUser.email,
id: newUser._id,
accessToken: newUser.accessToken,
});
} catch (err) {
res.status(400).json({ error: err.message });
}
});

app.post("/login", async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ error: "Invalid email or password" });
}
res.status(200).json({
email: user.email,
id: user._id,
accessToken: user.accessToken,
});
} catch {
res.status(400).json({ error: "Something went wrong" });
}
});

// Start the server
// Server startup
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
})
console.log(`Server running on http://localhost:${port}`);
});
23 changes: 23 additions & 0 deletions thoughts_IGNORE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
{
"id": 1,
"text": "I love coding!",
"category": "Project thoughts",
"hearts": 12,
"createdAt": "2025-07-20T12:00:00Z"
},
{
"id": 2,
"text": "Tacos for lunch today?",
"category": "Food thoughts",
"hearts": 5,
"createdAt": "2025-07-19T15:30:00Z"
},
{
"id": 3,
"text": "Remember to water the plants.",
"category": "Home thoughts",
"hearts": 3,
"createdAt": "2025-07-18T08:15:00Z"
}
]