579 lines
26 KiB
JavaScript
579 lines
26 KiB
JavaScript
export async function setEventTime(eventId, startHour, startMin, endHour, endMin, month, day, year = 2026) {
|
|
const start = new Date(`${year}-${String(month).padStart(2,'0')}-${String(day).padStart(2,'0')}T${String(startHour).padStart(2,'0')}:${String(startMin).padStart(2,'0')}:00-05:00`)
|
|
const end = new Date(`${year}-${String(month).padStart(2,'0')}-${String(day).padStart(2,'0')}T${String(endHour).padStart(2,'0')}:${String(endMin).padStart(2,'0')}:00-05:00`)
|
|
|
|
console.log(start, end)
|
|
await this.sql`
|
|
UPDATE events.events
|
|
SET time_start = ${start}, time_end = ${end}
|
|
WHERE id = ${eventId}
|
|
`
|
|
}
|
|
|
|
export async function changeTimes(_userId) {
|
|
await this.setEventTime(1, 18, 30, 19, 30, 4, 27)
|
|
|
|
await this.setEventTime(2, 19, 0, 20, 0, 4, 21)
|
|
}
|
|
|
|
export async function getEventsByNetwork(networkId) {
|
|
if (!Number.isInteger(networkId) || networkId <= 0) {
|
|
throw new global.ServerError(400, "Invalid network ID!");
|
|
}
|
|
|
|
// Returns each event with its linked calendar IDs, file attachments, and recurrence rule
|
|
return await this.sql`
|
|
SELECT e.*,
|
|
COALESCE(jsonb_agg(DISTINCT ec.calendar_id) FILTER (WHERE ec.calendar_id IS NOT NULL), '[]') AS calendars,
|
|
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('id', f.id, 'name', f.name, 'type', f.type, 'original_name', f.original_name, 'size_bytes', f.size_bytes)) FILTER (WHERE f.id IS NOT NULL), '[]') AS attachments,
|
|
row_to_json(er.*) AS recurrence
|
|
FROM events.events e
|
|
LEFT JOIN events.event_calendars ec ON ec.event_id = e.id
|
|
LEFT JOIN public.file_to_event fe ON fe.event_id = e.id
|
|
LEFT JOIN public.files f ON f.id = fe.file_id
|
|
LEFT JOIN events.event_recurrence er ON er.id = e.recurrence_id
|
|
WHERE e.network_id = ${networkId}
|
|
GROUP BY e.id, er.id
|
|
`;
|
|
}
|
|
|
|
export async function getCalendarsByNetwork(networkId) {
|
|
if (!Number.isInteger(networkId) || networkId <= 0) {
|
|
throw new global.ServerError(400, "Invalid network ID!");
|
|
}
|
|
|
|
return await this.sql`
|
|
SELECT c.* FROM events.calendars c
|
|
WHERE c.network_id = ${networkId}
|
|
`;
|
|
}
|
|
|
|
export async function addCalendar(userId, newCalendar, networkId) {
|
|
const { name, description, color } = newCalendar;
|
|
try {
|
|
let insertedCalendar;
|
|
|
|
await this.sql.begin(async tx => {
|
|
const [calendar] = await tx`
|
|
INSERT INTO events.calendars ${tx({
|
|
network_id: networkId,
|
|
owner_id: userId,
|
|
name: name,
|
|
description: description ?? null,
|
|
color: color
|
|
})}
|
|
RETURNING *
|
|
`;
|
|
insertedCalendar = calendar;
|
|
});
|
|
|
|
return { status: 200, calendar: insertedCalendar };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: 400, error: "Failed to add calendar" };
|
|
}
|
|
}
|
|
|
|
export async function editCalendar(userId, id, updatedCalendar, networkId) {
|
|
const { name, description, color } = updatedCalendar;
|
|
try {
|
|
const [calendar] = await this.sql`
|
|
UPDATE events.calendars
|
|
SET
|
|
name = ${name},
|
|
description = ${description ?? null},
|
|
color = ${color},
|
|
updated_at = NOW()
|
|
WHERE
|
|
id = ${id}
|
|
AND network_id = ${networkId}
|
|
AND owner_id = ${userId}
|
|
RETURNING *
|
|
`;
|
|
|
|
if (!calendar) return { status: 403, error: "Calendar not found or not authorized" };
|
|
return { status: 200, calendar: calendar };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: 400, error: "Failed to edit calendar" };
|
|
}
|
|
}
|
|
|
|
export async function deleteCalendar(userId, id, networkId) {
|
|
try {
|
|
const [calendar] = await this.sql`
|
|
DELETE FROM events.calendars
|
|
WHERE id = ${id} AND network_id = ${networkId} AND owner_id = ${userId}
|
|
RETURNING *
|
|
`;
|
|
if (!calendar) return { status: 403, error: "Calendar not found or not authorized" };
|
|
return { status: 200 };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: 400, error: "Failed to delete calendar" };
|
|
}
|
|
}
|
|
|
|
export async function addEvent(userId, newEvent, networkId) {
|
|
const { title, description, location, time_start, time_end, all_day, calendars, recurrence } = newEvent;
|
|
try {
|
|
let insertedEvent;
|
|
|
|
await this.sql.begin(async tx => {
|
|
let recurrenceId = null;
|
|
if (recurrence) {
|
|
const [rule] = await tx`
|
|
INSERT INTO events.event_recurrence ${tx({
|
|
frequency: recurrence.frequency,
|
|
interval: recurrence.interval ?? 1,
|
|
days_of_week: recurrence.days_of_week ?? null,
|
|
end_date: recurrence.end_date ?? null,
|
|
count: recurrence.count ?? null
|
|
})} RETURNING id
|
|
`;
|
|
recurrenceId = rule.id;
|
|
}
|
|
|
|
const [event] = await tx`
|
|
INSERT INTO events.events ${tx({
|
|
network_id: networkId,
|
|
creator_id: userId,
|
|
title,
|
|
description: description ?? null,
|
|
location: location ?? null,
|
|
time_start,
|
|
time_end,
|
|
all_day,
|
|
recurrence_id: recurrenceId
|
|
})} RETURNING *
|
|
`;
|
|
|
|
insertedEvent = event;
|
|
|
|
await Promise.all(calendars.map(calId => tx`
|
|
INSERT INTO events.event_calendars ${tx({ event_id: event.id, calendar_id: calId })}
|
|
`));
|
|
});
|
|
|
|
return { status: 200, event: { ...insertedEvent, calendars, recurrence: recurrence ?? null } };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: e.status, error: e.message };
|
|
}
|
|
}
|
|
|
|
export async function addAttachments(userId, eventId, files) {
|
|
const [event] = await this.sql`
|
|
SELECT id FROM events.events
|
|
WHERE id = ${eventId} AND creator_id = ${userId}
|
|
`;
|
|
if (!event) throw new global.ServerError(403, "Not authorized to add attachments to this event");
|
|
|
|
try {
|
|
let insertedFiles = [];
|
|
|
|
// Will rollback changes if it encounters an error
|
|
await this.sql.begin(async tx => {
|
|
insertedFiles = await Promise.all(
|
|
files.map(file =>
|
|
tx`
|
|
INSERT INTO public.files ${tx({
|
|
name: file.filename,
|
|
type: file.mimetype,
|
|
original_name: file.originalname,
|
|
size_bytes: file.size
|
|
})}
|
|
RETURNING *
|
|
`.then(([f]) => f)
|
|
)
|
|
);
|
|
|
|
await Promise.all(
|
|
insertedFiles.map(file => tx`
|
|
INSERT INTO public.file_to_event ${tx({
|
|
file_id: file.id,
|
|
event_id: eventId
|
|
})}
|
|
`)
|
|
);
|
|
})
|
|
|
|
console.log("addAttachments:", insertedFiles)
|
|
return { status: 200, files: insertedFiles };
|
|
} catch (e) {
|
|
console.error(e);
|
|
throw new global.ServerError(400, "Failed to add attachments!");
|
|
}
|
|
}
|
|
|
|
export async function deleteAttachment(userId, fileId, eventId) {
|
|
const [event] = await this.sql`
|
|
SELECT id FROM events.events
|
|
WHERE id = ${eventId} AND creator_id = ${userId}
|
|
`;
|
|
if (!event) throw new global.ServerError(403, "Not authorized to modify this event");
|
|
|
|
const [file] = await this.sql`
|
|
SELECT f.* FROM public.files f
|
|
JOIN public.file_to_event fte ON fte.file_id = f.id
|
|
WHERE f.id = ${fileId} AND fte.event_id = ${eventId}
|
|
`;
|
|
if (!file) throw new global.ServerError(404, "Attachment not found on this event");
|
|
|
|
try {
|
|
let deletedFile = null;
|
|
await this.sql.begin(async tx => {
|
|
await tx`DELETE FROM public.file_to_event WHERE file_id = ${fileId} AND event_id = ${eventId}`;
|
|
// Only remove the file record (and trigger disk unlink) if no other event still references it
|
|
const [stillReferenced] = await tx`SELECT 1 FROM public.file_to_event WHERE file_id = ${fileId} LIMIT 1`;
|
|
if (!stillReferenced) {
|
|
await tx`DELETE FROM public.files WHERE id = ${fileId}`;
|
|
deletedFile = file;
|
|
}
|
|
});
|
|
return { status: 200, file: deletedFile };
|
|
} catch (e) {
|
|
console.error(e);
|
|
throw new global.ServerError(400, "Failed to delete attachment");
|
|
}
|
|
}
|
|
|
|
export async function editEvent(userId, id, updatedEvent, networkId) {
|
|
const { title, description, location, time_start, time_end, all_day, calendars, recurrence, scope, exception_date } = updatedEvent;
|
|
|
|
try {
|
|
let editedEvent;
|
|
|
|
await this.sql.begin(async tx => {
|
|
const [current] = await tx`
|
|
SELECT * FROM events.events
|
|
WHERE id = ${id} AND network_id = ${networkId} AND creator_id = ${userId}
|
|
`;
|
|
if (!current) return;
|
|
|
|
const isRecurring = !!current.recurrence_id;
|
|
const isOverride = !!current.recurrence_parent_id;
|
|
|
|
if (!isRecurring || isOverride || !scope || scope === 'all') {
|
|
// Standard update: non-recurring, override row, or editing the whole series
|
|
let newRecurrenceId = current.recurrence_id;
|
|
|
|
if (isRecurring && recurrence) {
|
|
// Preserve end_date if not explicitly provided — it may have been set by a prior 'this and future' split
|
|
const [existingRule] = await tx`SELECT end_date FROM events.event_recurrence WHERE id = ${current.recurrence_id}`;
|
|
const preservedEndDate = recurrence.end_date ?? existingRule?.end_date ?? null;
|
|
await tx`
|
|
UPDATE events.event_recurrence SET
|
|
frequency = ${recurrence.frequency},
|
|
interval = ${recurrence.interval ?? 1},
|
|
days_of_week = ${recurrence.days_of_week ?? null},
|
|
end_date = ${preservedEndDate},
|
|
count = ${recurrence.count ?? null},
|
|
updated_at = NOW()
|
|
WHERE id = ${current.recurrence_id}
|
|
`;
|
|
} else if (!isRecurring && recurrence) {
|
|
// Adding recurrence to a previously non-recurring event
|
|
const [newRule] = await tx`
|
|
INSERT INTO events.event_recurrence ${tx({
|
|
frequency: recurrence.frequency,
|
|
interval: recurrence.interval ?? 1,
|
|
days_of_week: recurrence.days_of_week ?? null,
|
|
end_date: recurrence.end_date ?? null,
|
|
count: recurrence.count ?? null
|
|
})} RETURNING *
|
|
`;
|
|
newRecurrenceId = newRule.id;
|
|
} else if (isRecurring && !recurrence) {
|
|
// Removing recurrence from a recurring event
|
|
newRecurrenceId = null;
|
|
}
|
|
|
|
const [event] = await tx`
|
|
UPDATE events.events SET
|
|
title = ${title},
|
|
description = ${description ?? null},
|
|
location = ${location ?? null},
|
|
time_start = ${time_start},
|
|
time_end = ${time_end},
|
|
all_day = ${all_day},
|
|
recurrence_id = ${newRecurrenceId},
|
|
updated_at = NOW()
|
|
WHERE id = ${id} AND network_id = ${networkId} AND creator_id = ${userId}
|
|
RETURNING *
|
|
`;
|
|
if (!event) return;
|
|
editedEvent = event;
|
|
|
|
// Delete the orphaned rule after the FK is cleared
|
|
if (isRecurring && !recurrence) {
|
|
await tx`DELETE FROM events.event_recurrence WHERE id = ${current.recurrence_id}`;
|
|
}
|
|
await tx`DELETE FROM events.event_calendars WHERE event_id = ${id}`;
|
|
if (calendars.length > 0) {
|
|
await Promise.all(calendars.map(calId => tx`
|
|
INSERT INTO events.event_calendars ${tx({ event_id: id, calendar_id: calId })}
|
|
`));
|
|
}
|
|
|
|
} else if (scope === 'single') {
|
|
// Insert an override row for just this one occurrence
|
|
const [override] = await tx`
|
|
INSERT INTO events.events ${tx({
|
|
network_id: networkId,
|
|
creator_id: userId,
|
|
title,
|
|
description: description ?? null,
|
|
location: location ?? null,
|
|
time_start,
|
|
time_end,
|
|
all_day,
|
|
recurrence_parent_id: id,
|
|
recurrence_exception_date: exception_date
|
|
})} RETURNING *
|
|
`;
|
|
editedEvent = override;
|
|
// Inherit the parent template's attachments so the override starts with the same files
|
|
await tx`
|
|
INSERT INTO public.file_to_event (file_id, event_id)
|
|
SELECT file_id, ${override.id} FROM public.file_to_event WHERE event_id = ${id}
|
|
ON CONFLICT DO NOTHING
|
|
`;
|
|
if (calendars.length > 0) {
|
|
await Promise.all(calendars.map(calId => tx`
|
|
INSERT INTO events.event_calendars ${tx({ event_id: override.id, calendar_id: calId })}
|
|
`));
|
|
}
|
|
|
|
} else if (scope === 'future') {
|
|
// Capture current end_date before modifying — marks the boundary of A's existing series
|
|
const [existingRule] = current.recurrence_id
|
|
? await tx`SELECT * FROM events.event_recurrence WHERE id = ${current.recurrence_id}`
|
|
: [null];
|
|
const oldEndDate = existingRule?.end_date ?? null;
|
|
|
|
// Cap the current series at exception_date, then create a new series from there
|
|
await tx`
|
|
UPDATE events.event_recurrence SET
|
|
end_date = ${exception_date},
|
|
updated_at = NOW()
|
|
WHERE id = ${current.recurrence_id}
|
|
`;
|
|
|
|
// Remove intermediate descendants in [exception_date, oldEndDate); independent splits at/beyond oldEndDate are preserved
|
|
const descendants = await tx`
|
|
SELECT id, recurrence_id FROM events.events
|
|
WHERE parent_template_id = ${id}
|
|
AND time_start >= ${exception_date}
|
|
${oldEndDate ? tx`AND time_start < ${oldEndDate}` : tx``}
|
|
`;
|
|
if (descendants.length > 0) {
|
|
const descIds = descendants.map(d => d.id);
|
|
const descRuleIds = descendants.map(d => d.recurrence_id).filter(Boolean);
|
|
await tx`DELETE FROM events.events WHERE id = ANY(${descIds})`;
|
|
if (descRuleIds.length > 0) {
|
|
await tx`DELETE FROM events.event_recurrence WHERE id = ANY(${descRuleIds})`;
|
|
}
|
|
}
|
|
|
|
let newRecurrenceId = null;
|
|
if (recurrence) {
|
|
// If A was already capped, the new series must not extend past that boundary
|
|
const newEndDate = oldEndDate
|
|
? (!recurrence.end_date || new Date(recurrence.end_date) >= new Date(oldEndDate) ? oldEndDate : recurrence.end_date)
|
|
: (recurrence.end_date ?? null);
|
|
const [rule] = await tx`
|
|
INSERT INTO events.event_recurrence ${tx({
|
|
frequency: recurrence.frequency,
|
|
interval: recurrence.interval ?? 1,
|
|
days_of_week: recurrence.days_of_week ?? null,
|
|
end_date: newEndDate,
|
|
count: recurrence.count ?? null
|
|
})} RETURNING id
|
|
`;
|
|
newRecurrenceId = rule.id;
|
|
} else if (current.recurrence_id) {
|
|
// No new recurrence rule — copy A's rule with oldEndDate so the new series isn't infinite
|
|
if (existingRule) {
|
|
const [rule] = await tx`
|
|
INSERT INTO events.event_recurrence ${tx({
|
|
frequency: existingRule.frequency,
|
|
interval: existingRule.interval,
|
|
days_of_week: existingRule.days_of_week,
|
|
end_date: oldEndDate,
|
|
count: existingRule.count
|
|
})} RETURNING id
|
|
`;
|
|
newRecurrenceId = rule.id;
|
|
}
|
|
}
|
|
const [newEvent] = await tx`
|
|
INSERT INTO events.events ${tx({
|
|
network_id: networkId,
|
|
creator_id: userId,
|
|
title,
|
|
description: description ?? null,
|
|
location: location ?? null,
|
|
time_start,
|
|
time_end,
|
|
all_day,
|
|
recurrence_id: newRecurrenceId,
|
|
parent_template_id: id
|
|
})} RETURNING *
|
|
`;
|
|
editedEvent = newEvent;
|
|
// Inherit the original template's attachments so the new series starts with the same files
|
|
await tx`
|
|
INSERT INTO public.file_to_event (file_id, event_id)
|
|
SELECT file_id, ${newEvent.id} FROM public.file_to_event WHERE event_id = ${id}
|
|
ON CONFLICT DO NOTHING
|
|
`;
|
|
if (calendars.length > 0) {
|
|
await Promise.all(calendars.map(calId => tx`
|
|
INSERT INTO events.event_calendars ${tx({ event_id: newEvent.id, calendar_id: calId })}
|
|
`));
|
|
}
|
|
|
|
// Migrate A's overrides in [exception_date, oldEndDate) to the new template to preserve their customizations
|
|
await tx`
|
|
UPDATE events.events
|
|
SET recurrence_parent_id = ${newEvent.id}
|
|
WHERE recurrence_parent_id = ${id}
|
|
AND recurrence_exception_date >= ${exception_date}
|
|
${oldEndDate ? tx`AND recurrence_exception_date < ${oldEndDate}` : tx``}
|
|
`;
|
|
}
|
|
});
|
|
|
|
if (!editedEvent) return { status: 403, error: "Event not found or not authorized" };
|
|
return { status: 200, event: { ...editedEvent, calendars, recurrence: recurrence ?? null } };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: 400, error: "Failed to edit event" };
|
|
}
|
|
}
|
|
|
|
export async function deleteEvent(userId, eventId, networkId, scope, exception_date) {
|
|
try {
|
|
const [event] = await this.sql`
|
|
SELECT * FROM events.events
|
|
WHERE id = ${eventId} AND network_id = ${networkId} AND creator_id = ${userId}
|
|
`;
|
|
if (!event) return { status: 403, error: "Event not found or not authorized" };
|
|
|
|
const isRecurring = !!event.recurrence_id;
|
|
const isOverride = !!event.recurrence_parent_id;
|
|
|
|
if (isOverride) {
|
|
// Already a concrete row — just mark it cancelled
|
|
await this.sql`UPDATE events.events SET is_cancelled = true WHERE id = ${eventId}`;
|
|
return { status: 200 };
|
|
}
|
|
|
|
if (isRecurring && scope === 'single') {
|
|
// Virtual occurrence — insert a cancelled placeholder so it gets skipped
|
|
await this.sql`
|
|
INSERT INTO events.events ${this.sql({
|
|
network_id: networkId,
|
|
creator_id: userId,
|
|
title: event.title,
|
|
time_start: exception_date,
|
|
time_end: exception_date,
|
|
all_day: event.all_day,
|
|
recurrence_parent_id: eventId,
|
|
recurrence_exception_date: exception_date,
|
|
is_cancelled: true
|
|
})}
|
|
`;
|
|
return { status: 200 };
|
|
}
|
|
|
|
if (isRecurring && scope === 'future' && new Date(exception_date) > new Date(event.time_start)) {
|
|
// Capture current end_date before modifying — marks the boundary of independent later splits
|
|
const [existingRule] = await this.sql`SELECT end_date FROM events.event_recurrence WHERE id = ${event.recurrence_id}`;
|
|
const oldEndDate = existingRule?.end_date ?? null;
|
|
|
|
// Cap the series at this date
|
|
await this.sql`
|
|
UPDATE events.event_recurrence SET end_date = ${exception_date}, updated_at = NOW()
|
|
WHERE id = ${event.recurrence_id}
|
|
`;
|
|
// Promote future non-cancelled overrides (single-event edits) to standalone events
|
|
await this.sql`
|
|
UPDATE events.events
|
|
SET recurrence_parent_id = NULL, recurrence_exception_date = NULL, updated_at = NOW()
|
|
WHERE recurrence_parent_id = ${eventId}
|
|
AND recurrence_exception_date >= ${exception_date}
|
|
AND (is_cancelled IS NULL OR is_cancelled = false)
|
|
`;
|
|
// Drop future cancelled placeholders — they are meaningless without the series
|
|
await this.sql`
|
|
DELETE FROM events.events
|
|
WHERE recurrence_parent_id = ${eventId} AND recurrence_exception_date >= ${exception_date}
|
|
AND is_cancelled = true
|
|
`;
|
|
// Remove intermediate descendant templates in [exception_date, oldEndDate); independent splits at/beyond oldEndDate are preserved
|
|
const descendants = await this.sql`
|
|
SELECT id, recurrence_id FROM events.events
|
|
WHERE parent_template_id = ${eventId}
|
|
AND time_start >= ${exception_date}
|
|
${oldEndDate ? this.sql`AND time_start < ${oldEndDate}` : this.sql``}
|
|
`;
|
|
if (descendants.length > 0) {
|
|
const descIds = descendants.map(d => d.id);
|
|
const descRuleIds = descendants.map(d => d.recurrence_id).filter(Boolean);
|
|
await this.sql`DELETE FROM events.events WHERE id = ANY(${descIds})`;
|
|
if (descRuleIds.length > 0) {
|
|
await this.sql`DELETE FROM events.event_recurrence WHERE id = ANY(${descRuleIds})`;
|
|
}
|
|
}
|
|
return { status: 200 };
|
|
}
|
|
|
|
// Delete the whole event/series — collect files from the template AND all overrides,
|
|
// but only those not shared with surviving events (e.g. a 'this and future' split
|
|
// inherits the same file_ids; deleting the physical file would orphan that series)
|
|
const files = await this.sql`
|
|
SELECT DISTINCT f.* FROM public.files f
|
|
JOIN public.file_to_event fte ON fte.file_id = f.id
|
|
JOIN events.events e ON e.id = fte.event_id
|
|
WHERE (e.id = ${eventId} OR e.recurrence_parent_id = ${eventId})
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM public.file_to_event fte2
|
|
WHERE fte2.file_id = f.id
|
|
AND fte2.event_id NOT IN (
|
|
SELECT id FROM events.events WHERE id = ${eventId} OR recurrence_parent_id = ${eventId}
|
|
)
|
|
)
|
|
`;
|
|
const recurrenceId = event.recurrence_id;
|
|
|
|
await this.sql.begin(async tx => {
|
|
// Promote non-cancelled overrides (single-event edits) to standalone events before deleting the template
|
|
await tx`
|
|
UPDATE events.events
|
|
SET recurrence_parent_id = NULL, recurrence_exception_date = NULL, updated_at = NOW()
|
|
WHERE recurrence_parent_id = ${eventId}
|
|
AND (is_cancelled IS NULL OR is_cancelled = false)
|
|
`;
|
|
await tx`DELETE FROM public.file_to_event WHERE event_id = ${eventId}`;
|
|
if (files.length > 0) {
|
|
await tx`DELETE FROM public.files WHERE id IN ${tx(files.map(f => f.id))}`;
|
|
}
|
|
await tx`DELETE FROM events.event_calendars WHERE event_id = ${eventId}`;
|
|
await tx`DELETE FROM events.events WHERE id = ${eventId}`; // CASCADE removes remaining cancelled overrides
|
|
if (recurrenceId) {
|
|
await tx`DELETE FROM events.event_recurrence WHERE id = ${recurrenceId}`;
|
|
}
|
|
});
|
|
|
|
await Promise.all(files.map(async file => {
|
|
await fs.unlink(`${global.DB_PATH}/images/events/${file.name}`).catch(() => {});
|
|
}));
|
|
|
|
return { status: 200 };
|
|
} catch (e) {
|
|
console.error(e);
|
|
return { status: 400, error: "Failed to delete event" };
|
|
}
|
|
} |