Merge branch 'bug/fix-edit-event-cache-issues' into 'master'

Bug/fix edit event cache issues

Closes #179 et #166

See merge request framasoft/mobilizon!219
This commit is contained in:
Thomas Citharel 2019-10-06 12:38:07 +02:00
commit 4a83bb61db
15 changed files with 276 additions and 170 deletions

View File

@ -13,19 +13,14 @@ import NavBar from '@/components/NavBar.vue';
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { import {
AUTH_ACCESS_TOKEN, AUTH_ACCESS_TOKEN,
AUTH_USER_ACTOR_ID,
AUTH_USER_EMAIL, AUTH_USER_EMAIL,
AUTH_USER_ID, AUTH_USER_ID,
AUTH_USER_ROLE, AUTH_USER_ROLE,
} from '@/constants'; } from '@/constants';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { ICurrentUser } from '@/types/current-user.model';
import Footer from '@/components/Footer.vue'; import Footer from '@/components/Footer.vue';
import Logo from '@/components/Logo.vue'; import Logo from '@/components/Logo.vue';
import { CURRENT_ACTOR_CLIENT, IDENTITIES, UPDATE_CURRENT_ACTOR_CLIENT } from '@/graphql/actor'; import { initializeCurrentActor } from '@/utils/auth';
import { IPerson } from '@/types/actor';
import { changeIdentity, initializeCurrentActor, saveActorData } from '@/utils/auth';
@Component({ @Component({
apollo: { apollo: {
currentUser: { currentUser: {

View File

@ -119,6 +119,15 @@ export default class NavBar extends Vue {
}); });
if (data) { if (data) {
this.identities = data.identities.map(identity => new Person(identity)); this.identities = data.identities.map(identity => new Person(identity));
// If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow
if (this.identities.length === 0) {
await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: { email: this.currentUser.email, userAlreadyActivated: 'true' },
});
}
} }
} }

View File

@ -1,8 +1,8 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
export const FETCH_PERSON = gql` export const FETCH_PERSON = gql`
query($name:String!) { query($username: String!) {
person(preferredUsername: $name) { fetchPerson(preferredUsername: $username) {
id, id,
url, url,
name, name,
@ -29,6 +29,35 @@ query($name:String!) {
} }
`; `;
export const GET_PERSON = gql`
query($actorId: ID!) {
person(id: $actorId) {
id,
url,
name,
domain,
summary,
preferredUsername,
suspended,
avatar {
name,
url
},
banner {
url
},
feedTokens {
token
},
organizedEvents {
uuid,
title,
beginsOn
},
}
}
`;
export const LOGGED_PERSON = gql` export const LOGGED_PERSON = gql`
query { query {
loggedPerson { loggedPerson {
@ -172,9 +201,9 @@ mutation CreatePerson($preferredUsername: String!, $name: String!, $summary: Str
`; `;
export const UPDATE_PERSON = gql` export const UPDATE_PERSON = gql`
mutation UpdatePerson($preferredUsername: String!, $name: String, $summary: String, $avatar: PictureInput) { mutation UpdatePerson($id: ID!, $name: String, $summary: String, $avatar: PictureInput) {
updatePerson( updatePerson(
preferredUsername: $preferredUsername, id: $id,
name: $name, name: $name,
summary: $summary, summary: $summary,
avatar: $avatar avatar: $avatar
@ -191,8 +220,8 @@ export const UPDATE_PERSON = gql`
`; `;
export const DELETE_PERSON = gql` export const DELETE_PERSON = gql`
mutation DeletePerson($preferredUsername: String!) { mutation DeletePerson($id: ID!) {
deletePerson(preferredUsername: $preferredUsername) { deletePerson(id: $id) {
preferredUsername, preferredUsername,
} }
} }
@ -209,6 +238,7 @@ mutation ($preferredUsername: String!, $name: String!, $summary: String!, $email
summary: $summary, summary: $summary,
email: $email email: $email
) { ) {
id,
preferredUsername, preferredUsername,
name, name,
summary, summary,

View File

@ -221,6 +221,8 @@ export const CREATE_EVENT = gql`
id, id,
uuid, uuid,
title, title,
url,
local,
description, description,
beginsOn, beginsOn,
endsOn, endsOn,
@ -239,14 +241,25 @@ export const CREATE_EVENT = gql`
physicalAddress { physicalAddress {
${physicalAddressQuery} ${physicalAddressQuery}
}, },
organizerActor {
avatar {
url
},
preferredUsername,
domain,
name,
url,
id,
},
participantStats {
approved,
unapproved
},
tags { tags {
${tagsQuery} ${tagsQuery}
}, },
options { options {
${optionsQuery} ${optionsQuery}
},
organizerActor {
id
} }
} }
} }
@ -397,8 +410,8 @@ export const PARTICIPANTS = gql`
`; `;
export const EVENT_PERSON_PARTICIPATION = gql` export const EVENT_PERSON_PARTICIPATION = gql`
query($name: String!, $eventId: ID!) { query($actorId: ID!, $eventId: ID!) {
person(preferredUsername: $name) { person(id: $actorId) {
id, id,
participations(eventId: $eventId) { participations(eventId: $eventId) {
id, id,

View File

@ -104,7 +104,7 @@ import { CREATE_FEED_TOKEN_ACTOR } from '@/graphql/feed_tokens';
query: FETCH_PERSON, query: FETCH_PERSON,
variables() { variables() {
return { return {
name: this.$route.params.name, username: this.$route.params.name,
}; };
}, },
}, },

View File

@ -69,10 +69,12 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { IPerson } from '@/types/actor'; import { IPerson, Person } from '@/types/actor';
import { REGISTER_PERSON } from '@/graphql/actor'; import { IDENTITIES, REGISTER_PERSON } from '@/graphql/actor';
import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint'; import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { changeIdentity } from '@/utils/auth';
import { ICurrentUser } from '@/types/current-user.model';
@Component @Component
export default class Register extends Vue { export default class Register extends Vue {
@ -81,35 +83,42 @@ export default class Register extends Vue {
host?: string = MOBILIZON_INSTANCE_HOST; host?: string = MOBILIZON_INSTANCE_HOST;
person: IPerson = { person: IPerson = new Person();
preferredUsername: '',
name: '',
summary: '',
url: '',
suspended: false,
avatar: null,
banner: null,
domain: null,
feedTokens: [],
goingToEvents: [],
participations: [],
};
errors: object = {}; errors: object = {};
validationSent: boolean = false; validationSent: boolean = false;
sendingValidation: boolean = false; sendingValidation: boolean = false;
async mounted() {
// Make sure no one goes to this page if we don't want to
if (!this.email) {
await this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
}
}
async submit() { async submit() {
try { try {
this.sendingValidation = true; this.sendingValidation = true;
this.errors = {}; this.errors = {};
await this.$apollo.mutate({ const { data } = await this.$apollo.mutate<{ registerPerson: IPerson }>({
mutation: REGISTER_PERSON, mutation: REGISTER_PERSON,
variables: Object.assign({ email: this.email }, this.person), variables: Object.assign({ email: this.email }, this.person),
update: (store, { data }) => {
const identitiesData = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
if (identitiesData && data) {
identitiesData.identities.push(data.registerPerson);
store.writeQuery({ query: IDENTITIES, data: identitiesData });
}
},
}); });
if (data) {
this.validationSent = true; this.validationSent = true;
if (this.userAlreadyActivated) { if (this.userAlreadyActivated) {
this.$router.push({ name: RouteName.HOME }); await changeIdentity(this.$apollo.provider.defaultClient, data.registerPerson);
await this.$router.push({ name: RouteName.HOME });
}
} }
} catch (error) { } catch (error) {
this.errors = error.graphQLErrors.reduce((acc, error) => { this.errors = error.graphQLErrors.reduce((acc, error) => {

View File

@ -95,7 +95,7 @@ import { MOBILIZON_INSTANCE_HOST } from '@/api/_entrypoint';
import { Dialog } from 'buefy/dist/components/dialog'; import { Dialog } from 'buefy/dist/components/dialog';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { buildFileFromIPicture, buildFileVariable } from '@/utils/image'; import { buildFileFromIPicture, buildFileVariable } from '@/utils/image';
import { changeIdentity, saveActorData } from '@/utils/auth'; import { changeIdentity } from '@/utils/auth';
@Component({ @Component({
components: { components: {
@ -165,7 +165,9 @@ export default class EditIdentity extends Vue {
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: DELETE_PERSON, mutation: DELETE_PERSON,
variables: this.identity, variables: {
id: this.identity.id,
},
update: (store) => { update: (store) => {
const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES }); const data = store.readQuery<{ identities: IPerson[] }>({ query: IDENTITIES });
@ -278,11 +280,11 @@ export default class EditIdentity extends Vue {
const result = await this.$apollo.query({ const result = await this.$apollo.query({
query: FETCH_PERSON, query: FETCH_PERSON,
variables: { variables: {
name: this.identityName, username: this.identityName,
}, },
}); });
return new Person(result.data.person); return new Person(result.data.fetchPerson);
} }
private handleError(err: any) { private handleError(err: any) {

View File

@ -232,7 +232,7 @@ import {ParticipantRole} from "@/types/event.model";
</style> </style>
<script lang="ts"> <script lang="ts">
import { CREATE_EVENT, EDIT_EVENT, FETCH_EVENT } from '@/graphql/event'; import { CREATE_EVENT, EDIT_EVENT, EVENT_PERSON_PARTICIPATION, FETCH_EVENT } from '@/graphql/event';
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { import {
CommentModeration, CommentModeration,
@ -243,7 +243,7 @@ import {
IEvent, ParticipantRole, IEvent, ParticipantRole,
} from '@/types/event.model'; } from '@/types/event.model';
import { CURRENT_ACTOR_CLIENT, IDENTITIES, LOGGED_USER_DRAFTS, LOGGED_USER_PARTICIPATIONS } from '@/graphql/actor'; import { CURRENT_ACTOR_CLIENT, IDENTITIES, LOGGED_USER_DRAFTS, LOGGED_USER_PARTICIPATIONS } from '@/graphql/actor';
import { Person } from '@/types/actor'; import { IPerson, Person } from '@/types/actor';
import PictureUpload from '@/components/PictureUpload.vue'; import PictureUpload from '@/components/PictureUpload.vue';
import Editor from '@/components/Editor.vue'; import Editor from '@/components/Editor.vue';
import DateTimePicker from '@/components/Event/DateTimePicker.vue'; import DateTimePicker from '@/components/Event/DateTimePicker.vue';
@ -365,44 +365,9 @@ export default class EditEvent extends Vue {
const { data } = await this.$apollo.mutate({ const { data } = await this.$apollo.mutate({
mutation: CREATE_EVENT, mutation: CREATE_EVENT,
variables: this.buildVariables(), variables: this.buildVariables(),
update: (store, { data: { createEvent } }) => { update: (store, { data: { createEvent } }) => this.postCreateOrUpdate(store, createEvent),
if (createEvent.draft) { refetchQueries: ({ data: { createEvent } }) => this.postRefetchQueries(createEvent),
const data = store.readQuery<{ loggedUser: ICurrentUser }>({ query: LOGGED_USER_DRAFTS, variables: {
page: 1,
limit: 10,
} });
if (data) {
data.loggedUser.drafts.push(createEvent);
store.writeQuery({ query: LOGGED_USER_DRAFTS, variables: {
page: 1,
limit: 10,
}, data });
}
} else {
const data = store.readQuery<{ loggedUser: ICurrentUser }>({ query: LOGGED_USER_PARTICIPATIONS, variables: {
page: 1,
limit: 10,
afterDateTime: (new Date()).toISOString(),
} });
if (data) {
data.loggedUser.participations.push({
role: ParticipantRole.CREATOR,
actor: createEvent.organizerActor,
event: createEvent,
}); });
store.writeQuery({ query: LOGGED_USER_PARTICIPATIONS, variables: {
page: 1,
limit: 10,
afterDateTime: (new Date()).toISOString(),
}, data });
}
}
},
});
console.log('Event created', data);
await this.$router.push({ await this.$router.push({
name: 'Event', name: 'Event',
@ -418,50 +383,8 @@ export default class EditEvent extends Vue {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: EDIT_EVENT, mutation: EDIT_EVENT,
variables: this.buildVariables(), variables: this.buildVariables(),
update: (store, { data: { updateEvent } }) => { update: (store, { data: { updateEvent } }) => this.postCreateOrUpdate(store, updateEvent),
if (updateEvent.draft) { refetchQueries: ({ data: { updateEvent } }) => this.postRefetchQueries(updateEvent),
const data = store.readQuery<{ loggedUser: ICurrentUser }>({ query: LOGGED_USER_DRAFTS, variables: {
page: 1,
limit: 10,
} });
if (data) {
data.loggedUser.drafts.push(updateEvent);
store.writeQuery({ query: LOGGED_USER_DRAFTS, data });
}
} else {
let participationData: { loggedUser: ICurrentUser}|null = null;
try {
participationData = store.readQuery<{ loggedUser: ICurrentUser }>({
query: LOGGED_USER_PARTICIPATIONS, variables: {
page: 1,
limit: 10,
afterDateTime: (new Date()).toISOString(),
},
});
} catch (e) {
// no worries, it seems we can't update participation cache because it's not linked to an ID
}
if (participationData) {
participationData.loggedUser.participations.push({
role: ParticipantRole.CREATOR,
actor: updateEvent.organizerActor,
event: updateEvent,
});
store.writeQuery({ query: LOGGED_USER_PARTICIPATIONS, variables: {
page: 1,
limit: 10,
afterDateTime: (new Date()).toISOString(),
}, data: participationData });
}
const resultEvent: IEvent = Object.assign({}, updateEvent);
resultEvent.organizerActor = this.event.organizerActor;
resultEvent.relatedEvents = [];
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: updateEvent.uuid }, data: { event: resultEvent } });
}
},
}); });
await this.$router.push({ await this.$router.push({
@ -473,6 +396,61 @@ export default class EditEvent extends Vue {
} }
} }
/**
* Put in cache the updated or created event.
* If the event is not a draft anymore, also put in cache the participation
*/
private postCreateOrUpdate(store, updateEvent) {
const resultEvent: IEvent = Object.assign({}, updateEvent);
const organizerActor: IPerson = this.event.organizerActor as Person;
resultEvent.organizerActor = organizerActor;
resultEvent.relatedEvents = [];
store.writeQuery({ query: FETCH_EVENT, variables: { uuid: updateEvent.uuid }, data: { event: resultEvent } });
if (!updateEvent.draft) {
store.writeQuery({
query: EVENT_PERSON_PARTICIPATION,
variables: { eventId: updateEvent.id, name: organizerActor.preferredUsername },
data: {
person: {
__typename: 'Person',
id: organizerActor.id,
participations: [{
__typename: 'Participant',
id: 'unknown',
role: ParticipantRole.CREATOR,
actor: {
__typename: 'Actor',
id: organizerActor.id,
},
event: {
__typename: 'Event',
id: updateEvent.id,
},
}],
},
},
});
}
}
/**
* Refresh drafts or participation cache depending if the event is still draft or not
*/
private postRefetchQueries(updateEvent) {
if (updateEvent.draft) {
return [{
query: LOGGED_USER_DRAFTS,
}];
}
return [{
query: LOGGED_USER_PARTICIPATIONS,
variables: {
afterDateTime: new Date(),
},
}];
}
/** /**
* Build variables for Event GraphQL creation query * Build variables for Event GraphQL creation query
*/ */

View File

@ -48,7 +48,7 @@
<p class="tags" v-if="event.category || event.tags.length > 0"> <p class="tags" v-if="event.category || 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>
<!-- <span class="tag" v-if="event.category">{{ event.category }}</span>--> <!-- <span class="tag" v-if="event.category">{{ event.category }}</span>-->
<b-tag type="is-success" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</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 v-if="event.tags > 0"></span> <span v-if="event.tags > 0"></span>
<span class="visibility" v-if="!event.draft"> <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>
@ -256,13 +256,16 @@ import { RouteName } from '@/router';
variables() { variables() {
return { return {
eventId: this.event.id, eventId: this.event.id,
name: this.currentActor.preferredUsername, actorId: this.currentActor.id,
}; };
}, },
update: (data) => { update: (data) => {
if (data && data.person) return data.person.participations; if (data && data.person) return data.person.participations;
return []; return [];
}, },
skip() {
return !this.event.id || !this.currentActor.id;
},
}, },
}, },
}) })

View File

@ -135,7 +135,7 @@ export default class Register extends Vue {
this.validationSent = true; this.validationSent = true;
this.$router.push({ await this.$router.push({
name: RouteName.REGISTER_PROFILE, name: RouteName.REGISTER_PROFILE,
params: { email: this.credentials.email }, params: { email: this.credentials.email },
}); });

View File

@ -48,9 +48,12 @@ export default class Validate extends Vue {
const user = data.validateUser.user; const user = data.validateUser.user;
console.log(user); console.log(user);
if (user.defaultActor) { if (user.defaultActor) {
this.$router.push({ name: RouteName.HOME }); await this.$router.push({ name: RouteName.HOME });
} else { // If the user didn't register any profile yet, let's create one for them } else { // If the user didn't register any profile yet, let's create one for them
this.$router.push({ name: RouteName.REGISTER_PROFILE, params: { email: user.email, userAlreadyActivated: 'true' } }); await this.$router.push({
name: RouteName.REGISTER_PROFILE,
params: { email: user.email, userAlreadyActivated: 'true' },
});
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);

View File

@ -12,15 +12,29 @@ defmodule MobilizonWeb.Resolvers.Person do
alias Mobilizon.Users.User alias Mobilizon.Users.User
@doc """ @doc """
Find a person Get a person
""" """
def find_person(_parent, %{preferred_username: name}, _resolution) do def get_person(_parent, %{id: id}, _resolution) do
with {:ok, actor} <- ActivityPub.find_or_make_person_from_nickname(name), with %Actor{} = actor <- Actors.get_actor_with_preload(id),
actor <- proxify_pictures(actor) do actor <- proxify_pictures(actor) do
{:ok, actor} {:ok, actor}
else else
_ -> _ ->
{:error, "Person with name #{name} not found"} {:error, "Person with ID #{id} not found"}
end
end
@doc """
Find a person
"""
def fetch_person(_parent, %{preferred_username: preferred_username}, _resolution) do
with {:ok, %Actor{} = actor} <-
ActivityPub.find_or_make_actor_from_nickname(preferred_username),
actor <- proxify_pictures(actor) do
{:ok, actor}
else
_ ->
{:error, "Person with username #{preferred_username} not found"}
end end
end end
@ -74,13 +88,13 @@ defmodule MobilizonWeb.Resolvers.Person do
""" """
def update_person( def update_person(
_parent, _parent,
%{preferred_username: preferred_username} = args, %{id: id} = args,
%{context: %{current_user: user}} = _resolution %{context: %{current_user: user}} = _resolution
) do ) do
args = Map.put(args, :user_id, user.id) args = Map.put(args, :user_id, user.id)
with {:find_actor, %Actor{} = actor} <- with {:find_actor, %Actor{} = actor} <-
{:find_actor, Actors.get_actor_by_name(preferred_username)}, {:find_actor, Actors.get_actor(id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id), {:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
args <- save_attached_pictures(args), args <- save_attached_pictures(args),
{:ok, actor} <- Actors.update_actor(actor, args) do {:ok, actor} <- Actors.update_actor(actor, args) do
@ -103,11 +117,11 @@ defmodule MobilizonWeb.Resolvers.Person do
""" """
def delete_person( def delete_person(
_parent, _parent,
%{preferred_username: preferred_username} = _args, %{id: id} = _args,
%{context: %{current_user: user}} = _resolution %{context: %{current_user: user}} = _resolution
) do ) do
with {:find_actor, %Actor{} = actor} <- with {:find_actor, %Actor{} = actor} <-
{:find_actor, Actors.get_actor_by_name(preferred_username)}, {:find_actor, Actors.get_actor(id)},
{:is_owned, %Actor{}} <- User.owns_actor(user, actor.id), {:is_owned, %Actor{}} <- User.owns_actor(user, actor.id),
{:last_identity, false} <- {:last_identity, last_identity?(user)}, {:last_identity, false} <- {:last_identity, last_identity?(user)},
{:last_admin, false} <- {:last_admin, last_admin_of_a_group?(actor.id)}, {:last_admin, false} <- {:last_admin, last_admin_of_a_group?(actor.id)},

View File

@ -70,10 +70,16 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
resolve(&Person.get_current_person/3) resolve(&Person.get_current_person/3)
end end
@desc "Get a person by it's preferred username" @desc "Get a person by it's (federated) username"
field :person, :person do field :fetch_person, :person do
arg(:preferred_username, non_null(:string)) arg(:preferred_username, non_null(:string))
resolve(&Person.find_person/3) resolve(&Person.fetch_person/3)
end
@desc "Get a person by it's ID"
field :person, :person do
arg(:id, non_null(:id))
resolve(&Person.get_person/3)
end end
@desc "Get the persons for an user" @desc "Get the persons for an user"
@ -106,7 +112,7 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
@desc "Update an identity" @desc "Update an identity"
field :update_person, :person do field :update_person, :person do
arg(:preferred_username, non_null(:string)) arg(:id, non_null(:id))
arg(:name, :string, description: "The displayed name for this profile") arg(:name, :string, description: "The displayed name for this profile")
@ -127,7 +133,7 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
@desc "Delete an identity" @desc "Delete an identity"
field :delete_person, :person do field :delete_person, :person do
arg(:preferred_username, non_null(:string)) arg(:id, non_null(:id))
resolve(handle_errors(&Person.delete_person/3)) resolve(handle_errors(&Person.delete_person/3))
end end

View File

@ -1,5 +1,5 @@
# source: http://localhost:4000/api # source: http://localhost:4000/api
# timestamp: Wed Oct 02 2019 16:30:43 GMT+0200 (GMT+02:00) # timestamp: Fri Oct 04 2019 15:04:46 GMT+0200 (GMT+02:00)
schema { schema {
query: RootQueryType query: RootQueryType
@ -989,7 +989,7 @@ type RootMutationType {
deleteGroup(actorId: ID!, groupId: ID!): DeletedObject deleteGroup(actorId: ID!, groupId: ID!): DeletedObject
"""Delete an identity""" """Delete an identity"""
deletePerson(preferredUsername: String!): Person deletePerson(id: ID!): Person
deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject
"""Join an event""" """Join an event"""
@ -1082,10 +1082,10 @@ type RootMutationType {
The banner for the profile, either as an object or directly the ID of an existing Picture The banner for the profile, either as an object or directly the ID of an existing Picture
""" """
banner: PictureInput banner: PictureInput
id: ID!
"""The displayed name for this profile""" """The displayed name for this profile"""
name: String name: String
preferredUsername: String!
"""The summary for this profile""" """The summary for this profile"""
summary: String summary: String
@ -1119,6 +1119,9 @@ type RootQueryType {
"""Get all events""" """Get all events"""
events(limit: Int = 10, page: Int = 1): [Event] events(limit: Int = 10, page: Int = 1): [Event]
"""Get a person by it's (federated) username"""
fetchPerson(preferredUsername: String!): Person
"""Get a group by it's preferred username""" """Get a group by it's preferred username"""
group(preferredUsername: String!): Group group(preferredUsername: String!): Group
@ -1134,8 +1137,8 @@ type RootQueryType {
"""Get the current user""" """Get the current user"""
loggedUser: User loggedUser: User
"""Get a person by it's preferred username""" """Get a person by it's ID"""
person(preferredUsername: String!): Person person(id: ID!): Person
"""Get a picture""" """Get a picture"""
picture(id: String!): Picture picture(id: String!): Picture

View File

@ -1,18 +1,19 @@
defmodule MobilizonWeb.Resolvers.PersonResolverTest do defmodule MobilizonWeb.Resolvers.PersonResolverTest do
use MobilizonWeb.ConnCase use MobilizonWeb.ConnCase
alias MobilizonWeb.AbsintheHelpers alias MobilizonWeb.AbsintheHelpers
alias Mobilizon.Actors.Actor
import Mobilizon.Factory import Mobilizon.Factory
@non_existent_username "nonexistent" @non_existent_username "nonexistent"
describe "Person Resolver" do describe "Person Resolver" do
test "find_person/3 returns a person by it's username", context do test "get_person/3 returns a person by it's username", context do
user = insert(:user) user = insert(:user)
actor = insert(:actor, user: user) actor = insert(:actor, user: user)
query = """ query = """
{ {
person(preferredUsername: "#{actor.preferred_username}") { person(id: "#{actor.id}") {
preferredUsername, preferredUsername,
} }
} }
@ -27,7 +28,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
query = """ query = """
{ {
person(preferredUsername: "#{@non_existent_username}") { person(id: "6895567") {
preferredUsername, preferredUsername,
} }
} }
@ -40,7 +41,46 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
assert json_response(res, 200)["data"]["person"] == nil assert json_response(res, 200)["data"]["person"] == nil
assert hd(json_response(res, 200)["errors"])["message"] == assert hd(json_response(res, 200)["errors"])["message"] ==
"Person with name #{@non_existent_username} not found" "Person with ID 6895567 not found"
end
test "find_person/3 returns a person by it's username", context do
user = insert(:user)
actor = insert(:actor, user: user)
query = """
{
fetchPerson(preferredUsername: "#{actor.preferred_username}") {
preferredUsername,
}
}
"""
res =
context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["fetchPerson"]["preferredUsername"] ==
actor.preferred_username
query = """
{
fetchPerson(preferredUsername: "#{@non_existent_username}") {
preferredUsername,
}
}
"""
res =
context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
assert json_response(res, 200)["data"]["fetchPerson"] == nil
assert hd(json_response(res, 200)["errors"])["message"] ==
"Person with username #{@non_existent_username} not found"
end end
test "get_current_person/3 returns the current logged-in actor", context do test "get_current_person/3 returns the current logged-in actor", context do
@ -215,12 +255,12 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
test "update_person/3 updates an existing identity", context do test "update_person/3 updates an existing identity", context do
user = insert(:user) user = insert(:user)
insert(:actor, user: user, preferred_username: "riri") %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri")
mutation = """ mutation = """
mutation { mutation {
updatePerson( updatePerson(
preferredUsername: "riri", id: "#{person_id}",
name: "riri updated", name: "riri updated",
summary: "summary updated", summary: "summary updated",
banner: { banner: {
@ -286,12 +326,12 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
test "update_person/3 should fail to update a not owned identity", context do test "update_person/3 should fail to update a not owned identity", context do
user1 = insert(:user) user1 = insert(:user)
user2 = insert(:user) user2 = insert(:user)
insert(:actor, user: user2, preferred_username: "riri") %Actor{id: person_id} = insert(:actor, user: user2, preferred_username: "riri")
mutation = """ mutation = """
mutation { mutation {
updatePerson( updatePerson(
preferredUsername: "riri", id: "#{person_id}",
name: "riri updated", name: "riri updated",
) { ) {
id, id,
@ -317,7 +357,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
mutation = """ mutation = """
mutation { mutation {
updatePerson( updatePerson(
preferredUsername: "not_existing", id: "48918",
name: "riri updated", name: "riri updated",
) { ) {
id, id,
@ -339,11 +379,11 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
test "delete_person/3 should fail to update a not owned identity", context do test "delete_person/3 should fail to update a not owned identity", context do
user1 = insert(:user) user1 = insert(:user)
user2 = insert(:user) user2 = insert(:user)
insert(:actor, user: user2, preferred_username: "riri") %Actor{id: person_id} = insert(:actor, user: user2, preferred_username: "riri")
mutation = """ mutation = """
mutation { mutation {
deletePerson(preferredUsername: "riri") { deletePerson(id: "#{person_id}") {
id, id,
} }
} }
@ -366,7 +406,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
mutation = """ mutation = """
mutation { mutation {
deletePerson(preferredUsername: "fifi") { deletePerson(id: "9798665") {
id, id,
} }
} }
@ -385,11 +425,11 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
test "delete_person/3 should fail to delete the last user identity", context do test "delete_person/3 should fail to delete the last user identity", context do
user = insert(:user) user = insert(:user)
insert(:actor, user: user, preferred_username: "riri") %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri")
mutation = """ mutation = """
mutation { mutation {
deletePerson(preferredUsername: "riri") { deletePerson(id: "#{person_id}") {
id, id,
} }
} }
@ -421,7 +461,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
mutation = """ mutation = """
mutation { mutation {
deletePerson(preferredUsername: "last_admin") { deletePerson(id: "#{admin_actor.id}") {
id, id,
} }
} }
@ -440,12 +480,12 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
test "delete_person/3 should delete a user identity", context do test "delete_person/3 should delete a user identity", context do
user = insert(:user) user = insert(:user)
insert(:actor, user: user, preferred_username: "riri") %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri")
insert(:actor, user: user, preferred_username: "fifi") insert(:actor, user: user, preferred_username: "fifi")
mutation = """ mutation = """
mutation { mutation {
deletePerson(preferredUsername: "riri") { deletePerson(id: "#{person_id}") {
id, id,
} }
} }
@ -460,7 +500,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
query = """ query = """
{ {
person(preferredUsername: "riri") { person(id: "#{person_id}") {
id, id,
} }
} }
@ -471,7 +511,8 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
|> auth_conn(user) |> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "person")) |> get("/api", AbsintheHelpers.query_skeleton(query, "person"))
assert hd(json_response(res, 200)["errors"])["message"] == "Person with name riri not found" assert hd(json_response(res, 200)["errors"])["message"] ==
"Person with ID #{person_id} not found"
end end
end end
@ -522,7 +563,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
query = """ query = """
{ {
person(preferredUsername: "#{actor.preferred_username}") { person(id: "#{actor.id}") {
participations { participations {
event { event {
uuid, uuid,
@ -542,7 +583,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
query = """ query = """
{ {
person(preferredUsername: "#{actor_from_other_user.preferred_username}") { person(id: "#{actor_from_other_user.id}") {
participations { participations {
event { event {
uuid, uuid,
@ -573,7 +614,7 @@ defmodule MobilizonWeb.Resolvers.PersonResolverTest do
query = """ query = """
{ {
person(preferredUsername: "#{actor.preferred_username}") { person(id: "#{actor.id}") {
participations(eventId: "#{event.id}") { participations(eventId: "#{event.id}") {
event { event {
uuid, uuid,