mirror of
https://github.com/discourse/discourse.git
synced 2026-09-05 04:40:41 -05:00
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
257 lines
8.6 KiB
Ruby
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
|