298 lines
9.9 KiB
JavaScript
298 lines
9.9 KiB
JavaScript
import CalendarEvent from '../models/community/calendar_event.js';
|
|
import User from '../models/community/user.js';
|
|
import Friendship from '../models/community/friendship.js';
|
|
import UserParam from '../models/community/user_param.js';
|
|
import UserParamType from '../models/type/user_param.js';
|
|
import UserParamVisibility from '../models/community/user_param_visibility.js';
|
|
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
|
|
import { Op } from 'sequelize';
|
|
|
|
class CalendarService {
|
|
/**
|
|
* Get all calendar events for a user
|
|
* @param {string} hashedUserId - The user's hashed ID
|
|
* @param {object} options - Optional filters (startDate, endDate)
|
|
*/
|
|
async getEvents(hashedUserId, options = {}) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const where = { userId: user.id };
|
|
|
|
// Filter by date range if provided
|
|
if (options.startDate || options.endDate) {
|
|
where[Op.or] = [];
|
|
|
|
if (options.startDate && options.endDate) {
|
|
// Events that overlap with the requested range
|
|
where[Op.or].push({
|
|
startDate: { [Op.between]: [options.startDate, options.endDate] }
|
|
});
|
|
where[Op.or].push({
|
|
endDate: { [Op.between]: [options.startDate, options.endDate] }
|
|
});
|
|
where[Op.or].push({
|
|
[Op.and]: [
|
|
{ startDate: { [Op.lte]: options.startDate } },
|
|
{ endDate: { [Op.gte]: options.endDate } }
|
|
]
|
|
});
|
|
} else if (options.startDate) {
|
|
where[Op.or].push({ startDate: { [Op.gte]: options.startDate } });
|
|
where[Op.or].push({ endDate: { [Op.gte]: options.startDate } });
|
|
} else if (options.endDate) {
|
|
where[Op.or].push({ startDate: { [Op.lte]: options.endDate } });
|
|
}
|
|
}
|
|
|
|
const events = await CalendarEvent.findAll({
|
|
where,
|
|
order: [['startDate', 'ASC'], ['startTime', 'ASC']]
|
|
});
|
|
|
|
return events.map(e => this.formatEvent(e));
|
|
}
|
|
|
|
/**
|
|
* Get a single event by ID
|
|
*/
|
|
async getEvent(hashedUserId, eventId) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const event = await CalendarEvent.findOne({
|
|
where: { id: eventId, userId: user.id }
|
|
});
|
|
|
|
if (!event) {
|
|
throw new Error('Event not found');
|
|
}
|
|
|
|
return this.formatEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Create a new calendar event
|
|
*/
|
|
async createEvent(hashedUserId, eventData) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const event = await CalendarEvent.create({
|
|
userId: user.id,
|
|
title: eventData.title,
|
|
description: eventData.description || null,
|
|
categoryId: eventData.categoryId || 'personal',
|
|
startDate: eventData.startDate,
|
|
endDate: eventData.endDate || eventData.startDate,
|
|
startTime: eventData.allDay ? null : eventData.startTime,
|
|
endTime: eventData.allDay ? null : eventData.endTime,
|
|
allDay: eventData.allDay || false
|
|
});
|
|
|
|
return this.formatEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Update an existing calendar event
|
|
*/
|
|
async updateEvent(hashedUserId, eventId, eventData) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const event = await CalendarEvent.findOne({
|
|
where: { id: eventId, userId: user.id }
|
|
});
|
|
|
|
if (!event) {
|
|
throw new Error('Event not found');
|
|
}
|
|
|
|
await event.update({
|
|
title: eventData.title,
|
|
description: eventData.description || null,
|
|
categoryId: eventData.categoryId || 'personal',
|
|
startDate: eventData.startDate,
|
|
endDate: eventData.endDate || eventData.startDate,
|
|
startTime: eventData.allDay ? null : eventData.startTime,
|
|
endTime: eventData.allDay ? null : eventData.endTime,
|
|
allDay: eventData.allDay || false
|
|
});
|
|
|
|
return this.formatEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Delete a calendar event
|
|
*/
|
|
async deleteEvent(hashedUserId, eventId) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const event = await CalendarEvent.findOne({
|
|
where: { id: eventId, userId: user.id }
|
|
});
|
|
|
|
if (!event) {
|
|
throw new Error('Event not found');
|
|
}
|
|
|
|
await event.destroy();
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Get friends' birthdays that are visible to the user
|
|
* @param {string} hashedUserId - The user's hashed ID
|
|
* @param {number} year - The year to get birthdays for
|
|
*/
|
|
async getFriendsBirthdays(hashedUserId, year) {
|
|
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
// Get user's age for visibility check
|
|
const userAge = await this.getUserAge(user.id);
|
|
|
|
// Get all accepted friendships
|
|
const friendships = await Friendship.findAll({
|
|
where: {
|
|
accepted: true,
|
|
withdrawn: false,
|
|
denied: false,
|
|
[Op.or]: [
|
|
{ user1Id: user.id },
|
|
{ user2Id: user.id }
|
|
]
|
|
}
|
|
});
|
|
|
|
const birthdays = [];
|
|
|
|
for (const friendship of friendships) {
|
|
// Get the friend's user ID
|
|
const friendId = friendship.user1Id === user.id ? friendship.user2Id : friendship.user1Id;
|
|
|
|
// Get the friend's birthdate param with visibility
|
|
const birthdateParam = await UserParam.findOne({
|
|
where: { userId: friendId },
|
|
include: [
|
|
{
|
|
model: UserParamType,
|
|
as: 'paramType',
|
|
where: { description: 'birthdate' }
|
|
},
|
|
{
|
|
model: UserParamVisibility,
|
|
as: 'param_visibilities',
|
|
include: [{
|
|
model: UserParamVisibilityType,
|
|
as: 'visibility_type'
|
|
}]
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!birthdateParam || !birthdateParam.value) continue;
|
|
|
|
// Check visibility
|
|
const visibility = birthdateParam.param_visibilities?.[0]?.visibility_type?.description || 'Invisible';
|
|
if (!this.isBirthdayVisibleToFriend(visibility, userAge)) continue;
|
|
|
|
// Get friend's username
|
|
const friend = await User.findOne({
|
|
where: { id: friendId },
|
|
attributes: ['username', 'hashedId']
|
|
});
|
|
|
|
if (!friend) continue;
|
|
|
|
// Parse birthdate and create birthday event for the requested year
|
|
const birthdate = new Date(birthdateParam.value);
|
|
if (isNaN(birthdate.getTime())) continue;
|
|
|
|
const birthdayDate = `${year}-${String(birthdate.getMonth() + 1).padStart(2, '0')}-${String(birthdate.getDate()).padStart(2, '0')}`;
|
|
|
|
birthdays.push({
|
|
id: `birthday-${friend.hashedId}-${year}`,
|
|
title: friend.username,
|
|
categoryId: 'birthday',
|
|
startDate: birthdayDate,
|
|
endDate: birthdayDate,
|
|
allDay: true,
|
|
isBirthday: true,
|
|
friendHashedId: friend.hashedId
|
|
});
|
|
}
|
|
|
|
return birthdays;
|
|
}
|
|
|
|
/**
|
|
* Check if birthdate is visible to a friend
|
|
*/
|
|
isBirthdayVisibleToFriend(visibility, requestingUserAge) {
|
|
// Visible to friends if visibility is 'All', 'Friends', or 'FriendsAndAdults' (if adult)
|
|
return visibility === 'All' ||
|
|
visibility === 'Friends' ||
|
|
(visibility === 'FriendsAndAdults' && requestingUserAge >= 18);
|
|
}
|
|
|
|
/**
|
|
* Get user's age from birthdate
|
|
*/
|
|
async getUserAge(userId) {
|
|
const birthdateParam = await UserParam.findOne({
|
|
where: { userId },
|
|
include: [{
|
|
model: UserParamType,
|
|
as: 'paramType',
|
|
where: { description: 'birthdate' }
|
|
}]
|
|
});
|
|
|
|
if (!birthdateParam || !birthdateParam.value) return 0;
|
|
|
|
const birthdate = new Date(birthdateParam.value);
|
|
const today = new Date();
|
|
let age = today.getFullYear() - birthdate.getFullYear();
|
|
const monthDiff = today.getMonth() - birthdate.getMonth();
|
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthdate.getDate())) {
|
|
age--;
|
|
}
|
|
return age;
|
|
}
|
|
|
|
/**
|
|
* Format event for API response
|
|
*/
|
|
formatEvent(event) {
|
|
return {
|
|
id: event.id,
|
|
title: event.title,
|
|
description: event.description,
|
|
categoryId: event.categoryId,
|
|
startDate: event.startDate,
|
|
endDate: event.endDate,
|
|
startTime: event.startTime ? event.startTime.substring(0, 5) : null, // HH:MM format
|
|
endTime: event.endTime ? event.endTime.substring(0, 5) : null,
|
|
allDay: event.allDay,
|
|
createdAt: event.createdAt,
|
|
updatedAt: event.updatedAt
|
|
};
|
|
}
|
|
}
|
|
|
|
export default new CalendarService();
|