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
84 changes: 84 additions & 0 deletions src/controllers/feed.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Request, Response } from 'express';
import { Post } from '../entities/Post';
import { Follow } from '../entities/Follow';
import { User } from '../entities/User';
import { AppDataSource } from '../data-source';
import { In } from 'typeorm';

export class FeedController {
private postRepository = AppDataSource.getRepository(Post);
private followRepository = AppDataSource.getRepository(Follow);
private userRepository = AppDataSource.getRepository(User);

async getFeed(req: Request, res: Response) {
try {
const userId = parseInt(req.query.userId as string);
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;

if (!userId) {
return res.status(400).json({ message: 'User ID is required' });
}

const user = await this.userRepository.findOneBy({ id: userId });
if (!user) {
return res.status(404).json({ message: 'User not found' });
}

const following = await this.followRepository.find({
where: { followerId: userId },
select: ['followingId'],
});

const followingIds = following.map((f) => f.followingId);

if (followingIds.length === 0) {
return res.json({
data: [],
pagination: {
total: 0,
limit,
offset,
hasMore: false,
},
message: 'You are not following anyone yet',
});
}

const [posts, total] = await this.postRepository.findAndCount({
where: { userId: In(followingIds) },
relations: ['user', 'hashtags', 'likes'],
order: { createdAt: 'DESC' },
take: limit,
skip: offset,
});

const feedPosts = posts.map((post) => ({
id: post.id,
content: post.content,
createdAt: post.createdAt,
updatedAt: post.updatedAt,
author: {
id: post.user.id,
firstName: post.user.firstName,
lastName: post.user.lastName,
email: post.user.email,
},
hashtags: post.hashtags?.map((h) => h.name) || [],
likeCount: post.likes?.length || 0,
}));

res.json({
data: feedPosts,
pagination: {
total,
limit,
offset,
hasMore: offset + limit < total,
},
});
} catch (error) {
res.status(500).json({ message: 'Error fetching feed', error });
}
}
}
146 changes: 146 additions & 0 deletions src/controllers/follow.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Request, Response } from 'express';
import { Follow } from '../entities/Follow';
import { User } from '../entities/User';
import { AppDataSource } from '../data-source';

export class FollowController {
private followRepository = AppDataSource.getRepository(Follow);
private userRepository = AppDataSource.getRepository(User);

async getAllFollows(req: Request, res: Response) {
try {
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;

const [follows, total] = await this.followRepository.findAndCount({
relations: ['follower', 'following'],
order: { createdAt: 'DESC' },
take: limit,
skip: offset,
});

res.json({
data: follows,
pagination: {
total,
limit,
offset,
hasMore: offset + limit < total,
},
});
} catch (error) {
res.status(500).json({ message: 'Error fetching follows', error });
}
}

async getFollowById(req: Request, res: Response) {
try {
const follow = await this.followRepository.findOne({
where: { id: parseInt(req.params.id) },
relations: ['follower', 'following'],
});

if (!follow) {
return res.status(404).json({ message: 'Follow not found' });
}

res.json(follow);
} catch (error) {
res.status(500).json({ message: 'Error fetching follow', error });
}
}

async getFollowing(req: Request, res: Response) {
try {
const userId = parseInt(req.params.userId);
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;

const [follows, total] = await this.followRepository.findAndCount({
where: { followerId: userId },
relations: ['following'],
order: { createdAt: 'DESC' },
take: limit,
skip: offset,
});

res.json({
data: follows.map((f) => f.following),
pagination: {
total,
limit,
offset,
hasMore: offset + limit < total,
},
});
} catch (error) {
res.status(500).json({ message: 'Error fetching following list', error });
}
}

async createFollow(req: Request, res: Response) {
try {
const { followerId, followingId } = req.body;

if (followerId === followingId) {
return res.status(400).json({ message: 'Users cannot follow themselves' });
}

const follower = await this.userRepository.findOneBy({ id: followerId });
if (!follower) {
return res.status(404).json({ message: 'Follower user not found' });
}

const following = await this.userRepository.findOneBy({ id: followingId });
if (!following) {
return res.status(404).json({ message: 'User to follow not found' });
}

const existingFollow = await this.followRepository.findOne({
where: { followerId, followingId },
});

if (existingFollow) {
return res.status(409).json({ message: 'Already following this user', follow: existingFollow });
}

const follow = this.followRepository.create({ followerId, followingId });
const result = await this.followRepository.save(follow);

const savedFollow = await this.followRepository.findOne({
where: { id: result.id },
relations: ['follower', 'following'],
});

res.status(201).json(savedFollow);
} catch (error) {
res.status(500).json({ message: 'Error creating follow', error });
}
}

async deleteFollow(req: Request, res: Response) {
try {
const result = await this.followRepository.delete(parseInt(req.params.id));
if (result.affected === 0) {
return res.status(404).json({ message: 'Follow not found' });
}
res.status(204).send();
} catch (error) {
res.status(500).json({ message: 'Error deleting follow', error });
}
}

async unfollow(req: Request, res: Response) {
try {
const { followerId, followingId } = req.body;

const result = await this.followRepository.delete({ followerId, followingId });
if (result.affected === 0) {
return res.status(404).json({ message: 'Follow relationship not found' });
}
res.status(204).send();
} catch (error) {
res.status(500).json({ message: 'Error unfollowing user', error });
}
}
}
111 changes: 111 additions & 0 deletions src/controllers/hashtag.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Request, Response } from 'express';
import { Hashtag } from '../entities/Hashtag';
import { AppDataSource } from '../data-source';

export class HashtagController {
private hashtagRepository = AppDataSource.getRepository(Hashtag);

async getAllHashtags(req: Request, res: Response) {
try {
const limit = parseInt(req.query.limit as string) || 10;
const offset = parseInt(req.query.offset as string) || 0;

const [hashtags, total] = await this.hashtagRepository.findAndCount({
order: { createdAt: 'DESC' },
take: limit,
skip: offset,
});

res.json({
data: hashtags,
pagination: {
total,
limit,
offset,
hasMore: offset + limit < total,
},
});
} catch (error) {
res.status(500).json({ message: 'Error fetching hashtags', error });
}
}

async getHashtagById(req: Request, res: Response) {
try {
const hashtag = await this.hashtagRepository.findOne({
where: { id: parseInt(req.params.id) },
relations: ['posts'],
});

if (!hashtag) {
return res.status(404).json({ message: 'Hashtag not found' });
}

res.json({
...hashtag,
postCount: hashtag.posts?.length || 0,
});
} catch (error) {
res.status(500).json({ message: 'Error fetching hashtag', error });
}
}

async createHashtag(req: Request, res: Response) {
try {
const { name } = req.body;
const normalizedName = name.toLowerCase().trim();

const existingHashtag = await this.hashtagRepository.findOneBy({ name: normalizedName });
if (existingHashtag) {
return res.status(409).json({ message: 'Hashtag already exists', hashtag: existingHashtag });
}

const hashtag = this.hashtagRepository.create({ name: normalizedName });
const result = await this.hashtagRepository.save(hashtag);

res.status(201).json(result);
} catch (error) {
res.status(500).json({ message: 'Error creating hashtag', error });
}
}

async updateHashtag(req: Request, res: Response) {
try {
const hashtagId = parseInt(req.params.id);
const { name } = req.body;

const hashtag = await this.hashtagRepository.findOneBy({ id: hashtagId });
if (!hashtag) {
return res.status(404).json({ message: 'Hashtag not found' });
}

if (name) {
const normalizedName = name.toLowerCase().trim();

const existingHashtag = await this.hashtagRepository.findOneBy({ name: normalizedName });
if (existingHashtag && existingHashtag.id !== hashtagId) {
return res.status(409).json({ message: 'Hashtag with this name already exists' });
}

hashtag.name = normalizedName;
}

const result = await this.hashtagRepository.save(hashtag);
res.json(result);
} catch (error) {
res.status(500).json({ message: 'Error updating hashtag', error });
}
}

async deleteHashtag(req: Request, res: Response) {
try {
const result = await this.hashtagRepository.delete(parseInt(req.params.id));
if (result.affected === 0) {
return res.status(404).json({ message: 'Hashtag not found' });
}
res.status(204).send();
} catch (error) {
res.status(500).json({ message: 'Error deleting hashtag', error });
}
}
}
Loading