Merge branch 'more-fixes' into 'master'

Even more fixes

See merge request framasoft/mobilizon!472
This commit is contained in:
Thomas Citharel 2020-06-15 20:04:03 +02:00
commit 061f51447e
17 changed files with 213 additions and 67 deletions

View file

@ -7,31 +7,11 @@
<date-calendar-icon :date="participation.event.beginsOn" /> <date-calendar-icon :date="participation.event.beginsOn" />
</div> </div>
<router-link :to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }"> <router-link :to="{ name: RouteName.EVENT, params: { uuid: participation.event.uuid } }">
<h2 class="title">{{ participation.event.title }}</h2> <h3 class="title">{{ participation.event.title }}</h3>
</router-link> </router-link>
</div> </div>
<div class="participation-actor has-text-grey"> <div class="participation-actor has-text-grey">
<span
v-if="
participation.event.physicalAddress && participation.event.physicalAddress.locality
"
>{{ participation.event.physicalAddress.locality }} -</span
>
<span> <span>
<span>
{{
$t("Organized by {name}", {
name: participation.event.organizerActor.displayName(),
})
}}
</span>
<span v-if="participation.role === ParticipantRole.PARTICIPANT">
{{ $t("Going as {name}", { name: participation.actor.displayName() }) }}
</span>
</span>
</div>
<div class="columns">
<span class="column is-narrow">
<b-icon icon="earth" v-if="participation.event.visibility === EventVisibility.PUBLIC" /> <b-icon icon="earth" v-if="participation.event.visibility === EventVisibility.PUBLIC" />
<b-icon <b-icon
icon="link" icon="link"
@ -42,7 +22,42 @@
v-else-if="participation.event.visibility === EventVisibility.PRIVATE" v-else-if="participation.event.visibility === EventVisibility.PRIVATE"
/> />
</span> </span>
<span class="column is-narrow participant-stats"> <span
v-if="
participation.event.physicalAddress && participation.event.physicalAddress.locality
"
>{{ participation.event.physicalAddress.locality }} -</span
>
<span>
<i18n tag="span" path="Organized by {name}">
<popover-actor-card
slot="name"
:actor="participation.event.organizerActor"
:inline="true"
>
{{ participation.event.organizerActor.displayName() }}
</popover-actor-card>
</i18n>
<i18n
v-if="participation.role === ParticipantRole.PARTICIPANT"
path="Going as {name}"
tag="span"
>
<popover-actor-card slot="name" :actor="participation.actor" :inline="true">
{{ participation.actor.displayName() }}
</popover-actor-card>
</i18n>
</span>
</div>
<div>
<span
class="participant-stats"
v-if="
![ParticipantRole.PARTICIPANT, ParticipantRole.NOT_APPROVED].includes(
participation.role
)
"
>
<span v-if="participation.event.options.maximumAttendeeCapacity !== 0"> <span v-if="participation.event.options.maximumAttendeeCapacity !== 0">
{{ {{
$t("{approved} / {total} seats", { $t("{approved} / {total} seats", {
@ -176,6 +191,7 @@ import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import EventMixin from "../../mixins/event"; import EventMixin from "../../mixins/event";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { changeIdentity } from "../../utils/auth"; import { changeIdentity } from "../../utils/auth";
import PopoverActorCard from "../Account/PopoverActorCard.vue";
const defaultOptions: IEventCardOptions = { const defaultOptions: IEventCardOptions = {
hideDate: true, hideDate: true,
@ -187,6 +203,7 @@ const defaultOptions: IEventCardOptions = {
@Component({ @Component({
components: { components: {
DateCalendarIcon, DateCalendarIcon,
PopoverActorCard,
}, },
apollo: { apollo: {
currentActor: { currentActor: {

View file

@ -206,6 +206,7 @@ export const LOGGED_USER_PARTICIPATIONS = gql`
preferredUsername preferredUsername
name name
domain domain
summary
avatar { avatar {
url url
} }
@ -226,6 +227,7 @@ export const LOGGED_USER_PARTICIPATIONS = gql`
preferredUsername preferredUsername
name name
domain domain
summary
avatar { avatar {
url url
} }

View file

@ -184,6 +184,11 @@ export const LOGS = gql`
domain domain
name name
} }
... on User {
id
email
confirmedAt
}
} }
insertedAt insertedAt
} }

View file

@ -256,7 +256,7 @@
"On {date} from {startTime} to {endTime}": "On {date} from {startTime} to {endTime}", "On {date} from {startTime} to {endTime}": "On {date} from {startTime} to {endTime}",
"On {date} starting at {startTime}": "On {date} starting at {startTime}", "On {date} starting at {startTime}": "On {date} starting at {startTime}",
"On {date}": "On {date}", "On {date}": "On {date}",
"Only accessible through link and search (private)": "Only accessible through link and search (private)", "Only accessible through link (private)": "Only accessible through link (private)",
"Only alphanumeric characters and underscores are supported.": "Only alphanumeric characters and underscores are supported.", "Only alphanumeric characters and underscores are supported.": "Only alphanumeric characters and underscores are supported.",
"Open": "Open", "Open": "Open",
"Opened reports": "Opened reports", "Opened reports": "Opened reports",
@ -581,12 +581,12 @@
"Every hour": "Every hour", "Every hour": "Every hour",
"Every day": "Every day", "Every day": "Every day",
"report #{report_number}": "report #{report_number}", "report #{report_number}": "report #{report_number}",
"{actor} closed {report}": "{actor} closed {report}", "{moderator} closed {report}": "{moderator} closed {report}",
"a non-existent report": "a non-existent report", "a non-existent report": "a non-existent report",
"{actor} reopened {report}": "{actor} reopened {report}", "{moderator} reopened {report}": "{moderator} reopened {report}",
"{actor} marked {report} as resolved": "{actor} marked {report} as resolved", "{moderator} marked {report} as resolved": "{moderator} marked {report} as resolved",
"{actor} added a note on {report}": "{actor} added a note on {report}", "{moderator} added a note on {report}": "{moderator} added a note on {report}",
"{actor} deleted an event named \"{title}\"": "{actor} deleted an event named \"{title}\"", "{moderator} deleted an event named \"{title}\"": "{moderator} deleted an event named \"{title}\"",
"If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.": "If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.", "If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.": "If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.",
"change the world, one byte at a time": "change the world, one byte at a time", "change the world, one byte at a time": "change the world, one byte at a time",
"Concieved with care for humans": "Concieved with care for humans", "Concieved with care for humans": "Concieved with care for humans",
@ -623,7 +623,7 @@
"Participations": "Participations", "Participations": "Participations",
"Nothing to see here": "Nothing to see here", "Nothing to see here": "Nothing to see here",
"Not confirmed": "Not confirmed", "Not confirmed": "Not confirmed",
"{actor} suspended profile {profile}": "{actor} suspended profile {profile}", "{moderator} suspended profile {profile}": "{moderator} suspended profile {profile}",
"Suspend": "Suspend", "Suspend": "Suspend",
"Unsuspend": "Unsuspend", "Unsuspend": "Unsuspend",
"None": "None", "None": "None",
@ -642,5 +642,7 @@
"I agree to the {instanceRules} and {termsOfService}": "I agree to the {instanceRules} and {termsOfService}", "I agree to the {instanceRules} and {termsOfService}": "I agree to the {instanceRules} and {termsOfService}",
"This email is already used.": "This email is already used.", "This email is already used.": "This email is already used.",
"Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.", "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.",
"more than 1360 contributors": "more than 1360 contributors" "more than 1360 contributors": "more than 1360 contributors",
"{moderator} has unsuspended profile {profile}": "{moderator} has unsuspended profile {profile}",
"{moderator} has deleted user {user}": "{moderator} has deleted user {user}"
} }

View file

@ -321,7 +321,7 @@
"On {date} starting at {startTime}": "Le {date} à partir de {startTime}", "On {date} starting at {startTime}": "Le {date} à partir de {startTime}",
"One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont", "One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont",
"Ongoing tasks": "Tâches en cours", "Ongoing tasks": "Tâches en cours",
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privé)", "Only accessible through link (private)": "Uniquement accessible par lien (privé)",
"Only alphanumeric characters and underscores are supported.": "Seuls les caractères alphanumériques et les tirets bas sont acceptés.", "Only alphanumeric characters and underscores are supported.": "Seuls les caractères alphanumériques et les tirets bas sont acceptés.",
"Open": "Ouvert", "Open": "Ouvert",
"Opened reports": "Signalements ouverts", "Opened reports": "Signalements ouverts",
@ -603,12 +603,12 @@
"Every hour": "À chaque heure", "Every hour": "À chaque heure",
"Every day": "Chaque jour", "Every day": "Chaque jour",
"report #{report_number}": "le signalement #{report_number}", "report #{report_number}": "le signalement #{report_number}",
"{actor} closed {report}": "{actor} a fermé {report}", "{moderator} closed {report}": "{moderator} a fermé {report}",
"a non-existent report": "un signalement non-existant", "a non-existent report": "un signalement non-existant",
"{actor} reopened {report}": "{actor} a réouvert {report}", "{moderator} reopened {report}": "{moderator} a réouvert {report}",
"{actor} marked {report} as resolved": "{actor} a marqué {report} comme résolu", "{moderator} marked {report} as resolved": "{moderator} a marqué {report} comme résolu",
"{actor} added a note on {report}": "{actor} a ajouté une note sur {report}", "{moderator} added a note on {report}": "{moderator} a ajouté une note sur {report}",
"{actor} deleted an event named \"{title}\"": "{actor} a supprimé un événement nommé \"{title}\"", "{moderator} deleted an event named \"{title}\"": "{moderator} a supprimé un événement nommé \"{title}\"",
"If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.": "Si la direction donnée par léquipe de développement ne vous convient pas, vous avez légalement le droit de créer votre version du logiciel avec vos propres choix de gouvernance.", "If the direction given by the development team does not suit you, you have the legal right to create your own version of the software, with your own governance choices.": "Si la direction donnée par léquipe de développement ne vous convient pas, vous avez légalement le droit de créer votre version du logiciel avec vos propres choix de gouvernance.",
"change the world, one byte at a time": "changer le monde, un octet à la fois", "change the world, one byte at a time": "changer le monde, un octet à la fois",
"Concieved with care for humans": "Conçu avec soin pour les humains", "Concieved with care for humans": "Conçu avec soin pour les humains",
@ -621,7 +621,7 @@
"Mobilizon is under development, we will add new features to this site during regular updates, until the release of <b>version 1 of the software in the fall of 2020</b>.": "Mobilizon est en cours de développement, nous ajouterons de nouvelles fonctionnalités à ce site lors de mises à jour régulières, jusqu'à la publication de <b>la version 1 du logiciel à l'automne 2020</b>.", "Mobilizon is under development, we will add new features to this site during regular updates, until the release of <b>version 1 of the software in the fall of 2020</b>.": "Mobilizon est en cours de développement, nous ajouterons de nouvelles fonctionnalités à ce site lors de mises à jour régulières, jusqu'à la publication de <b>la version 1 du logiciel à l'automne 2020</b>.",
"To activate more notifications, head over to the notification settings.": "Pour activer plus de notifications, rendez-vous dans vos paramètres de notification.", "To activate more notifications, head over to the notification settings.": "Pour activer plus de notifications, rendez-vous dans vos paramètres de notification.",
"Manage my notifications": "Gérer mes notifications", "Manage my notifications": "Gérer mes notifications",
"We use your timezone to make sure you get notifications for an event at the correct time.": "Nous utilisons votre fuseau hoaire pour nous assurer que vous recevez les notifications pour un événement au bon moment.", "We use your timezone to make sure you get notifications for an event at the correct time.": "Nous utilisons votre fuseau horaire pour nous assurer que vous recevez les notifications pour un événement au bon moment.",
"Your timezone was detected as {timezone}.": "Votre fuseau horaire a été détecté en tant que {timezone}.", "Your timezone was detected as {timezone}.": "Votre fuseau horaire a été détecté en tant que {timezone}.",
"Manage my settings": "Gérer mes paramètres", "Manage my settings": "Gérer mes paramètres",
"Let's define a few settings": "Définissons quelques paramètres", "Let's define a few settings": "Définissons quelques paramètres",
@ -646,7 +646,7 @@
"Participations": "Participations", "Participations": "Participations",
"Nothing to see here": "Il n'y a rien à voir ici", "Nothing to see here": "Il n'y a rien à voir ici",
"Not confirmed": "Non confirmé·e", "Not confirmed": "Non confirmé·e",
"{actor} suspended profile {profile}": "{actor} a suspendu le profil {profile}", "{moderator} suspended profile {profile}": "{moderator} a suspendu le profil {profile}",
"Suspend": "Suspendre", "Suspend": "Suspendre",
"Unsuspend": "Annuler la suspension", "Unsuspend": "Annuler la suspension",
"None": "Aucun", "None": "Aucun",
@ -665,5 +665,7 @@
"I agree to the {instanceRules} and {termsOfService}": "J'accepte les {instanceRules} et les {termsOfService}", "I agree to the {instanceRules} and {termsOfService}": "J'accepte les {instanceRules} et les {termsOfService}",
"This email is already used.": "Cette adresse email est déjà utilisée.", "This email is already used.": "Cette adresse email est déjà utilisée.",
"Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Propulsé par {mobilizon}. © 2018 - {date} Les contributeur·ices Mobilizon - Fait avec le soutien financier de {contributors}.", "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Propulsé par {mobilizon}. © 2018 - {date} Les contributeur·ices Mobilizon - Fait avec le soutien financier de {contributors}.",
"more than 1360 contributors": "plus de 1360 contributeur·ices" "more than 1360 contributors": "plus de 1360 contributeur·ices",
"{moderator} has unsuspended profile {profile}": "{moderator} a annulé la suspension de {profile}",
"{moderator} has deleted user {user}": "{moderator} a supprimé l'utilisateur·ice {user}"
} }

View file

@ -41,6 +41,7 @@ export enum ActionLogAction {
COMMENT_DELETION = "COMMENT_DELETION", COMMENT_DELETION = "COMMENT_DELETION",
ACTOR_SUSPENSION = "ACTOR_SUSPENSION", ACTOR_SUSPENSION = "ACTOR_SUSPENSION",
ACTOR_UNSUSPENSION = "ACTOR_UNSUSPENSION", ACTOR_UNSUSPENSION = "ACTOR_UNSUSPENSION",
USER_DELETION = "USER_DELETION",
} }
export interface IActionLog { export interface IActionLog {

View file

@ -35,7 +35,12 @@
</router-link> </router-link>
</b-table-column> </b-table-column>
<b-table-column field="confirmedAt" :label="$t('Confirmed at')" :centered="true"> <b-table-column field="confirmedAt" :label="$t('Confirmed at')" :centered="true">
{{ props.row.confirmedAt | formatDateTimeString }} <template v-if="props.row.confirmedAt">
{{ props.row.confirmedAt | formatDateTimeString }}
</template>
<template v-else>
{{ $t("Not confirmed") }}
</template>
</b-table-column> </b-table-column>
<b-table-column field="locale" :label="$t('Language')" :centered="true"> <b-table-column field="locale" :label="$t('Language')" :centered="true">
{{ props.row.locale }} {{ props.row.locale }}

View file

@ -92,7 +92,7 @@
v-model="event.visibility" v-model="event.visibility"
name="eventVisibility" name="eventVisibility"
:native-value="EventVisibility.UNLISTED" :native-value="EventVisibility.UNLISTED"
>{{ $t("Only accessible through link and search (private)") }}</b-radio >{{ $t("Only accessible through link (private)") }}</b-radio
> >
</div> </div>
<!-- <div class="field"> <!-- <div class="field">

View file

@ -7,10 +7,10 @@
<i18n <i18n
v-if="log.action === ActionLogAction.REPORT_UPDATE_CLOSED" v-if="log.action === ActionLogAction.REPORT_UPDATE_CLOSED"
tag="span" tag="span"
path="{actor} closed {report}" path="{moderator} closed {report}"
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -23,10 +23,10 @@
<i18n <i18n
v-else-if="log.action === ActionLogAction.REPORT_UPDATE_OPENED" v-else-if="log.action === ActionLogAction.REPORT_UPDATE_OPENED"
tag="span" tag="span"
path="{actor} reopened {report}" path="{moderator} reopened {report}"
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -39,10 +39,10 @@
<i18n <i18n
v-else-if="log.action === ActionLogAction.REPORT_UPDATE_RESOLVED" v-else-if="log.action === ActionLogAction.REPORT_UPDATE_RESOLVED"
tag="span" tag="span"
path="{actor} marked {report} as resolved" path="{moderator} marked {report} as resolved"
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -55,10 +55,10 @@
<i18n <i18n
v-else-if="log.action === ActionLogAction.NOTE_CREATION" v-else-if="log.action === ActionLogAction.NOTE_CREATION"
tag="span" tag="span"
path="{actor} added a note on {report}" path="{moderator} added a note on {report}"
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -73,10 +73,10 @@
<i18n <i18n
v-else-if="log.action === ActionLogAction.EVENT_DELETION" v-else-if="log.action === ActionLogAction.EVENT_DELETION"
tag="span" tag="span"
path='{actor} deleted an event named "{title}"' path='{moderator} deleted an event named "{title}"'
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -85,10 +85,10 @@
<i18n <i18n
v-else-if="log.action === ActionLogAction.ACTOR_SUSPENSION" v-else-if="log.action === ActionLogAction.ACTOR_SUSPENSION"
tag="span" tag="span"
path="{actor} suspended profile {profile}" path="{moderator} suspended profile {profile}"
> >
<router-link <router-link
slot="actor" slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }" :to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link >@{{ log.actor.preferredUsername }}</router-link
> >
@ -98,6 +98,40 @@
>{{ displayNameAndUsername(log.object) }} >{{ displayNameAndUsername(log.object) }}
</router-link> </router-link>
</i18n> </i18n>
<i18n
v-else-if="log.action === ActionLogAction.ACTOR_UNSUSPENSION"
tag="span"
path="{moderator} has unsuspended profile {profile}"
>
<router-link
slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link
>
<router-link
slot="profile"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.object.id } }"
>{{ displayNameAndUsername(log.object) }}
</router-link>
</i18n>
<i18n
v-else-if="log.action === ActionLogAction.USER_DELETION"
tag="span"
path="{moderator} has deleted user {user}"
>
<router-link
slot="moderator"
:to="{ name: RouteName.ADMIN_PROFILE, params: { id: log.actor.id } }"
>@{{ log.actor.preferredUsername }}</router-link
>
<router-link
v-if="log.object.confirmedAt"
slot="user"
:to="{ name: RouteName.ADMIN_USER_PROFILE, params: { id: log.object.id } }"
>{{ log.object.email }}
</router-link>
<b v-else slot="user">{{ log.object.email }}</b>
</i18n>
<br /> <br />
<small>{{ log.insertedAt | formatDateTimeString }}</small> <small>{{ log.insertedAt | formatDateTimeString }}</small>
</div> </div>

View file

@ -111,6 +111,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
} }
end end
defp transform_action_log(User, :delete, %ActionLog{changes: changes}) do
%{
action: :user_deletion,
object: convert_changes_to_struct(User, changes)
}
end
# Changes are stored as %{"key" => "value"} so we need to convert them back as struct # Changes are stored as %{"key" => "value"} so we need to convert them back as struct
defp convert_changes_to_struct(struct, %{"report_id" => _report_id} = changes) do defp convert_changes_to_struct(struct, %{"report_id" => _report_id} = changes) do
with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}), with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}),

View file

@ -110,6 +110,29 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:ok, stats.participant + stats.moderator + stats.administrator + stats.creator} {:ok, stats.participant + stats.moderator + stats.administrator + stats.creator}
end end
def stats_participants(
%Event{participant_stats: %EventParticipantStats{} = stats, id: event_id} = _event,
_args,
%{context: %{current_user: %User{id: user_id} = _user}} = _resolution
) do
if Events.is_user_moderator_for_event?(user_id, event_id) do
stats =
Map.put(
stats,
:going,
stats.participant + stats.moderator + stats.administrator + stats.creator
)
{:ok, stats}
else
{:ok, %EventParticipantStats{}}
end
end
def stats_participants(_event, _args, _resolution) do
{:ok, %EventParticipantStats{}}
end
@doc """ @doc """
List related events List related events
""" """

View file

@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
import Mobilizon.Users.Guards import Mobilizon.Users.Guards
alias Mobilizon.{Actors, Config, Events, Users} alias Mobilizon.{Actors, Admin, Config, Events, Users}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto alias Mobilizon.Crypto
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
@ -390,11 +390,20 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end end
def delete_account(_parent, %{user_id: user_id}, %{ def delete_account(_parent, %{user_id: user_id}, %{
context: %{current_user: %User{role: role}} context: %{current_user: %User{role: role} = moderator_user}
}) })
when is_moderator(role) do when is_moderator(role) do
with %User{} = user <- Users.get_user(user_id) do with {:moderator_actor, %Actor{} = moderator_actor} <-
do_delete_account(%User{} = user) {:moderator_actor, Users.get_actor_for_user(moderator_user)},
%User{disabled: false} = user <- Users.get_user(user_id),
{:ok, %User{}} <- do_delete_account(%User{} = user) do
Admin.log_action(moderator_actor, "delete", user)
else
{:moderator_actor, nil} ->
{:error, "No actor found for the moderator user"}
%User{disabled: true} ->
{:error, "User already disabled"}
end end
end end

View file

@ -9,6 +9,7 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
alias Mobilizon.Conversations.Comment alias Mobilizon.Conversations.Comment
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Reports.{Note, Report} alias Mobilizon.Reports.{Note, Report}
alias Mobilizon.Users.User
alias Mobilizon.GraphQL.Resolvers.Admin alias Mobilizon.GraphQL.Resolvers.Admin
@ -32,6 +33,7 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
value(:event_update) value(:event_update)
value(:actor_suspension) value(:actor_suspension)
value(:actor_unsuspension) value(:actor_unsuspension)
value(:user_deletion)
end end
@desc "The objects that can be in an action log" @desc "The objects that can be in an action log"
@ -54,6 +56,9 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
%Actor{type: "Person"}, _ -> %Actor{type: "Person"}, _ ->
:person :person
%User{}, _ ->
:user
_, _ -> _, _ ->
nil nil
end) end)

View file

@ -63,7 +63,10 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
field(:draft, :boolean, description: "Whether or not the event is a draft") field(:draft, :boolean, description: "Whether or not the event is a draft")
field(:participant_stats, :participant_stats) field(:participant_stats, :participant_stats,
description: "Statistics on the event",
resolve: &Event.stats_participants/3
)
field(:participants, :paginated_participant_list, description: "The event's participants") do field(:participants, :paginated_participant_list, description: "The event's participants") do
arg(:page, :integer, default_value: 1) arg(:page, :integer, default_value: 1)
@ -121,11 +124,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
end end
object :participant_stats do object :participant_stats do
field(:going, :integer, field(:going, :integer, description: "The number of approved participants")
description: "The number of approved participants",
resolve: &Event.stats_participants_going/3
)
field(:not_approved, :integer, description: "The number of not approved participants") field(:not_approved, :integer, description: "The number of not approved participants")
field(:not_confirmed, :integer, description: "The number of not confirmed participants") field(:not_confirmed, :integer, description: "The number of not confirmed participants")
field(:rejected, :integer, description: "The number of rejected participants") field(:rejected, :integer, description: "The number of rejected participants")

View file

@ -15,7 +15,8 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
@desc "A local user of Mobilizon" @desc "A local user of Mobilizon"
object :user do object :user do
field(:id, non_null(:id), description: "The user's ID") interfaces([:action_log_object])
field(:id, :id, description: "The user's ID")
field(:email, non_null(:string), description: "The user's email") field(:email, non_null(:string), description: "The user's email")
field(:actors, non_null(list_of(:person)), field(:actors, non_null(list_of(:person)),

View file

@ -425,6 +425,16 @@ defmodule Mobilizon.Events do
|> Repo.all() |> Repo.all()
end end
@spec is_user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean
def is_user_moderator_for_event?(user_id, event_id) do
Participant
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|> where([p, _a], p.event_id == ^event_id)
|> where([_p, a], a.user_id == ^user_id)
|> where([p, _a], p.role == ^:creator)
|> Repo.exists?()
end
@doc """ @doc """
Finds close events to coordinates. Finds close events to coordinates.
Radius is in meters and defaults to 50km. Radius is in meters and defaults to 50km.
@ -741,7 +751,8 @@ defmodule Mobilizon.Events do
|> Repo.one() |> Repo.one()
end end
@default_participant_roles [:participant, :moderator, :administrator, :creator] @moderator_roles [:moderator, :administrator, :creator]
@default_participant_roles [:participant] ++ @moderator_roles
@doc """ @doc """
Returns the list of participants for an event. Returns the list of participants for an event.
@ -810,7 +821,7 @@ defmodule Mobilizon.Events do
where: where:
p.event_id == ^event_id and p.event_id == ^event_id and
p.actor_id == p.actor_id ==
^actor_id and p.role in ^[:moderator, :administrator, :creator] ^actor_id and p.role in ^@moderator_roles
) )
) == nil) ) == nil)
end end

View file

@ -554,7 +554,8 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
test "stats_participants_for_event/3 give the number of (un)approved participants", %{ test "stats_participants_for_event/3 give the number of (un)approved participants", %{
conn: conn, conn: conn,
actor: actor actor: actor,
user: user
} do } do
event = event =
@event @event
@ -577,6 +578,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
res = res =
conn conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "event")) |> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid) assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
@ -623,12 +625,33 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
res = res =
conn conn
|> auth_conn(user)
|> get("/api", AbsintheHelpers.query_skeleton(query, "event")) |> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid) assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid)
assert json_response(res, 200)["data"]["event"]["participantStats"]["going"] == 2 assert json_response(res, 200)["data"]["event"]["participantStats"]["going"] == 2
assert json_response(res, 200)["data"]["event"]["participantStats"]["notApproved"] == 1 assert json_response(res, 200)["data"]["event"]["participantStats"]["notApproved"] == 1
assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 1 assert json_response(res, 200)["data"]["event"]["participantStats"]["rejected"] == 1
query = """
{
event(uuid: "#{event.uuid}") {
uuid,
participantStats {
going,
notApproved,
rejected
}
}
}
"""
res =
conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "event"))
assert is_nil(json_response(res, 200)["errors"])
assert json_response(res, 200)["data"]["event"]["going"] == nil
end end
end end