Initial commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
const { ValidationError } = require('../shared/errors');
|
||||
const config = require('../config');
|
||||
|
||||
class BetsService {
|
||||
constructor({ betsRepository, usersRepository, dataStitchingService }) {
|
||||
this.betsRepository = betsRepository;
|
||||
this.usersRepository = usersRepository;
|
||||
this.dataStitchingService = dataStitchingService;
|
||||
}
|
||||
|
||||
async getAllBets() {
|
||||
const bets = await this.betsRepository.findAll();
|
||||
return await this.dataStitchingService.populateBetsWithUsers(bets);
|
||||
}
|
||||
|
||||
async getBetById(betId) {
|
||||
const bet = await this.betsRepository.findById(betId);
|
||||
const populated = await this.dataStitchingService.populateBetsWithUsers([bet]);
|
||||
return populated[0];
|
||||
}
|
||||
|
||||
async getUserBets(userId) {
|
||||
const bets = await this.betsRepository.findByUser(userId);
|
||||
return await this.dataStitchingService.populateBetsWithUsers(bets);
|
||||
}
|
||||
|
||||
async getGroupBets(groupId) {
|
||||
const bets = await this.betsRepository.findByGroup(groupId);
|
||||
return await this.dataStitchingService.populateBetsWithUsers(bets);
|
||||
}
|
||||
|
||||
async getPublicBetsExcludingUser(userId) {
|
||||
const bets = await this.betsRepository.findPublicBetsExcludingUser(userId);
|
||||
return await this.dataStitchingService.populateBetsWithUsers(bets);
|
||||
}
|
||||
|
||||
async createBet(betData) {
|
||||
const { title, description, stake, participants, isPublic, groupId } = betData;
|
||||
|
||||
if (!title || title.trim() === '') {
|
||||
throw new ValidationError('Bet title is required');
|
||||
}
|
||||
|
||||
if (!stake || stake.trim() === '') {
|
||||
throw new ValidationError('Bet stake is required');
|
||||
}
|
||||
|
||||
if (!participants || !Array.isArray(participants) || participants.length === 0) {
|
||||
throw new ValidationError('At least one participant is required');
|
||||
}
|
||||
|
||||
const normalizedParticipants = participants.map((p) => ({
|
||||
...p,
|
||||
user: parseInt(p.user, 10),
|
||||
}));
|
||||
|
||||
const userIds = normalizedParticipants.map((p) => p.user);
|
||||
for (const userId of userIds) {
|
||||
await this.usersRepository.findById(userId);
|
||||
}
|
||||
|
||||
const bet = await this.betsRepository.create({
|
||||
title,
|
||||
description,
|
||||
stake,
|
||||
participants: normalizedParticipants,
|
||||
isPublic: isPublic || false,
|
||||
groupId: groupId || null,
|
||||
});
|
||||
|
||||
const populated = await this.dataStitchingService.populateBetsWithUsers([bet]);
|
||||
return populated[0];
|
||||
}
|
||||
|
||||
async updateBet(betId, updateData) {
|
||||
if (updateData.title && updateData.title.trim() === '') {
|
||||
throw new ValidationError('Bet title cannot be empty');
|
||||
}
|
||||
|
||||
const bet = await this.betsRepository.update(betId, updateData);
|
||||
const populated = await this.dataStitchingService.populateBetsWithUsers([bet]);
|
||||
return populated[0];
|
||||
}
|
||||
|
||||
async deleteBet(betId) {
|
||||
return await this.betsRepository.delete(betId);
|
||||
}
|
||||
|
||||
async declareWinner(betId, winnerId) {
|
||||
await this.usersRepository.findById(winnerId);
|
||||
|
||||
const bet = await this.betsRepository.update(betId, {
|
||||
winner: winnerId,
|
||||
status: config.BET_STATUS.COMPLETED,
|
||||
endDate: new Date(),
|
||||
});
|
||||
|
||||
const populated = await this.dataStitchingService.populateBetsWithUsers([bet]);
|
||||
return populated[0];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BetsService;
|
||||
@@ -0,0 +1,41 @@
|
||||
class DataStitchingService {
|
||||
constructor({ usersRepository }) {
|
||||
this.usersRepository = usersRepository;
|
||||
}
|
||||
|
||||
async populateBetsWithUsers(bets) {
|
||||
if (!bets || bets.length === 0) return [];
|
||||
|
||||
const userIds = new Set();
|
||||
bets.forEach((bet) => {
|
||||
bet.participants.forEach((p) => userIds.add(p.user));
|
||||
if (bet.winner) {
|
||||
userIds.add(bet.winner);
|
||||
}
|
||||
});
|
||||
|
||||
if (userIds.size === 0)
|
||||
return bets.map((b) => (typeof b.toJSON === 'function' ? b.toJSON() : b));
|
||||
|
||||
const users = await this.usersRepository.findAll({
|
||||
where: { id: Array.from(userIds) },
|
||||
});
|
||||
const userMap = new Map(
|
||||
users.map((u) => [u.id, typeof u.toJSON === 'function' ? u.toJSON() : u])
|
||||
);
|
||||
|
||||
return bets.map((bet) => {
|
||||
const betJson = typeof bet.toJSON === 'function' ? bet.toJSON() : bet;
|
||||
betJson.participants = betJson.participants.map((p) => ({
|
||||
...p,
|
||||
user: userMap.get(p.user) || null,
|
||||
}));
|
||||
if (betJson.winner) {
|
||||
betJson.winner = userMap.get(betJson.winner) || null;
|
||||
}
|
||||
return betJson;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DataStitchingService;
|
||||
@@ -0,0 +1,102 @@
|
||||
const { ValidationError, ConflictError } = require('../shared/errors');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
class FriendsService {
|
||||
constructor({
|
||||
friendshipsRepository,
|
||||
friendRequestsRepository,
|
||||
usersRepository,
|
||||
sequelize,
|
||||
}) {
|
||||
this.friendshipsRepository = friendshipsRepository;
|
||||
this.friendRequestsRepository = friendRequestsRepository;
|
||||
this.usersRepository = usersRepository;
|
||||
this.sequelize = sequelize;
|
||||
}
|
||||
|
||||
async getFriendsByUserId(userId) {
|
||||
return await this.friendshipsRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
async getPendingRequests(userId) {
|
||||
return await this.friendRequestsRepository.findPendingByUserId(userId);
|
||||
}
|
||||
|
||||
async sendFriendRequest(fromUserId, identifier) {
|
||||
let toUser;
|
||||
|
||||
if (typeof identifier === 'number' || !isNaN(identifier)) {
|
||||
toUser = await this.usersRepository.findById(parseInt(identifier, 10));
|
||||
} else {
|
||||
toUser = await this.usersRepository.findOne({
|
||||
[Op.or]: [{ name: identifier }, { email: identifier }],
|
||||
});
|
||||
|
||||
if (!toUser) {
|
||||
throw new ValidationError(
|
||||
'User not found with provided name or email'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (toUser.id === fromUserId) {
|
||||
throw new ValidationError('Cannot send friend request to yourself');
|
||||
}
|
||||
|
||||
const friendshipExists = await this.friendshipsRepository.exists(
|
||||
fromUserId,
|
||||
toUser.id
|
||||
);
|
||||
if (friendshipExists) {
|
||||
throw new ConflictError('You are already friends with this user');
|
||||
}
|
||||
|
||||
const existingRequest = await this.friendRequestsRepository.findExisting(
|
||||
fromUserId,
|
||||
toUser.id
|
||||
);
|
||||
if (existingRequest) {
|
||||
throw new ConflictError('Friend request already exists');
|
||||
}
|
||||
|
||||
return await this.friendRequestsRepository.create(fromUserId, toUser.id);
|
||||
}
|
||||
|
||||
async acceptFriendRequest(requestId, userId) {
|
||||
const request = await this.friendRequestsRepository.findById(requestId);
|
||||
|
||||
if (request.toUserId !== userId) {
|
||||
throw new ValidationError('You can only accept requests sent to you');
|
||||
}
|
||||
|
||||
if (request.status !== 'PENDING') {
|
||||
throw new ValidationError('This request has already been processed');
|
||||
}
|
||||
|
||||
const t = await this.sequelize.transaction();
|
||||
try {
|
||||
await this.friendRequestsRepository.updateStatus(requestId, 'ACCEPTED');
|
||||
await this.friendshipsRepository.create(request.fromUserId, request.toUserId);
|
||||
await this.friendRequestsRepository.delete(requestId);
|
||||
await t.commit();
|
||||
|
||||
return { message: 'Friend request accepted successfully' };
|
||||
} catch (error) {
|
||||
await t.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async rejectFriendRequest(requestId, userId) {
|
||||
const request = await this.friendRequestsRepository.findById(requestId);
|
||||
|
||||
if (request.toUserId !== userId && request.fromUserId !== userId) {
|
||||
throw new ValidationError('You can only manage your own requests');
|
||||
}
|
||||
|
||||
await this.friendRequestsRepository.delete(requestId);
|
||||
return { message: 'Friend request rejected successfully' };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FriendsService;
|
||||
@@ -0,0 +1,76 @@
|
||||
const { ValidationError } = require('../shared/errors');
|
||||
|
||||
class GroupsService {
|
||||
constructor({ groupsRepository, usersRepository }) {
|
||||
this.groupsRepository = groupsRepository;
|
||||
this.usersRepository = usersRepository;
|
||||
}
|
||||
|
||||
async getAllGroups() {
|
||||
return await this.groupsRepository.findAll({
|
||||
include: [
|
||||
{
|
||||
model: this.usersRepository.User,
|
||||
as: 'members',
|
||||
through: { attributes: [] },
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async getGroupById(groupId) {
|
||||
return await this.groupsRepository.findById(groupId, true);
|
||||
}
|
||||
|
||||
async createGroup(groupData) {
|
||||
const { name, members } = groupData;
|
||||
|
||||
if (!name || name.trim() === '') {
|
||||
throw new ValidationError('Group name is required');
|
||||
}
|
||||
|
||||
if (!members || !Array.isArray(members) || members.length === 0) {
|
||||
throw new ValidationError('At least one member is required');
|
||||
}
|
||||
|
||||
for (const userId of members) {
|
||||
await this.usersRepository.findById(userId);
|
||||
}
|
||||
|
||||
const group = await this.groupsRepository.create({ name });
|
||||
return await this.groupsRepository.addMembers(group.id, members);
|
||||
}
|
||||
|
||||
async updateGroup(groupId, updateData) {
|
||||
const { name } = updateData;
|
||||
|
||||
if (name && name.trim() === '') {
|
||||
throw new ValidationError('Group name cannot be empty');
|
||||
}
|
||||
|
||||
return await this.groupsRepository.update(groupId, { name });
|
||||
}
|
||||
|
||||
async deleteGroup(groupId) {
|
||||
return await this.groupsRepository.delete(groupId);
|
||||
}
|
||||
|
||||
async addMembersToGroup(groupId, userIds) {
|
||||
if (!userIds || !Array.isArray(userIds) || userIds.length === 0) {
|
||||
throw new ValidationError('User IDs are required');
|
||||
}
|
||||
|
||||
for (const userId of userIds) {
|
||||
await this.usersRepository.findById(userId);
|
||||
}
|
||||
|
||||
return await this.groupsRepository.addMembers(groupId, userIds);
|
||||
}
|
||||
|
||||
async removeMemberFromGroup(groupId, userId) {
|
||||
await this.usersRepository.findById(userId);
|
||||
return await this.groupsRepository.removeMember(groupId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GroupsService;
|
||||
@@ -0,0 +1,36 @@
|
||||
const { ValidationError } = require('../shared/errors');
|
||||
|
||||
class UsersService {
|
||||
constructor({ usersRepository }) {
|
||||
this.usersRepository = usersRepository;
|
||||
}
|
||||
|
||||
async getAllUsers() {
|
||||
return await this.usersRepository.findAll();
|
||||
}
|
||||
|
||||
async getUserById(id) {
|
||||
return await this.usersRepository.findById(id);
|
||||
}
|
||||
|
||||
async updateUserProfile(id, updateData) {
|
||||
const { name, email, avatarUrl } = updateData;
|
||||
|
||||
if (name && name.trim() === '') {
|
||||
throw new ValidationError('Name cannot be empty');
|
||||
}
|
||||
|
||||
if (email && email.trim() === '') {
|
||||
throw new ValidationError('Email cannot be empty');
|
||||
}
|
||||
|
||||
const dataToUpdate = {};
|
||||
if (name !== undefined) dataToUpdate.name = name;
|
||||
if (email !== undefined) dataToUpdate.email = email;
|
||||
if (avatarUrl !== undefined) dataToUpdate.avatarUrl = avatarUrl;
|
||||
|
||||
return await this.usersRepository.update(id, dataToUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UsersService;
|
||||
Reference in New Issue
Block a user