Refactor Core things, including Ecto handling, ActivityPub & Transmogrifier modules
* Data doesn't need anymore to be converted to ActivityStream format to be saved (this was taken from Pleroma and not at all a good idea here) * Everything saved when creating an event is inserted into PostgreSQL in a single transaction
This commit is contained in:
parent
814cfbc8eb
commit
cc820d6b63
@ -14,7 +14,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
participation: {
|
||||
event: {
|
||||
event: {
|
||||
title: 'Vue Styleguidist first meetup: learn the basics!',
|
||||
id: 5,
|
||||
uuid: 'some uuid',
|
||||
@ -31,7 +31,7 @@ export default {
|
||||
},
|
||||
participantStats: {
|
||||
approved: 1,
|
||||
unapproved: 2
|
||||
notApproved: 2
|
||||
}
|
||||
},
|
||||
actor: {
|
||||
@ -75,20 +75,20 @@ export default {
|
||||
</span>
|
||||
<span class="column is-narrow participant-stats">
|
||||
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
|
||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.participants, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||
{{ $t('{approved} / {total} seats', {approved: participation.event.participantStats.participant, total: participation.event.options.maximumAttendeeCapacity }) }}
|
||||
<!-- <b-progress-->
|
||||
<!-- v-if="participation.event.options.maximumAttendeeCapacity > 0"-->
|
||||
<!-- size="is-medium"-->
|
||||
<!-- :value="participation.event.participantStats.participants * 100 / participation.event.options.maximumAttendeeCapacity">-->
|
||||
<!-- :value="participation.event.participantStats.participant * 100 / participation.event.options.maximumAttendeeCapacity">-->
|
||||
<!-- </b-progress>-->
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $tc('{count} participants', participation.event.participantStats.participants, { count: participation.event.participantStats.participants })}}
|
||||
{{ $tc('{count} participants', participation.event.participantStats.participant, { count: participation.event.participantStats.participant })}}
|
||||
</span>
|
||||
<span
|
||||
v-if="participation.event.participantStats.unapproved > 0">
|
||||
v-if="participation.event.participantStats.notApproved > 0">
|
||||
<b-button type="is-text" @click="gotToWithCheck(participation, { name: RouteName.PARTICIPATIONS, params: { eventId: participation.event.uuid } })">
|
||||
{{ $tc('{count} requests waiting', participation.event.participantStats.unapproved, { count: participation.event.participantStats.unapproved })}}
|
||||
{{ $tc('{count} requests waiting', participation.event.participantStats.notApproved, { count: participation.event.participantStats.notApproved })}}
|
||||
</b-button>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -113,9 +113,8 @@ query LoggedUserParticipations($afterDateTime: DateTime, $beforeDateTime: DateTi
|
||||
}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved,
|
||||
participants
|
||||
notApproved
|
||||
participant
|
||||
},
|
||||
options {
|
||||
maximumAttendeeCapacity
|
||||
@ -161,8 +160,8 @@ export const LOGGED_USER_DRAFTS = gql`
|
||||
}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved
|
||||
going,
|
||||
notApproved
|
||||
},
|
||||
options {
|
||||
maximumAttendeeCapacity
|
||||
|
@ -102,9 +102,9 @@ export const FETCH_EVENT = gql`
|
||||
# name,
|
||||
# },
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved,
|
||||
participants
|
||||
going,
|
||||
notApproved,
|
||||
participant
|
||||
},
|
||||
tags {
|
||||
${tagsQuery}
|
||||
@ -259,9 +259,9 @@ export const CREATE_EVENT = gql`
|
||||
id,
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved,
|
||||
participants
|
||||
going,
|
||||
notApproved,
|
||||
participant
|
||||
},
|
||||
tags {
|
||||
${tagsQuery}
|
||||
@ -344,9 +344,9 @@ export const EDIT_EVENT = gql`
|
||||
id,
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved,
|
||||
participants
|
||||
going,
|
||||
notApproved,
|
||||
participant
|
||||
},
|
||||
tags {
|
||||
${tagsQuery}
|
||||
@ -410,10 +410,10 @@ export const PARTICIPANTS = gql`
|
||||
${participantQuery}
|
||||
},
|
||||
participantStats {
|
||||
approved,
|
||||
unapproved,
|
||||
going,
|
||||
notApproved,
|
||||
rejected,
|
||||
participants
|
||||
participant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ import { IPerson } from '@/types/actor';
|
||||
@Component
|
||||
export default class EventMixin extends mixins(Vue) {
|
||||
async openDeleteEventModal (event: IEvent, currentActor: IPerson) {
|
||||
const participantsLength = event.participantStats.approved;
|
||||
const participantsLength = event.participantStats.participant;
|
||||
const prefix = participantsLength
|
||||
? this.$tc('There are {participants} participants.', event.participantStats.approved, {
|
||||
participants: event.participantStats.approved,
|
||||
? this.$tc('There are {participants} participants.', event.participantStats.participant, {
|
||||
participants: event.participantStats.participant,
|
||||
})
|
||||
: '';
|
||||
|
||||
|
@ -94,10 +94,13 @@ export enum CommentModeration {
|
||||
}
|
||||
|
||||
export interface IEventParticipantStats {
|
||||
approved: number;
|
||||
unapproved: number;
|
||||
notApproved: number;
|
||||
rejected: number;
|
||||
participants: number;
|
||||
participant: number;
|
||||
creator: number;
|
||||
moderator: number;
|
||||
administrator: number;
|
||||
going: number;
|
||||
}
|
||||
|
||||
export interface IEvent {
|
||||
@ -192,7 +195,7 @@ export class EventModel implements IEvent {
|
||||
|
||||
publishAt = new Date();
|
||||
|
||||
participantStats = { approved: 0, unapproved: 0, rejected: 0, participants: 0 };
|
||||
participantStats = { notApproved: 0, rejected: 0, participant: 0, moderator: 0, administrator: 0, creator: 0, going: 0 };
|
||||
participants: IParticipant[] = [];
|
||||
|
||||
relatedEvents: IEvent[] = [];
|
||||
|
@ -16,18 +16,18 @@ import {ParticipantRole} from "@/types/event.model";
|
||||
<h1 class="title">{{ event.title }}</h1>
|
||||
<span>
|
||||
<router-link v-if="actorIsOrganizer" :to="{ name: RouteName.PARTICIPATIONS, params: {eventId: event.uuid}}">
|
||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||
<small v-if="event.participantStats.going > 0 && !actorIsParticipant">
|
||||
{{ $tc('One person is going', event.participantStats.going, {approved: event.participantStats.going}) }}
|
||||
</small>
|
||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
|
||||
{{ $tc('You and one other person are going to this event', event.participantStats.participants, { approved: event.participantStats.participants }) }}
|
||||
<small v-else-if="event.participantStats.going > 0 && actorIsParticipant">
|
||||
{{ $tc('You and one other person are going to this event', event.participantStats.participant, { approved: event.participantStats.participant }) }}
|
||||
</small>
|
||||
</router-link>
|
||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant && !actorIsOrganizer">
|
||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||
<small v-if="event.participantStats.going > 0 && !actorIsParticipant && !actorIsOrganizer">
|
||||
{{ $tc('One person is going', event.participantStats.going, {approved: event.participantStats.going}) }}
|
||||
</small>
|
||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant && !actorIsOrganizer">
|
||||
{{ $tc('You and one other person are going to this event', event.participantStats.participants, { approved: event.participantStats.participants }) }}
|
||||
<small v-else-if="event.participantStats.going > 0 && actorIsParticipant && !actorIsOrganizer">
|
||||
{{ $tc('You and one other person are going to this event', event.participantStats.participant, { approved: event.participantStats.participant }) }}
|
||||
</small>
|
||||
<small v-if="event.options.maximumAttendeeCapacity">
|
||||
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
|
||||
@ -443,10 +443,10 @@ export default class Event extends EventMixin {
|
||||
}
|
||||
|
||||
if (data.joinEvent.role === ParticipantRole.NOT_APPROVED) {
|
||||
event.participantStats.unapproved = event.participantStats.unapproved + 1;
|
||||
event.participantStats.notApproved = event.participantStats.notApproved + 1;
|
||||
} else {
|
||||
event.participantStats.approved = event.participantStats.approved + 1;
|
||||
event.participantStats.participants = event.participantStats.participants + 1;
|
||||
event.participantStats.going = event.participantStats.going + 1;
|
||||
event.participantStats.participant = event.participantStats.participant + 1;
|
||||
}
|
||||
|
||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||
@ -514,10 +514,10 @@ export default class Event extends EventMixin {
|
||||
return;
|
||||
}
|
||||
if (participation.role === ParticipantRole.NOT_APPROVED) {
|
||||
event.participantStats.unapproved = event.participantStats.unapproved - 1;
|
||||
event.participantStats.notApproved = event.participantStats.notApproved - 1;
|
||||
} else {
|
||||
event.participantStats.approved = event.participantStats.approved - 1;
|
||||
event.participantStats.participants = event.participantStats.participants - 1;
|
||||
event.participantStats.going = event.participantStats.going - 1;
|
||||
event.participantStats.participant = event.participantStats.participant - 1;
|
||||
}
|
||||
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: this.uuid }, data: { event } });
|
||||
},
|
||||
@ -591,11 +591,11 @@ export default class Event extends EventMixin {
|
||||
|
||||
get eventCapacityOK(): boolean {
|
||||
if (!this.event.options.maximumAttendeeCapacity) return true;
|
||||
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participants;
|
||||
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participant;
|
||||
}
|
||||
|
||||
get numberOfPlacesStillAvailable(): number {
|
||||
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participants;
|
||||
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participant;
|
||||
}
|
||||
|
||||
urlToHostname(url: string): string|null {
|
||||
|
@ -4,7 +4,7 @@
|
||||
<b-tab-item>
|
||||
<template slot="header">
|
||||
<b-icon icon="account-multiple"></b-icon>
|
||||
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.approved }} </b-tag> </span>
|
||||
<span>{{ $t('Participants')}} <b-tag rounded> {{ participantStats.going }} </b-tag> </span>
|
||||
</template>
|
||||
<template>
|
||||
<section v-if="participantsAndCreators.length > 0">
|
||||
@ -22,10 +22,10 @@
|
||||
</section>
|
||||
</template>
|
||||
</b-tab-item>
|
||||
<b-tab-item :disabled="participantStats.unapproved === 0">
|
||||
<b-tab-item :disabled="participantStats.notApproved === 0">
|
||||
<template slot="header">
|
||||
<b-icon icon="account-multiple-plus"></b-icon>
|
||||
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.unapproved }} </b-tag> </span>
|
||||
<span>{{ $t('Requests') }} <b-tag rounded> {{ participantStats.notApproved }} </b-tag> </span>
|
||||
</template>
|
||||
<template>
|
||||
<section v-if="queue.length > 0">
|
||||
@ -182,7 +182,7 @@ export default class Participants extends Vue {
|
||||
@Watch('participantStats', { deep: true })
|
||||
watchParticipantStats(stats: IEventParticipantStats) {
|
||||
if (!stats) return;
|
||||
if ((stats.unapproved === 0 && this.activeTab === 1) || stats.rejected === 0 && this.activeTab === 2 ) {
|
||||
if ((stats.notApproved === 0 && this.activeTab === 1) || stats.rejected === 0 && this.activeTab === 2 ) {
|
||||
this.activeTab = 0;
|
||||
}
|
||||
}
|
||||
@ -223,9 +223,9 @@ export default class Participants extends Vue {
|
||||
if (data) {
|
||||
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
||||
this.rejected = this.rejected.filter(participant => participant.id !== data.updateParticipation.id);
|
||||
this.event.participantStats.approved += 1;
|
||||
this.event.participantStats.going += 1;
|
||||
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
||||
this.event.participantStats.unapproved -= 1;
|
||||
this.event.participantStats.notApproved -= 1;
|
||||
}
|
||||
if (participant.role === ParticipantRole.REJECTED) {
|
||||
this.event.participantStats.rejected -= 1;
|
||||
@ -253,11 +253,11 @@ export default class Participants extends Vue {
|
||||
this.queue = this.queue.filter(participant => participant.id !== data.updateParticipation.id);
|
||||
this.event.participantStats.rejected += 1;
|
||||
if (participant.role === ParticipantRole.PARTICIPANT) {
|
||||
this.event.participantStats.participants -= 1;
|
||||
this.event.participantStats.approved -= 1;
|
||||
this.event.participantStats.participant -= 1;
|
||||
this.event.participantStats.going -= 1;
|
||||
}
|
||||
if (participant.role === ParticipantRole.NOT_APPROVED) {
|
||||
this.event.participantStats.unapproved -= 1;
|
||||
this.event.participantStats.notApproved -= 1;
|
||||
}
|
||||
participant.role = ParticipantRole.REJECTED;
|
||||
this.rejected = this.rejected.filter(participantIn => participantIn.id !== participant.id);
|
||||
|
67
lib/mix/tasks/mobilizon/move_participant_stats.ex
Normal file
67
lib/mix/tasks/mobilizon/move_participant_stats.ex
Normal file
@ -0,0 +1,67 @@
|
||||
defmodule Mix.Tasks.Mobilizon.MoveParticipantStats do
|
||||
@moduledoc """
|
||||
Temporary task to move participant stats in the events table
|
||||
|
||||
This task will be removed in version 1.0.0-beta.3
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias Mobilizon.Storage.Repo
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Events.ParticipantRole
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Move participant stats to events table"
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
events =
|
||||
Event
|
||||
|> preload([e], :tags)
|
||||
|> Repo.all()
|
||||
|
||||
nb_events = length(events)
|
||||
|
||||
IO.puts(
|
||||
"\nStarting inserting participants stats into #{nb_events} events, this can take a while…\n"
|
||||
)
|
||||
|
||||
insert_participants_stats_into_events(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_participants_stats_into_events([%Event{url: url} = event | events], nb_events) do
|
||||
with roles <- ParticipantRole.__enum_map__(),
|
||||
counts <-
|
||||
Enum.reduce(roles, %{}, fn role, acc ->
|
||||
Map.put(acc, role, count_participants(event, role))
|
||||
end),
|
||||
{:ok, _} <-
|
||||
Events.update_event(event, %{
|
||||
participant_stats: counts
|
||||
}) do
|
||||
Logger.debug("Added participants stats to event #{url}")
|
||||
else
|
||||
{:error, res} ->
|
||||
Logger.error("Error while adding participants stats to event #{url} : #{inspect(res)}")
|
||||
end
|
||||
|
||||
ProgressBar.render(nb_events - length(events), nb_events)
|
||||
|
||||
insert_participants_stats_into_events(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_participants_stats_into_events([], nb_events) do
|
||||
IO.puts("\nFinished inserting participant stats for #{nb_events} events!\n")
|
||||
end
|
||||
|
||||
defp count_participants(%Event{id: event_id}, role) when is_atom(role) do
|
||||
event_id
|
||||
|> Events.count_participants_query()
|
||||
|> Events.filter_role(role)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
end
|
49
lib/mix/tasks/mobilizon/setup_search.ex
Normal file
49
lib/mix/tasks/mobilizon/setup_search.ex
Normal file
@ -0,0 +1,49 @@
|
||||
defmodule Mix.Tasks.Mobilizon.SetupSearch do
|
||||
@moduledoc """
|
||||
Temporary task to insert search data from existing events
|
||||
|
||||
This task will be removed in version 1.0.0-beta.3
|
||||
"""
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias Mobilizon.Service.Search
|
||||
alias Mobilizon.Storage.Repo
|
||||
alias Mobilizon.Events.Event
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Insert search data"
|
||||
def run([]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
events =
|
||||
Event
|
||||
|> preload([e], :tags)
|
||||
|> Repo.all()
|
||||
|
||||
nb_events = length(events)
|
||||
|
||||
IO.puts("\nStarting setting up search for #{nb_events} events, this can take a while…\n")
|
||||
insert_search_event(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_search_event([%Event{url: url} = event | events], nb_events) do
|
||||
case Search.insert_search_event(event) do
|
||||
{:ok, _} ->
|
||||
Logger.debug("Added event #{url} to the search")
|
||||
|
||||
{:error, res} ->
|
||||
Logger.error("Error while adding event #{url} to the search: #{inspect(res)}")
|
||||
end
|
||||
|
||||
ProgressBar.render(nb_events - length(events), nb_events)
|
||||
|
||||
insert_search_event(events, nb_events)
|
||||
end
|
||||
|
||||
defp insert_search_event([], nb_events) do
|
||||
IO.puts("\nFinished setting up search for #{nb_events} events!\n")
|
||||
end
|
||||
end
|
@ -5,18 +5,20 @@ defmodule Mix.Tasks.Mobilizon.Toot do
|
||||
|
||||
use Mix.Task
|
||||
|
||||
alias MobilizonWeb.API
|
||||
alias MobilizonWeb.API.Comments
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
require Logger
|
||||
|
||||
@shortdoc "Toot to an user"
|
||||
def run([from, content]) do
|
||||
def run([from, text]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
case API.Comments.create_comment(from, content) do
|
||||
{:ok, _, _} ->
|
||||
Mix.shell().info("Tooted")
|
||||
|
||||
with {:local_actor, %Actor{} = actor} <- {:local_actor, Actors.get_local_actor_by_name(from)},
|
||||
{:ok, _, _} <- Comments.create_comment(%{actor: actor, text: text}) do
|
||||
Mix.shell().info("Tooted")
|
||||
else
|
||||
{:local_actor, _, _} ->
|
||||
Mix.shell().error("Failed to toot.\nActor #{from} doesn't exist")
|
||||
|
||||
|
@ -13,6 +13,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
alias Mobilizon.Media.File
|
||||
alias Mobilizon.Reports.{Note, Report}
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Mention
|
||||
|
||||
alias MobilizonWeb.Endpoint
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
@ -46,6 +47,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
created_reports: [Report.t()],
|
||||
subject_reports: [Report.t()],
|
||||
report_notes: [Note.t()],
|
||||
mentions: [Mention.t()],
|
||||
memberships: [t]
|
||||
}
|
||||
|
||||
@ -139,6 +141,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
||||
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
||||
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
||||
has_many(:mentions, Mention)
|
||||
many_to_many(:memberships, __MODULE__, join_through: Member)
|
||||
|
||||
timestamps()
|
||||
|
@ -88,7 +88,10 @@ defmodule Mobilizon.Actors do
|
||||
"""
|
||||
@spec get_actor_by_url(String.t(), boolean) ::
|
||||
{:ok, Actor.t()} | {:error, :actor_not_found}
|
||||
def get_actor_by_url(url, preload \\ false) do
|
||||
def get_actor_by_url(url, preload \\ false)
|
||||
def get_actor_by_url(nil, _preload), do: {:error, :actor_not_found}
|
||||
|
||||
def get_actor_by_url(url, preload) do
|
||||
case Repo.get_by(Actor, url: url) do
|
||||
nil ->
|
||||
{:error, :actor_not_found}
|
||||
|
@ -65,8 +65,7 @@ defmodule Mobilizon.Addresses.Address do
|
||||
|
||||
@spec set_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp set_url(%Ecto.Changeset{changes: changes} = changeset) do
|
||||
uuid = Ecto.UUID.generate()
|
||||
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{uuid}")
|
||||
url = Map.get(changes, :url, "#{MobilizonWeb.Endpoint.url()}/address/#{Ecto.UUID.generate()}")
|
||||
|
||||
put_change(changeset, :url, url)
|
||||
end
|
||||
|
@ -8,7 +8,8 @@ defmodule Mobilizon.Events.Comment do
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.{Comment, CommentVisibility, Event}
|
||||
alias Mobilizon.Events.{Comment, CommentVisibility, Event, Tag}
|
||||
alias Mobilizon.Mention
|
||||
|
||||
alias MobilizonWeb.Endpoint
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
@ -22,6 +23,8 @@ defmodule Mobilizon.Events.Comment do
|
||||
actor: Actor.t(),
|
||||
attributed_to: Actor.t(),
|
||||
event: Event.t(),
|
||||
tags: [Tag.t()],
|
||||
mentions: [Mention.t()],
|
||||
in_reply_to_comment: t,
|
||||
origin_comment: t
|
||||
}
|
||||
@ -42,6 +45,8 @@ defmodule Mobilizon.Events.Comment do
|
||||
belongs_to(:event, Event, foreign_key: :event_id)
|
||||
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
||||
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
||||
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||
has_many(:mentions, Mention)
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
@ -57,16 +62,45 @@ defmodule Mobilizon.Events.Comment do
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
def changeset(%__MODULE__{} = comment, attrs) do
|
||||
uuid = attrs["uuid"] || Ecto.UUID.generate()
|
||||
url = attrs["url"] || generate_url(uuid)
|
||||
uuid = Map.get(attrs, :uuid) || Ecto.UUID.generate()
|
||||
url = Map.get(attrs, :url) || generate_url(uuid)
|
||||
|
||||
comment
|
||||
|> cast(attrs, @attrs)
|
||||
|> put_change(:uuid, uuid)
|
||||
|> put_change(:url, url)
|
||||
|> put_tags(attrs)
|
||||
|> put_mentions(attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
|
||||
@spec generate_url(String.t()) :: String.t()
|
||||
defp generate_url(uuid), do: Routes.page_url(Endpoint, :comment, uuid)
|
||||
|
||||
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||
defp put_tags(changeset, %{"tags" => tags}),
|
||||
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||
|
||||
defp put_tags(changeset, %{tags: tags}),
|
||||
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||
|
||||
defp put_tags(changeset, _), do: changeset
|
||||
|
||||
@spec put_mentions(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||
defp put_mentions(changeset, %{"mentions" => mentions}),
|
||||
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
|
||||
|
||||
defp put_mentions(changeset, %{mentions: mentions}),
|
||||
do: put_assoc(changeset, :mentions, Enum.map(mentions, &process_mention/1))
|
||||
|
||||
defp put_mentions(changeset, _), do: changeset
|
||||
|
||||
# We need a changeset instead of a raw struct because of slug which is generated in changeset
|
||||
defp process_tag(tag) do
|
||||
Tag.changeset(%Tag{}, tag)
|
||||
end
|
||||
|
||||
defp process_mention(tag) do
|
||||
Mention.changeset(%Mention{}, tag)
|
||||
end
|
||||
end
|
||||
|
@ -6,22 +6,31 @@ defmodule Mobilizon.Events.Event do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
alias Ecto.Changeset
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Addresses.Address
|
||||
|
||||
alias Mobilizon.Addresses
|
||||
|
||||
alias Mobilizon.Events.{
|
||||
EventOptions,
|
||||
EventStatus,
|
||||
EventVisibility,
|
||||
JoinOptions,
|
||||
EventParticipantStats,
|
||||
Participant,
|
||||
Session,
|
||||
Tag,
|
||||
Track
|
||||
}
|
||||
|
||||
alias Mobilizon.Media
|
||||
alias Mobilizon.Media.Picture
|
||||
alias Mobilizon.Mention
|
||||
|
||||
alias MobilizonWeb.Endpoint
|
||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
url: String.t(),
|
||||
@ -47,30 +56,15 @@ defmodule Mobilizon.Events.Event do
|
||||
picture: Picture.t(),
|
||||
tracks: [Track.t()],
|
||||
sessions: [Session.t()],
|
||||
mentions: [Mention.t()],
|
||||
tags: [Tag.t()],
|
||||
participants: [Actor.t()]
|
||||
}
|
||||
|
||||
@required_attrs [:title, :begins_on, :organizer_actor_id, :url, :uuid]
|
||||
@update_required_attrs [:title, :begins_on, :organizer_actor_id]
|
||||
@required_attrs @update_required_attrs ++ [:url, :uuid]
|
||||
|
||||
@optional_attrs [
|
||||
:slug,
|
||||
:description,
|
||||
:ends_on,
|
||||
:category,
|
||||
:status,
|
||||
:draft,
|
||||
:visibility,
|
||||
:publish_at,
|
||||
:online_address,
|
||||
:phone_address,
|
||||
:picture_id,
|
||||
:physical_address_id
|
||||
]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@update_required_attrs @required_attrs
|
||||
|
||||
@update_optional_attrs [
|
||||
:slug,
|
||||
:description,
|
||||
:ends_on,
|
||||
@ -85,7 +79,9 @@ defmodule Mobilizon.Events.Event do
|
||||
:picture_id,
|
||||
:physical_address_id
|
||||
]
|
||||
@update_attrs @update_required_attrs ++ @update_optional_attrs
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@update_attrs @update_required_attrs ++ @optional_attrs
|
||||
|
||||
schema "events" do
|
||||
field(:url, :string)
|
||||
@ -105,13 +101,15 @@ defmodule Mobilizon.Events.Event do
|
||||
field(:phone_address, :string)
|
||||
field(:category, :string)
|
||||
|
||||
embeds_one(:options, EventOptions, on_replace: :update)
|
||||
embeds_one(:options, EventOptions, on_replace: :delete)
|
||||
embeds_one(:participant_stats, EventParticipantStats, on_replace: :update)
|
||||
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
|
||||
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
|
||||
belongs_to(:physical_address, Address)
|
||||
belongs_to(:picture, Picture)
|
||||
belongs_to(:physical_address, Address, on_replace: :update)
|
||||
belongs_to(:picture, Picture, on_replace: :update)
|
||||
has_many(:tracks, Track)
|
||||
has_many(:sessions, Session)
|
||||
has_many(:mentions, Mention)
|
||||
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
||||
many_to_many(:participants, Actor, join_through: Participant)
|
||||
|
||||
@ -119,28 +117,41 @@ defmodule Mobilizon.Events.Event do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
@spec changeset(t, map) :: Changeset.t()
|
||||
def changeset(%__MODULE__{} = event, attrs) do
|
||||
attrs = Map.update(attrs, :uuid, Ecto.UUID.generate(), & &1)
|
||||
attrs = Map.update(attrs, :url, Routes.page_url(Endpoint, :event, attrs.uuid), & &1)
|
||||
|
||||
event
|
||||
|> cast(attrs, @attrs)
|
||||
|> cast_embed(:options)
|
||||
|> common_changeset(attrs)
|
||||
|> put_creator_if_published(:create)
|
||||
|> validate_required(@required_attrs)
|
||||
|> validate_lengths()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec update_changeset(t, map) :: Ecto.Changeset.t()
|
||||
@spec update_changeset(t, map) :: Changeset.t()
|
||||
def update_changeset(%__MODULE__{} = event, attrs) do
|
||||
event
|
||||
|> Ecto.Changeset.cast(attrs, @update_attrs)
|
||||
|> cast_embed(:options)
|
||||
|> put_tags(attrs)
|
||||
|> cast(attrs, @update_attrs)
|
||||
|> common_changeset(attrs)
|
||||
|> put_creator_if_published(:update)
|
||||
|> validate_required(@update_required_attrs)
|
||||
|> validate_lengths()
|
||||
end
|
||||
|
||||
@spec validate_lengths(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp validate_lengths(%Ecto.Changeset{} = changeset) do
|
||||
@spec common_changeset(Changeset.t(), map) :: Changeset.t()
|
||||
defp common_changeset(%Changeset{} = changeset, attrs) do
|
||||
changeset
|
||||
|> cast_embed(:options)
|
||||
|> put_tags(attrs)
|
||||
|> put_address(attrs)
|
||||
|> put_picture(attrs)
|
||||
end
|
||||
|
||||
@spec validate_lengths(Changeset.t()) :: Changeset.t()
|
||||
defp validate_lengths(%Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_length(:title, min: 3, max: 200)
|
||||
|> validate_length(:online_address, min: 3, max: 2000)
|
||||
@ -161,7 +172,80 @@ defmodule Mobilizon.Events.Event do
|
||||
|
||||
def can_be_managed_by(_event, _actor), do: {:event_can_be_managed, false}
|
||||
|
||||
@spec put_tags(Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||
defp put_tags(changeset, %{"tags" => tags}), do: put_assoc(changeset, :tags, tags)
|
||||
defp put_tags(changeset, _), do: changeset
|
||||
@spec put_tags(Changeset.t(), map) :: Changeset.t()
|
||||
defp put_tags(%Changeset{} = changeset, %{tags: tags}),
|
||||
do: put_assoc(changeset, :tags, Enum.map(tags, &process_tag/1))
|
||||
|
||||
defp put_tags(%Changeset{} = changeset, _), do: changeset
|
||||
|
||||
# We need a changeset instead of a raw struct because of slug which is generated in changeset
|
||||
defp process_tag(tag) do
|
||||
Tag.changeset(%Tag{}, tag)
|
||||
end
|
||||
|
||||
# In case the provided addresses is an existing one
|
||||
@spec put_address(Changeset.t(), map) :: Changeset.t()
|
||||
defp put_address(%Changeset{} = changeset, %{physical_address: %{id: id} = _physical_address}) do
|
||||
case Addresses.get_address!(id) do
|
||||
%Address{} = address ->
|
||||
put_assoc(changeset, :physical_address, address)
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
# In case it's a new address
|
||||
defp put_address(%Changeset{} = changeset, _attrs) do
|
||||
cast_assoc(changeset, :physical_address)
|
||||
end
|
||||
|
||||
# In case the provided picture is an existing one
|
||||
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
||||
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
|
||||
case Media.get_picture!(id) do
|
||||
%Picture{} = picture ->
|
||||
put_assoc(changeset, :picture, picture)
|
||||
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
# In case it's a new picture
|
||||
defp put_picture(%Changeset{} = changeset, _attrs) do
|
||||
cast_assoc(changeset, :picture)
|
||||
end
|
||||
|
||||
# Created or updated with draft parameter: don't publish
|
||||
defp put_creator_if_published(
|
||||
%Changeset{changes: %{draft: true}} = changeset,
|
||||
_action
|
||||
) do
|
||||
cast_embed(changeset, :participant_stats)
|
||||
end
|
||||
|
||||
# Created with any other value: publish
|
||||
defp put_creator_if_published(
|
||||
%Changeset{} = changeset,
|
||||
:create
|
||||
) do
|
||||
changeset
|
||||
|> put_embed(:participant_stats, %{creator: 1})
|
||||
end
|
||||
|
||||
# Updated from draft false to true: publish
|
||||
defp put_creator_if_published(
|
||||
%Changeset{
|
||||
data: %{draft: false},
|
||||
changes: %{draft: true}
|
||||
} = changeset,
|
||||
:update
|
||||
) do
|
||||
changeset
|
||||
|> put_embed(:participant_stats, %{creator: 1})
|
||||
end
|
||||
|
||||
defp put_creator_if_published(%Changeset{} = changeset, _),
|
||||
do: cast_embed(changeset, :participant_stats)
|
||||
end
|
||||
|
44
lib/mobilizon/events/event_participant_stats.ex
Normal file
44
lib/mobilizon/events/event_participant_stats.ex
Normal file
@ -0,0 +1,44 @@
|
||||
defmodule Mobilizon.Events.EventParticipantStats do
|
||||
@moduledoc """
|
||||
Participation stats on event
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
not_approved: integer(),
|
||||
rejected: integer(),
|
||||
participant: integer(),
|
||||
moderator: integer(),
|
||||
administrator: integer(),
|
||||
creator: integer()
|
||||
}
|
||||
|
||||
@attrs [
|
||||
:not_approved,
|
||||
:rejected,
|
||||
:participant,
|
||||
:moderator,
|
||||
:administrator,
|
||||
:moderator,
|
||||
:creator
|
||||
]
|
||||
|
||||
@primary_key false
|
||||
@derive Jason.Encoder
|
||||
embedded_schema do
|
||||
field(:not_approved, :integer, default: 0)
|
||||
field(:rejected, :integer, default: 0)
|
||||
field(:participant, :integer, default: 0)
|
||||
field(:moderator, :integer, default: 0)
|
||||
field(:administrator, :integer, default: 0)
|
||||
field(:creator, :integer, default: 0)
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||
def changeset(%__MODULE__{} = event_options, attrs) do
|
||||
cast(event_options, attrs, @attrs)
|
||||
end
|
||||
end
|
@ -7,6 +7,7 @@ defmodule Mobilizon.Events do
|
||||
|
||||
import Ecto.Query
|
||||
import EctoEnum
|
||||
alias Ecto.{Multi, Changeset}
|
||||
|
||||
import Mobilizon.Storage.Ecto
|
||||
|
||||
@ -17,6 +18,7 @@ defmodule Mobilizon.Events do
|
||||
alias Mobilizon.Events.{
|
||||
Comment,
|
||||
Event,
|
||||
EventParticipantStats,
|
||||
FeedToken,
|
||||
Participant,
|
||||
Session,
|
||||
@ -82,6 +84,8 @@ defmodule Mobilizon.Events do
|
||||
|
||||
@event_preloads [
|
||||
:organizer_actor,
|
||||
:attributed_to,
|
||||
:mentions,
|
||||
:sessions,
|
||||
:tracks,
|
||||
:tags,
|
||||
@ -90,7 +94,7 @@ defmodule Mobilizon.Events do
|
||||
:picture
|
||||
]
|
||||
|
||||
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment]
|
||||
@comment_preloads [:actor, :attributed_to, :in_reply_to_comment, :tags, :mentions]
|
||||
|
||||
@doc """
|
||||
Gets a single event.
|
||||
@ -235,81 +239,103 @@ defmodule Mobilizon.Events do
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_or_create_event(%{"url" => url} = attrs) do
|
||||
case Repo.get_by(Event, url: url) do
|
||||
%Event{} = event -> {:ok, Repo.preload(event, @event_preloads)}
|
||||
nil -> create_event(attrs)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates an event.
|
||||
"""
|
||||
@spec create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||
def create_event(attrs \\ %{}) do
|
||||
with {:ok, %Event{draft: false} = event} <- do_create_event(attrs),
|
||||
{:ok, %Participant{} = _participant} <-
|
||||
create_participant(%{
|
||||
actor_id: event.organizer_actor_id,
|
||||
role: :creator,
|
||||
event_id: event.id
|
||||
}) do
|
||||
with {:ok, %{insert: %Event{} = event}} <- do_create_event(attrs),
|
||||
%Event{} = event <- Repo.preload(event, @event_preloads) do
|
||||
Task.start(fn -> Search.insert_search_event(event) end)
|
||||
{:ok, event}
|
||||
else
|
||||
# We don't create a creator participant if the event is a draft
|
||||
{:ok, %Event{draft: true} = event} -> {:ok, event}
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
# We start by inserting the event and then insert a first participant if the event is not a draft
|
||||
@spec do_create_event(map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||
defp do_create_event(attrs) do
|
||||
with {:ok, %Event{} = event} <-
|
||||
%Event{}
|
||||
|> Event.changeset(attrs)
|
||||
|> Ecto.Changeset.put_assoc(:tags, Map.get(attrs, "tags", []))
|
||||
|> Repo.insert(),
|
||||
%Event{} = event <-
|
||||
Repo.preload(event, [:tags, :organizer_actor, :physical_address, :picture]) do
|
||||
{:ok, event}
|
||||
end
|
||||
Multi.new()
|
||||
|> Multi.insert(:insert, Event.changeset(%Event{}, attrs))
|
||||
|> Multi.run(:write, fn _repo, %{insert: %Event{draft: draft} = event} ->
|
||||
with {:is_draft, false} <- {:is_draft, draft},
|
||||
{:ok, %Participant{} = participant} <-
|
||||
create_participant(
|
||||
%{
|
||||
event_id: event.id,
|
||||
role: :creator,
|
||||
actor_id: event.organizer_actor_id
|
||||
},
|
||||
false
|
||||
) do
|
||||
{:ok, participant}
|
||||
else
|
||||
{:is_draft, true} -> {:ok, nil}
|
||||
err -> err
|
||||
end
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates an event.
|
||||
|
||||
We start by updating the event and then insert a first participant if the event is not a draft anymore
|
||||
"""
|
||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update_event(
|
||||
%Event{draft: old_draft_status, id: event_id, organizer_actor_id: organizer_actor_id} =
|
||||
old_event,
|
||||
attrs
|
||||
) do
|
||||
with %Ecto.Changeset{changes: changes} = changeset <-
|
||||
old_event |> Repo.preload(:tags) |> Event.update_changeset(attrs) do
|
||||
with {:ok, %Event{draft: new_draft_status} = new_event} <- Repo.update(changeset) do
|
||||
# If the event is no longer a draft
|
||||
if old_draft_status == true && new_draft_status == false do
|
||||
{:ok, %Participant{} = _participant} =
|
||||
create_participant(%{
|
||||
event_id: event_id,
|
||||
role: :creator,
|
||||
actor_id: organizer_actor_id
|
||||
})
|
||||
end
|
||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||
def update_event(%Event{} = old_event, attrs) do
|
||||
with %Changeset{changes: changes} = changeset <-
|
||||
Event.update_changeset(Repo.preload(old_event, :tags), attrs),
|
||||
{:ok, %{update: %Event{} = new_event}} <-
|
||||
Multi.new()
|
||||
|> Multi.update(
|
||||
:update,
|
||||
changeset
|
||||
)
|
||||
|> Multi.run(:write, fn _repo, %{update: %Event{draft: draft} = event} ->
|
||||
with {:is_draft, false} <- {:is_draft, draft},
|
||||
{:ok, %Participant{} = participant} <-
|
||||
create_participant(
|
||||
%{
|
||||
event_id: event.id,
|
||||
role: :creator,
|
||||
actor_id: event.organizer_actor_id
|
||||
},
|
||||
false
|
||||
) do
|
||||
{:ok, participant}
|
||||
else
|
||||
{:is_draft, true} -> {:ok, nil}
|
||||
err -> err
|
||||
end
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
Cachex.del(:ics, "event_#{new_event.uuid}")
|
||||
|
||||
Cachex.del(:ics, "event_#{new_event.uuid}")
|
||||
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
|
||||
old_event,
|
||||
new_event,
|
||||
changes
|
||||
)
|
||||
|
||||
Mobilizon.Service.Events.Tool.calculate_event_diff_and_send_notifications(
|
||||
old_event,
|
||||
new_event,
|
||||
changes
|
||||
)
|
||||
Task.start(fn -> Search.update_search_event(new_event) end)
|
||||
|
||||
Task.start(fn -> Search.update_search_event(new_event) end)
|
||||
|
||||
{:ok, new_event}
|
||||
end
|
||||
{:ok, Repo.preload(new_event, @event_preloads)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an event.
|
||||
"""
|
||||
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec delete_event(Event.t()) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||
def delete_event(%Event{} = event), do: Repo.delete(event)
|
||||
|
||||
@doc """
|
||||
@ -450,7 +476,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Gets an existing tag or creates the new one.
|
||||
"""
|
||||
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||
def get_or_create_tag(%{"name" => "#" <> title}) do
|
||||
case Repo.get_by(Tag, title: title) do
|
||||
%Tag{} = tag ->
|
||||
@ -461,10 +487,24 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets an existing tag or creates the new one.
|
||||
"""
|
||||
@spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||
def get_or_create_tag(title) do
|
||||
case Repo.get_by(Tag, title: title) do
|
||||
%Tag{} = tag ->
|
||||
{:ok, tag}
|
||||
|
||||
nil ->
|
||||
create_tag(%{"title" => title})
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a tag.
|
||||
"""
|
||||
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||
def create_tag(attrs \\ %{}) do
|
||||
%Tag{}
|
||||
|> Tag.changeset(attrs)
|
||||
@ -474,7 +514,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Updates a tag.
|
||||
"""
|
||||
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec update_tag(Tag.t(), map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||
def update_tag(%Tag{} = tag, attrs) do
|
||||
tag
|
||||
|> Tag.changeset(attrs)
|
||||
@ -484,7 +524,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Deletes a tag.
|
||||
"""
|
||||
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec delete_tag(Tag.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
|
||||
def delete_tag(%Tag{} = tag), do: Repo.delete(tag)
|
||||
|
||||
@doc """
|
||||
@ -524,7 +564,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Creates a relation between two tags.
|
||||
"""
|
||||
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec create_tag_relation(map) :: {:ok, TagRelation.t()} | {:error, Changeset.t()}
|
||||
def create_tag_relation(attrs \\ {}) do
|
||||
%TagRelation{}
|
||||
|> TagRelation.changeset(attrs)
|
||||
@ -538,7 +578,7 @@ defmodule Mobilizon.Events do
|
||||
Removes a tag relation.
|
||||
"""
|
||||
@spec delete_tag_relation(TagRelation.t()) ::
|
||||
{:ok, TagRelation.t()} | {:error, Ecto.Changeset.t()}
|
||||
{:ok, TagRelation.t()} | {:error, Changeset.t()}
|
||||
def delete_tag_relation(%TagRelation{} = tag_relation) do
|
||||
Repo.delete(tag_relation)
|
||||
end
|
||||
@ -763,7 +803,7 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts participant participants.
|
||||
Counts participant participants (participants with no extra role)
|
||||
"""
|
||||
@spec count_participant_participants(integer | String.t()) :: integer
|
||||
def count_participant_participants(event_id) do
|
||||
@ -773,17 +813,6 @@ defmodule Mobilizon.Events do
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts unapproved participants.
|
||||
"""
|
||||
@spec count_unapproved_participants(integer | String.t()) :: integer
|
||||
def count_unapproved_participants(event_id) do
|
||||
event_id
|
||||
|> count_participants_query()
|
||||
|> filter_unapproved_role()
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts rejected participants.
|
||||
"""
|
||||
@ -805,12 +834,40 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Creates a participant.
|
||||
"""
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create_participant(attrs \\ %{}) do
|
||||
with {:ok, %Participant{} = participant} <-
|
||||
%Participant{}
|
||||
|> Participant.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
@spec create_participant(map) :: {:ok, Participant.t()} | {:error, Changeset.t()}
|
||||
def create_participant(attrs \\ %{}, update_event_participation_stats \\ true) do
|
||||
with {:ok, %{participant: %Participant{} = participant}} <-
|
||||
Multi.new()
|
||||
|> Multi.insert(:participant, Participant.changeset(%Participant{}, attrs))
|
||||
|> Multi.run(:update_event_participation_stats, fn _repo,
|
||||
%{
|
||||
participant:
|
||||
%Participant{
|
||||
role: role,
|
||||
event_id: event_id
|
||||
} = _participant
|
||||
} ->
|
||||
with {:update_event_participation_stats, true} <-
|
||||
{:update_event_participation_stats, update_event_participation_stats},
|
||||
{:ok, %Event{} = event} <- get_event(event_id),
|
||||
%EventParticipantStats{} = participant_stats <-
|
||||
Map.get(event, :participant_stats),
|
||||
%EventParticipantStats{} = participant_stats <-
|
||||
Map.update(participant_stats, role, 0, &(&1 + 1)),
|
||||
{:ok, %Event{} = event} <-
|
||||
event
|
||||
|> Event.update_changeset(%{
|
||||
participant_stats: Map.from_struct(participant_stats)
|
||||
})
|
||||
|> Repo.update() do
|
||||
{:ok, event}
|
||||
else
|
||||
{:update_event_participation_stats, false} -> {:ok, nil}
|
||||
{:error, :event_not_found} -> {:error, :event_not_found}
|
||||
err -> {:error, err}
|
||||
end
|
||||
end)
|
||||
|> Repo.transaction() do
|
||||
{:ok, Repo.preload(participant, [:event, :actor])}
|
||||
end
|
||||
end
|
||||
@ -819,7 +876,7 @@ defmodule Mobilizon.Events do
|
||||
Updates a participant.
|
||||
"""
|
||||
@spec update_participant(Participant.t(), map) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
{:ok, Participant.t()} | {:error, Changeset.t()}
|
||||
def update_participant(%Participant{} = participant, attrs) do
|
||||
participant
|
||||
|> Participant.changeset(attrs)
|
||||
@ -830,7 +887,7 @@ defmodule Mobilizon.Events do
|
||||
Deletes a participant.
|
||||
"""
|
||||
@spec delete_participant(Participant.t()) ::
|
||||
{:ok, Participant.t()} | {:error, Ecto.Changeset.t()}
|
||||
{:ok, Participant.t()} | {:error, Changeset.t()}
|
||||
def delete_participant(%Participant{} = participant), do: Repo.delete(participant)
|
||||
|
||||
@doc """
|
||||
@ -843,7 +900,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Creates a session.
|
||||
"""
|
||||
@spec create_session(map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec create_session(map) :: {:ok, Session.t()} | {:error, Changeset.t()}
|
||||
def create_session(attrs \\ %{}) do
|
||||
%Session{}
|
||||
|> Session.changeset(attrs)
|
||||
@ -853,7 +910,7 @@ defmodule Mobilizon.Events do
|
||||
@doc """
|
||||
Updates a session.
|
||||
"""
|
||||
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Ecto.Changeset.t()}
|
||||
@spec update_session(Session.t(), map) :: {:ok, Session.t()} | {:error, Changeset.t()}
|
||||
def update_session(%Session{} = session, attrs) do
|
||||
session
|
||||
|> Session.changeset(attrs)
|
||||