Kalender-RSVP-Beiträge

:information_source: Zusammenfassung Erstellt kurze Themenantworten für RSVP-Ereignisse
:hammer_and_wrench: Repository-Link GitHub - mariodsantana/discourse-calendar-rsvp-posts
:open_book: Installationsanleitung So installieren Sie Plugins in Discourse

Funktionen

  • Echtzeit-Benachrichtigungen – Da RSVP-Ereignisse nun einen Antwortbeitrag im Thema erstellen, löst jede RSVP Benachrichtigungen für Themenbeobachter aus
  • Minimale Unordnung – Bisherige Benachrichtigungsbeiträge werden gelöscht, sodass maximal 2 Beiträge gleichzeitig vorhanden sind (1 Verlauf + 1 neueste Benachrichtigung)
  • Vollständiger Verlauf – Alle RSVP-Aktivitäten werden mit Zeitstempeln in chronologischer Reihenfolge gespeichert
  • Entmutigt das Hin- und Herwechseln – Zeitstempel machen wiederholte RSVP-Änderungen sichtbar

Konfiguration

Die relevanten Website-Einstellungen (unter Admin > Einstellungen > Plugins) konfigurieren hauptsächlich, welche RSVP-Änderungen einen Beitrag auslösen sollen – Zusagen, Interessenten, Absagen oder Entfernung einer bestehenden Zusage – und ob bei RSVP-Änderungen für Ereignisse, die in der Vergangenheit beginnen, ausgelöst werden soll.

Die verbleibende Einstellung schaltet den Verlaufsmodus um. Wenn der Verlaufsmodus deaktiviert ist, bleibt nur der neueste Benachrichtigungsbeitrag im Thema erhalten. Wenn der Verlaufsmodus aktiviert ist, verwaltet das Plugin einen zusätzlichen „Verlaufs“-Kommentar wie folgt:

  • Bei der ersten Zusage wird ein einfacher Benachrichtigungsbeitrag erstellt, der die Zusage ankündigt
  • Bei der zweiten Zusage wird der erste Beitrag in einen zeitgestempelten Verlaufsbeitrag umgewandelt und dann ein neuer Benachrichtigungsbeitrag erstellt
  • Bei nachfolgenden Zusagen wird die Zusage an den Verlaufsbeitrag mit Zeitstempel angehängt, der vorherige Benachrichtigungsbeitrag gelöscht und ein neuer erstellt

Einstellungen

Name Beschreibung
calendar_rsvp_posts_on_new_going Beitrag bei neuer „Zusage“
calendar_rsvp_posts_on_new_interested Beitrag bei neuem „Interessent“
calendar_rsvp_posts_on_new_not_going Beitrag bei neuer „Absage“
calendar_rsvp_posts_on_removed_rsvp Beitrag, wenn eine Zusage entfernt wird
calendar_rsvp_posts_allow_past_events ob für Ereignisse gepostet werden soll, die in der Vergangenheit beginnen
calendar_rsvp_posts_enable_history einen zeitgestempelten Verlaufsbeitrag beibehalten (Standard: aktiviert)
6 „Gefällt mir“

Das wird das Eventmanagement für viele verbessern!! Vielen Dank für die Bereitstellung!!

1 „Gefällt mir“

Danke für das Plugin :+1:

nur eine kleine Frage: gibt es die Möglichkeit, die Antworten in eine Datei zu übersetzen?

Ich mag diese Idee. Ich könnte einen Link zum Verlaufspost hinzufügen, der eine CSV-Datei herunterlädt. Ist das die Art von Sache, die Sie sich vorstellen?

Ich habe mich falsch ausgedrückt :sweat_smile: , ich wollte wissen, wie man die Antwortbenachrichtigungen übersetzt, da sie auf Englisch sind und ich sie auf Französisch haben möchte.

1 „Gefällt mir“

LOL – Ich verstehe jetzt. Im Moment steht der englische Text direkt im Quellcode. Ich bin neu bei Discourse, aber ich bin sicher, dass es eine Standardmethode gibt, ein Plugin übersetzbar zu machen. Ich werde es mir ansehen, wenn ich etwas Zeit habe.

Ich werde wahrscheinlich auch den CSV-Link implementieren. :wink:

1 „Gefällt mir“

Kein Problem :wink: . Nimm dir Zeit, ich habe es nicht eilig.

OK! Neue Version im Repository. Nicht nur mit i18n, sondern auch ein Fehler wurde gefunden und behoben, der verhinderte, dass Updates gepostet wurden, wenn jemand seine Zusage zurückzog.

Lassen Sie mich wissen, wenn Sie Probleme haben!

1 „Gefällt mir“

Hallo, ich hatte ein Problem mit den Benutzern, wenn sie an einer Veranstaltung teilnehmen oder nicht. Es erschienen zwei Avatare in der Veranstaltung anstatt nur einer. Nach einem Neuladen war das Problem zwar behoben, aber es verwirrte die Benutzer. Ich habe den Code auf meiner Seite angepasst; ich weiß nicht, ob dich das interessiert.

# frozen_string_literal: true
# name: discourse-calendar-rsvp-posts
# about: Erstelle kurze Themenantworten für RSVP-Veranstaltungen
# version: 0.4
# authors: Mario Santana

after_initialize do
 module ::CalendarRsvpPosts
   PLUGIN_NAME = "discourse-calendar-rsvp-posts"
   
   def self.history_marker
     I18n.t('calendar_rsvp_posts.markers.history')
   end
   
   def self.notification_marker
     I18n.t('calendar_rsvp_posts.markers.notification')
   end

   # Hilfsfunktion zur Entscheidung, ob wir diese Veranstaltung ignorieren sollen
   def self.should_post_for_event?(event)
     return false if event.nil?
     return true if SiteSetting.calendar_rsvp_posts_allow_past_events
     return false if event.starts_at.nil?
     event.starts_at >= Time.current
   end

   # Finde alle RSVP-Beiträge für eine Veranstaltung (Verlauf oder einfache Benachrichtigung)
   def self.find_rsvp_posts(event)
     return [] if event.nil? || event.post.nil? || event.post.topic.nil?
     
     event.post.topic.posts
       .where(user_id: Discourse.system_user.id)
       .where("raw LIKE ? OR raw LIKE ?", 
              "%#{history_marker}%",
              "%#{notification_marker}%")
       .order(created_at: :asc)
   end

   # Finde speziell den Verlaufsbeitrag
   def self.find_history_post(event)
     return nil if event.nil? || event.post.nil? || event.post.topic.nil?
     
     event.post.topic.posts
       .where(user_id: Discourse.system_user.id)
       .where("raw LIKE ?", "%#{history_marker}%")
       .order(created_at: :asc)
       .first
   end

   # Finde und lösche alle Benachrichtigungsbeiträge (aber nicht den Verlaufsbeitrag)
   def self.delete_notification_posts(event)
     return if event.nil? || event.post.nil? || event.post.topic.nil?
     
     notification_posts = event.post.topic.posts
       .where(user_id: Discourse.system_user.id)
       .where("raw LIKE ?", "%#{notification_marker}%")
     
     notification_posts.each do |post|
       begin
         PostDestroyer.new(Discourse.system_user, post, context: "calendar-rsvp-posts cleanup").destroy
       rescue StandardError => e
         Rails.logger.warn("calendar-rsvp-posts: konnte Benachrichtigungsbeitrag #{post.id} nicht löschen: #{e}")
       end
     end
   end

   # Erstelle einen Verlaufszeileneintrag mit Zeitstempel
   def self.build_history_entry(username, action_label, extra_text = nil)
     timestamp = Time.current.strftime("%Y-%m-%d %H:%M UTC")
     entry = "- **#{timestamp}** - #{username} #{action_label}"
     entry += " (#{extra_text})" if extra_text.present?
     entry
   end

   # Erstelle oder aktualisiere den Inhalt des Verlaufsbeitrags
   def self.build_history_raw(event, new_entry)
     event_title = (event.name.presence || event.post.topic.title).to_s
     parts = []
     parts << history_marker
     parts << "### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}"
     parts << ""
     parts << new_entry
     parts.join("\n")
   end

   # Füge neuen Eintrag zum bestehenden Verlauf hinzu
   def self.append_to_history(existing_raw, new_entry)
     lines = existing_raw.split("\n")
     header_end_idx = lines.index { |line| line.start_with?("### RSVP History") }
     
     if header_end_idx
       insert_idx = header_end_idx + 2
       lines.insert(insert_idx, new_entry)
     else
       lines << new_entry
     end
     
     lines.join("\n")
   end

   # Konvertiere einen einfachen Benachrichtigungsbeitrag ins Verlaufsformat
   def self.convert_to_history(simple_post, event)
     raw = simple_post.raw
     
     going_label = I18n.t('calendar_rsvp_posts.actions.going').gsub('(', '\\(').gsub(')', '\\)')
     interested_label = I18n.t('calendar_rsvp_posts.actions.interested').gsub('(', '\\(').gsub(')', '\\)')
     not_going_label = I18n.t('calendar_rsvp_posts.actions.not_going').gsub('(', '\\(').gsub(')', '\\)')
     removed_label = I18n.t('calendar_rsvp_posts.actions.removed').gsub('(', '\\(').gsub(')', '\\)')
     
     pattern = /\*\*([^\*]+)\s+(#{Regexp.escape(going_label)}|#{Regexp.escape(interested_label)}|#{Regexp.escape(not_going_label)}|#{Regexp.escape(removed_label)})\*\*/
     match = raw.match(pattern)
     
     if match
       username = match[1]
       action = match[2]
       
       extra_match = raw.match(/\.\s+([^.]+)\.$/)
       extra_text = extra_match ? extra_match[1] : nil
       
       timestamp = simple_post.created_at.strftime("%Y-%m-%d %H:%M UTC")
       first_entry = "- **#{timestamp}** - #{username} #{action}"
       first_entry += " (#{extra_text})" if extra_text.present?
       
       event_title = (event.name.presence || event.post.topic.title).to_s
       parts = []
       parts << history_marker
       parts << "### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}"
       parts << ""
       parts << first_entry
       parts.join("\n")
     else
       event_title = (event.name.presence || event.post.topic.title).to_s
       history_marker + "\n### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}\n\n" + raw
     end
   end

   # Erstelle einen Benachrichtigungsbeitrag
   def self.build_notification_raw(username, action_label, event, extra_text = nil)
     event_title = (event.name.presence || event.post.topic.title).to_s
     extra_text_formatted = extra_text.present? ? "#{extra_text} " : ""
     
     notification_raw = notification_marker + "\n"
     notification_raw += I18n.t('calendar_rsvp_posts.notification.template', 
                               event_title: event_title,
                               extra: extra_text_formatted,
                               username: username,
                               action: action_label)
     notification_raw
   end

   # Zentrale Logikmethode zur Handhabung der Erstellung/Aktualisierung von Beiträgen
   def self.publish_rsvp_update(event, username, action_label, extra_text = nil)
     if SiteSetting.calendar_rsvp_posts_enable_history
       history_post = find_history_post(event)
       all_rsvp_posts = find_rsvp_posts(event)
       new_entry = build_history_entry(username, action_label, extra_text)

       if all_rsvp_posts.empty?
         notification_raw = build_notification_raw(username, action_label, event, extra_text)
         PostCreator.create!(
           Discourse.system_user,
           topic_id: event.post.topic_id,
           raw: notification_raw,
           skip_validations: true
         )
       elsif history_post.nil?
         first_post = all_rsvp_posts.first
         history_raw = convert_to_history(first_post, event)
         history_raw = append_to_history(history_raw, new_entry)
         
         PostRevisor.new(first_post, event.post.topic).revise!(
           Discourse.system_user,
           raw: history_raw,
           skip_validations: true,
           skip_revision: false
         )

         notification_raw = build_notification_raw(username, action_label, event, extra_text)
         PostCreator.create!(
           Discourse.system_user,
           topic_id: event.post.topic_id,
           raw: notification_raw,
           skip_validations: true
         )
       else
         updated_raw = append_to_history(history_post.raw, new_entry)
         PostRevisor.new(history_post, event.post.topic).revise!(
           Discourse.system_user,
           raw: updated_raw,
           skip_validations: true,
           skip_revision: false
         )

         delete_notification_posts(event)

         notification_raw = build_notification_raw(username, action_label, event, extra_text)
         PostCreator.create!(
           Discourse.system_user,
           topic_id: event.post.topic_id,
           raw: notification_raw,
           skip_validations: true
         )
       end
     else
       all_rsvp_posts = find_rsvp_posts(event)
       
       all_rsvp_posts.each do |post|
         begin
           PostDestroyer.new(Discourse.system_user, post, context: "calendar-rsvp-posts cleanup").destroy
         rescue StandardError => e
           Rails.logger.warn("calendar-rsvp-posts: konnte Beitrag #{post.id} nicht löschen: #{e}")
         end
       end

       notification_raw = build_notification_raw(username, action_label, event, extra_text)
       PostCreator.create!(
         Discourse.system_user,
         topic_id: event.post.topic_id,
         raw: notification_raw,
         skip_validations: true
       )
     end
   end
 end

 # ==========================================
 # Hintergrund-Job-Definition
 # ==========================================
 module ::Jobs
   class ProcessCalendarRsvpPost < ::Jobs::Base
     def execute(args)
       event_id = args[:event_id]
       username = args[:username]
       action_label = args[:action_label]
       extra_text = args[:extra_text]

       event = DiscoursePostEvent::Event.find_by(id: event_id)
       return unless event

       ::CalendarRsvpPosts.publish_rsvp_update(event, username, action_label, extra_text)
     end
   end
 end

 # ==========================================
 # Event-Handler
 # ==========================================

 # Handler für Erstellung/Aktualisierung der Teilnahme
 proc_handler = proc do |invitee|
   begin
     event = invitee&.event
     next if event.nil?
     next unless CalendarRsvpPosts.should_post_for_event?(event)

     going_val = DiscoursePostEvent::Invitee.statuses[:going]
     interested_val = DiscoursePostEvent::Invitee.statuses[:interested]
     not_going_val = DiscoursePostEvent::Invitee.statuses[:not_going]

     new_status = invitee.status
     prev_status =
       if invitee.respond_to?(:previous_changes) && invitee.previous_changes["status"]
         invitee.previous_changes["status"][0]
       else
         nil
       end

     next if prev_status && prev_status == new_status

     username = invitee.user&.username || "jemand"
     action_label = nil

     if new_status == going_val && SiteSetting.calendar_rsvp_posts_on_new_going
       action_label = I18n.t('calendar_rsvp_posts.actions.going')
     elsif new_status == interested_val && SiteSetting.calendar_rsvp_posts_on_new_interested
       action_label = I18n.t('calendar_rsvp_posts.actions.interested')
     elsif new_status == not_going_val && SiteSetting.calendar_rsvp_posts_on_new_not_going
       action_label = I18n.t('calendar_rsvp_posts.actions.not_going')
     end

     next if action_label.nil?

     extra_text = nil
     if event.max_attendees.present?
       current_going = event.going_count
       prev_going =
         if prev_status == going_val && new_status != going_val
           current_going + 1
         elsif prev_status != going_val && new_status == going_val
           current_going - 1
         else
           current_going
         end

       was_full = prev_going >= event.max_attendees
       is_full = current_going >= event.max_attendees

       if was_full && !is_full
         extra_text = I18n.t('calendar_rsvp_posts.capacity.spots_available')
       elsif !was_full && is_full
         extra_text = I18n.t('calendar_rsvp_posts.capacity.now_full')
       end
     end

     # Job in die Warteschlange stellen, anstatt die Anfrage zu blockieren
     Jobs.enqueue(:process_calendar_rsvp_post, 
       event_id: event.id, 
       username: username, 
       action_label: action_label, 
       extra_text: extra_text
     )

   rescue StandardError => e
     Rails.logger.warn("calendar-rsvp-posts: Handler-Fehler: #{e}")
     Rails.logger.warn(e.backtrace.join("\n"))
   end
 end

 on(:discourse_calendar_post_event_invitee_status_changed, &proc_handler)

 # Handler für explizite Löschen von Teilnehmern (entfernte RSVP)
 if defined?(DiscoursePostEvent::Invitee)
   DiscoursePostEvent::Invitee.class_eval do
     after_destroy do
       event = self.event
       
       should_process = event.present? &&
                        SiteSetting.calendar_rsvp_posts_on_removed_rsvp &&
                        (SiteSetting.calendar_rsvp_posts_allow_past_events || event.starts_at.nil? || event.starts_at >= Time.current)
       
       if should_process
         begin
           username = self.user&.username || "jemand"
           action_label = I18n.t('calendar_rsvp_posts.actions.removed')

           # Job in die Warteschlange stellen, anstatt die Anfrage zu blockieren
           Jobs.enqueue(:process_calendar_rsvp_post, 
             event_id: event.id, 
             username: username, 
             action_label: action_label,
             extra_text: nil
           )
           
         rescue StandardError => e
           Rails.logger.warn("calendar-rsvp-posts: after_destroy-Handler-Fehler: #{e}")
           Rails.logger.warn(e.backtrace.join("\n"))
         end
       end
     end
   end
 end
end
1 „Gefällt mir“

Das ist eine großartige Idee, und ich kann nachvollziehen, wie es zu Verwirrungen in der Benutzeroberfläche führen könnte. Ich habe einen Mutex hinzugefügt, um Race Conditions zu vermeiden, und eine neue Version des Plugins veröffentlicht.

Lass mich wissen, was du denkst!

1 „Gefällt mir“

seit der Version 2026.9.0-latest. Ich erhalte keine Benachrichtigungen mehr über Ereignisse. Ich weiß nicht, ob ich der Einzige mit diesem Problem bin.

Ich glaube, ich habe herausgefunden, warum das nicht funktioniert: Das Discourse-Kalender-Plugin wurde durch Events ersetzt.

Ich habe die Datei plugin.rb auf meiner Seite angepasst, damit sie mit der neuen Version von Discourse kompatibel ist.

# frozen_string_literal: true
# name: discourse-calendar-rsvp-posts
# about: Erstelle kurze Themenantworten für RSVP-Events
# version: 0.9
# authors: Mario Santana, gilles

after_initialize do
  module ::CalendarRsvpPosts
    PLUGIN_NAME = "discourse-calendar-rsvp-posts"

    def self.event_class
      "DiscourseEvents::Events::Event".safe_constantize ||
      "DiscourseEvents::Event".safe_constantize ||
      "DiscourseCalendar::Event".safe_constantize ||
      "DiscoursePostEvent::Event".safe_constantize
    end

    def self.invitee_class
      "DiscourseEvents::Events::Invitee".safe_constantize ||
      "DiscourseEvents::Invitee".safe_constantize ||
      "DiscourseCalendar::Invitee".safe_constantize ||
      "DiscoursePostEvent::Invitee".safe_constantize
    end
    
    def self.history_marker
      I18n.t('calendar_rsvp_posts.markers.history')
    end
    
    def self.notification_marker
      I18n.t('calendar_rsvp_posts.markers.notification')
    end

    # Hilfsfunktion, um zu entscheiden, ob dieses Event ignoriert werden sollte
    def self.should_post_for_event?(event)
      return false if event.nil?
      return true if SiteSetting.calendar_rsvp_posts_allow_past_events
      return false if event.starts_at.nil?
      event.starts_at >= Time.current
    end

    # Finde alle RSVP-Beiträge für ein Event (Verlauf oder einfache Benachrichtigung)
    def self.find_rsvp_posts(event)
      return [] if event.nil? || event.post.nil? || event.post.topic.nil?
      
      event.post.topic.posts
        .where(user_id: Discourse.system_user.id)
        .where("raw LIKE ? OR raw LIKE ?", 
               "%#{history_marker}%",
               "%#{notification_marker}%")
        .order(created_at: :asc)
    end

    # Finde den Verlaufseintrag spezifisch
    def self.find_history_post(event)
      return nil if event.nil? || event.post.nil? || event.post.topic.nil?
      
      event.post.topic.posts
        .where(user_id: Discourse.system_user.id)
        .where("raw LIKE ?", "%#{history_marker}%")
        .order(created_at: :asc)
        .first
    end

    # Finde und lösche alle Benachrichtigungsbeiträge (aber nicht den Verlaufsbeitrag)
    def self.delete_notification_posts(event)
      return if event.nil? || event.post.nil? || event.post.topic.nil?
      
      notification_posts = event.post.topic.posts
        .where(user_id: Discourse.system_user.id)
        .where("raw LIKE ?", "%#{notification_marker}%")
      
      notification_posts.each do |post|
        begin
          PostDestroyer.new(Discourse.system_user, post, context: "calendar-rsvp-posts cleanup").destroy
        rescue StandardError => e
          Rails.logger.warn("calendar-rsvp-posts: failed to delete notification post #{post.id}: #{e}")
        end
      end
    end

    # Erstelle eine Zeile für einen Verlaufseintrag mit Zeitstempel
    def self.build_history_entry(username, action_label, extra_text = nil)
      timestamp = Time.current.strftime("%Y-%m-%d %H:%M UTC")
      entry = "- **#{timestamp}** - #{username} #{action_label}"
      entry += " (#{extra_text})" if extra_text.present?
      entry
    end

    # Erstelle oder aktualisiere den Inhalt des Verlaufsbeitrags
    def self.build_history_raw(event, new_entry)
      event_title = (event.name.presence || event.post.topic.title).to_s
      parts = []
      parts << history_marker
      parts << "### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}"
      parts << ""
      parts << new_entry
      parts.join("\n")
    end

    # Füge neuen Eintrag zu bestehendem Verlauf hinzu
    def self.append_to_history(existing_raw, new_entry)
      lines = existing_raw.split("\n")
      header_end_idx = lines.index { |line| line.start_with?("### RSVP History") }
      
      if header_end_idx
        insert_idx = header_end_idx + 2
        lines.insert(insert_idx, new_entry)
      else
        lines << new_entry
      end
      
      lines.join("\n")
    end

    # Wandle einen einfachen Benachrichtigungsbeitrag in das Verlaufsformat um
    def self.convert_to_history(simple_post, event)
      raw = simple_post.raw
      
      going_label = I18n.t('calendar_rsvp_posts.actions.going').gsub('(', '\\(').gsub(')', '\\)')
      interested_label = I18n.t('calendar_rsvp_posts.actions.interested').gsub('(', '\\(').gsub(')', '\\)')
      not_going_label = I18n.t('calendar_rsvp_posts.actions.not_going').gsub('(', '\\(').gsub(')', '\\)')
      removed_label = I18n.t('calendar_rsvp_posts.actions.removed').gsub('(', '\\(').gsub(')', '\\)')
      
      pattern = /\*\*([^\*]+)\s+(#{Regexp.escape(going_label)}|#{Regexp.escape(interested_label)}|#{Regexp.escape(not_going_label)}|#{Regexp.escape(removed_label)})\*\*/
      match = raw.match(pattern)
      
      if match
        username = match[1]
        action = match[2]
        
        extra_match = raw.match(/\.\s+([^.]+)\.$/)
        extra_text = extra_match ? extra_match[1] : nil
        
        timestamp = simple_post.created_at.strftime("%Y-%m-%d %H:%M UTC")
        first_entry = "- **#{timestamp}** - #{username} #{action}"
        first_entry += " (#{extra_text})" if extra_text.present?
        
        event_title = (event.name.presence || event.post.topic.title).to_s
        parts = []
        parts << history_marker
        parts << "### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}"
        parts << ""
        parts << first_entry
        parts.join("\n")
      else
        event_title = (event.name.presence || event.post.topic.title).to_s
        history_marker + "\n### #{I18n.t('calendar_rsvp_posts.history.header', event_title: event_title)}\n\n" + raw
      end
    end

    # Erstelle einen Benachrichtigungsbeitrag
    def self.build_notification_raw(username, action_label, event, extra_text = nil)
      event_title = (event.name.presence || event.post.topic.title).to_s
      extra_text_formatted = extra_text.present? ? "#{extra_text} " : ""
      
      notification_raw = notification_marker + "\n"
      notification_raw += I18n.t('calendar_rsvp_posts.notification.template', 
                                 event_title: event_title,
                                 extra: extra_text_formatted,
                                 username: username,
                                 action: action_label)
      notification_raw
    end

    # Zentrale Logik-Methode zur Behandlung der Erstellung/Aktualisierung von Beiträgen
    def self.publish_rsvp_update(event, username, action_label, extra_text = nil)
      if SiteSetting.calendar_rsvp_posts_enable_history
        history_post = find_history_post(event)
        all_rsvp_posts = find_rsvp_posts(event)
        new_entry = build_history_entry(username, action_label, extra_text)

        if all_rsvp_posts.empty?
          notification_raw = build_notification_raw(username, action_label, event, extra_text)
          PostCreator.create!(
            Discourse.system_user,
            topic_id: event.post.topic_id,
            raw: notification_raw,
            skip_validations: true
          )
        elsif history_post.nil?
          first_post = all_rsvp_posts.first
          history_raw = convert_to_history(first_post, event)
          history_raw = append_to_history(history_raw, new_entry)
          
          PostRevisor.new(first_post, event.post.topic).revise!(
            Discourse.system_user,
            raw: history_raw,
            skip_validations: true,
            skip_revision: false
          )

          notification_raw = build_notification_raw(username, action_label, event, extra_text)
          PostCreator.create!(
            Discourse.system_user,
            topic_id: event.post.topic_id,
            raw: notification_raw,
            skip_validations: true
          )
        else
          updated_raw = append_to_history(history_post.raw, new_entry)
          PostRevisor.new(history_post, event.post.topic).revise!(
            Discourse.system_user,
            raw: updated_raw,
            skip_validations: true,
            skip_revision: false
          )

          delete_notification_posts(event)

          notification_raw = build_notification_raw(username, action_label, event, extra_text)
          PostCreator.create!(
            Discourse.system_user,
            topic_id: event.post.topic_id,
            raw: notification_raw,
            skip_validations: true
          )
        end
      else
        all_rsvp_posts = find_rsvp_posts(event)
        
        all_rsvp_posts.each do |post|
          begin
            PostDestroyer.new(Discourse.system_user, post, context: "calendar-rsvp-posts cleanup").destroy
          rescue StandardError => e
            Rails.logger.warn("calendar-rsvp-posts: failed to delete post #{post.id}: #{e}")
          end
        end

        notification_raw = build_notification_raw(username, action_label, event, extra_text)
        PostCreator.create!(
          Discourse.system_user,
          topic_id: event.post.topic_id,
          raw: notification_raw,
          skip_validations: true
        )
      end
    end
  end

  # ==========================================
  # Definition des Hintergrundjobs
  # ==========================================
  module ::Jobs
    class ProcessCalendarRsvpPost < ::Jobs::Base
      def execute(args)
        event_id = args[:event_id]
        username = args[:username]
        action_label = args[:action_label]
        extra_text = args[:extra_text]

        event_cls = ::CalendarRsvpPosts.event_class
        return unless event_cls

        event = event_cls.find_by(id: event_id)
        return unless event

        ::CalendarRsvpPosts.publish_rsvp_update(event, username, action_label, extra_text)
      end
    end
  end

  # ==========================================
  # Event-Handler
  # ==========================================

  # Handler für das Erstellen/Aktualisieren der Teilnahme
  proc_handler = proc do |invitee|
    begin
      event = invitee&.event
      next if event.nil?
      next unless CalendarRsvpPosts.should_post_for_event?(event)

      invitee_cls = CalendarRsvpPosts.invitee_class
      next unless invitee_cls

      going_val = invitee_cls.statuses[:going]
      interested_val = invitee_cls.statuses[:interested]
      not_going_val = invitee_cls.statuses[:not_going]

      new_status = invitee.status
      prev_status =
        if invitee.respond_to?(:previous_changes) && invitee.previous_changes["status"]
          invitee.previous_changes["status"][0]
        else
          nil
        end

      next if prev_status && prev_status == new_status

      username = invitee.user&.username || "someone"
      action_label = nil

      if new_status == going_val && SiteSetting.calendar_rsvp_posts_on_new_going
        action_label = I18n.t('calendar_rsvp_posts.actions.going')
      elsif new_status == interested_val && SiteSetting.calendar_rsvp_posts_on_new_interested
        action_label = I18n.t('calendar_rsvp_posts.actions.interested')
      elsif new_status == not_going_val && SiteSetting.calendar_rsvp_posts_on_new_not_going
        action_label = I18n.t('calendar_rsvp_posts.actions.not_going')
      end

      next if action_label.nil?

      extra_text = nil
      if event.max_attendees.present?
        current_going = event.going_count
        prev_going =
          if prev_status == going_val && new_status != going_val
            current_going + 1
          elsif prev_status != going_val && new_status == going_val
            current_going - 1
          else
            current_going
          end

        was_full = prev_going >= event.max_attendees
        is_full = current_going >= event.max_attendees

        if was_full && !is_full
          extra_text = I18n.t('calendar_rsvp_posts.capacity.spots_available')
        elsif !was_full && is_full
          extra_text = I18n.t('calendar_rsvp_posts.capacity.now_full')
        end
      end

      # Stelle den Job in die Warteschlange, anstatt die Anfrage zu blockieren
      Jobs.enqueue(:process_calendar_rsvp_post, 
        event_id: event.id, 
        username: username, 
        action_label: action_label, 
        extra_text: extra_text
      )

    rescue StandardError => e
      Rails.logger.warn("calendar-rsvp-posts: handler error: #{e}")
      Rails.logger.warn(e.backtrace.join("\n"))
    end
  end

  on(:discourse_calendar_post_event_invitee_status_changed, &proc_handler)

  # Handler für explizite Löschungen von Eingeladenen (entferntes RSVP)
  invitee_cls = CalendarRsvpPosts.invitee_class
  if invitee_cls
    invitee_cls.class_eval do
      after_destroy do
        event = self.event
        
        should_process = event.present? &&
                         SiteSetting.calendar_rsvp_posts_on_removed_rsvp &&
                         (SiteSetting.calendar_rsvp_posts_allow_past_events || event.starts_at.nil? || event.starts_at >= Time.current)
        
        if should_process
          begin
            username = self.user&.username || "someone"
            action_label = I18n.t('calendar_rsvp_posts.actions.removed')

            # Stelle den Job in die Warteschlange, anstatt die Anfrage zu blockieren
            Jobs.enqueue(:process_calendar_rsvp_post, 
              event_id: event.id, 
              username: username, 
              action_label: action_label,
              extra_text: nil
            )
            
          rescue StandardError => e
            Rails.logger.warn("calendar-rsvp-posts: after_destroy handler error: #{e}")
            Rails.logger.warn(e.backtrace.join("\n"))
          end
        end
      end
    end
  end
end