Improve GraphQL documentation and cleanup API

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
master
Thomas Citharel 2 years ago
parent e8a3b6aa94
commit 3eacbb2ca3
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
  1. 8
      .graphqlconfig.yaml
  2. 3231
      js/schema.graphql
  3. 1
      js/src/components/Comment/Comment.vue
  4. 2
      js/src/components/Comment/CommentTree.vue
  5. 1
      js/src/components/Editor.vue
  6. 3
      js/src/components/Editor/Image.ts
  7. 2
      js/src/components/Event/EventListCard.vue
  8. 1
      js/src/components/Participation/ParticipationWithoutAccount.vue
  9. 14
      js/src/graphql/comment.ts
  10. 4
      js/src/graphql/discussion.ts
  11. 12
      js/src/graphql/event.ts
  12. 2
      js/src/graphql/group.ts
  13. 10
      js/src/graphql/report.ts
  14. 4
      js/src/graphql/upload.ts
  15. 7
      js/src/mixins/event.ts
  16. 1
      js/src/views/Discussions/Create.vue
  17. 12
      js/src/views/Event/Event.vue
  18. 3
      js/src/views/Event/Participants.vue
  19. 5
      js/src/views/Group/Create.vue
  20. 8
      js/src/views/Group/Group.vue
  21. 4
      js/src/views/Moderation/Report.vue
  22. 7
      js/src/views/User/EmailValidate.vue
  23. 2
      js/tsconfig.json
  24. 6
      lib/graphql/api/groups.ex
  25. 15
      lib/graphql/resolvers/comment.ex
  26. 9
      lib/graphql/resolvers/discussion.ex
  27. 21
      lib/graphql/resolvers/event.ex
  28. 7
      lib/graphql/resolvers/group.ex
  29. 3
      lib/graphql/resolvers/member.ex
  30. 9
      lib/graphql/resolvers/participant.ex
  31. 4
      lib/graphql/resolvers/person.ex
  32. 10
      lib/graphql/resolvers/picture.ex
  33. 42
      lib/graphql/resolvers/report.ex
  34. 3
      lib/graphql/resolvers/todos.ex
  35. 9
      lib/graphql/schema/actor.ex
  36. 3
      lib/graphql/schema/actors/follower.ex
  37. 73
      lib/graphql/schema/actors/group.ex
  38. 43
      lib/graphql/schema/actors/member.ex
  39. 62
      lib/graphql/schema/actors/person.ex
  40. 63
      lib/graphql/schema/address.ex
  41. 189
      lib/graphql/schema/admin.ex
  42. 242
      lib/graphql/schema/config.ex
  43. 64
      lib/graphql/schema/discussions/comment.ex
  44. 48
      lib/graphql/schema/discussions/discussion.ex
  45. 141
      lib/graphql/schema/event.ex
  46. 15
      lib/graphql/schema/events/feed_token.ex
  47. 54
      lib/graphql/schema/events/participant.ex
  48. 21
      lib/graphql/schema/picture.ex
  49. 40
      lib/graphql/schema/post.ex
  50. 47
      lib/graphql/schema/report.ex
  51. 59
      lib/graphql/schema/resource.ex
  52. 37
      lib/graphql/schema/search.ex
  53. 4
      lib/graphql/schema/sort.ex
  54. 4
      lib/graphql/schema/tag.ex
  55. 27
      lib/graphql/schema/todos/todo.ex
  56. 9
      lib/graphql/schema/todos/todo_list.ex
  57. 163
      lib/graphql/schema/user.ex
  58. 2
      lib/web/templates/email/report.text.eex
  59. 210
      priv/gettext/ar/LC_MESSAGES/errors.po
  60. 210
      priv/gettext/be/LC_MESSAGES/errors.po
  61. 210
      priv/gettext/ca/LC_MESSAGES/errors.po
  62. 210
      priv/gettext/cs/LC_MESSAGES/errors.po
  63. 210
      priv/gettext/de/LC_MESSAGES/errors.po
  64. 210
      priv/gettext/en/LC_MESSAGES/errors.po
  65. 210
      priv/gettext/errors.pot
  66. 212
      priv/gettext/es/LC_MESSAGES/errors.po
  67. 210
      priv/gettext/fi/LC_MESSAGES/errors.po
  68. 357
      priv/gettext/fr/LC_MESSAGES/errors.po
  69. 210
      priv/gettext/gl/LC_MESSAGES/errors.po
  70. 210
      priv/gettext/hu/LC_MESSAGES/errors.po
  71. 210
      priv/gettext/it/LC_MESSAGES/errors.po
  72. 210
      priv/gettext/ja/LC_MESSAGES/errors.po
  73. 210
      priv/gettext/nl/LC_MESSAGES/errors.po
  74. 210
      priv/gettext/nn/LC_MESSAGES/errors.po
  75. 348
      priv/gettext/oc/LC_MESSAGES/errors.po
  76. 210
      priv/gettext/pl/LC_MESSAGES/errors.po
  77. 210
      priv/gettext/pt/LC_MESSAGES/errors.po
  78. 210
      priv/gettext/pt_BR/LC_MESSAGES/errors.po
  79. 210
      priv/gettext/ru/LC_MESSAGES/errors.po
  80. 210
      priv/gettext/sv/LC_MESSAGES/errors.po
  81. 2808
      schema.graphql
  82. 28
      test/graphql/resolvers/comment_test.exs
  83. 33
      test/graphql/resolvers/event_test.exs
  84. 32
      test/graphql/resolvers/group_test.exs
  85. 30
      test/graphql/resolvers/participant_test.exs
  86. 10
      test/graphql/resolvers/picture_test.exs
  87. 116
      test/graphql/resolvers/report_test.exs

@ -1,8 +0,0 @@
projects:
Mobilizon:
schemaPath: schema.graphql
extensions:
endpoints:
dev:
url: 'http://localhost:4000/api'
introspect: true

File diff suppressed because it is too large Load Diff

@ -288,7 +288,6 @@ export default class Comment extends Vue {
mutation: CREATE_REPORT,
variables: {
eventId: this.event.id,
reporterId: this.currentActor.id,
reportedId: this.comment.actor.id,
commentsIds: [this.comment.id],
content,

@ -112,7 +112,6 @@ export default class CommentTree extends Vue {
mutation: CREATE_COMMENT_FROM_EVENT,
variables: {
eventId: this.event.id,
actorId: comment.actor.id,
text: comment.text,
inReplyToCommentId: comment.inReplyToComment ? comment.inReplyToComment.id : null,
},
@ -204,7 +203,6 @@ export default class CommentTree extends Vue {
mutation: DELETE_COMMENT,
variables: {
commentId: comment.id,
actorId: this.currentActor.id,
},
update: (store, { data }) => {
if (data == null) return;

@ -530,7 +530,6 @@ export default class EditorComponent extends Vue {
variables: {
file: image,
name: image.name,
actorId: this.currentActor.id,
},
});
if (data.uploadPicture && data.uploadPicture.url) {

@ -88,15 +88,12 @@ export default class Image extends Node {
});
if (!coordinates) return false;
const client = apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>;
const editorElem = document.getElementById("tiptab-editor");
const actorId = editorElem && editorElem.dataset.actorId;
try {
images.forEach(async (image) => {
const { data } = await client.mutate({
mutation: UPLOAD_PICTURE,
variables: {
actorId,
file: image,
name: image.name,
},

@ -246,7 +246,7 @@ export default class EventListCard extends mixins(ActorMixin, EventMixin) {
async openDeleteEventModalWrapper(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await this.openDeleteEventModal(this.participation.event, this.currentActor);
await this.openDeleteEventModal(this.participation.event);
}
async gotToWithCheck(participation: IParticipant, route: RawLocation): Promise<Route> {

@ -127,7 +127,6 @@ export default class ParticipationWithoutAccount extends Vue {
mutation: JOIN_EVENT,
variables: {
eventId: this.event.id,
actorId: this.config.anonymous.actorId,
email: this.anonymousParticipation.email,
message: this.anonymousParticipation.message,
locale: this.$i18n.locale,

@ -68,18 +68,8 @@ export const COMMENTS_THREADS = gql`
`;
export const CREATE_COMMENT_FROM_EVENT = gql`
mutation CreateCommentFromEvent(
$eventId: ID!
$actorId: ID!
$text: String!
$inReplyToCommentId: ID
) {
createComment(
eventId: $eventId
actorId: $actorId
text: $text
inReplyToCommentId: $inReplyToCommentId
) {
mutation CreateCommentFromEvent($eventId: ID!, $text: String!, $inReplyToCommentId: ID) {
createComment(eventId: $eventId, text: $text, inReplyToCommentId: $inReplyToCommentId) {
...CommentRecursive
}
}

@ -84,8 +84,8 @@ export const DISCUSSION_FIELDS_FRAGMENT = gql`
`;
export const CREATE_DISCUSSION = gql`
mutation createDiscussion($title: String!, $creatorId: ID!, $actorId: ID!, $text: String!) {
createDiscussion(title: $title, text: $text, creatorId: $creatorId, actorId: $actorId) {
mutation createDiscussion($title: String!, $actorId: ID!, $text: String!) {
createDiscussion(title: $title, text: $text, actorId: $actorId) {
...DiscussionFields
}
}

@ -498,8 +498,8 @@ export const CONFIRM_PARTICIPATION = gql`
`;
export const UPDATE_PARTICIPANT = gql`
mutation AcceptParticipant($id: ID!, $moderatorActorId: ID!, $role: ParticipantRoleEnum!) {
updateParticipation(id: $id, moderatorActorId: $moderatorActorId, role: $role) {
mutation UpdateParticipant($id: ID!, $role: ParticipantRoleEnum!) {
updateParticipation(id: $id, role: $role) {
role
id
}
@ -507,20 +507,20 @@ export const UPDATE_PARTICIPANT = gql`
`;
export const DELETE_EVENT = gql`
mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
deleteEvent(eventId: $eventId, actorId: $actorId) {
mutation DeleteEvent($eventId: ID!) {
deleteEvent(eventId: $eventId) {
id
}
}
`;
export const PARTICIPANTS = gql`
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String, $actorId: ID!) {
query($uuid: UUID!, $page: Int, $limit: Int, $roles: String) {
event(uuid: $uuid) {
id,
uuid,
title,
participants(page: $page, limit: $limit, roles: $roles, actorId: $actorId) {
participants(page: $page, limit: $limit, roles: $roles) {
${participantsQuery}
},
participantStats {

@ -223,7 +223,6 @@ export const GET_GROUP = gql`
export const CREATE_GROUP = gql`
mutation CreateGroup(
$creatorActorId: ID!
$preferredUsername: String!
$name: String!
$summary: String
@ -231,7 +230,6 @@ export const CREATE_GROUP = gql`
$banner: PictureInput
) {
createGroup(
creatorActorId: $creatorActorId
preferredUsername: $preferredUsername
name: $name
summary: $summary

@ -121,7 +121,6 @@ export const REPORT = gql`
export const CREATE_REPORT = gql`
mutation CreateReport(
$eventId: ID
$reporterId: ID!
$reportedId: ID!
$content: String
$commentsIds: [ID]
@ -129,7 +128,6 @@ export const CREATE_REPORT = gql`
) {
createReport(
eventId: $eventId
reporterId: $reporterId
reportedId: $reportedId
content: $content
commentsIds: $commentsIds
@ -141,8 +139,8 @@ export const CREATE_REPORT = gql`
`;
export const UPDATE_REPORT = gql`
mutation UpdateReport($reportId: ID!, $moderatorId: ID!, $status: ReportStatus!) {
updateReportStatus(reportId: $reportId, moderatorId: $moderatorId, status: $status) {
mutation UpdateReport($reportId: ID!, $status: ReportStatus!) {
updateReportStatus(reportId: $reportId, status: $status) {
...ReportFragment
}
}
@ -150,8 +148,8 @@ export const UPDATE_REPORT = gql`
`;
export const CREATE_REPORT_NOTE = gql`
mutation CreateReportNote($reportId: ID!, $moderatorId: ID!, $content: String!) {
createReportNote(reportId: $reportId, moderatorId: $moderatorId, content: $content) {
mutation CreateReportNote($reportId: ID!, $content: String!) {
createReportNote(reportId: $reportId, content: $content) {
id
content
insertedAt

@ -2,8 +2,8 @@ import gql from "graphql-tag";
/* eslint-disable import/prefer-default-export */
export const UPLOAD_PICTURE = gql`
mutation UploadPicture($file: Upload!, $alt: String, $name: String!, $actorId: ID!) {
uploadPicture(file: $file, alt: $alt, name: $name, actorId: $actorId) {
mutation UploadPicture($file: Upload!, $alt: String, $name: String!) {
uploadPicture(file: $file, alt: $alt, name: $name) {
url
id
}

@ -90,7 +90,7 @@ export default class EventMixin extends mixins(Vue) {
this.$notifier.success(this.$t("You have cancelled your participation") as string);
}
protected async openDeleteEventModal(event: IEvent, currentActor: IPerson): Promise<void> {
protected async openDeleteEventModal(event: IEvent): Promise<void> {
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
@ -115,11 +115,11 @@ export default class EventMixin extends mixins(Vue) {
placeholder: event.title,
pattern: escapeRegExp(event.title),
},
onConfirm: () => this.deleteEvent(event, currentActor),
onConfirm: () => this.deleteEvent(event),
});
}
private async deleteEvent(event: IEvent, currentActor: IPerson) {
private async deleteEvent(event: IEvent) {
const eventTitle = event.title;
try {
@ -127,7 +127,6 @@ export default class EventMixin extends mixins(Vue) {
mutation: DELETE_EVENT,
variables: {
eventId: event.id,
actorId: currentActor.id,
},
});
/**

@ -70,7 +70,6 @@ export default class CreateDiscussion extends Vue {
title: this.discussion.title,
text: this.discussion.text,
actorId: parseInt(this.group.id, 10),
creatorId: parseInt(this.currentActor.id, 10),
},
});

@ -761,7 +761,7 @@ export default class Event extends EventMixin {
* Delete the event, then redirect to home.
*/
async openDeleteEventModalWrapper(): Promise<void> {
await this.openDeleteEventModal(this.event, this.currentActor);
await this.openDeleteEventModal(this.event);
}
async reportEvent(content: string, forward: boolean): Promise<void> {
@ -771,19 +771,12 @@ export default class Event extends EventMixin {
this.$refs.reportModal.close();
if (!this.event.organizerActor) return;
const eventTitle = this.event.title;
let reporterId = null;
if (this.currentActor.id) {
reporterId = this.currentActor.id;
} else if (this.config.anonymous.reports.allowed) {
reporterId = this.config.anonymous.actorId;
}
if (!reporterId) return;
try {
await this.$apollo.mutate<IReport>({
mutation: CREATE_REPORT,
variables: {
eventId: this.event.id,
reporterId,
reportedId: this.actorForReport ? this.actorForReport.id : null,
content,
forward,
@ -808,7 +801,6 @@ export default class Event extends EventMixin {
mutation: JOIN_EVENT,
variables: {
eventId: this.event.id,
actorId: identity.id,
message,
},
update: (store, { data }) => {

@ -214,7 +214,6 @@ const MESSAGE_ELLIPSIS_LENGTH = 130;
page: 1,
limit: PARTICIPANTS_PER_PAGE,
roles: this.roles,
actorId: this.currentActor.id,
};
},
skip() {
@ -298,7 +297,6 @@ export default class Participants extends Vue {
mutation: UPDATE_PARTICIPANT,
variables: {
id: participant.id,
moderatorActorId: this.currentActor.id,
role: ParticipantRole.PARTICIPANT,
},
});
@ -313,7 +311,6 @@ export default class Participants extends Vue {
mutation: UPDATE_PARTICIPANT,
variables: {
id: participant.id,
moderatorActorId: this.currentActor.id,
role: ParticipantRole.REJECTED,
},
});

@ -178,15 +178,10 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
};
}
const currentActor = {
creatorActorId: this.currentActor.id,
};
return {
...this.group,
...avatarObj,
...bannerObj,
...currentActor,
};
}

@ -481,18 +481,10 @@ export default class Group extends mixins(GroupMixin) {
// @ts-ignore
this.$refs.reportModal.close();
const groupTitle = this.group.name || usernameWithDomain(this.group);
let reporterId = null;
if (this.currentActor.id) {
reporterId = this.currentActor.id;
} else if (this.config.anonymous.reports.allowed) {
reporterId = this.config.anonymous.actorId;
}
if (!reporterId) return;
try {
await this.$apollo.mutate<IReport>({
mutation: CREATE_REPORT,
variables: {
reporterId,
reportedId: this.group.id,
content,
forward,

@ -303,7 +303,6 @@ export default class Report extends Vue {
mutation: CREATE_REPORT_NOTE,
variables: {
reportId: this.report.id,
moderatorId: this.currentActor.id,
content: this.noteContent,
},
update: (store, { data }) => {
@ -372,7 +371,6 @@ export default class Report extends Vue {
mutation: DELETE_EVENT,
variables: {
eventId: this.report.event.id.toString(),
actorId: this.currentActor.id,
},
});
@ -395,7 +393,6 @@ export default class Report extends Vue {
mutation: DELETE_COMMENT,
variables: {
commentId: comment.id,
actorId: this.currentActor.id,
},
});
this.$notifier.success(this.$t("Comment deleted") as string);
@ -410,7 +407,6 @@ export default class Report extends Vue {
mutation: UPDATE_REPORT,
variables: {
reportId: this.report.id,
moderatorId: this.currentActor.id,
status,
},
update: (store, { data }) => {

@ -30,11 +30,11 @@ export default class Validate extends Vue {
failed = false;
async created() {
async created(): Promise<void> {
await this.validateAction();
}
async validateAction() {
async validateAction(): Promise<void> {
try {
await this.$apollo.mutate<{ validateEmail: ICurrentUser }>({
mutation: VALIDATE_EMAIL,
@ -43,11 +43,10 @@ export default class Validate extends Vue {
},
});
this.loading = false;
return await this.$router.push({ name: RouteName.HOME });
await this.$router.push({ name: RouteName.HOME });
} catch (err) {
console.error(err);
this.failed = true;
return undefined;
}
}
}

@ -13,7 +13,7 @@
"resolveJsonModule": true,
"sourceMap": true,
"baseUrl": ".",
"types": ["webpack-env", "mocha", "chai"],
"types": ["webpack-env"],
"typeRoots": ["./@types", "./node_modules/@types"],
"paths": {
"@/*": ["src/*"]

@ -26,9 +26,6 @@ defmodule Mobilizon.GraphQL.API.Groups do
else
{:existing_group, _} ->
{:error, "A group with this name already exists"}
{:is_owned, nil} ->
{:error, "Profile is not owned by authenticated user"}
end
end
@ -42,9 +39,6 @@ defmodule Mobilizon.GraphQL.API.Groups do
else
{:existing_group, _} ->
{:error, "A group with this name already exists"}
{:is_owned, nil} ->
{:error, "Profile is not owned by authenticated user"}
end
end
end

@ -3,11 +3,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Handles the comment-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Discussions, Events}
alias Mobilizon.{Actors, Admin, Discussions, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment, as: CommentModel
alias Mobilizon.Events.{Event, EventOptions}
alias Mobilizon.Users
alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@ -21,14 +20,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
def create_comment(
_parent,
%{actor_id: actor_id, event_id: event_id} = args,
%{event_id: event_id} = args,
%{
context: %{
current_user: %User{} = user
}
}
) do
with {:is_owned, %Actor{} = _organizer_actor} <- User.owns_actor(user, actor_id),
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:find_event,
{:ok,
%Event{
@ -36,18 +35,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
organizer_actor_id: organizer_actor_id
}}} <-
{:find_event, Events.get_event(event_id)},
{actor_id, ""} <- Integer.parse(actor_id),
{:allowed, true} <-
{:allowed, comment_moderation != :closed || actor_id == organizer_actor_id},
args <- Map.put(args, :actor_id, actor_id),
{:ok, _, %CommentModel{} = comment} <-
Comments.create_comment(args) do
{:ok, comment}
else
{:allowed, false} ->
{:error, :unauthorized}
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
end
end
@ -107,9 +103,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
else
%CommentModel{deleted_at: deleted_at} when not is_nil(deleted_at) ->
{:error, dgettext("errors", "Comment is already deleted")}
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
end
end

@ -62,6 +62,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
end
end
def get_discussion(_parent, _args, %{
context: %{
current_user: %User{} = _user
}
}),
do:
{:error,
dgettext("errors", "You must provide either an ID or a slug to access a discussion")}
def get_discussion(_parent, _args, _resolution),
do: {:error, dgettext("errors", "You need to be logged-in to access discussions")}

@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
Handles the event-related GraphQL calls.
"""
alias Mobilizon.{Actors, Admin, Events}
alias Mobilizon.{Actors, Admin, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Events.{Event, EventParticipantStats}
@ -74,10 +74,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
"""
def list_participants_for_event(
%Event{id: event_id},
%{page: page, limit: limit, roles: roles, actor_id: actor_id},
%{page: page, limit: limit, roles: roles},
%{context: %{current_user: %User{} = user}} = _resolution
) do
with {:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id),
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
# Check that moderator has right
{:actor_approve_permission, true} <-
{:actor_approve_permission, Events.moderator_for_event?(event_id, actor_id)} do
@ -96,9 +96,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
participants = Events.list_participants_for_event(event_id, roles, page, limit)
{:ok, participants}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Moderator profile is not owned by authenticated user")}
{:actor_approve_permission, _} ->
{:error,
dgettext("errors", "Provided moderator profile doesn't have permission on this event")}
@ -191,8 +188,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
%{context: %{current_user: user}} = _resolution
) do
# See https://github.com/absinthe-graphql/absinthe/issues/490
with args <- Map.put(args, :options, args[:options] || %{}),
{:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
with {:is_owned, %Actor{} = organizer_actor} <- User.owns_actor(user, organizer_actor_id),
args <- Map.put(args, :options, args[:options] || %{}),
args_with_organizer <- Map.put(args, :organizer_actor, organizer_actor),
{:ok, %Activity{data: %{"object" => %{"type" => "Event"}}}, %Event{} = event} <-
API.Events.create_event(args_with_organizer) do
@ -257,12 +254,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
"""
def delete_event(
_parent,
%{event_id: event_id, actor_id: actor_id},
%{event_id: event_id},
%{context: %{current_user: %User{role: role} = user}}
) do
with {:ok, %Event{local: is_local} = event} <- Events.get_event_with_preload(event_id),
{actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id) do
%Actor{id: actor_id} = actor <- Users.get_actor_for_user(user) do
cond do
{:event_can_be_managed, true} == Event.can_be_managed_by(event, actor_id) ->
do_delete_event(event, actor)
@ -281,9 +277,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
else
{:error, :event_not_found} ->
{:error, dgettext("errors", "Event not found")}
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
end
end

@ -121,10 +121,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
}
}
) do
with creator_actor_id <- Map.get(args, :creator_actor_id),
{:is_owned, %Actor{} = creator_actor} <- User.owns_actor(user, creator_actor_id),
with %Actor{id: creator_actor_id} = creator_actor <- Users.get_actor_for_user(user),
args <- Map.update(args, :preferred_username, "", &String.downcase/1),
args <- Map.put(args, :creator_actor, creator_actor),
args <- Map.put(args, :creator_actor_id, creator_actor_id),
args <- save_attached_pictures(args),
{:ok, _activity, %Actor{type: :Group} = group} <-
API.Groups.create_group(args) do
@ -132,9 +132,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
else
{:error, err} when is_binary(err) ->
{:error, err}
{:is_owned, nil} ->
{:error, dgettext("errors", "Creator profile is not owned by the current user")}
end
end

@ -76,9 +76,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
{:ok, _activity, %Member{} = member} <- ActivityPub.invite(group, actor, target_actor) do
{:ok, member}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :group_not_found} ->
{:error, dgettext("errors", "Group not found")}

@ -2,7 +2,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
@moduledoc """
Handles the participation-related GraphQL calls.
"""
alias Mobilizon.{Actors, Config, Crypto, Events}
alias Mobilizon.{Actors, Config, Crypto, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.GraphQL.API.Participations
@ -206,7 +206,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
def update_participation(
_parent,
%{id: participation_id, moderator_actor_id: moderator_actor_id, role: new_role},
%{id: participation_id, role: new_role},
%{
context: %{
current_user: user
@ -214,7 +214,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
}
) do
# Check that moderator provided is rightly authenticated
with {:is_owned, moderator_actor} <- User.owns_actor(user, moderator_actor_id),
with %Actor{id: moderator_actor_id} = moderator_actor <- Users.get_actor_for_user(user),
# Check that participation already exists
{:has_participation, %Participant{role: old_role} = participation} <-
{:has_participation, Events.get_participant(participation_id)},
@ -227,9 +227,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
Participations.update(participation, moderator_actor, new_role) do
{:ok, participation}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Moderator profile is not owned by authenticated user")}
{:has_participation, nil} ->
{:error, dgettext("errors", "Participant not found")}

@ -5,12 +5,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
import Mobilizon.Users.Guards
alias Mobilizon.Actors
alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.Participant
alias Mobilizon.Storage.Page
alias Mobilizon.Users
alias Mobilizon.Users.User
import Mobilizon.Web.Gettext

@ -4,9 +4,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Media
alias Mobilizon.{Media, Users}
alias Mobilizon.Media.Picture
alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@doc """
@ -46,10 +45,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
@spec upload_picture(map, map, map) :: {:ok, Picture.t()} | {:error, any}
def upload_picture(
_parent,
%{file: %Plug.Upload{} = file, actor_id: actor_id} = args,
%{file: %Plug.Upload{} = file} = args,
%{context: %{current_user: user}}
) do
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:ok, %{name: _name, url: url, content_type: content_type, size: size}} <-
Mobilizon.Web.Upload.store(file),
args <-
@ -68,9 +67,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
size: picture.file.size
}}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:error, :mime_type_not_allowed} ->
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}

@ -5,10 +5,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
import Mobilizon.Users.Guards
alias Mobilizon.Actors
alias Mobilizon.{Actors, Config, Reports, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Reports
alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Users.User
import Mobilizon.Web.Gettext
@ -48,16 +46,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
"""
def create_report(
_parent,
%{reporter_id: reporter_id} = args,
args,
%{context: %{current_user: %User{} = user}} = _resolution
) do
with {:is_owned, %Actor{}} <- User.owns_actor(user, reporter_id),
{:ok, _, %Report{} = report} <- API.Reports.report(args) do
with %Actor{id: reporter_id} <- Users.get_actor_for_user(user),
{:ok, _, %Report{} = report} <-
args |> Map.put(:reporter_id, reporter_id) |> API.Reports.report() do
{:ok, report}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Reporter profile is not owned by authenticated user")}
_error ->
{:error, dgettext("errors", "Error while saving report")}
end
@ -65,47 +61,37 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
def create_report(
_parent,
%{reporter_id: reporter_id} = args,
args,
_resolution
) do
with {:anonymous_reporting_allowed, true} <-
{:anonymous_reporting_allowed, Config.anonymous_reporting?()},
{:wrong_id, true} <- {:wrong_id, reporter_id == to_string(Config.anonymous_actor_id())},
{:ok, _, %Report{} = report} <- API.Reports.report(args) do
{:ok, _, %Report{} = report} <-
args |> Map.put(:reporter_id, Config.anonymous_actor_id()) |> API.Reports.report() do
{:ok, report}
else
{:anonymous_reporting_allowed, _} ->
{:error, dgettext("errors", "You need to be logged-in to create reports")}
{:wrong_id, _} ->
{:error, dgettext("errors", "Reporter ID does not match the anonymous profile id")}
_error ->
{:error, dgettext("errors", "Error while saving report")}
end
end
def create_report(_parent, _args, _resolution) do
{:error, dgettext("errors", "You need to be logged-in to create reports")}
end
@doc """
Update a report's status
"""
def update_report(
_parent,
%{report_id: report_id, moderator_id: moderator_id, status: status},
%{report_id: report_id, status: status},
%{context: %{current_user: %User{role: role} = user}}
)
when is_moderator(role) do
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, moderator_id),
with %Actor{} = actor <- Users.get_actor_for_user(user),
%Report{} = report <- Mobilizon.Reports.get_report(report_id),
{:ok, %Report{} = report} <- API.Reports.update_report_status(actor, report, status) do
{:ok, report}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
_error ->
{:error, dgettext("errors", "Error while updating report")}
end
@ -117,11 +103,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
def create_report_note(
_parent,
%{report_id: report_id, moderator_id: moderator_id, content: content},
%{report_id: report_id, content: content},
%{context: %{current_user: %User{role: role} = user}}
)
when is_moderator(role) do
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
with %Actor{id: moderator_id} <- Users.get_actor_for_user(user),
%Report{} = report <- Reports.get_report(report_id),
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
{:ok, %Note{} = note} <- API.Reports.create_report_note(report, moderator, content) do
@ -131,11 +117,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Report do
def delete_report_note(
_parent,
%{note_id: note_id, moderator_id: moderator_id},
%{note_id: note_id},
%{context: %{current_user: %User{role: role} = user}}
)
when is_moderator(role) do
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
with %Actor{id: moderator_id} <- Users.get_actor_for_user(user),
%Note{} = note <- Reports.get_note(note_id),
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
{:ok, %Note{} = note} <- API.Reports.delete_report_note(note, moderator) do

@ -53,9 +53,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
%Page{} = page <- Todos.get_todos_for_todo_list(todo_list) do
{:ok, page}
else
{:is_owned, nil} ->
{:error, dgettext("errors", "Profile is not owned by authenticated user")}
{:member, _} ->
{:error, dgettext("errors", "Profile is not member of group")}
end

@ -62,18 +62,21 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
end
object :actor_mutations do
@desc "Suspend an actor"
field :suspend_profile, :deleted_object do
arg(:id, non_null(:id), description: "The profile ID to suspend")
arg(:id, non_null(:id), description: "The remote profile ID to suspend")
resolve(&ActorResolver.suspend_profile/3)
end
@desc "Unsuspend an actor"
field :unsuspend_profile, :actor do
arg(:id, non_null(:id), description: "The profile ID to unsuspend")
arg(:id, non_null(:id), description: "The remote profile ID to unsuspend")
resolve(&ActorResolver.unsuspend_profile/3)
end
@desc "Refresh a profile"
field :refresh_profile, :actor do
arg(:id, non_null(:id))
arg(:id, non_null(:id), description: "The remote profile ID to refresh")
resolve(&ActorResolver.refresh_profile/3)
end
end

@ -19,6 +19,9 @@ defmodule Mobilizon.GraphQL.Schema.Actors.FollowerType do
field(:updated_at, :datetime, description: "When the follow was updated")
end
@desc """
A paginated list of follower objects
"""
object :paginated_follower_list do
field(:elements, list_of(:follower), description: "A list of followers")
field(:total, :integer, description: "The total number of elements in the list")

@ -54,10 +54,18 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
# This one should have a privacy setting
field :organized_events, :paginated_event_list do
arg(:after_datetime, :datetime, default_value: nil)
arg(:before_datetime, :datetime, default_value: nil)
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:after_datetime, :datetime,
default_value: nil,
description: "Filter events that begin after this datetime"
)
arg(:before_datetime, :datetime,
default_value: nil,
description: "Filter events that begin before this datetime"
)
arg(:page, :integer, default_value: 1, description: "The page in the paginated event list")
arg(:limit, :integer, default_value: 10, description: "The limit of events per page")
resolve(&Group.find_events_for_group/3)
description("A list of the events this actor has organized")
end
@ -74,23 +82,27 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
)
field :members, :paginated_member_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:roles, :string, default_value: "")
arg(:page, :integer, default_value: 1, description: "The page in the paginated member list")
arg(:limit, :integer, default_value: 10, description: "The limit of members per page")
arg(:roles, :string, default_value: "", description: "Filter members by their role")
resolve(&Member.find_members_for_group/3)
description("A paginated list of group members")
end
field :resources, :paginated_resource_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:page, :integer,
default_value: 1,
description: "The page in the paginated resource list"
)
arg(:limit, :integer, default_value: 10, description: "The limit of resources per page")
resolve(&Resource.find_resources_for_group/3)
description("A paginated list of the resources this group has")
end
field :posts, :paginated_post_list do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:page, :integer, default_value: 1, description: "The page in the paginated post list")
arg(:limit, :integer, default_value: 10, description: "The limit of posts per page")
resolve(&Post.find_posts_for_group/3)
description("A paginated list of the posts this group has")
end
@ -120,9 +132,12 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
value(:open, description: "The actor is open to followings")
end
@desc """
A paginated list of groups
"""
object :paginated_group_list do
field(:elements, list_of(:group), description: "A list of groups")
field(:total, :integer, description: "The total number of elements in the list")
field(:total, :integer, description: "The total number of groups in the list")
end
@desc "The list of visibility options for a group"
@ -134,25 +149,33 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
object :group_queries do
@desc "Get all groups"
field :groups, :paginated_group_list do
arg(:preferred_username, :string, default_value: "")
arg(:name, :string, default_value: "")
arg(:domain, :string, default_value: "")
arg(:local, :boolean, default_value: true)
arg(:suspended, :boolean, default_value: false)
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
arg(:preferred_username, :string, default_value: "", description: "Filter by username")
arg(:name, :string, default_value: "", description: "Filter by name")
arg(:domain, :string, default_value: "", description: "Filter by domain")
arg(:local, :boolean,
default_value: true,
description: "Filter whether group is local or not"
)
arg(:suspended, :boolean, default_value: false, description: "Filter by suspended status")
arg(:page, :integer, default_value: 1, description: "The page in the paginated group list")
arg(:limit, :integer, default_value: 10, description: "The limit of groups per page")
resolve(&Group.list_groups/3)
end
@desc "Get a group by its ID"
field :get_group, :group do
arg(:id, non_null(:id))
arg(:id, non_null(:id), description: "The group ID")
resolve(&Group.get_group/3)
end
@desc "Get a group by its preferred username"
field :group, :group do
arg(:preferred_username, non_null(:string))
arg(:preferred_username, non_null(:string),
description: "The group preferred_username, eventually containing their domain if remote"
)
resolve(&Group.find_group/3)
end
end
@ -162,8 +185,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
field :create_group, :group do
arg(:preferred_username, non_null(:string), description: "The name for the group")
arg(:creator_actor_id, non_null(:id), description: "The identity that creates the group")
arg(:name, :string, description: "The displayed name for the group")
arg(:summary, :string, description: "The summary for the group", default_value: "")
@ -182,7 +203,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
"The banner for the group, either as an object or directly the ID of an existing Picture"
)
arg(:physical_address, :address_input)
arg(:physical_address, :address_input, description: "The physical address for the group")
resolve(&Group.create_group/3)
end
@ -210,14 +231,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
"The banner for the group, either as an object or directly the ID of an existing Picture"
)
arg(:physical_address, :address_input)
arg(:physical_address, :address_input, description: "The physical address for the group")
resolve(&Group.update_group/3)
end
@desc "Delete a group"
field :delete_group, :deleted_object do
arg(:group_id, non_null(:id))
arg(:group_id, non_null(:id), description: "The group ID")
resolve(&Group.delete_group/3)
end