Merge branch 'feature/related_events' into 'master'
Feature/related events See merge request framasoft/mobilizon!113
This commit is contained in:
commit
10bfc17306
|
@ -43,6 +43,7 @@ export const FETCH_EVENT = gql`
|
||||||
organizerActor {
|
organizerActor {
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
preferredUsername,
|
preferredUsername,
|
||||||
|
domain,
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
# attributedTo {
|
# attributedTo {
|
||||||
|
@ -56,6 +57,20 @@ export const FETCH_EVENT = gql`
|
||||||
tags {
|
tags {
|
||||||
slug,
|
slug,
|
||||||
title
|
title
|
||||||
|
},
|
||||||
|
relatedEvents {
|
||||||
|
uuid,
|
||||||
|
title,
|
||||||
|
beginsOn,
|
||||||
|
physicalAddress {
|
||||||
|
description
|
||||||
|
},
|
||||||
|
organizerActor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
domain,
|
||||||
|
name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: mobilizon 0.1.0\n"
|
"Project-Id-Version: mobilizon 0.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-04-10 16:31+0200\n"
|
"POT-Creation-Date: 2019-04-12 16:47+0200\n"
|
||||||
"PO-Revision-Date: 2019-04-08 20:58+0200\n"
|
"PO-Revision-Date: 2019-04-08 20:58+0200\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
|
@ -29,7 +29,7 @@ msgstr "A validation email was sent to %{email}"
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "About"
|
msgstr "About"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:138
|
#: src/views/Event/Event.vue:137
|
||||||
msgid "About this event"
|
msgid "About this event"
|
||||||
msgstr "About this event"
|
msgstr "About this event"
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ msgstr "About this instance"
|
||||||
msgid "Add a new profile"
|
msgid "Add a new profile"
|
||||||
msgstr "Add a new profile"
|
msgstr "Add a new profile"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:217
|
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:216
|
||||||
msgid "Add to my calendar"
|
msgid "Add to my calendar"
|
||||||
msgstr "Add to my calendar"
|
msgstr "Add to my calendar"
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ msgstr "Are you going to this event?"
|
||||||
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
msgstr "Before you can login, you need to click on the link inside it to validate your account"
|
msgstr "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:101
|
#: src/views/Event/Event.vue:100
|
||||||
msgid "By %{ name }"
|
msgid "By %{ name }"
|
||||||
msgstr "By %{ name }"
|
msgstr "By %{ name }"
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ msgstr "Create your communities and your events"
|
||||||
msgid "Current"
|
msgid "Current"
|
||||||
msgstr "Current"
|
msgstr "Current"
|
||||||
|
|
||||||
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
|
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:63
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Delete"
|
msgstr "Delete"
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ msgstr "Delete"
|
||||||
msgid "Didn't receive the instructions ?"
|
msgid "Didn't receive the instructions ?"
|
||||||
msgstr "Didn't receive the instructions ?"
|
msgstr "Didn't receive the instructions ?"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:59
|
#: src/views/Event/Event.vue:58
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Edit"
|
msgstr "Edit"
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ msgstr "Members"
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "My account"
|
msgstr "My account"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:70
|
#: src/views/Event/Event.vue:69
|
||||||
msgid "No address defined"
|
msgid "No address defined"
|
||||||
msgstr "No address defined"
|
msgstr "No address defined"
|
||||||
|
|
||||||
|
@ -304,7 +304,7 @@ msgstr "Reset my password"
|
||||||
msgid "RSS/Atom Feed"
|
msgid "RSS/Atom Feed"
|
||||||
msgstr "RSS/Atom Feed"
|
msgstr "RSS/Atom Feed"
|
||||||
|
|
||||||
#: src/views/PageNotFound.vue:18 src/components/SearchField.vue:19
|
#: src/views/PageNotFound.vue:19 src/components/SearchField.vue:19
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "Search"
|
msgstr "Search"
|
||||||
|
|
||||||
|
@ -320,11 +320,11 @@ msgstr "Send confirmation email again"
|
||||||
msgid "Send email to reset my password"
|
msgid "Send email to reset my password"
|
||||||
msgstr "Send email to reset my password"
|
msgstr "Send email to reset my password"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:206
|
#: src/views/Event/Event.vue:205
|
||||||
msgid "Share this event"
|
msgid "Share this event"
|
||||||
msgstr "Share this event"
|
msgstr "Share this event"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:79
|
#: src/views/Event/Event.vue:78
|
||||||
msgid "Show map"
|
msgid "Show map"
|
||||||
msgstr "Show map"
|
msgstr "Show map"
|
||||||
|
|
||||||
|
@ -340,7 +340,7 @@ msgstr "The %{ date } at %{ time }"
|
||||||
msgid "The %{ date } from %{ startTime } to %{ endTime }"
|
msgid "The %{ date } from %{ startTime } to %{ endTime }"
|
||||||
msgstr "The %{ date } from %{ startTime } to %{ endTime }"
|
msgstr "The %{ date } from %{ startTime } to %{ endTime }"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:141
|
#: src/views/Event/Event.vue:140
|
||||||
msgid "The event organizer didn't add any description."
|
msgid "The event organizer didn't add any description."
|
||||||
msgstr "The event organizer didn't add any description."
|
msgstr "The event organizer didn't add any description."
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ msgstr "The event organizer didn't add any description."
|
||||||
msgid "The page you're looking for doesn't exist."
|
msgid "The page you're looking for doesn't exist."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:224
|
#: src/views/Event/Event.vue:223
|
||||||
msgid "These events may interest you"
|
msgid "These events may interest you"
|
||||||
msgstr "These events may interest you"
|
msgstr "These events may interest you"
|
||||||
|
|
||||||
|
@ -434,6 +434,6 @@ msgstr "Your local administrator resumed it's policy:"
|
||||||
msgid "World map"
|
msgid "World map"
|
||||||
msgstr "World map"
|
msgstr "World map"
|
||||||
|
|
||||||
#: src/views/PageNotFound.vue:40
|
#: src/views/PageNotFound.vue:42
|
||||||
msgid "Search events, groups, etc."
|
msgid "Search events, groups, etc."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: mobilizon 0.1.0\n"
|
"Project-Id-Version: mobilizon 0.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-04-10 16:31+0200\n"
|
"POT-Creation-Date: 2019-04-12 16:47+0200\n"
|
||||||
"PO-Revision-Date: 2019-04-10 16:33+0200\n"
|
"PO-Revision-Date: 2019-04-12 16:45+0200\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
|
@ -30,7 +30,7 @@ msgstr "Un email de validation a été envoyé à %{email}"
|
||||||
msgid "About"
|
msgid "About"
|
||||||
msgstr "À propos"
|
msgstr "À propos"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:138
|
#: src/views/Event/Event.vue:137
|
||||||
msgid "About this event"
|
msgid "About this event"
|
||||||
msgstr "À propos de cet événement"
|
msgstr "À propos de cet événement"
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ msgstr "À propos de cette instance"
|
||||||
msgid "Add a new profile"
|
msgid "Add a new profile"
|
||||||
msgstr "Ajouter un nouveau profil"
|
msgstr "Ajouter un nouveau profil"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:217
|
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:216
|
||||||
msgid "Add to my calendar"
|
msgid "Add to my calendar"
|
||||||
msgstr "Ajouter à mon agenda"
|
msgstr "Ajouter à mon agenda"
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ msgstr "Allez-vous à cet événement ?"
|
||||||
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
msgstr "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte"
|
msgstr "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:101
|
#: src/views/Event/Event.vue:100
|
||||||
msgid "By %{ name }"
|
msgid "By %{ name }"
|
||||||
msgstr "Par %{name}"
|
msgstr "Par %{name}"
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ msgstr "Créer vos communautés et vos événements"
|
||||||
msgid "Current"
|
msgid "Current"
|
||||||
msgstr "Actuel"
|
msgstr "Actuel"
|
||||||
|
|
||||||
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
|
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:63
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ msgstr "Supprimer"
|
||||||
msgid "Didn't receive the instructions ?"
|
msgid "Didn't receive the instructions ?"
|
||||||
msgstr "Vous n'avez pas reçu les instructions ?"
|
msgstr "Vous n'avez pas reçu les instructions ?"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:59
|
#: src/views/Event/Event.vue:58
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr "Éditer"
|
msgstr "Éditer"
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ msgstr "Membres"
|
||||||
msgid "My account"
|
msgid "My account"
|
||||||
msgstr "Mon compte"
|
msgstr "Mon compte"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:70
|
#: src/views/Event/Event.vue:69
|
||||||
msgid "No address defined"
|
msgid "No address defined"
|
||||||
msgstr "Aucune adresse définie"
|
msgstr "Aucune adresse définie"
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ msgstr "Réinitialiser mon mot de passe"
|
||||||
msgid "RSS/Atom Feed"
|
msgid "RSS/Atom Feed"
|
||||||
msgstr "Flux RSS/Atom"
|
msgstr "Flux RSS/Atom"
|
||||||
|
|
||||||
#: src/views/PageNotFound.vue:18 src/components/SearchField.vue:19
|
#: src/views/PageNotFound.vue:19 src/components/SearchField.vue:19
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "Rechercher"
|
msgstr "Rechercher"
|
||||||
|
|
||||||
|
@ -321,11 +321,11 @@ msgstr "Envoyer l'email de confirmation à nouveau"
|
||||||
msgid "Send email to reset my password"
|
msgid "Send email to reset my password"
|
||||||
msgstr "Envoyer un email pour réinitialiser mon mot de passe"
|
msgstr "Envoyer un email pour réinitialiser mon mot de passe"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:206
|
#: src/views/Event/Event.vue:205
|
||||||
msgid "Share this event"
|
msgid "Share this event"
|
||||||
msgstr "Partager cet événement"
|
msgstr "Partager l'événement"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:79
|
#: src/views/Event/Event.vue:78
|
||||||
msgid "Show map"
|
msgid "Show map"
|
||||||
msgstr "Afficher la carte"
|
msgstr "Afficher la carte"
|
||||||
|
|
||||||
|
@ -341,7 +341,7 @@ msgstr "Le %{ date } à %{ time }"
|
||||||
msgid "The %{ date } from %{ startTime } to %{ endTime }"
|
msgid "The %{ date } from %{ startTime } to %{ endTime }"
|
||||||
msgstr "Le %{ date } de %{ startTime } à %{ endTime }"
|
msgstr "Le %{ date } de %{ startTime } à %{ endTime }"
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:141
|
#: src/views/Event/Event.vue:140
|
||||||
msgid "The event organizer didn't add any description."
|
msgid "The event organizer didn't add any description."
|
||||||
msgstr "L'organisateur de l'événement n'a pas ajouté de description."
|
msgstr "L'organisateur de l'événement n'a pas ajouté de description."
|
||||||
|
|
||||||
|
@ -349,7 +349,7 @@ msgstr "L'organisateur de l'événement n'a pas ajouté de description."
|
||||||
msgid "The page you're looking for doesn't exist."
|
msgid "The page you're looking for doesn't exist."
|
||||||
msgstr "La page que vous recherchez n'existe pas."
|
msgstr "La page que vous recherchez n'existe pas."
|
||||||
|
|
||||||
#: src/views/Event/Event.vue:224
|
#: src/views/Event/Event.vue:223
|
||||||
msgid "These events may interest you"
|
msgid "These events may interest you"
|
||||||
msgstr "Ces événements peuvent vous intéresser"
|
msgstr "Ces événements peuvent vous intéresser"
|
||||||
|
|
||||||
|
@ -435,6 +435,6 @@ msgstr "Votre administrateur local a résumé sa politique ainsi :"
|
||||||
msgid "World map"
|
msgid "World map"
|
||||||
msgstr "Carte mondiale"
|
msgstr "Carte mondiale"
|
||||||
|
|
||||||
#: src/views/PageNotFound.vue:40
|
#: src/views/PageNotFound.vue:42
|
||||||
msgid "Search events, groups, etc."
|
msgid "Search events, groups, etc."
|
||||||
msgstr "Rechercher des événements, des groupes, etc."
|
msgstr "Rechercher des événements, des groupes, etc."
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -69,6 +69,8 @@ export interface IEvent {
|
||||||
attributedTo: IActor;
|
attributedTo: IActor;
|
||||||
participants: IParticipant[];
|
participants: IParticipant[];
|
||||||
|
|
||||||
|
relatedEvents: IEvent[];
|
||||||
|
|
||||||
onlineAddress?: string;
|
onlineAddress?: string;
|
||||||
phoneAddress?: string;
|
phoneAddress?: string;
|
||||||
physicalAddress?: IAddress;
|
physicalAddress?: IAddress;
|
||||||
|
@ -94,6 +96,7 @@ export class EventModel implements IEvent {
|
||||||
visibility: EventVisibility = EventVisibility.PUBLIC;
|
visibility: EventVisibility = EventVisibility.PUBLIC;
|
||||||
attributedTo: IActor = new Actor();
|
attributedTo: IActor = new Actor();
|
||||||
organizerActor: IActor = new Actor();
|
organizerActor: IActor = new Actor();
|
||||||
|
relatedEvents: IEvent[] = [];
|
||||||
onlineAddress: string = '';
|
onlineAddress: string = '';
|
||||||
phoneAddress: string = '';
|
phoneAddress: string = '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,6 +204,7 @@
|
||||||
<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"><translate>Share this event</translate></h3>
|
<h3 class="title"><translate>Share this event</translate></h3>
|
||||||
|
<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>
|
||||||
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
|
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
|
||||||
|
@ -211,6 +212,7 @@
|
||||||
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
|
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
|
||||||
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
|
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="column is-half has-text-right add-to-calendar">
|
<div class="column is-half has-text-right add-to-calendar">
|
||||||
<h3 @click="downloadIcsEvent()">
|
<h3 @click="downloadIcsEvent()">
|
||||||
|
@ -223,8 +225,8 @@
|
||||||
<section class="more-events container">
|
<section class="more-events container">
|
||||||
<h3 class="title has-text-centered"><translate>These events may interest you</translate></h3>
|
<h3 class="title has-text-centered"><translate>These events may interest you</translate></h3>
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" v-for="index in 3" :key="index">
|
<div class="column" v-for="relatedEvent in event.relatedEvents" :key="relatedEvent.uuid">
|
||||||
<EventCard :event="event" />
|
<EventCard :event="relatedEvent" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -45,6 +45,35 @@ defmodule Mobilizon.Events do
|
||||||
{:ok, events, count_events}
|
{:ok, events, count_events}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get an actor's eventual upcoming public event
|
||||||
|
"""
|
||||||
|
@spec get_actor_upcoming_public_event(Actor.t(), String.t()) :: Event.t() | nil
|
||||||
|
def get_actor_upcoming_public_event(%Actor{id: actor_id} = _actor, not_event_uuid \\ nil) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
e in Event,
|
||||||
|
where:
|
||||||
|
e.organizer_actor_id == ^actor_id and e.visibility in [^:public, ^:unlisted] and
|
||||||
|
e.begins_on > ^DateTime.utc_now(),
|
||||||
|
order_by: [asc: :begins_on],
|
||||||
|
limit: 1,
|
||||||
|
preload: [
|
||||||
|
:organizer_actor,
|
||||||
|
:tags,
|
||||||
|
:participants,
|
||||||
|
:physical_address
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
query =
|
||||||
|
if is_nil(not_event_uuid),
|
||||||
|
do: query,
|
||||||
|
else: from(q in query, where: q.uuid != ^not_event_uuid)
|
||||||
|
|
||||||
|
Repo.one(query)
|
||||||
|
end
|
||||||
|
|
||||||
def count_local_events do
|
def count_local_events do
|
||||||
Repo.one(
|
Repo.one(
|
||||||
from(
|
from(
|
||||||
|
@ -231,18 +260,42 @@ defmodule Mobilizon.Events do
|
||||||
[%Event{}, ...]
|
[%Event{}, ...]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def list_events(page \\ nil, limit \\ nil) do
|
@spec list_events(integer(), integer(), atom(), atom()) :: list(Event.t())
|
||||||
|
def list_events(
|
||||||
|
page \\ nil,
|
||||||
|
limit \\ nil,
|
||||||
|
sort \\ :begins_on,
|
||||||
|
direction \\ :asc,
|
||||||
|
unlisted \\ false,
|
||||||
|
future \\ true
|
||||||
|
) do
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
e in Event,
|
e in Event,
|
||||||
where: e.visibility == ^:public,
|
|
||||||
preload: [:organizer_actor, :participants]
|
preload: [:organizer_actor, :participants]
|
||||||
)
|
)
|
||||||
|> paginate(page, limit)
|
|> paginate(page, limit)
|
||||||
|
|> sort(sort, direction)
|
||||||
|
|> restrict_future_events(future)
|
||||||
|
|> allow_unlisted(unlisted)
|
||||||
|
|
||||||
Repo.all(query)
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Make sure we only show future events
|
||||||
|
@spec restrict_future_events(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
|
||||||
|
defp restrict_future_events(query, true),
|
||||||
|
do: from(q in query, where: q.begins_on > ^DateTime.utc_now())
|
||||||
|
|
||||||
|
defp restrict_future_events(query, false), do: query
|
||||||
|
|
||||||
|
# Make sure unlisted events don't show up where they're not allowed
|
||||||
|
@spec allow_unlisted(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
|
||||||
|
defp allow_unlisted(query, true),
|
||||||
|
do: from(q in query, where: q.visibility in [^:public, ^:unlisted])
|
||||||
|
|
||||||
|
defp allow_unlisted(query, false), do: from(q in query, where: q.visibility == ^:public)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Find events by name
|
Find events by name
|
||||||
"""
|
"""
|
||||||
|
@ -276,6 +329,28 @@ defmodule Mobilizon.Events do
|
||||||
%{total: Task.await(total), elements: Task.await(elements)}
|
%{total: Task.await(total), elements: Task.await(elements)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Find events with the same tags
|
||||||
|
"""
|
||||||
|
@spec find_similar_events_by_common_tags(list(), integer()) :: {:ok, list(Event.t())}
|
||||||
|
def find_similar_events_by_common_tags(tags, limit \\ 2) do
|
||||||
|
tags_ids = Enum.map(tags, & &1.id)
|
||||||
|
|
||||||
|
query =
|
||||||
|
from(e in Event,
|
||||||
|
distinct: e.uuid,
|
||||||
|
join: te in "events_tags",
|
||||||
|
on: e.id == te.event_id,
|
||||||
|
where: e.begins_on > ^DateTime.utc_now(),
|
||||||
|
where: e.visibility in [^:public, ^:unlisted],
|
||||||
|
where: te.tag_id in ^tags_ids,
|
||||||
|
order_by: [asc: e.begins_on],
|
||||||
|
limit: ^limit
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a event.
|
Creates a event.
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,14 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
Handles the event-related GraphQL calls
|
Handles the event-related GraphQL calls
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Activity
|
alias Mobilizon.Activity
|
||||||
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Participant}
|
alias Mobilizon.Events.{Event, Participant}
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
# We limit the max number of events that can be retrieved
|
# We limit the max number of events that can be retrieved
|
||||||
@event_max_limit 100
|
@event_max_limit 100
|
||||||
|
@number_of_related_events 3
|
||||||
|
|
||||||
def list_events(_parent, %{page: page, limit: limit}, _resolution)
|
def list_events(_parent, %{page: page, limit: limit}, _resolution)
|
||||||
when limit < @event_max_limit do
|
when limit < @event_max_limit do
|
||||||
|
@ -43,6 +45,52 @@ defmodule MobilizonWeb.Resolvers.Event do
|
||||||
{:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)}
|
{:ok, Mobilizon.Events.list_participants_for_event(uuid, 1, 10)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
List related events
|
||||||
|
"""
|
||||||
|
def list_related_events(
|
||||||
|
%Event{tags: tags, organizer_actor: organizer_actor, uuid: uuid},
|
||||||
|
_args,
|
||||||
|
_resolution
|
||||||
|
) do
|
||||||
|
# We get the organizer's next public event
|
||||||
|
events =
|
||||||
|
[Events.get_actor_upcoming_public_event(organizer_actor, uuid)] |> Enum.filter(&is_map/1)
|
||||||
|
|
||||||
|
# We find similar events with the same tags
|
||||||
|
# uniq_by : It's possible event_from_same_actor is inside events_from_tags
|
||||||
|
events =
|
||||||
|
(events ++
|
||||||
|
Events.find_similar_events_by_common_tags(
|
||||||
|
tags,
|
||||||
|
@number_of_related_events
|
||||||
|
))
|
||||||
|
|> uniq_events()
|
||||||
|
|
||||||
|
# TODO: We should use tag_relations to find more appropriate events
|
||||||
|
|
||||||
|
# We've considered all recommended events, so we fetch the latest events
|
||||||
|
events =
|
||||||
|
if @number_of_related_events - length(events) > 0 do
|
||||||
|
(events ++
|
||||||
|
Events.list_events(1, @number_of_related_events, :begins_on, :asc, true, true))
|
||||||
|
|> uniq_events()
|
||||||
|
else
|
||||||
|
events
|
||||||
|
end
|
||||||
|
|
||||||
|
events =
|
||||||
|
events
|
||||||
|
# We remove the same event from the results
|
||||||
|
|> Enum.filter(fn event -> event.uuid != uuid end)
|
||||||
|
# We return only @number_of_related_events right now
|
||||||
|
|> Enum.take(@number_of_related_events)
|
||||||
|
|
||||||
|
{:ok, events}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp uniq_events(events), do: Enum.uniq_by(events, fn event -> event.uuid end)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Join an event for an actor
|
Join an event for an actor
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -56,6 +56,11 @@ defmodule MobilizonWeb.Schema.EventType do
|
||||||
description: "The event's participants"
|
description: "The event's participants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
field(:related_events, list_of(:event),
|
||||||
|
resolve: &MobilizonWeb.Resolvers.Event.list_related_events/3,
|
||||||
|
description: "Events related to this one"
|
||||||
|
)
|
||||||
|
|
||||||
# field(:tracks, list_of(:track))
|
# field(:tracks, list_of(:track))
|
||||||
# field(:sessions, list_of(:session))
|
# field(:sessions, list_of(:session))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
defmodule Mobilizon.Repo.Migrations.EventEventTagOnDelete do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
drop(constraint(:events_tags, "events_tags_event_id_fkey"))
|
||||||
|
drop(constraint(:events_tags, "events_tags_tag_id_fkey"))
|
||||||
|
|
||||||
|
alter table(:events_tags) do
|
||||||
|
modify(:event_id, references(:events, on_delete: :delete_all))
|
||||||
|
modify(:tag_id, references(:tags, on_delete: :delete_all))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
drop(constraint(:events_tags, "events_tags_event_id_fkey"))
|
||||||
|
drop(constraint(:events_tags, "events_tags_tag_id_fkey"))
|
||||||
|
|
||||||
|
alter table(:events_tags) do
|
||||||
|
modify(:event_id, references(:events))
|
||||||
|
modify(:tag_id, references(:tags))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,7 +20,7 @@ defmodule Mobilizon.EventsTest do
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor, visibility: :public)
|
||||||
{:ok, actor: actor, event: event}
|
{:ok, actor: actor, event: event}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -322,5 +322,59 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
|
|
||||||
assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete"
|
assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "list_related_events/3 should give related events", %{
|
||||||
|
conn: conn,
|
||||||
|
actor: actor
|
||||||
|
} do
|
||||||
|
tag1 = insert(:tag, title: "Elixir", slug: "elixir")
|
||||||
|
tag2 = insert(:tag, title: "PostgreSQL", slug: "postgresql")
|
||||||
|
|
||||||
|
event = insert(:event, title: "Initial event", organizer_actor: actor, tags: [tag1, tag2])
|
||||||
|
|
||||||
|
event2 =
|
||||||
|
insert(:event,
|
||||||
|
title: "Event from same actor",
|
||||||
|
organizer_actor: actor,
|
||||||
|
visibility: :public,
|
||||||
|
begins_on: Timex.shift(DateTime.utc_now(), days: 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
event3 =
|
||||||
|
insert(:event,
|
||||||
|
title: "Event with same tags",
|
||||||
|
tags: [tag1, tag2],
|
||||||
|
visibility: :public,
|
||||||
|
begins_on: Timex.shift(DateTime.utc_now(), days: 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
query = """
|
||||||
|
{
|
||||||
|
event(uuid: "#{event.uuid}") {
|
||||||
|
uuid,
|
||||||
|
title,
|
||||||
|
tags {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
related_events {
|
||||||
|
uuid,
|
||||||
|
title,
|
||||||
|
tags {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
res =
|
||||||
|
conn
|
||||||
|
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
|
||||||
|
|
||||||
|
assert hd(json_response(res, 200)["data"]["event"]["related_events"])["uuid"] == event2.uuid
|
||||||
|
|
||||||
|
assert hd(tl(json_response(res, 200)["data"]["event"]["related_events"]))["uuid"] ==
|
||||||
|
event3.uuid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -95,7 +95,7 @@ defmodule Mobilizon.Factory do
|
||||||
|
|
||||||
def event_factory do
|
def event_factory do
|
||||||
actor = build(:actor)
|
actor = build(:actor)
|
||||||
start = Timex.now()
|
start = Timex.shift(DateTime.utc_now(), hours: 2)
|
||||||
uuid = Ecto.UUID.generate()
|
uuid = Ecto.UUID.generate()
|
||||||
|
|
||||||
%Mobilizon.Events.Event{
|
%Mobilizon.Events.Event{
|
||||||
|
@ -108,6 +108,7 @@ defmodule Mobilizon.Factory do
|
||||||
category: sequence("something"),
|
category: sequence("something"),
|
||||||
physical_address: build(:address),
|
physical_address: build(:address),
|
||||||
visibility: :public,
|
visibility: :public,
|
||||||
|
tags: build_list(3, :tag),
|
||||||
url: "#{actor.url}/#{uuid}",
|
url: "#{actor.url}/#{uuid}",
|
||||||
uuid: uuid
|
uuid: uuid
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue