Files
discourse/plugins/discourse-events/app/controllers/discourse_events/events_controller.rb
T
Gabriel Grubba b8565672b9 DEV: Remove the post event format filter (#43287)
Previously, the events finder could filter events by whether they had a
URL, a location, or both, exposed as the `event_format` parameter added
in #43131.

We are still figuring how should we define the event_format
2026-09-04 16:46:52 -03:00

257 lines
8.6 KiB
Ruby

# frozen_string_literal: true
module DiscourseEvents
class EventsController < BaseController
requires_login except: %i[index show]
skip_before_action :check_xhr, only: [:index], if: :ics_request?
def index
search_params = filtered_events_params.to_h
if ics_request?
search_params["after"] ||= 3.months.ago.iso8601
search_params["order"] ||= "asc"
end
@events =
DiscourseEvents::Events::Finder.search(current_user, search_params).includes(
:event_dates,
:image_upload,
post: {
topic: %i[tags category],
},
)
respond_to do |format|
format.ics do
@calendar_name = calendar_name_for_ics
after_time =
Events::Finder.time_param(filtered_events_params[:after], :after) || Time.current
before_time =
Events::Finder.time_param(filtered_events_params[:before], :before) || 1.year.from_now
@ics_events =
@events.flat_map do |event|
if event.recurring?
expanded =
DiscourseEvents::Events::Action::ExpandOccurrences.call(
event: event,
after: after_time,
before: before_time,
limit: 52,
current_occurrence_only: current_occurrence_only_event_ids.include?(event.id),
)
expanded[:occurrences].map { |occ| { event: event, **occ } }
else
[{ event: event, starts_at: event.starts_at, ends_at: event.ends_at }]
end
end
@ics_events = @ics_events.sort_by { |e| e[:starts_at] || Time.current }.first(500)
filename = "events-#{Digest::SHA1.hexdigest(@events.map(&:id).sort.join("-"))}.ics"
response.headers["Content-Disposition"] = "attachment; filename=\"#{filename}\""
end
format.json do
serializer =
(
if params[:include_details] == "true"
Events::EventSerializer
elsif params[:include_card] == "true"
Events::EventCardSerializer
else
Events::BasicEventSerializer
end
)
sample_invitees_by_event =
Events::EventCardSerializer.sample_invitees_by_event(@events) if serializer ==
Events::EventCardSerializer
after_time =
Events::Finder.time_param(filtered_events_params[:after], :after) || Time.current
before_time = Events::Finder.time_param(filtered_events_params[:before], :before)
serialized_events =
@events.map do |event|
expanded =
DiscourseEvents::Events::Action::ExpandOccurrences.call(
event: event,
after: after_time,
before: before_time,
limit: event_limit,
current_occurrence_only: current_occurrence_only_event_ids.include?(event.id),
)
formatted_occurrences =
expanded[:occurrences].map do |occurrence|
{
starts_at: format_time(event, occurrence[:starts_at]),
ends_at: format_time(event, occurrence[:ends_at]),
}
end
serializer.new(
event,
scope: guardian,
root: false,
occurrences: formatted_occurrences,
include_occurrences: true,
sample_invitees_by_event:,
).as_json
end
render json: { events: serialized_events }
end
end
end
def invite
Events::Invite.call(service_params.deep_merge(params: { event_id: params[:id] })) do
on_success { render json: success_json }
on_model_not_found(:event) { raise Discourse::NotFound }
on_failed_policy(:can_act_on_event) { raise Discourse::InvalidAccess }
on_failed_contract { raise Discourse::InvalidParameters }
end
end
def show
event = Events::Event.includes(:image_upload).find(params[:id])
guardian.ensure_can_see!(event.post)
serializer = Events::EventSerializer.new(event, scope: guardian)
render_json_dump(serializer)
end
def destroy
Events::DestroyEvent.call(service_params.deep_merge(params: { event_id: params[:id] })) do
on_success { render json: success_json }
on_model_not_found(:event) { raise Discourse::NotFound }
on_failed_policy(:can_act_on_event) { raise Discourse::InvalidAccess }
on_failed_contract { raise Discourse::InvalidParameters }
end
end
def csv_bulk_invite
file = params[:file] || (params[:files] || []).first
# Render (don't raise) every outcome: exceptions raised inside `hijack`
# surface as a 500 instead of the intended status.
hijack do
Events::CsvBulkInvite.call(
service_params.deep_merge(params: { event_id: params[:id], file: file }),
) do
on_success { render json: success_json }
on_model_not_found(:event) { render json: failed_json, status: :not_found }
on_failed_policy(:can_edit_post) { render json: failed_json, status: :forbidden }
on_failed_policy(:can_create_event) { render json: failed_json, status: :forbidden }
on_failed_policy(:file_present) do
render json: failed_json.merge(error_type: "invalid_parameters"), status: :bad_request
end
on_model_not_found(:invitees) { render_bulk_invite_error }
on_failed_contract { render_bulk_invite_error }
on_exceptions { render_bulk_invite_error }
on_failure { render_bulk_invite_error }
end
end
end
def bulk_invite
Events::BulkInvite.call(service_params.deep_merge(params: { event_id: params[:id] })) do
on_success { render json: success_json }
on_model_not_found(:event) { raise Discourse::NotFound }
on_failed_policy(:can_edit_post) { raise Discourse::InvalidAccess }
on_failed_policy(:can_create_event) { raise Discourse::InvalidAccess }
on_failed_policy(:invitees_present) { raise Discourse::InvalidParameters.new(:invitees) }
on_failed_contract { raise Discourse::InvalidParameters }
on_exceptions { render_bulk_invite_error }
on_failure { render_bulk_invite_error }
end
end
private
def render_bulk_invite_error
render json:
failed_json.merge(errors: [I18n.t("discourse_post_event.errors.bulk_invite.error")]),
status: :unprocessable_entity
end
def ics_request?
request.format.symbol == :ics
end
def filtered_events_params
params.permit(
:post_id,
:topic_id,
:category_id,
:include_subcategories,
:include_interested,
:include_ongoing,
:include_closed,
:limit,
:attending_user,
:before,
:after,
:order,
:tags,
:search,
:status,
tags: [],
)
end
def event_limit
Events::Finder.limit_param(filtered_events_params[:limit]) || 50
end
def current_occurrence_only_event_ids
@current_occurrence_only_event_ids ||= single_occurrence_rsvp_event_ids
end
def single_occurrence_rsvp_event_ids
attending_username = filtered_events_params[:attending_user]
return Set.new if attending_username.blank?
attending_user = User.find_by(username_lower: attending_username.downcase)
return Set.new if attending_user.blank?
DiscourseEvents::Events::Invitee
.unscoped
.with_status(:going)
.where(post_id: @events.map(&:id), user_id: attending_user.id, recurring: false)
.pluck(:post_id)
.to_set
end
def format_time(event, time)
return nil unless time
return time.utc.strftime("%Y-%m-%d") if event.all_day
if event.show_local_time
time.in_time_zone(event.timezone).strftime("%Y-%m-%dT%H:%M:%S")
else
time.in_time_zone(event.timezone).iso8601(3)
end
end
def calendar_name_for_ics
translation_key =
if filtered_events_params[:attending_user].present? &&
current_user&.username_lower == filtered_events_params[:attending_user].downcase
"my_events_feed_name"
else
"all_events_feed_name"
end
I18n.t(
"discourse_events.calendar_subscriptions.#{translation_key}",
site_title: SiteSetting.title,
)
end
end
end