PageSourceSearch

https://app.medi-one.ai/modules/calendarApi.js

js medi-one.ai collected 2026-10-05 14:14:03 UTC 9,747 bytes, 261 lines download raw bytes

1// phase8e-calendar-api: central client for all calendar Firestore operations now via REST API.
2import { Auth } from './auth.js';
3// calendar-sync-v1 (2026-07-27): Jede Terminaenderung meldet sich, damit andere
4// offene Ansichten (Kalender, digitale Rezeption) nicht auf altem Stand stehen
5// bleiben. opts.source sagt, wer geschrieben hat - der Kalender aktualisiert
6// sich selbst und braucht die Meldung dann nicht ein zweites Mal.
7import { notifyCalendarChanged } from './calendarSync.js';
8
9const cache = {
10  connections: { data: null, ts: 0 },
11};
12const TTL_MS = 8000;
13
14async function _fetch(path, init = {}) {
15  const token = await Auth.getIdToken();
16  const headers = { 'Content-Type': 'application/json', ...(init.headers || {}) };
17  if (token) headers['Authorization'] = `Bearer ${token}`;
18  const res = await fetch(path, { ...init, headers, credentials: 'include' });
19  if (!res.ok) {
20    let body = null;
21    try { body = await res.json(); } catch (_) { /* noop */ }
22    const err = new Error(body?.error || `HTTP ${res.status}`);
23    err.status = res.status;
24    err.body = body;
25    throw err;
26  }
27  return res.json();
28}
29
30// --- CONNECTIONS ---
31export async function listConnections({ force = false } = {}) {
32  const now = Date.now();
33  if (!force && cache.connections.data && (now - cache.connections.ts) < TTL_MS) {
34    return cache.connections.data;
35  }
36  const r = await _fetch('/api/calendar/connections');
37  cache.connections = { data: r.connections || {}, ts: now };
38  return cache.connections.data;
39}
40
41export async function getConnection(provider) {
42  const r = await _fetch(`/api/calendar/connections/${encodeURIComponent(provider)}`);
43  return { exists: !!r.exists, data: r.connection || null };
44}
45
46export async function setConnection(provider, data, { merge = false } = {}) {
47  cache.connections = { data: null, ts: 0 };
48  return _fetch(`/api/calendar/connections/${encodeURIComponent(provider)}${merge ? '?merge=1' : ''}`, {
49    method: 'POST',
50    body: JSON.stringify(data || {}),
51  });
52}
53
54export async function patchConnection(provider, data) {
55  cache.connections = { data: null, ts: 0 };
56  return _fetch(`/api/calendar/connections/${encodeURIComponent(provider)}`, {
57    method: 'PATCH',
58    body: JSON.stringify(data || {}),
59  });
60}
61
62export async function deleteConnection(provider) {
63  cache.connections = { data: null, ts: 0 };
64  return _fetch(`/api/calendar/connections/${encodeURIComponent(provider)}`, { method: 'DELETE' });
65}
66
67// --- INTEGRATIONS (alt location) ---
68export async function getIntegration(provider) {
69  const r = await _fetch(`/api/calendar/integrations/${encodeURIComponent(provider)}`);
70  return { exists: !!r.exists, data: r.integration || null };
71}
72
73export async function setIntegration(provider, data, { merge = false } = {}) {
74  return _fetch(`/api/calendar/integrations/${encodeURIComponent(provider)}${merge ? '?merge=1' : ''}`, {
75    method: 'POST',
76    body: JSON.stringify(data || {}),
77  });
78}
79
80export async function deleteIntegration(provider) {
81  return _fetch(`/api/calendar/integrations/${encodeURIComponent(provider)}`, { method: 'DELETE' });
82}
83
84// --- CALENDAR TYPES (existing endpoints in routes/calendar.py) ---
85export async function listCalendarTypes() {
86  const r = await _fetch('/api/calendar/types');
87  return r.types || r.calendarTypes || r.items || (Array.isArray(r) ? r : []);
88}
89
90export async function getCalendarType(id) {
91  return _fetch(`/api/calendar/types/${encodeURIComponent(id)}`);
92}
93
94export async function createCalendarType(data) {
95  return _fetch('/api/calendar/types', { method: 'POST', body: JSON.stringify(data || {}) });
96}
97
98export async function updateCalendarType(id, data) {
99  return _fetch(`/api/calendar/types/${encodeURIComponent(id)}`, {
100    method: 'PUT',
101    body: JSON.stringify(data || {}),
102  });
103}
104
105export async function deleteCalendarType(id) {
106  return _fetch(`/api/calendar/types/${encodeURIComponent(id)}`, { method: 'DELETE' });
107}
108
109// --- EVENT MAPPINGS ---
110export async function listEventMappings(filters = {}) {
111  const qs = new URLSearchParams();
112  for (const [k, v] of Object.entries(filters)) {
113    if (v != null && v !== '') qs.append(k, v);
114  }
115  const r = await _fetch(`/api/calendar/event-mappings${qs.toString() ? '?' + qs.toString() : ''}`);
116  return r.mappings || [];
117}
118
119export async function createEventMapping(data) {
120  return _fetch('/api/calendar/event-mappings', { method: 'POST', body: JSON.stringify(data || {}) });
121}
122
123export async function createEventMappingsBatch(mappings) {
124  return _fetch('/api/calendar/event-mappings', {
125    method: 'POST',
126    body: JSON.stringify({ mappings: mappings || [] }),
127  });
128}
129
130export async function deleteEventMapping(id) {
131  return _fetch(`/api/calendar/event-mappings/${encodeURIComponent(id)}`, { method: 'DELETE' });
132}
133
134export async function deleteEventMappingsBatch({ ids = null, provider = null, calendar_id = null } = {}) {
135  return _fetch('/api/calendar/event-mappings', {
136    method: 'DELETE',
137    body: JSON.stringify({ ids, provider, calendar_id }),
138  });
139}
140
141// --- EVENTS (top-level calendar collection) ---
142export async function listEvents({ from, to, calendar_id, limit } = {}) {
143  const qs = new URLSearchParams();
144  if (from) qs.append('from', from);
145  if (to) qs.append('to', to);
146  if (calendar_id) qs.append('calendar_id', calendar_id);
147  if (limit) qs.append('limit', limit);
148  const r = await _fetch(`/api/calendar/events${qs.toString() ? '?' + qs.toString() : ''}`);
149  return r.events || r.items || (Array.isArray(r) ? r : []);
150}
151
152export async function listEventsByCalendar({ calendar_id, googleEventId, from, to } = {}) {
153  const qs = new URLSearchParams();
154  if (calendar_id) qs.append('calendar_id', calendar_id);
155  if (googleEventId) qs.append('googleEventId', googleEventId);
156  if (from) qs.append('from', from);
157  if (to) qs.append('to', to);
158  const r = await _fetch(`/api/calendar/events/by-calendar?${qs.toString()}`);
159  return r.events || [];
160}
161
162export async function getEvent(id) {
163  return _fetch(`/api/calendar/events/${encodeURIComponent(id)}`);
164}
165
166export async function createEvent(data, opts) {
167  const _r = await _fetch('/api/calendar/events', { method: 'POST', body: JSON.stringify(data || {}) });
168  notifyCalendarChanged({ source: (opts && opts.source) || 'kalender', action: 'create', eventId: (_r && (_r.id || _r.eventId)) || '' });
169  return _r;
170}
171
172export async function updateEvent(id, data, opts) {
173  const _r = await _fetch(`/api/calendar/events/${encodeURIComponent(id)}`, {
174    method: 'PUT',
175    body: JSON.stringify(data || {}),
176  });
177  notifyCalendarChanged({ source: (opts && opts.source) || 'kalender', action: 'update', eventId: id });
178  return _r;
179}
180
181export async function deleteEvent(id, opts) {
182  const _r = await _fetch(`/api/calendar/events/${encodeURIComponent(id)}`, { method: 'DELETE' });
183  notifyCalendarChanged({ source: (opts && opts.source) || 'kalender', action: 'delete', eventId: id });
184  return _r;
185}
186
187export async function batchEvents(ops, opts) {
188  const _r = await _fetch('/api/calendar/events/batch', {
189    method: 'POST',
190    body: JSON.stringify({ ops: ops || [] }),
191  });
192  notifyCalendarChanged({ source: (opts && opts.source) || 'kalender', action: 'batch', eventId: '' });
193  return _r;
194}
195
196// --- BOOKING SETTINGS ---
197export async function getBookingSettings() {
198  const r = await _fetch('/api/calendar/booking-settings');
199  return r.bookingSettings || {};
200}
201
202export async function patchBookingSettings(bookingSettings) {
203  return _fetch('/api/calendar/booking-settings', {
204    method: 'PATCH',
205    body: JSON.stringify({ bookingSettings: bookingSettings || {} }),
206  });
207}
208
209export async function getSmsUsage() {
210  return _fetch('/api/calendar/sms-usage');
211}
212
213// --- OUTLOOK SHADOW EVENTS ---
214export async function getOutlookEvent(id) {
215  const r = await _fetch(`/api/calendar/events-outlook/${encodeURIComponent(id)}`);
216  return { exists: !!r.exists, data: r.event || null };
217}
218
219export async function setOutlookEvent(id, data) {
220  return _fetch(`/api/calendar/events-outlook/${encodeURIComponent(id)}`, {
221    method: 'POST',
222    body: JSON.stringify(data || {}),
223  });
224}
225
226export async function deleteOutlookEvent(id) {
227  return _fetch(`/api/calendar/events-outlook/${encodeURIComponent(id)}`, { method: 'DELETE' });
228}
229
230// --- PROVIDER MAPPINGS (per-user subcollections) ---
231export async function listProviderMappings(provider) {
232  const r = await _fetch(`/api/calendar/${encodeURIComponent(prov
232ider)}-mappings`);
233  return r.mappings || [];
234}
235
236export async function createProviderMappings(provider, mappings) {
237  return _fetch(`/api/calendar/${encodeURIComponent(provider)}-mappings`, {
238    method: 'POST',
239    body: JSON.stringify({ mappings: Array.isArray(mappings) ? mappings : [mappings] }),
240  });
241}
242
243export async function deleteProviderMappingsAll(provider, ids = null) {
244  return _fetch(`/api/calendar/${encodeURIComponent(provider)}-mappings`, {
245    method: 'DELETE',
246    body: JSON.stringify(ids ? { ids } : {}),
247  });
248}
249
250const calendarApi = {
251  listConnections, getConnection, setConnection, patchConnection, deleteConnection,
252  getIntegration, setIntegration, deleteIntegration,
253  listCalendarTypes, getCalendarType, createCalendarType, updateCalendarType, deleteCalendarType,
254  listEventMappings, createEventMapping, createEventMappingsBatch, deleteEventMapping, deleteEventMappingsBatch,
255  listEvents, listEventsByCalendar, getEvent, createEvent, updateEvent, deleteEvent, batchEvents,
256  getBookingSettings, patchBookingSettings, getSmsUsage, getOutlookEvent, setOutlookEvent, deleteOutlookEvent, listProviderMappings, createProviderMappings, deleteProviderMappingsAll,
257  invalidateCache() { cache.connections = { data: null, ts: 0 }; },
258};
259
260export { calendarApi };
261export default calendarApi;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.