@@ -35,11 +40,17 @@
diff --git a/js/src/views/Account/Register.vue b/js/src/views/Account/Register.vue
index b872b629..3c125d9b 100644
--- a/js/src/views/Account/Register.vue
+++ b/js/src/views/Account/Register.vue
@@ -93,6 +93,8 @@ export default class Register extends Vue {
avatarUrl: '',
bannerUrl: '',
domain: null,
+ feedTokens: [],
+ goingToEvents: [],
};
errors: object = {};
validationSent: boolean = false;
diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue
index c3acf532..436b378c 100644
--- a/js/src/views/Event/Event.vue
+++ b/js/src/views/Event/Event.vue
@@ -107,6 +107,7 @@ import { IEvent, IParticipant } from '@/types/event.model';
import { IPerson } from '@/types/actor.model';
import { RouteName } from '@/router';
import 'vue-simple-markdown/dist/vue-simple-markdown.css';
+import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
@Component({
apollo: {
@@ -199,19 +200,15 @@ export default class Event extends Vue {
}
}
- downloadIcsEvent() {
- // FIXME: remove eventFetch
- // eventFetch(`/events/${this.uuid}/ics`, this.$store, { responseType: 'arraybuffer' })
- // .then(response => response.text())
- // .then((response) => {
- // const blob = new Blob([ response ], { type: 'text/calendar' });
- // const link = document.createElement('a');
- // link.href = window.URL.createObjectURL(blob);
- // link.download = `${this.event.title}.ics`;
- // document.body.appendChild(link);
- // link.click();
- // document.body.removeChild(link);
- // });
+ async downloadIcsEvent() {
+ const data = await (await fetch(`${GRAPHQL_API_ENDPOINT}/events/${this.uuid}/export/ics`)).text();
+ const blob = new Blob([data], { type: 'text/calendar' });
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = `${this.event.title}.ics`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
}
actorIsParticipant() {
diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue
index 29cb47aa..b755a481 100644
--- a/js/src/views/Group/Group.vue
+++ b/js/src/views/Group/Group.vue
@@ -33,7 +33,7 @@
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index 25a6fb91..e3aa5848 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -18,6 +18,50 @@
>Welcome back %{username}
+
+ Events you're going at
+
+
+
+
+
+ You have one event today.
+
+
+ You have one event tomorrow.
+
+
+ You have one event in %{ days } days.
+
+
+
+
+
+
+ You're not going to any event yet
+
+
Events nearby you
@@ -41,11 +85,13 @@ import ngeohash from 'ngeohash';
import { FETCH_EVENTS } from '@/graphql/event';
import { Component, Vue } from 'vue-property-decorator';
import EventCard from '@/components/Event/EventCard.vue';
-import { LOGGED_PERSON } from '@/graphql/actor';
-import { IPerson } from '@/types/actor.model';
+import { LOGGED_PERSON_WITH_GOING_TO_EVENTS } from '@/graphql/actor';
+import { IPerson, Person } from '@/types/actor.model';
import { ICurrentUser } from '@/types/current-user.model';
import { CURRENT_USER_CLIENT } from '@/graphql/user';
import { RouteName } from '@/router';
+import { IEvent } from '@/types/event.model';
+import DateComponent from '@/components/Event/Date.vue';
@Component({
apollo: {
@@ -54,13 +100,14 @@ import { RouteName } from '@/router';
fetchPolicy: 'no-cache', // Debug me: https://github.com/apollographql/apollo-client/issues/3030
},
loggedPerson: {
- query: LOGGED_PERSON,
+ query: LOGGED_PERSON_WITH_GOING_TO_EVENTS,
},
currentUser: {
query: CURRENT_USER_CLIENT,
},
},
components: {
+ DateComponent,
EventCard,
},
})
@@ -69,8 +116,7 @@ export default class Home extends Vue {
locations = [];
city = { name: null };
country = { name: null };
- // FIXME: correctly parse local storage
- loggedPerson!: IPerson;
+ loggedPerson: IPerson = new Person();
currentUser!: ICurrentUser;
get displayed_name() {
@@ -79,13 +125,47 @@ export default class Home extends Vue {
: this.loggedPerson.name;
}
- fetchLocations() {
- // FIXME: remove eventFetch
- // eventFetch('/locations', this.$store)
- // .then(response => (response.json()))
- // .then((response) => {
- // this.locations = response;
- // });
+ isToday(date: string) {
+ return (new Date(date)).toDateString() === (new Date()).toDateString();
+ }
+
+ isTomorrow(date: string) :boolean {
+ return this.isInDays(date, 1);
+ }
+
+ isInDays(date: string, nbDays: number) :boolean {
+ return this.calculateDiffDays(date) === nbDays;
+ }
+
+ isBefore(date: string, nbDays: number) :boolean {
+ return this.calculateDiffDays(date) > nbDays;
+ }
+
+ // FIXME: Use me
+ isInLessThanSevenDays(date: string): boolean {
+ return this.isInDays(date, 7);
+ }
+
+ calculateDiffDays(date: string): number {
+ const dateObj = new Date(date);
+ return Math.ceil((dateObj.getTime() - (new Date()).getTime()) / 1000 / 60 / 60 / 24);
+ }
+
+ get goingToEvents(): Map {
+ const res = this.$data.loggedPerson.goingToEvents.filter((event) => {
+ return event.beginsOn != null && this.isBefore(event.beginsOn, 0)
+ });
+ res.sort(
+ (a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn),
+ );
+ const groups = res.reduce((acc: Map, event: IEvent) => {
+ const day = (new Date(event.beginsOn)).toDateString();
+ const events: IEvent[] = acc.get(day) || [];
+ events.push(event);
+ acc.set(day, events);
+ return acc;
+ }, new Map());
+ return groups;
}
geoLocalize() {
@@ -144,6 +224,6 @@ export default class Home extends Vue {
}
.events-nearby {
- margin-bottom: 25px;
+ margin: 25px auto;
}
diff --git a/lib/mobilizon_web/resolvers/person.ex b/lib/mobilizon_web/resolvers/person.ex
index 43725edd..ce1814cb 100644
--- a/lib/mobilizon_web/resolvers/person.ex
+++ b/lib/mobilizon_web/resolvers/person.ex
@@ -6,6 +6,7 @@ defmodule MobilizonWeb.Resolvers.Person do
alias Mobilizon.Actors.Actor
alias Mobilizon.Users.User
alias Mobilizon.Users
+ alias Mobilizon.Events
alias Mobilizon.Service.ActivityPub
@doc """
@@ -83,4 +84,34 @@ defmodule MobilizonWeb.Resolvers.Person do
{:error, e}
end
end
+
+ @doc """
+ Returns the list of events this person is going to
+ """
+ def person_going_to_events(%Actor{id: actor_id}, _args, %{
+ context: %{current_user: user}
+ }) do
+ with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
+ events <- Events.list_event_participations_for_actor(actor) do
+ {:ok, events}
+ else
+ {:is_owned, false} ->
+ {:error, "Actor id is not owned by authenticated user"}
+ end
+ end
+
+ @doc """
+ Returns the list of events this person is going to
+ """
+ def person_going_to_events(_parent, %{}, %{
+ context: %{current_user: user}
+ }) do
+ with %Actor{} = actor <- Users.get_actor_for_user(user),
+ events <- Events.list_event_participations_for_actor(actor) do
+ {:ok, events}
+ else
+ {:is_owned, false} ->
+ {:error, "Actor id is not owned by authenticated user"}
+ end
+ end
end
diff --git a/lib/mobilizon_web/schema/actors/person.ex b/lib/mobilizon_web/schema/actors/person.ex
index 8fe4c8dd..c542009a 100644
--- a/lib/mobilizon_web/schema/actors/person.ex
+++ b/lib/mobilizon_web/schema/actors/person.ex
@@ -53,6 +53,11 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
resolve: dataloader(Events),
description: "A list of the events this actor has organized"
)
+
+ @desc "The list of events this person goes to"
+ field :going_to_events, list_of(:event) do
+ resolve(&Resolvers.Person.person_going_to_events/3)
+ end
end
object :person_queries do
diff --git a/lib/service/export/feed.ex b/lib/service/export/feed.ex
index ea5d8931..398e6740 100644
--- a/lib/service/export/feed.ex
+++ b/lib/service/export/feed.ex
@@ -88,7 +88,9 @@ defmodule Mobilizon.Service.Export.Feed do
# Create an entry for the Atom feed
@spec get_entry(Event.t()) :: any()
defp get_entry(%Event{} = event) do
- with {:ok, html, []} <- Earmark.as_html(event.description) do
+ description = event.description || ""
+
+ with {:ok, html, []} <- Earmark.as_html(description) do
entry =
Entry.new(event.url, event.publish_at || event.inserted_at, event.title)
|> Entry.link(event.url, rel: "alternate", type: "text/html")
diff --git a/test/mobilizon_web/resolvers/person_resolver_test.exs b/test/mobilizon_web/resolvers/person_resolver_test.exs
index d5dbf64c..698e05f7 100644
--- a/test/mobilizon_web/resolvers/person_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/person_resolver_test.exs
@@ -137,4 +137,86 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
MapSet.new([actor.preferred_username, "new_identity"])
end
end
+
+ test "get_current_person/3 can return the events the person is going to", context do
+ user = insert(:user)
+ actor = insert(:actor, user: user)
+
+ query = """
+ {
+ loggedPerson {
+ goingToEvents {
+ uuid,
+ title
+ }
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> auth_conn(user)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
+
+ assert json_response(res, 200)["data"]["loggedPerson"]["goingToEvents"] == []
+
+ event = insert(:event, %{organizer_actor: actor})
+ insert(:participant, %{actor: actor, event: event})
+
+ res =
+ context.conn
+ |> auth_conn(user)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person"))
+
+ assert json_response(res, 200)["data"]["loggedPerson"]["goingToEvents"] == [
+ %{"title" => event.title, "uuid" => event.uuid}
+ ]
+ end
+
+ test "find_person/3 can return the events an identity is going to if it's the same actor",
+ context do
+ user = insert(:user)
+ actor = insert(:actor, user: user)
+ insert(:actor, user: user)
+ actor_from_other_user = insert(:actor)
+
+ query = """
+ {
+ person(preferredUsername: "#{actor.preferred_username}") {
+ goingToEvents {
+ uuid,
+ title
+ }
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> auth_conn(user)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
+
+ assert json_response(res, 200)["data"]["person"]["goingToEvents"] == []
+
+ query = """
+ {
+ person(preferredUsername: "#{actor_from_other_user.preferred_username}") {
+ goingToEvents {
+ uuid,
+ title
+ }
+ }
+ }
+ """
+
+ res =
+ context.conn
+ |> auth_conn(user)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
+
+ assert json_response(res, 200)["data"]["person"]["goingToEvents"] == nil
+
+ assert hd(json_response(res, 200)["errors"])["message"] ==
+ "Actor id is not owned by authenticated user"
+ end
end