new features
This commit is contained in:
@@ -390,60 +390,63 @@ class EventsService {
|
||||
* Capped at 100 instances and 2 years from the start date. */
|
||||
private generateRecurrenceDates(startDate: Date, _endDate: Date, config: WiederholungConfig): Date[] {
|
||||
const dates: Date[] = [];
|
||||
const limitDate = new Date(config.bis);
|
||||
const limitDate = config.bis ? new Date(config.bis + 'T23:59:59Z') : new Date(0);
|
||||
const interval = config.intervall ?? 1;
|
||||
// Cap at 100 instances max, and 2 years
|
||||
const maxDate = new Date(startDate);
|
||||
maxDate.setFullYear(maxDate.getFullYear() + 2);
|
||||
maxDate.setUTCFullYear(maxDate.getUTCFullYear() + 2);
|
||||
const effectiveLimit = limitDate < maxDate ? limitDate : maxDate;
|
||||
|
||||
let current = new Date(startDate);
|
||||
const originalDay = startDate.getDate();
|
||||
// Work in UTC to avoid timezone shifts
|
||||
let currentMs = startDate.getTime();
|
||||
const originalDay = startDate.getUTCDate();
|
||||
const startHours = startDate.getUTCHours();
|
||||
const startMinutes = startDate.getUTCMinutes();
|
||||
|
||||
while (dates.length < 100) {
|
||||
let current = new Date(currentMs);
|
||||
// Advance to next occurrence
|
||||
switch (config.typ) {
|
||||
case 'wöchentlich':
|
||||
current = new Date(current);
|
||||
current.setDate(current.getDate() + 7 * interval);
|
||||
current.setUTCDate(current.getUTCDate() + 7 * interval);
|
||||
break;
|
||||
case 'zweiwöchentlich':
|
||||
current = new Date(current);
|
||||
current.setDate(current.getDate() + 14);
|
||||
current.setUTCDate(current.getUTCDate() + 14);
|
||||
break;
|
||||
case 'monatlich_datum': {
|
||||
current = new Date(current);
|
||||
const targetMonth = current.getMonth() + 1;
|
||||
current.setDate(1);
|
||||
current.setMonth(targetMonth);
|
||||
const lastDay = new Date(current.getFullYear(), current.getMonth() + 1, 0).getDate();
|
||||
current.setDate(Math.min(originalDay, lastDay));
|
||||
const targetMonth = current.getUTCMonth() + interval;
|
||||
current.setUTCDate(1);
|
||||
current.setUTCMonth(targetMonth);
|
||||
const lastDay = new Date(Date.UTC(current.getUTCFullYear(), current.getUTCMonth() + 1, 0)).getUTCDate();
|
||||
current.setUTCDate(Math.min(originalDay, lastDay));
|
||||
current.setUTCHours(startHours, startMinutes, 0, 0);
|
||||
break;
|
||||
}
|
||||
case 'monatlich_erster_wochentag': {
|
||||
const targetWeekday = config.wochentag ?? 0; // 0=Mon
|
||||
current = new Date(current);
|
||||
current.setMonth(current.getMonth() + 1);
|
||||
current.setDate(1);
|
||||
current.setUTCMonth(current.getUTCMonth() + 1);
|
||||
current.setUTCDate(1);
|
||||
// Convert JS Sunday=0 to Monday=0: (getDay()+6)%7
|
||||
while ((current.getDay() + 6) % 7 !== targetWeekday) {
|
||||
current.setDate(current.getDate() + 1);
|
||||
while ((current.getUTCDay() + 6) % 7 !== targetWeekday) {
|
||||
current.setUTCDate(current.getUTCDate() + 1);
|
||||
}
|
||||
current.setUTCHours(startHours, startMinutes, 0, 0);
|
||||
break;
|
||||
}
|
||||
case 'monatlich_letzter_wochentag': {
|
||||
const targetWeekday = config.wochentag ?? 0;
|
||||
current = new Date(current);
|
||||
// Go to last day of next month
|
||||
current.setMonth(current.getMonth() + 2);
|
||||
current.setDate(0);
|
||||
while ((current.getDay() + 6) % 7 !== targetWeekday) {
|
||||
current.setDate(current.getDate() - 1);
|
||||
current.setUTCMonth(current.getUTCMonth() + 2);
|
||||
current.setUTCDate(0);
|
||||
while ((current.getUTCDay() + 6) % 7 !== targetWeekday) {
|
||||
current.setUTCDate(current.getUTCDate() - 1);
|
||||
}
|
||||
current.setUTCHours(startHours, startMinutes, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current > effectiveLimit) break;
|
||||
currentMs = current.getTime();
|
||||
dates.push(new Date(current));
|
||||
}
|
||||
return dates;
|
||||
@@ -515,16 +518,63 @@ class EventsService {
|
||||
* Hard-deletes an event (and any recurrence children) from the database.
|
||||
* Returns true if the event was found and deleted, false if not found.
|
||||
*/
|
||||
async deleteEvent(id: string): Promise<boolean> {
|
||||
logger.info('Hard-deleting event', { id });
|
||||
// Delete recurrence children first (wiederholung_parent_id references)
|
||||
await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE wiederholung_parent_id = $1`,
|
||||
async deleteEvent(id: string, mode: 'all' | 'single' | 'future' = 'all'): Promise<boolean> {
|
||||
logger.info('Hard-deleting event', { id, mode });
|
||||
|
||||
if (mode === 'single') {
|
||||
// Delete only this single instance
|
||||
const result = await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
if (mode === 'future') {
|
||||
// Delete this instance and all later instances in the same series
|
||||
const event = await pool.query(
|
||||
`SELECT id, datum_von, wiederholung_parent_id FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (event.rows.length === 0) return false;
|
||||
const row = event.rows[0];
|
||||
const parentId = row.wiederholung_parent_id ?? row.id;
|
||||
const datumVon = new Date(row.datum_von);
|
||||
|
||||
// Delete this instance and all siblings/children with datum_von >= this one
|
||||
await pool.query(
|
||||
`DELETE FROM veranstaltungen
|
||||
WHERE (wiederholung_parent_id = $1 OR id = $1)
|
||||
AND datum_von >= $2
|
||||
AND id != $1`,
|
||||
[parentId, datumVon]
|
||||
);
|
||||
// Also delete the selected instance itself
|
||||
await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// mode === 'all': Delete parent + all children (original behavior)
|
||||
// First check if this is a child instance — find the parent
|
||||
const event = await pool.query(
|
||||
`SELECT id, wiederholung_parent_id FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (event.rows.length === 0) return false;
|
||||
const parentId = event.rows[0].wiederholung_parent_id ?? id;
|
||||
|
||||
// Delete all children of the parent
|
||||
await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE wiederholung_parent_id = $1`,
|
||||
[parentId]
|
||||
);
|
||||
// Delete the parent itself
|
||||
const result = await pool.query(
|
||||
`DELETE FROM veranstaltungen WHERE id = $1`,
|
||||
[id]
|
||||
[parentId]
|
||||
);
|
||||
return (result.rowCount ?? 0) > 0;
|
||||
}
|
||||
@@ -603,9 +653,9 @@ class EventsService {
|
||||
FROM (
|
||||
SELECT unnest(authentik_groups) AS group_name
|
||||
FROM users
|
||||
WHERE is_active = true
|
||||
WHERE authentik_groups IS NOT NULL
|
||||
) g
|
||||
WHERE group_name LIKE 'dashboard_%'
|
||||
WHERE group_name != 'dashboard_admin'
|
||||
ORDER BY group_name`
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user