diff --git a/backend/app/api/calendar.py b/backend/app/api/calendar.py index 773e78f..9ff6c69 100644 --- a/backend/app/api/calendar.py +++ b/backend/app/api/calendar.py @@ -11,6 +11,52 @@ from app.models.calendar import Calendar, CalendarEvent, CalendarShare from app.models.user import User +def _redact_if_private(event_dict: dict, is_owner: bool) -> dict: + """For shared viewers, strip summary/description/location from private + events so only the time slot remains visible.""" + if is_owner or not event_dict.get('is_private'): + return event_dict + d = dict(event_dict) + d['summary'] = 'Privat' + d['description'] = None + d['location'] = None + return d + + +def _redact_vevent(raw: str) -> str: + """Strip SUMMARY/DESCRIPTION/LOCATION from a VEVENT block and set + CLASS:PRIVATE. Used for shared iCal exports and CalDAV responses.""" + if not raw: + return raw + import re as _re + out_lines = [] + has_class = False + for line in raw.split('\n'): + stripped = line.rstrip('\r') + upper = stripped.split(':', 1)[0].split(';', 1)[0].upper() + if upper == 'SUMMARY': + out_lines.append('SUMMARY:Privat') + elif upper in ('DESCRIPTION', 'LOCATION'): + continue + elif upper == 'CLASS': + has_class = True + out_lines.append('CLASS:PRIVATE') + else: + out_lines.append(stripped) + if not has_class: + # Inject CLASS right after UID if possible, else before END:VEVENT + for i, l in enumerate(out_lines): + if l.startswith('UID:'): + out_lines.insert(i + 1, 'CLASS:PRIVATE') + break + else: + for i, l in enumerate(out_lines): + if l.upper().startswith('END:VEVENT'): + out_lines.insert(i, 'CLASS:PRIVATE') + break + return '\r\n'.join(out_lines) + + def _get_calendar_or_err(cal_id, user, need_write=False): cal = db.session.get(Calendar, cal_id) if not cal: @@ -136,7 +182,8 @@ def list_events(cal_id): pass events = query.order_by(CalendarEvent.dtstart).all() - return jsonify([e.to_dict() for e in events]), 200 + is_owner = (cal.owner_id == user.id) + return jsonify([_redact_if_private(e.to_dict(), is_owner) for e in events]), 200 @api_bp.route('/calendars//events', methods=['POST']) @@ -184,6 +231,7 @@ def create_event(cal_id): dtend=dtend_dt, all_day=all_day, recurrence_rule=rrule or None, + is_private=bool(data.get('is_private', False)), ) db.session.add(event) db.session.commit() @@ -217,6 +265,8 @@ def update_event(event_id): event.all_day = data['all_day'] if 'recurrence_rule' in data: event.recurrence_rule = (data['recurrence_rule'] or '').strip() or None + if 'is_private' in data: + event.is_private = bool(data['is_private']) if 'calendar_id' in data: new_cal, cerr = _get_calendar_or_err(data['calendar_id'], user, need_write=True) if cerr: @@ -477,7 +527,10 @@ def ical_export(token): ] for e in events: if e.ical_data: - lines.append(e.ical_data) + block = _redact_vevent(e.ical_data) if e.is_private else e.ical_data + lines.append(block) + elif e.is_private: + lines.append(_build_vevent(e.uid, 'Privat', e.dtstart, e.dtend, e.all_day)) else: lines.append(_build_vevent(e.uid, e.summary, e.dtstart, e.dtend, e.all_day)) lines.append('END:VCALENDAR') diff --git a/backend/app/models/calendar.py b/backend/app/models/calendar.py index ddc1eb0..05c000a 100644 --- a/backend/app/models/calendar.py +++ b/backend/app/models/calendar.py @@ -21,6 +21,7 @@ class Calendar(db.Model): cascade='all, delete-orphan') shares = db.relationship('CalendarShare', backref='calendar', lazy='dynamic', cascade='all, delete-orphan') + owner = db.relationship('User', foreign_keys=[owner_id]) def to_dict(self): return { @@ -50,6 +51,7 @@ class CalendarEvent(db.Model): all_day = db.Column(db.Boolean, default=False) recurrence_rule = db.Column(db.Text, nullable=True) exdates = db.Column(db.Text, nullable=True) # Komma-separiert, ISO-Datum (YYYY-MM-DD) + is_private = db.Column(db.Boolean, default=False, nullable=False) created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) @@ -67,6 +69,7 @@ class CalendarEvent(db.Model): 'all_day': self.all_day, 'recurrence_rule': self.recurrence_rule, 'exdates': self.exdates.split(',') if self.exdates else [], + 'is_private': bool(self.is_private), 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None, } diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue index 0a8b011..54f594b 100644 --- a/frontend/src/views/CalendarView.vue +++ b/frontend/src/views/CalendarView.vue @@ -74,6 +74,9 @@
+
+ +
@@ -146,10 +149,27 @@