Merge branch 'bug/make-sure-we-cant-participate-in-a-limited-participation-event' into 'master'
Bug/make sure we cant participate in a limited participation event Closes #200 See merge request framasoft/mobilizon!247
This commit is contained in:
commit
e8a9ee92b7
@ -188,5 +188,9 @@ nav {
|
|||||||
.navbar-item.has-dropdown a.navbar-link figure {
|
.navbar-item.has-dropdown a.navbar-link figure {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.navbar-item:focus-within {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -101,7 +101,8 @@ export const FETCH_EVENT = gql`
|
|||||||
# },
|
# },
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
approved,
|
||||||
unapproved
|
unapproved,
|
||||||
|
participants
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
@ -257,7 +258,8 @@ export const CREATE_EVENT = gql`
|
|||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
approved,
|
||||||
unapproved
|
unapproved,
|
||||||
|
participants
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
@ -341,7 +343,8 @@ export const EDIT_EVENT = gql`
|
|||||||
},
|
},
|
||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
approved,
|
||||||
unapproved
|
unapproved,
|
||||||
|
participants
|
||||||
},
|
},
|
||||||
tags {
|
tags {
|
||||||
${tagsQuery}
|
${tagsQuery}
|
||||||
@ -407,7 +410,8 @@ export const PARTICIPANTS = gql`
|
|||||||
participantStats {
|
participantStats {
|
||||||
approved,
|
approved,
|
||||||
unapproved,
|
unapproved,
|
||||||
rejected
|
rejected,
|
||||||
|
participants
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,5 +273,6 @@
|
|||||||
"{count} participants": "{count} participants",
|
"{count} participants": "{count} participants",
|
||||||
"{count} requests waiting": "{count} requests waiting",
|
"{count} requests waiting": "{count} requests waiting",
|
||||||
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.",
|
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.",
|
||||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
|
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
|
||||||
|
"All the places have already been taken": "All the places have been taken|One place is still available|{places} places are still available"
|
||||||
}
|
}
|
@ -312,5 +312,6 @@
|
|||||||
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
|
"{count} participants": "Un⋅e participant⋅e|{count} participant⋅e⋅s",
|
||||||
"{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
|
"{count} requests waiting": "Un⋅e demande en attente|{count} demandes en attente",
|
||||||
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.",
|
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.",
|
||||||
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines"
|
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
|
||||||
|
"All the places have already been taken": "Toutes les places ont été prises|Une place est encore disponible|{places} places sont encore disponibles"
|
||||||
}
|
}
|
@ -112,6 +112,7 @@ export interface IEvent {
|
|||||||
approved: number;
|
approved: number;
|
||||||
unapproved: number;
|
unapproved: number;
|
||||||
rejected: number;
|
rejected: number;
|
||||||
|
participants: number;
|
||||||
};
|
};
|
||||||
participants: IParticipant[];
|
participants: IParticipant[];
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ export class EventModel implements IEvent {
|
|||||||
|
|
||||||
publishAt = new Date();
|
publishAt = new Date();
|
||||||
|
|
||||||
participantStats = { approved: 0, unapproved: 0, rejected: 0 };
|
participantStats = { approved: 0, unapproved: 0, rejected: 0, participants: 0 };
|
||||||
participants: IParticipant[] = [];
|
participants: IParticipant[] = [];
|
||||||
|
|
||||||
relatedEvents: IEvent[] = [];
|
relatedEvents: IEvent[] = [];
|
||||||
|
@ -17,17 +17,24 @@
|
|||||||
<div class="date-component">
|
<div class="date-component">
|
||||||
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="title">{{ event.title }}</h1>
|
<div class="title-and-informations">
|
||||||
|
<h1 class="title">{{ event.title }}</h1>
|
||||||
|
<span>
|
||||||
|
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
||||||
|
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
||||||
|
</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>
|
||||||
|
<small v-if="event.options.maximumAttendeeCapacity">
|
||||||
|
{{ $tc('All the places have already been taken', numberOfPlacesStillAvailable, { places: numberOfPlacesStillAvailable}) }}
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="has-text-right" v-if="new Date(endDate) > new Date()">
|
<div class="event-participation has-text-right" v-if="new Date(endDate) > new Date()">
|
||||||
<small v-if="event.participantStats.approved > 0 && !actorIsParticipant">
|
|
||||||
{{ $tc('One person is going', event.participantStats.approved, {approved: event.participantStats.approved}) }}
|
|
||||||
</small>
|
|
||||||
<small v-else-if="event.participantStats.approved > 0 && actorIsParticipant">
|
|
||||||
{{ $tc('You and one other person are going to this event', event.participantStats.approved - 1, {approved: event.participantStats.approved - 1}) }}
|
|
||||||
</small>
|
|
||||||
<participation-button
|
<participation-button
|
||||||
v-if="currentActor.id && !actorIsOrganizer && !event.draft"
|
v-if="currentActor.id && !actorIsOrganizer && !event.draft && (eventCapacityOK || actorIsParticipant)"
|
||||||
:participation="participations[0]"
|
:participation="participations[0]"
|
||||||
:current-actor="currentActor"
|
:current-actor="currentActor"
|
||||||
@joinEvent="joinEvent"
|
@joinEvent="joinEvent"
|
||||||
@ -46,14 +53,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="metadata columns">
|
<div class="metadata columns">
|
||||||
<div class="column is-three-quarters-desktop">
|
<div class="column is-three-quarters-desktop">
|
||||||
<p class="tags" v-if="event.category || event.tags.length > 0">
|
<p class="tags" v-if="event.tags.length > 0">
|
||||||
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
|
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ $t('Draft') }}</b-tag>
|
||||||
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
|
<span class="visibility" v-if="!event.draft">
|
||||||
<span v-if="event.tags > 0">⋅</span>
|
|
||||||
<span class="visibility" v-if="!event.draft">
|
|
||||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
|
<b-tag type="is-info" v-if="event.visibility === EventVisibility.PUBLIC">{{ $t('Public event') }}</b-tag>
|
||||||
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
<b-tag type="is-info" v-if="event.visibility === EventVisibility.UNLISTED">{{ $t('Private event') }}</b-tag>
|
||||||
</span>
|
</span>
|
||||||
|
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags" :key="tag.title">{{ tag.title }}</b-tag>
|
||||||
|
<span v-if="event.tags > 0">⋅</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="date-and-add-to-calendar">
|
<div class="date-and-add-to-calendar">
|
||||||
<div class="date-and-privacy" v-if="event.beginsOn">
|
<div class="date-and-privacy" v-if="event.beginsOn">
|
||||||
@ -147,6 +154,9 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-half has-text-centered">
|
<div class="column is-half has-text-centered">
|
||||||
<h3 class="title">{{ $t('Share this event') }}</h3>
|
<h3 class="title">{{ $t('Share this event') }}</h3>
|
||||||
|
<small class="maximumNumberOfPlacesWarning" v-if="!eventCapacityOK">
|
||||||
|
{{ $t('All the places have already been taken') }}
|
||||||
|
</small>
|
||||||
<div>
|
<div>
|
||||||
<b-icon icon="mastodon" size="is-large" type="is-primary" />
|
<b-icon icon="mastodon" size="is-large" type="is-primary" />
|
||||||
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
|
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
|
||||||
@ -346,7 +356,7 @@ export default class Event extends EventMixin {
|
|||||||
|
|
||||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||||
query: EVENT_PERSON_PARTICIPATION,
|
query: EVENT_PERSON_PARTICIPATION,
|
||||||
variables: { eventId: this.event.id, name: identity.preferredUsername },
|
variables: { eventId: this.event.id, actorId: identity.id },
|
||||||
});
|
});
|
||||||
if (participationCachedData == null) return;
|
if (participationCachedData == null) return;
|
||||||
const { person } = participationCachedData;
|
const { person } = participationCachedData;
|
||||||
@ -357,7 +367,7 @@ export default class Event extends EventMixin {
|
|||||||
person.participations.push(data.joinEvent);
|
person.participations.push(data.joinEvent);
|
||||||
store.writeQuery({
|
store.writeQuery({
|
||||||
query: EVENT_PERSON_PARTICIPATION,
|
query: EVENT_PERSON_PARTICIPATION,
|
||||||
variables: { eventId: this.event.id, name: identity.preferredUsername },
|
variables: { eventId: this.event.id, actorId: identity.id },
|
||||||
data: { person },
|
data: { person },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -408,7 +418,7 @@ export default class Event extends EventMixin {
|
|||||||
|
|
||||||
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
const participationCachedData = store.readQuery<{ person: IPerson }>({
|
||||||
query: EVENT_PERSON_PARTICIPATION,
|
query: EVENT_PERSON_PARTICIPATION,
|
||||||
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername },
|
variables: { eventId: this.event.id, actorId: this.currentActor.id },
|
||||||
});
|
});
|
||||||
if (participationCachedData == null) return;
|
if (participationCachedData == null) return;
|
||||||
const { person } = participationCachedData;
|
const { person } = participationCachedData;
|
||||||
@ -420,7 +430,7 @@ export default class Event extends EventMixin {
|
|||||||
person.participations = [];
|
person.participations = [];
|
||||||
store.writeQuery({
|
store.writeQuery({
|
||||||
query: EVENT_PERSON_PARTICIPATION,
|
query: EVENT_PERSON_PARTICIPATION,
|
||||||
variables: { eventId: this.event.id, name: this.currentActor.preferredUsername },
|
variables: { eventId: this.event.id, actorId: this.currentActor.id },
|
||||||
data: { person },
|
data: { person },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -497,6 +507,15 @@ export default class Event extends EventMixin {
|
|||||||
return meta.getAttribute('content') || '';
|
return meta.getAttribute('content') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get eventCapacityOK(): boolean {
|
||||||
|
if (!this.event.options.maximumAttendeeCapacity) return true;
|
||||||
|
return this.event.options.maximumAttendeeCapacity > this.event.participantStats.participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
get numberOfPlacesStillAvailable(): number {
|
||||||
|
return this.event.options.maximumAttendeeCapacity - this.event.participantStats.participants;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -600,22 +619,40 @@ export default class Event extends EventMixin {
|
|||||||
/*align-self: center;*/
|
/*align-self: center;*/
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
/*align-content: space-around;*/
|
/*align-content: space-around;*/
|
||||||
padding: 15px 10px 0;
|
padding: 7.5px 10px 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
div.title-wrapper {
|
div.title-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
div.title-and-informations {
|
||||||
|
h1.title {
|
||||||
|
font-weight: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: 1.7em;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span small {
|
||||||
|
&:not(:last-child):after {
|
||||||
|
content: '⋅'
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
div.date-component {
|
div.date-component {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h1.title {
|
.event-participation small {
|
||||||
font-weight: normal;
|
display: block;
|
||||||
word-break: break-word;
|
|
||||||
font-size: 1.7em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.participate-button {
|
.participate-button {
|
||||||
@ -660,12 +697,16 @@ export default class Event extends EventMixin {
|
|||||||
|
|
||||||
p.tags {
|
p.tags {
|
||||||
span {
|
span {
|
||||||
&.tag.is-success {
|
&.tag {
|
||||||
&::before {
|
margin: 0 2px 4px;
|
||||||
content: '#';
|
|
||||||
|
&.is-success {
|
||||||
|
&::before {
|
||||||
|
content: '#';
|
||||||
|
}
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #111111;
|
||||||
}
|
}
|
||||||
text-transform: uppercase;
|
|
||||||
color: #111111;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
margin: auto 5px;
|
margin: auto 5px;
|
||||||
@ -733,6 +774,36 @@ export default class Event extends EventMixin {
|
|||||||
padding: 10rem 0;
|
padding: 10rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
display: block;
|
||||||
|
color: $primary;
|
||||||
|
font-size: 3rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: $secondary;
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column:first-child {
|
||||||
|
h3 {
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
small.maximumNumberOfPlacesWarning {
|
||||||
|
margin: 0 auto 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column:last-child {
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.add-to-calendar {
|
.add-to-calendar {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 400px;
|
background-size: 400px;
|
||||||
@ -749,19 +820,6 @@ export default class Event extends EventMixin {
|
|||||||
height: 40%;
|
height: 40%;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
display: block;
|
|
||||||
color: $primary;
|
|
||||||
font-size: 3rem;
|
|
||||||
text-decoration: underline;
|
|
||||||
text-decoration-color: $secondary;
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 20rem;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -762,6 +762,17 @@ defmodule Mobilizon.Events do
|
|||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Counts participant participants.
|
||||||
|
"""
|
||||||
|
@spec count_participant_participants(integer | String.t()) :: integer
|
||||||
|
def count_participant_participants(event_id) do
|
||||||
|
event_id
|
||||||
|
|> count_participants_query()
|
||||||
|
|> filter_participant_role()
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Counts unapproved participants.
|
Counts unapproved participants.
|
||||||
"""
|
"""
|
||||||
@ -1457,6 +1468,11 @@ defmodule Mobilizon.Events do
|
|||||||
from(p in query, where: p.role not in ^[:not_approved, :rejected])
|
from(p in query, where: p.role not in ^[:not_approved, :rejected])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec filter_participant_role(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
|
defp filter_participant_role(query) do
|
||||||
|
from(p in query, where: p.role == ^:participant)
|
||||||
|
end
|
||||||
|
|
||||||
@spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t()
|
@spec filter_unapproved_role(Ecto.Query.t()) :: Ecto.Query.t()
|
||||||
defp filter_unapproved_role(query) do
|
defp filter_unapproved_role(query) do
|
||||||
from(p in query, where: p.role == ^:not_approved)
|
from(p in query, where: p.role == ^:not_approved)
|
||||||
|
@ -101,7 +101,8 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||||||
%{
|
%{
|
||||||
approved: Mobilizon.Events.count_approved_participants(id),
|
approved: Mobilizon.Events.count_approved_participants(id),
|
||||||
unapproved: Mobilizon.Events.count_unapproved_participants(id),
|
unapproved: Mobilizon.Events.count_unapproved_participants(id),
|
||||||
rejected: Mobilizon.Events.count_rejected_participants(id)
|
rejected: Mobilizon.Events.count_rejected_participants(id),
|
||||||
|
participants: Mobilizon.Events.count_participant_participants(id)
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -170,6 +171,9 @@ defmodule MobilizonWeb.Resolvers.Event do
|
|||||||
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
|> Map.put(:actor, Person.proxify_pictures(actor)) do
|
||||||
{:ok, participant}
|
{:ok, participant}
|
||||||
else
|
else
|
||||||
|
{:maximum_attendee_capacity, _} ->
|
||||||
|
{:error, "The event has already reached it's maximum capacity"}
|
||||||
|
|
||||||
{:has_event, _} ->
|
{:has_event, _} ->
|
||||||
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
{:error, "Event with this ID #{inspect(event_id)} doesn't exist"}
|
||||||
|
|
||||||
|
@ -115,6 +115,10 @@ defmodule MobilizonWeb.Schema.EventType do
|
|||||||
field(:approved, :integer, description: "The number of approved participants")
|
field(:approved, :integer, description: "The number of approved participants")
|
||||||
field(:unapproved, :integer, description: "The number of unapproved participants")
|
field(:unapproved, :integer, description: "The number of unapproved participants")
|
||||||
field(:rejected, :integer, description: "The number of rejected participants")
|
field(:rejected, :integer, description: "The number of rejected participants")
|
||||||
|
|
||||||
|
field(:participants, :integer,
|
||||||
|
description: "The number of simple participants (excluding creators)"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
object :event_offer do
|
object :event_offer do
|
||||||
|
@ -411,8 +411,16 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
|
|
||||||
def join(object, actor, local \\ true)
|
def join(object, actor, local \\ true)
|
||||||
|
|
||||||
def join(%Event{} = event, %Actor{} = actor, local) do
|
def join(%Event{options: options} = event, %Actor{} = actor, local) do
|
||||||
with role <- Mobilizon.Events.get_default_participant_role(event),
|
# TODO Refactor me for federation
|
||||||
|
with maximum_attendee_capacity <-
|
||||||
|
Map.get(options, :maximum_attendee_capacity, 2_000_000) || false,
|
||||||
|
{:maximum_attendee_capacity, true} <-
|
||||||
|
{:maximum_attendee_capacity,
|
||||||
|
!maximum_attendee_capacity ||
|
||||||
|
Mobilizon.Events.count_participant_participants(event.id) <
|
||||||
|
maximum_attendee_capacity},
|
||||||
|
role <- Mobilizon.Events.get_default_participant_role(event),
|
||||||
{:ok, %Participant{} = participant} <-
|
{:ok, %Participant{} = participant} <-
|
||||||
Mobilizon.Events.create_participant(%{
|
Mobilizon.Events.create_participant(%{
|
||||||
role: role,
|
role: role,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# source: http://localhost:4000/api
|
# source: http://localhost:4000/api
|
||||||
# timestamp: Fri Oct 04 2019 15:04:46 GMT+0200 (GMT+02:00)
|
# timestamp: Fri Oct 11 2019 11:53:52 GMT+0200 (Central European Summer Time)
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: RootQueryType
|
query: RootQueryType
|
||||||
@ -681,6 +681,9 @@ type ParticipantStats {
|
|||||||
"""The number of approved participants"""
|
"""The number of approved participants"""
|
||||||
approved: Int
|
approved: Int
|
||||||
|
|
||||||
|
"""The number of simple participants (excluding creators)"""
|
||||||
|
participants: Int
|
||||||
|
|
||||||
"""The number of rejected participants"""
|
"""The number of rejected participants"""
|
||||||
rejected: Int
|
rejected: Int
|
||||||
|
|
||||||
|
@ -74,6 +74,76 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|
|||||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "already a participant"
|
assert hd(json_response(res, 200)["errors"])["message"] =~ "already a participant"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "actor_join_event/3 doesn't work if the event already has too much participants", %{
|
||||||
|
conn: conn,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
event = insert(:event, options: %{maximum_attendee_capacity: 2})
|
||||||
|
insert(:participant, event: event, actor: actor, role: :creator)
|
||||||
|
insert(:participant, event: event, role: :participant)
|
||||||
|
insert(:participant, event: event, role: :not_approved)
|
||||||
|
insert(:participant, event: event, role: :rejected)
|
||||||
|
user_participant = insert(:user)
|
||||||
|
actor_participant = insert(:actor, user: user_participant)
|
||||||
|
|
||||||
|
mutation = """
|
||||||
|
mutation {
|
||||||
|
joinEvent(
|
||||||
|
actor_id: #{actor_participant.id},
|
||||||
|
event_id: #{event.id}
|
||||||
|
) {
|
||||||
|
role,
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
event {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user_participant)
|
||||||
|
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||||
|
|
||||||
|
assert json_response(res, 200)["errors"] == nil
|
||||||
|
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "PARTICIPANT"
|
||||||
|
assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
|
||||||
|
|
||||||
|
assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] ==
|
||||||
|
to_string(actor_participant.id)
|
||||||
|
|
||||||
|
user_participant_2 = insert(:user)
|
||||||
|
actor_participant_2 = insert(:actor, user: user_participant_2)
|
||||||
|
|
||||||
|
mutation = """
|
||||||
|
mutation {
|
||||||
|
joinEvent(
|
||||||
|
actor_id: #{actor_participant_2.id},
|
||||||
|
event_id: #{event.id}
|
||||||
|
) {
|
||||||
|
role,
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
event {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> auth_conn(user_participant_2)
|
||||||
|
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
|
||||||
|
|
||||||
|
assert hd(json_response(res, 200)["errors"])["message"] ==
|
||||||
|
"The event has already reached it's maximum capacity"
|
||||||
|
end
|
||||||
|
|
||||||
test "actor_join_event/3 should check the actor is owned by the user", %{
|
test "actor_join_event/3 should check the actor is owned by the user", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
user: user
|
user: user
|
||||||
|
@ -124,7 +124,8 @@ defmodule Mobilizon.Factory do
|
|||||||
url: Routes.page_url(Endpoint, :event, uuid),
|
url: Routes.page_url(Endpoint, :event, uuid),
|
||||||
picture: insert(:picture),
|
picture: insert(:picture),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
join_options: :free
|
join_options: :free,
|
||||||
|
options: %{}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user