Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
12dbd85
added functions to thoughtServices
HolaCarmensita May 27, 2025
68f644e
All thoughtServices done
HolaCarmensita May 27, 2025
e48b4be
Testade mina thoughtServices med assert WOW va grymt
HolaCarmensita May 27, 2025
03510ad
Ändrade package.json utan module och importear data på samma sätt som…
HolaCarmensita May 28, 2025
2bd403b
routes for get all thoughts, post createThought, post likeTHought och…
HolaCarmensita May 28, 2025
eede8e2
added a service for getting one thought, added that one and update an…
HolaCarmensita May 30, 2025
0c3d349
Streach goal regardning 404 single‐item–hantering
HolaCarmensita May 30, 2025
d575cca
paginate implemeted
HolaCarmensita May 30, 2025
4a7566f
sorting works
HolaCarmensita May 30, 2025
f6c0287
added link to render.com sajten
HolaCarmensita May 30, 2025
f3c9678
added dotenv
HolaCarmensita May 30, 2025
4e6e3b6
just started to add moongoose things but realised that I want to refa…
HolaCarmensita Jun 3, 2025
2746036
refactored the code to have controllers
HolaCarmensita Jun 3, 2025
7c46cb5
installed mongoose
HolaCarmensita Jun 3, 2025
9d9ae8a
test
HolaCarmensita Jun 3, 2025
435e0ed
installed mondodb and added info to README
HolaCarmensita Jun 4, 2025
e02ae73
refactored getAllThoughts, getOneThought, like and unlike
HolaCarmensita Jun 4, 2025
8ec062f
UpdateThought works now with mongo method
HolaCarmensita Jun 4, 2025
f2cccb3
Delete thought done
HolaCarmensita Jun 4, 2025
dcf58ce
remove thought refactored
HolaCarmensita Jun 5, 2025
36f24b9
deleted mockdata filen som jag använde tidigare
HolaCarmensita Jun 5, 2025
2acfdd0
test
HolaCarmensita Jun 5, 2025
ec745e2
deleted utils and a unesssesary import
HolaCarmensita Jun 9, 2025
49456d7
changed hearts to likes
HolaCarmensita Jun 9, 2025
b9871e4
mongo URL
HolaCarmensita Jun 11, 2025
c305b8d
usermodel and first post using the userRouter to add signup
HolaCarmensita Jun 11, 2025
e9cea9e
post login set
HolaCarmensita Jun 11, 2025
da1be4b
added middleware to use to autheticate the user, not applaied on any …
HolaCarmensita Jun 11, 2025
a896b5a
using bearer token and the middleware works great on addThought, yey
HolaCarmensita Jun 11, 2025
67e9806
middleware on put and delete thought and controllers refactored
HolaCarmensita Jun 17, 2025
50e7fea
dont remeber what I did, forgot to commit. I think it was the adding …
HolaCarmensita Jun 23, 2025
ca69441
toggle like works
HolaCarmensita Jun 23, 2025
4c739a5
refactored the userRoute to using userController for easier readning
HolaCarmensita Jun 23, 2025
4f6aa9f
populate insted of just ID
HolaCarmensita Jun 26, 2025
a01c6f4
Having trouble to deploy to render.com
HolaCarmensita Jun 27, 2025
7e5ca47
changed the dependencis
HolaCarmensita Jun 27, 2025
2d1aec7
trying things out to make this render.com work
HolaCarmensita Jun 27, 2025
bf7667f
trying everything to make this build work on render
HolaCarmensita Jun 27, 2025
aaa66f4
forgot to savepacjege.json
HolaCarmensita Jun 27, 2025
32169d1
installed npm install --save-dev @babel/cli
HolaCarmensita Jun 27, 2025
88a385a
Update README.md
HolaCarmensita Jun 27, 2025
4280368
Update README.md
HolaCarmensita Jun 27, 2025
53f5962
added npx
HolaCarmensita Jun 27, 2025
f270f10
Revert "added npx"
HolaCarmensita Jun 27, 2025
e8b90a2
Merge remote-tracking branch 'origin/HEAD'
HolaCarmensita Jun 27, 2025
ce4510e
npx
HolaCarmensita Jun 27, 2025
ff6ddee
delited modules
HolaCarmensita Jun 27, 2025
06d26a8
GAAAH
HolaCarmensita Jun 27, 2025
690c056
delited babel
HolaCarmensita Jun 27, 2025
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
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# Project API
# 🧠 Projekt: Happy Thoughts API

This project includes the packages and babel setup for an express server, and is just meant to make things a little simpler to get up and running with.
[Hola-Happy-Server](https://hola-happy-server.onrender.com)

## Getting started
[Hola-Happy-App](https://holahappythoughts.netlify.app/)

Install dependencies with `npm install`, then start the server by running `npm run dev`

## View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
186 changes: 186 additions & 0 deletions controllers/thoughtController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Thought } from '../models/Thoughts.js';
import mongoose from 'mongoose';

export const listAllThoughts = async (req, res) => {
const sortBy = req.query.sortBy || 'createdAt';
const sortDir = req.query.sortDir === 'ascending' ? 1 : -1;

try {
const thoughts = await Thought.find()
.sort({ [sortBy]: sortDir })
.populate('createdBy', '_id email');
res.json(thoughts);
} catch (error) {
console.error('Mongoose error on listAllThoughts:', error);
res.status(500).json({ error: 'Could not fetch thoughts from database' });
}
};

export const getOneThought = async (req, res) => {
const { id } = req.params;

try {
let thought = await Thought.findById(id);

if (!thought) {
return res.status(404).json({
error: 'Thought not found',
requestedId: id,
});
}
thought = await thought.populate('createdBy', '_id email');
res.json(thought);
} catch (error) {
console.error('Mongoose error on getOneThought:', error);
res.status(400).json({ error: 'Invalid ID format or other error' });
}
};

export const addThought = async (req, res) => {
const { message } = req.body;

// Validate message length
if (!message || message.length < 4 || message.length > 140) {
return res.status(400).json({
error: 'Message is required and must be between 5 and 140 characters',
});
}

try {
const newThought = await Thought.create({
message,
createdBy: req.user._id,
// likes and createdAt will be set by defaults in the model
});

const populatedThought = await newThought.populate(
'createdBy',
'_id email'
);
Comment on lines +49 to +59

Choose a reason for hiding this comment

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

res.status(201).json(populatedThought);
} catch (error) {
console.error('Mongoose error on addThought:', error);
if (error.name === 'ValidationError') {
res
.status(400)
.json({ error: 'Validation failed', details: error.errors });
} else {
res.status(400).json({ error: 'Could not add your thought' });
}
}
};

export const likeThought = async (req, res) => {
const { id } = req.params;
const userId = req.user?._id;

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

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

if (userId) {
const userIdStr = userId.toString();
// Convert likedBy to array of strings for logic
let likedByStrArr = thought.likedBy.map((id) => id.toString());
console.log('Before toggle:', { userIdStr, likedBy: likedByStrArr });
const hasLiked = likedByStrArr.includes(userIdStr);

if (hasLiked) {
likedByStrArr = likedByStrArr.filter((id) => id !== userIdStr);
console.log('User unliked. After removal:', likedByStrArr);
} else {
likedByStrArr.push(userIdStr);
console.log('User liked. After addition:', likedByStrArr);
}
thought.likes = likedByStrArr.length;
console.log('Final likedBy and likes:', {
likedBy: likedByStrArr,
likes: thought.likes,
});
// Convert likedBy back to ObjectIds before saving
thought.likedBy = likedByStrArr.map(
(id) => new mongoose.Types.ObjectId(id)
);

const updatedThought = await thought.save();
const populatedThought = await updatedThought.populate(
'createdBy',
'_id email'
);
return res.status(200).json(populatedThought);
}

// Guests should not be able to like
return res.status(401).json({ error: 'Authentication required to like' });
} catch (error) {
console.error('Error in likeThought:', error);
res
.status(500)
.json({ error: 'Could not toggle like', details: error.message });
}
};

export const updateThought = async (req, res) => {
const { id } = req.params;
const { message } = req.body;

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

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

// Kontrollera att den inloggade användaren äger tanken
if (thought.createdBy.toString() !== req.user._id.toString()) {
return res
.status(403)
.json({ error: 'You are not allowed to update this thought' });
}

thought.message = message;
const updatedThought = await thought.save();
const populatedThought = await updatedThought.populate(
'createdBy',
'_id email'
);
res.status(200).json(populatedThought);
} catch (err) {
res
.status(500)
.json({ error: 'Could not update thought', details: err.message });
}
};

export const removeThought = async (req, res) => {
const { id } = req.params;

try {
// Validate if the ID is a valid MongoDB ObjectId
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).json({ error: 'Invalid thought ID format' });
}

const thought = await Thought.findById(id);

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

if (thought.createdBy.toString() !== req.user._id.toString()) {
return res
.status(403)
.json({ error: 'You are not allowed to delete this thought' });
}

await thought.deleteOne();
res.status(200).json({ message: 'Thought deleted successfully' });
} catch (err) {
res
.status(500)
.json({ error: 'Could not delete thought', details: err.message });
}
};
63 changes: 63 additions & 0 deletions controllers/userController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import User from '../models/Users.js';
import bcrypt from 'bcrypt';
import crypto from 'crypto';

export const signup = async (req, res) => {
const { email, password } = req.body;

try {
if (!email || !password) {
return res.status(400).json({ error: 'Email and password are required' });
}
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'Email already exists' });
}
const salt = bcrypt.genSaltSync();
const hashedPassword = bcrypt.hashSync(password, salt);
const newUser = await new User({ email, password: hashedPassword }).save();
res.status(201).json({
email: newUser.email,
accessToken: newUser.accessToken,
id: newUser._id,
});
} catch (err) {
res
.status(500)
.json({ error: 'Internal server error', details: err.message });
}
};

export const login = async (req, res) => {
const { email, password } = req.body;

try {
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid email or password' });
}
const passwordMatch = bcrypt.compareSync(password, user.password);
if (!passwordMatch) {
return res.status(401).json({ error: 'Invalid email or password' });
}
res.status(200).json({
email: user.email,
accessToken: user.accessToken,
id: user._id,
});
} catch (err) {
res
.status(500)
.json({ error: 'Internal server error', details: err.message });
}
};

export const logout = async (req, res) => {
try {
req.user.accessToken = crypto.randomBytes(64).toString('hex');
await req.user.save();
res.status(200).json({ message: 'Logged out successfully' });
} catch (err) {
res.status(500).json({ error: 'Could not log out', details: err.message });
}
};
121 changes: 0 additions & 121 deletions data.json

This file was deleted.

Loading