From 27f2597b07bb891d94266b1b933345883c208485 Mon Sep 17 00:00:00 2001
From: Thomas Citharel
Date: Mon, 9 Sep 2019 09:31:08 +0200
Subject: [PATCH] Add admin dashboard, event reporting, moderation report
screens, moderation log
Close #156 and #158
Signed-off-by: Thomas Citharel
---
js/src/App.vue | 9 +-
js/src/apollo/user.ts | 5 +-
js/src/components/Event/EventCard.vue | 2 +-
js/src/components/Event/EventFullDate.vue | 11 +-
js/src/components/NavBar.vue | 13 +-
js/src/components/Report/ReportCard.vue | 45 +++
js/src/components/Report/ReportModal.vue | 101 +++++++
js/src/constants.ts | 1 +
js/src/filters/datetime.ts | 19 ++
js/src/filters/index.ts | 9 +
js/src/graphql/actor.ts | 2 +-
js/src/graphql/admin.ts | 19 ++
js/src/graphql/auth.ts | 3 +-
js/src/graphql/event.ts | 22 +-
js/src/graphql/feed_tokens.ts | 2 +-
js/src/graphql/report.ts | 161 +++++++++++
js/src/graphql/user.ts | 5 +-
js/src/main.ts | 2 +
js/src/router/admin.ts | 16 ++
js/src/router/index.ts | 6 +
js/src/router/moderation.ts | 34 +++
js/src/types/admin.model.ts | 9 +
js/src/types/current-user.model.ts | 7 +
js/src/types/report.model.ts | 47 +++
js/src/utils/auth.ts | 7 +-
js/src/views/Admin/Dashboard.vue | 73 +++++
js/src/views/Event/Event.vue | 58 +++-
js/src/views/Moderation/Logs.vue | 80 ++++++
js/src/views/Moderation/Report.vue | 271 ++++++++++++++++++
js/src/views/Moderation/ReportList.vue | 90 ++++++
js/src/views/User/Login.vue | 1 +
lib/mobilizon/admin.ex | 3 +-
lib/mobilizon/admin/action_log.ex | 12 +-
lib/mobilizon/application.ex | 15 +
lib/mobilizon/events/event_options.ex | 1 +
lib/mobilizon/reports.ex | 16 +-
lib/mobilizon/reports/note.ex | 1 +
lib/mobilizon/reports/report.ex | 4 +-
lib/mobilizon_web/api/events.ex | 9 +
lib/mobilizon_web/api/reports.ex | 16 +-
lib/mobilizon_web/api/utils.ex | 4 +-
.../controllers/node_info_controller.ex | 10 +-
lib/mobilizon_web/resolvers/admin.ex | 87 +++++-
lib/mobilizon_web/resolvers/event.ex | 36 ++-
lib/mobilizon_web/resolvers/group.ex | 12 +-
lib/mobilizon_web/resolvers/report.ex | 12 +-
lib/mobilizon_web/router.ex | 3 +
lib/mobilizon_web/schema.ex | 7 +-
lib/mobilizon_web/schema/actor.ex | 2 +-
lib/mobilizon_web/schema/actors/group.ex | 10 +-
lib/mobilizon_web/schema/actors/member.ex | 8 +-
lib/mobilizon_web/schema/actors/person.ex | 2 +-
lib/mobilizon_web/schema/address.ex | 4 +-
lib/mobilizon_web/schema/admin.ex | 29 +-
lib/mobilizon_web/schema/event.ex | 7 +-
lib/mobilizon_web/schema/events/feed_token.ex | 2 +-
.../schema/events/participant.ex | 8 +-
lib/mobilizon_web/schema/report.ex | 21 +-
lib/mobilizon_web/schema/user.ex | 8 +
.../templates/email/report.html.eex | 8 +-
.../templates/email/report.text.eex | 10 +-
lib/service/activity_pub/activity_pub.ex | 9 +-
lib/service/activity_pub/utils.ex | 4 +-
lib/service/admin/action_log_service.ex | 12 +-
lib/service/statistics.ex | 31 ++
schema.graphql | 94 ++++--
.../activity_pub/activity_pub_test.exs | 21 +-
.../service/admin/action_log_service_test.exs | 4 +-
test/mobilizon_web/api/report_test.exs | 10 +-
.../controllers/nodeinfo_controller_test.exs | 5 +-
.../resolvers/admin_resolver_test.exs | 62 +++-
.../resolvers/event_resolver_test.exs | 68 ++++-
.../resolvers/feed_token_resolver_test.exs | 11 +-
.../resolvers/group_resolver_test.exs | 2 +-
.../resolvers/member_resolver_test.exs | 8 +-
.../resolvers/participant_resolver_test.exs | 12 +-
.../resolvers/report_resolver_test.exs | 33 ++-
77 files changed, 1682 insertions(+), 201 deletions(-)
create mode 100644 js/src/components/Report/ReportCard.vue
create mode 100644 js/src/components/Report/ReportModal.vue
create mode 100644 js/src/filters/datetime.ts
create mode 100644 js/src/filters/index.ts
create mode 100644 js/src/graphql/admin.ts
create mode 100644 js/src/graphql/report.ts
create mode 100644 js/src/router/admin.ts
create mode 100644 js/src/router/moderation.ts
create mode 100644 js/src/types/admin.model.ts
create mode 100644 js/src/types/report.model.ts
create mode 100644 js/src/views/Admin/Dashboard.vue
create mode 100644 js/src/views/Moderation/Logs.vue
create mode 100644 js/src/views/Moderation/Report.vue
create mode 100644 js/src/views/Moderation/ReportList.vue
create mode 100644 lib/service/statistics.ex
diff --git a/js/src/App.vue b/js/src/App.vue
index 73839d96e..cf47cef3b 100644
--- a/js/src/App.vue
+++ b/js/src/App.vue
@@ -11,7 +11,7 @@
diff --git a/js/src/components/Report/ReportCard.vue b/js/src/components/Report/ReportCard.vue
new file mode 100644
index 000000000..e0bc9d46d
--- /dev/null
+++ b/js/src/components/Report/ReportCard.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
Reported by
@{{ report.reporter.preferredUsername }}
+
+
+
{{ report.event.title }}
+
+
{{ report.reportContent }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/components/Report/ReportModal.vue b/js/src/components/Report/ReportModal.vue
new file mode 100644
index 000000000..5a25e6770
--- /dev/null
+++ b/js/src/components/Report/ReportModal.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/constants.ts b/js/src/constants.ts
index 79dbe270f..e815c8b18 100644
--- a/js/src/constants.ts
+++ b/js/src/constants.ts
@@ -3,3 +3,4 @@ export const AUTH_REFRESH_TOKEN = 'auth-refresh-token';
export const AUTH_USER_ID = 'auth-user-id';
export const AUTH_USER_EMAIL = 'auth-user-email';
export const AUTH_USER_ACTOR = 'auth-user-actor';
+export const AUTH_USER_ROLE = 'auth-user-role';
diff --git a/js/src/filters/datetime.ts b/js/src/filters/datetime.ts
new file mode 100644
index 000000000..89b149476
--- /dev/null
+++ b/js/src/filters/datetime.ts
@@ -0,0 +1,19 @@
+function parseDateTime(value: string): Date {
+ return new Date(value);
+}
+
+function formatDateString(value: string): string {
+ return parseDateTime(value).toLocaleString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
+}
+
+function formatTimeString(value: string): string {
+ return parseDateTime(value).toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' });
+}
+
+function formatDateTimeString(value: string): string {
+ return parseDateTime(value).toLocaleTimeString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
+}
+
+
+
+export { formatDateString, formatTimeString, formatDateTimeString };
diff --git a/js/src/filters/index.ts b/js/src/filters/index.ts
new file mode 100644
index 000000000..89a7267a5
--- /dev/null
+++ b/js/src/filters/index.ts
@@ -0,0 +1,9 @@
+import { formatDateString, formatTimeString, formatDateTimeString } from './datetime';
+
+export default {
+ install(vue) {
+ vue.filter('formatDateString', formatDateString);
+ vue.filter('formatTimeString', formatTimeString);
+ vue.filter('formatDateTimeString', formatDateTimeString);
+ },
+};
diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts
index 7dc92ca20..88d99de49 100644
--- a/js/src/graphql/actor.ts
+++ b/js/src/graphql/actor.ts
@@ -177,7 +177,7 @@ query($name:String!) {
export const CREATE_GROUP = gql`
mutation CreateGroup(
- $creatorActorId: Int!,
+ $creatorActorId: ID!,
$preferredUsername: String!,
$name: String!,
$summary: String,
diff --git a/js/src/graphql/admin.ts b/js/src/graphql/admin.ts
new file mode 100644
index 000000000..d646bb565
--- /dev/null
+++ b/js/src/graphql/admin.ts
@@ -0,0 +1,19 @@
+import gql from 'graphql-tag';
+
+export const DASHBOARD = gql`
+ query {
+ dashboard {
+ lastPublicEventPublished {
+ title,
+ picture {
+ alt
+ url
+ },
+ },
+ numberOfUsers,
+ numberOfEvents,
+ numberOfComments,
+ numberOfReports
+ }
+ }
+ `;
diff --git a/js/src/graphql/auth.ts b/js/src/graphql/auth.ts
index 580236196..438c46c18 100644
--- a/js/src/graphql/auth.ts
+++ b/js/src/graphql/auth.ts
@@ -7,7 +7,8 @@ mutation Login($email: String!, $password: String!) {
refreshToken,
user {
id,
- email
+ email,
+ role
}
},
}
diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts
index 0c4af8f67..d6e98cff7 100644
--- a/js/src/graphql/event.ts
+++ b/js/src/graphql/event.ts
@@ -70,8 +70,8 @@ export const FETCH_EVENT = gql`
},
publishAt,
category,
- online_address,
- phone_address,
+ onlineAddress,
+ phoneAddress,
physicalAddress {
${physicalAddressQuery}
}
@@ -218,8 +218,8 @@ export const CREATE_EVENT = gql`
},
publishAt,
category,
- online_address,
- phone_address,
+ onlineAddress,
+ phoneAddress,
physicalAddress {
${physicalAddressQuery}
},
@@ -240,7 +240,7 @@ export const EDIT_EVENT = gql`
$description: String,
$beginsOn: DateTime,
$endsOn: DateTime,
- $status: Int,
+ $status: EventStatus,
$visibility: EventVisibility
$tags: [String],
$picture: PictureInput,
@@ -280,8 +280,8 @@ export const EDIT_EVENT = gql`
},
publishAt,
category,
- online_address,
- phone_address,
+ onlineAddress,
+ phoneAddress,
physicalAddress {
${physicalAddressQuery}
},
@@ -296,7 +296,7 @@ export const EDIT_EVENT = gql`
`;
export const JOIN_EVENT = gql`
- mutation JoinEvent($eventId: Int!, $actorId: Int!) {
+ mutation JoinEvent($eventId: ID!, $actorId: ID!) {
joinEvent(
eventId: $eventId,
actorId: $actorId
@@ -307,7 +307,7 @@ export const JOIN_EVENT = gql`
`;
export const LEAVE_EVENT = gql`
- mutation LeaveEvent($eventId: Int!, $actorId: Int!) {
+ mutation LeaveEvent($eventId: ID!, $actorId: ID!) {
leaveEvent(
eventId: $eventId,
actorId: $actorId
@@ -320,9 +320,9 @@ export const LEAVE_EVENT = gql`
`;
export const DELETE_EVENT = gql`
- mutation DeleteEvent($id: Int!, $actorId: Int!) {
+ mutation DeleteEvent($eventId: ID!, $actorId: ID!) {
deleteEvent(
- eventId: $id,
+ eventId: $eventId,
actorId: $actorId
) {
id
diff --git a/js/src/graphql/feed_tokens.ts b/js/src/graphql/feed_tokens.ts
index e43c11ca9..eeb17f5cd 100644
--- a/js/src/graphql/feed_tokens.ts
+++ b/js/src/graphql/feed_tokens.ts
@@ -12,7 +12,7 @@ query {
}`;
export const CREATE_FEED_TOKEN_ACTOR = gql`
-mutation createFeedToken($actor_id: Int!) {
+mutation createFeedToken($actor_id: ID!) {
createFeedToken(actorId: $actor_id) {
token,
actor {
diff --git a/js/src/graphql/report.ts b/js/src/graphql/report.ts
new file mode 100644
index 000000000..9cd58c2f1
--- /dev/null
+++ b/js/src/graphql/report.ts
@@ -0,0 +1,161 @@
+import gql from 'graphql-tag';
+
+export const REPORTS = gql`
+ query Reports($status: ReportStatus) {
+ reports(status: $status) {
+ id,
+ reported {
+ id,
+ preferredUsername,
+ name,
+ avatar {
+ url
+ }
+ },
+ reporter {
+ id,
+ preferredUsername,
+ name,
+ avatar {
+ url
+ }
+ },
+ event {
+ id,
+ uuid,
+ title,
+ picture {
+ url
+ }
+ },
+ status
+ }
+ }
+`;
+
+const REPORT_FRAGMENT = gql`
+ fragment ReportFragment on Report {
+ id,
+ reported {
+ id,
+ preferredUsername,
+ name,
+ avatar {
+ url
+ }
+ },
+ reporter {
+ id,
+ preferredUsername,
+ name,
+ avatar {
+ url
+ }
+ },
+ event {
+ id,
+ uuid,
+ title,
+ description,
+ picture {
+ url
+ }
+ },
+ notes {
+ id,
+ content
+ moderator {
+ preferredUsername,
+ name,
+ avatar {
+ url
+ }
+ },
+ insertedAt
+ },
+ insertedAt,
+ updatedAt,
+ status,
+ content
+ }
+`;
+
+export const REPORT = gql`
+ query Report($id: ID!) {
+ report(id: $id) {
+ ...ReportFragment
+ }
+ }
+ ${REPORT_FRAGMENT}
+`;
+
+export const CREATE_REPORT = gql`
+ mutation CreateReport(
+ $eventId: ID!,
+ $reporterActorId: ID!,
+ $reportedActorId: ID!,
+ $content: String
+ ) {
+ createReport(eventId: $eventId, reporterActorId: $reporterActorId, reportedActorId: $reportedActorId, content: $content) {
+ id
+ }
+ }
+ `;
+
+export const UPDATE_REPORT = gql`
+ mutation UpdateReport(
+ $reportId: ID!,
+ $moderatorId: ID!,
+ $status: ReportStatus!
+ ) {
+ updateReportStatus(reportId: $reportId, moderatorId: $moderatorId, status: $status) {
+ ...ReportFragment
+ }
+ }
+ ${REPORT_FRAGMENT}
+`;
+
+export const CREATE_REPORT_NOTE = gql`
+ mutation CreateReportNote(
+ $reportId: ID!,
+ $moderatorId: ID!,
+ $content: String!
+ ) {
+ createReportNote(reportId: $reportId, moderatorId: $moderatorId, content: $content) {
+ id,
+ content,
+ insertedAt
+ }
+ }
+ `;
+
+export const LOGS = gql`
+ query {
+ actionLogs {
+ id,
+ action,
+ actor {
+ id,
+ preferredUsername
+ avatar {
+ url
+ }
+ },
+ object {
+ ...on Report {
+ id
+ },
+ ... on ReportNote {
+ report {
+ id,
+ }
+ }
+ ... on Event {
+ id,
+ title
+ }
+ },
+ insertedAt
+ }
+ }
+ `;
diff --git a/js/src/graphql/user.ts b/js/src/graphql/user.ts
index b23b5da32..5365ab600 100644
--- a/js/src/graphql/user.ts
+++ b/js/src/graphql/user.ts
@@ -31,12 +31,13 @@ query {
id,
email,
isLoggedIn,
+ role
}
}
`;
export const UPDATE_CURRENT_USER_CLIENT = gql`
-mutation UpdateCurrentUser($id: Int!, $email: String!, $isLoggedIn: Boolean!) {
- updateCurrentUser(id: $id, email: $email, isLoggedIn: $isLoggedIn) @client
+mutation UpdateCurrentUser($id: Int!, $email: String!, $isLoggedIn: Boolean!, $role: UserRole!) {
+ updateCurrentUser(id: $id, email: $email, isLoggedIn: $isLoggedIn, role: $role) @client
}
`;
diff --git a/js/src/main.ts b/js/src/main.ts
index 25a5e6068..7878e9605 100644
--- a/js/src/main.ts
+++ b/js/src/main.ts
@@ -7,6 +7,7 @@ import App from '@/App.vue';
import router from '@/router';
import { apolloProvider } from './vue-apollo';
import { NotifierPlugin } from '@/plugins/notifier';
+import filters from '@/filters';
const translations = require('@/i18n/translations.json');
@@ -14,6 +15,7 @@ Vue.config.productionTip = false;
Vue.use(Buefy);
Vue.use(NotifierPlugin);
+Vue.use(filters);
const language = (window.navigator as any).userLanguage || window.navigator.language;
diff --git a/js/src/router/admin.ts b/js/src/router/admin.ts
new file mode 100644
index 000000000..58ed37e64
--- /dev/null
+++ b/js/src/router/admin.ts
@@ -0,0 +1,16 @@
+import { RouteConfig } from 'vue-router';
+import Dashboard from '@/views/Admin/Dashboard.vue';
+
+export enum AdminRouteName {
+ DASHBOARD = 'Dashboard',
+}
+
+export const adminRoutes: RouteConfig[] = [
+ {
+ path: '/admin',
+ name: AdminRouteName.DASHBOARD,
+ component: Dashboard,
+ props: true,
+ meta: { requiredAuth: true },
+ },
+];
diff --git a/js/src/router/index.ts b/js/src/router/index.ts
index cca5e3726..44534e842 100644
--- a/js/src/router/index.ts
+++ b/js/src/router/index.ts
@@ -5,9 +5,11 @@ import Home from '@/views/Home.vue';
import { UserRouteName, userRoutes } from './user';
import { EventRouteName, eventRoutes } from '@/router/event';
import { ActorRouteName, actorRoutes, MyAccountRouteName } from '@/router/actor';
+import { AdminRouteName, adminRoutes } from '@/router/admin';
import { ErrorRouteName, errorRoutes } from '@/router/error';
import { authGuardIfNeeded } from '@/router/guards/auth-guard';
import Search from '@/views/Search.vue';
+import { ModerationRouteName, moderationRoutes } from '@/router/moderation';
Vue.use(Router);
@@ -35,6 +37,8 @@ export const RouteName = {
...EventRouteName,
...ActorRouteName,
...MyAccountRouteName,
+ ...AdminRouteName,
+ ...ModerationRouteName,
...ErrorRouteName,
};
@@ -46,6 +50,8 @@ const router = new Router({
...userRoutes,
...eventRoutes,
...actorRoutes,
+ ...adminRoutes,
+ ...moderationRoutes,
...errorRoutes,
{
path: '/search/:searchTerm/:searchType?',
diff --git a/js/src/router/moderation.ts b/js/src/router/moderation.ts
new file mode 100644
index 000000000..528345242
--- /dev/null
+++ b/js/src/router/moderation.ts
@@ -0,0 +1,34 @@
+import { RouteConfig } from 'vue-router';
+import ReportList from '@/views/Moderation/ReportList.vue';
+import Report from '@/views/Moderation/Report.vue';
+import Logs from '@/views/Moderation/Logs.vue';
+
+export enum ModerationRouteName {
+ REPORTS = 'Reports',
+ REPORT = 'Report',
+ LOGS = 'Logs',
+}
+
+export const moderationRoutes: RouteConfig[] = [
+ {
+ path: '/moderation/reports/:filter?',
+ name: ModerationRouteName.REPORTS,
+ component: ReportList,
+ props: true,
+ meta: { requiredAuth: true },
+ },
+ {
+ path: '/moderation/report/:reportId',
+ name: ModerationRouteName.REPORT,
+ component: Report,
+ props: true,
+ meta: { requiredAuth: true },
+ },
+ {
+ path: '/moderation/logs',
+ name: ModerationRouteName.LOGS,
+ component: Logs,
+ props: true,
+ meta: { requiredAuth: true },
+ },
+];
diff --git a/js/src/types/admin.model.ts b/js/src/types/admin.model.ts
new file mode 100644
index 000000000..226b2fb59
--- /dev/null
+++ b/js/src/types/admin.model.ts
@@ -0,0 +1,9 @@
+import { IEvent } from '@/types/event.model';
+
+export interface IDashboard {
+ lastPublicEventPublished: IEvent;
+ numberOfUsers: number;
+ numberOfEvents: number;
+ numberOfComments: number;
+ numberOfReports: number;
+}
diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts
index 0c1f6277b..0bafaac51 100644
--- a/js/src/types/current-user.model.ts
+++ b/js/src/types/current-user.model.ts
@@ -1,5 +1,12 @@
+export enum ICurrentUserRole {
+ USER = 'USER',
+ MODERATOR = 'MODERATOR',
+ ADMINISTRATOR = 'ADMINISTRATOR',
+}
+
export interface ICurrentUser {
id: number;
email: string;
isLoggedIn: boolean;
+ role: ICurrentUserRole;
}
diff --git a/js/src/types/report.model.ts b/js/src/types/report.model.ts
new file mode 100644
index 000000000..b1cea059f
--- /dev/null
+++ b/js/src/types/report.model.ts
@@ -0,0 +1,47 @@
+import { IActor, IPerson } from '@/types/actor';
+import { IEvent } from '@/types/event.model';
+
+export enum ReportStatusEnum {
+ OPEN = 'OPEN',
+ CLOSED = 'CLOSED',
+ RESOLVED = 'RESOLVED',
+}
+
+export interface IReport extends IActionLogObject {
+ id: string;
+ reported: IActor;
+ reporter: IPerson;
+ event?: IEvent;
+ content: string;
+ notes: IReportNote[];
+ insertedAt: Date;
+ updatedAt: Date;
+ status: ReportStatusEnum;
+}
+
+export interface IReportNote extends IActionLogObject{
+ id: string;
+ content: string;
+ moderator: IActor;
+}
+
+export interface IActionLogObject {
+ id: string;
+}
+
+export enum ActionLogAction {
+ NOTE_CREATION = 'NOTE_CREATION',
+ NOTE_DELETION = 'NOTE_DELETION',
+ REPORT_UPDATE_CLOSED = 'REPORT_UPDATE_CLOSED',
+ REPORT_UPDATE_OPENED = 'REPORT_UPDATE_OPENED',
+ REPORT_UPDATE_RESOLVED = 'REPORT_UPDATE_RESOLVED',
+ EVENT_DELETION = 'EVENT_DELETION',
+}
+
+export interface IActionLog {
+ id: string;
+ object: IReport|IReportNote|IEvent;
+ actor: IActor;
+ action: ActionLogAction;
+ insertedAt: Date;
+}
diff --git a/js/src/utils/auth.ts b/js/src/utils/auth.ts
index 29698bf7a..75ec0a835 100644
--- a/js/src/utils/auth.ts
+++ b/js/src/utils/auth.ts
@@ -1,12 +1,14 @@
-import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_EMAIL, AUTH_USER_ID } from '@/constants';
+import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_EMAIL, AUTH_USER_ID, AUTH_USER_ROLE } from '@/constants';
import { ILogin, IToken } from '@/types/login.model';
import { UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogout } from '@/vue-apollo';
import ApolloClient from 'apollo-client';
+import { ICurrentUserRole } from '@/types/current-user.model';
export function saveUserData(obj: ILogin) {
localStorage.setItem(AUTH_USER_ID, `${obj.user.id}`);
localStorage.setItem(AUTH_USER_EMAIL, obj.user.email);
+ localStorage.setItem(AUTH_USER_ROLE, obj.user.role);
saveTokenData(obj);
}
@@ -17,7 +19,7 @@ export function saveTokenData(obj: IToken) {
}
export function deleteUserData() {
- for (const key of [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN]) {
+ for (const key of [AUTH_USER_ID, AUTH_USER_EMAIL, AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN, AUTH_USER_ROLE]) {
localStorage.removeItem(key);
}
}
@@ -29,6 +31,7 @@ export function logout(apollo: ApolloClient) {
id: null,
email: null,
isLoggedIn: false,
+ role: ICurrentUserRole.USER,
},
});
diff --git a/js/src/views/Admin/Dashboard.vue b/js/src/views/Admin/Dashboard.vue
new file mode 100644
index 000000000..7628d1e66
--- /dev/null
+++ b/js/src/views/Admin/Dashboard.vue
@@ -0,0 +1,73 @@
+
+
+ Administration
+
+
+
+
+
+ {{ dashboard.numberOfEvents }}
+ événements publiés
+
+
+ {{ dashboard.numberOfComments}}
+ commentaires
+
+
+
+
+ {{ dashboard.numberOfUsers }}
+ utilisateurices
+
+
+
+ {{ dashboard.numberOfReports }}
+ signalements ouverts
+
+
+
+
+
+
+ Dernier événement publié
+ {{ dashboard.lastPublicEventPublished.title }}
+
+
+
+
+
+
+
+
+
+
Bienvenue sur votre espace d'administration
+
With even more content
+
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam semper diam at erat pulvinar, at pulvinar felis blandit. Vestibulum volutpat tellus diam, consequat gravida libero rhoncus ut. Morbi maximus, leo sit amet vehicula eleifend, nunc dui porta orci, quis semper odio felis ut quam.
+
Suspendisse varius ligula in molestie lacinia. Maecenas varius eget ligula a sagittis. Pellentesque interdum, nisl nec interdum maximus, augue diam porttitor lorem, et sollicitudin felis neque sit amet erat. Maecenas imperdiet felis nisi, fringilla luctus felis hendrerit sit amet. Aenean vitae gravida diam, finibus dignissim turpis. Sed eget varius ligula, at volutpat tortor.
+
Integer sollicitudin, tortor a mattis commodo, velit urna rhoncus erat, vitae congue lectus dolor consequat libero. Donec leo ligula, maximus et pellentesque sed, gravida a metus. Cras ullamcorper a nunc ac porta. Aliquam ut aliquet lacus, quis faucibus libero. Quisque non semper leo.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue
index 658bdaa73..eb21b4a87 100644
--- a/js/src/views/Event/Event.vue
+++ b/js/src/views/Event/Event.vue
@@ -53,8 +53,8 @@
@@ -241,6 +249,9 @@ import BIcon from 'buefy/src/components/icon/Icon.vue';
import EventCard from '@/components/Event/EventCard.vue';
import EventFullDate from '@/components/Event/EventFullDate.vue';
import ActorLink from '@/components/Account/ActorLink.vue';
+import ReportModal from '@/components/Report/ReportModal.vue';
+import { IReport } from '@/types/report.model';
+import { CREATE_REPORT } from '@/graphql/report';
@Component({
components: {
@@ -249,6 +260,7 @@ import ActorLink from '@/components/Account/ActorLink.vue';
EventCard,
BIcon,
DateCalendarIcon,
+ ReportModal,
// tslint:disable:space-in-parens
'map-leaflet': () => import(/* webpackChunkName: "map" */ '@/components/Map.vue'),
// tslint:enable
@@ -274,6 +286,7 @@ export default class Event extends Vue {
loggedPerson!: IPerson;
validationSent: boolean = false;
showMap: boolean = false;
+ isReportModalActive: boolean = false;
EventVisibility = EventVisibility;
@@ -285,24 +298,47 @@ export default class Event extends Vue {
type: 'is-danger',
title: this.$gettext('Delete event'),
message: this.$gettextInterpolate(
- `${prefix}` +
- 'Are you sure you want to delete this event? This action cannot be reverted. ' +
- 'To confirm, type your event title "%{eventTitle}"',
- { participants: this.event.participants.length, eventTitle: this.event.title },
+ `${prefix}` +
+ 'Are you sure you want to delete this event? This action cannot be reverted. ' +
+ 'To confirm, type your event title "%{eventTitle}"',
+ { participants: this.event.participants.length, eventTitle: this.event.title },
),
confirmText: this.$gettextInterpolate(
- 'Delete %{eventTitle}',
- { eventTitle: this.event.title },
+ 'Delete %{eventTitle}',
+ { eventTitle: this.event.title },
),
inputAttrs: {
placeholder: this.event.title,
pattern: this.event.title,
},
-
onConfirm: () => this.deleteEvent(),
});
}
+ async reportEvent(content: string, forward: boolean) {
+ this.isReportModalActive = false;
+ const eventTitle = this.event.title;
+ try {
+ await this.$apollo.mutate({
+ mutation: CREATE_REPORT,
+ variables: {
+ eventId: this.event.id,
+ reporterActorId: this.loggedPerson.id,
+ reportedActorId: this.event.organizerActor.id,
+ content,
+ },
+ });
+ this.$buefy.notification.open({
+ message: this.$gettextInterpolate('Event %{eventTitle} reported', { eventTitle }),
+ type: 'is-success',
+ position: 'is-bottom-right',
+ duration: 5000,
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
async joinEvent() {
try {
await this.$apollo.mutate<{ joinEvent: IParticipant }>({
@@ -408,7 +444,7 @@ export default class Event extends Vue {
await this.$apollo.mutate({
mutation: DELETE_EVENT,
variables: {
- id: this.event.id,
+ eventId: this.event.id,
actorId: this.loggedPerson.id,
},
});
diff --git a/js/src/views/Moderation/Logs.vue b/js/src/views/Moderation/Logs.vue
new file mode 100644
index 000000000..aae6f4c9e
--- /dev/null
+++ b/js/src/views/Moderation/Logs.vue
@@ -0,0 +1,80 @@
+import {ReportStatusEnum} from "@/types/report.model";
+
+
+
+
+
+
+
+
+
+
@{{ log.actor.preferredUsername }}
+
+ closed report #{{ log.object.id }}
+
+
+ reopened report #{{ log.object.id }}
+
+
+ marked report #{{ log.object.id }} as resolved
+
+
+ added a note on
+ report #{{ log.object.report.id }}
+ a non-existent report
+
+
+ deleted an event named « {{ log.object.title }} »
+
+
+
{{ log.insertedAt | formatDateTimeString }}
+
+
+
+
+
+ No moderation logs yet
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/views/Moderation/Report.vue b/js/src/views/Moderation/Report.vue
new file mode 100644
index 000000000..07c84bce4
--- /dev/null
+++ b/js/src/views/Moderation/Report.vue
@@ -0,0 +1,271 @@
+
+
+ {{ error }}
+
+
+
+ Dashboard
+ Reports
+ Report
+
+
+
+ Mark as resolved
+ Reopen
+ Close
+
+
+
+
+
+
+
+ Compte signalé
+
+
+ @{{ report.reported.preferredUsername }}
+
+
+
+
+ Signalé par
+
+
+ @{{ report.reporter.preferredUsername }}
+
+
+
+
+ Signalé
+ {{ report.insertedAt | formatDateTimeString }}
+
+
+ Mis à jour
+ {{ report.updatedAt | formatDateTimeString }}
+
+
+ Statut
+
+ Ouvert
+ Fermé
+ Résolu
+ Inconnu
+
+
+
+
+
+
+
+
+
+
{{ report.content }}
+
Pas de commentaire
+
+
+
+
+
+
+ {{ report.event.title }}
+
+
+
Edit
+
Delete
+
+
+
Notes
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/views/Moderation/ReportList.vue b/js/src/views/Moderation/ReportList.vue
new file mode 100644
index 000000000..5aa09f15a
--- /dev/null
+++ b/js/src/views/Moderation/ReportList.vue
@@ -0,0 +1,90 @@
+import {ReportStatusEnum} from "@/types/report.model";
+
+
+
+
+
+
+
+ Ouvert
+
+
+ Résolus
+
+
+ Fermés
+
+
+
+
+ No open reports yet
+ No resolved reports yet
+ No closed reports yet
+
+
+
+
+
\ No newline at end of file
diff --git a/js/src/views/User/Login.vue b/js/src/views/User/Login.vue
index 8778b5ab2..49ae7d5c4 100644
--- a/js/src/views/User/Login.vue
+++ b/js/src/views/User/Login.vue
@@ -143,6 +143,7 @@ export default class Login extends Vue {
id: data.login.user.id,
email: this.credentials.email,
isLoggedIn: true,
+ role: data.login.user.role,
},
});
diff --git a/lib/mobilizon/admin.ex b/lib/mobilizon/admin.ex
index fba5ee050..33c2286ea 100644
--- a/lib/mobilizon/admin.ex
+++ b/lib/mobilizon/admin.ex
@@ -22,7 +22,8 @@ defmodule Mobilizon.Admin do
def list_action_logs(page \\ nil, limit \\ nil) do
from(
r in ActionLog,
- preload: [:actor]
+ preload: [:actor],
+ order_by: [desc: :id]
)
|> paginate(page, limit)
|> Repo.all()
diff --git a/lib/mobilizon/admin/action_log.ex b/lib/mobilizon/admin/action_log.ex
index 2b3f80f7e..62977e4ea 100644
--- a/lib/mobilizon/admin/action_log.ex
+++ b/lib/mobilizon/admin/action_log.ex
@@ -1,3 +1,11 @@
+import EctoEnum
+
+defenum(Mobilizon.Admin.ActionLogAction, [
+ "update",
+ "create",
+ "delete"
+])
+
defmodule Mobilizon.Admin.ActionLog do
@moduledoc """
ActionLog entity schema
@@ -5,11 +13,13 @@ defmodule Mobilizon.Admin.ActionLog do
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Actors.Actor
+ alias Mobilizon.Admin.ActionLogAction
+ @timestamps_opts [type: :utc_datetime]
@required_attrs [:action, :target_type, :target_id, :changes, :actor_id]
schema "admin_action_logs" do
- field(:action, :string)
+ field(:action, ActionLogAction)
field(:target_type, :string)
field(:target_id, :integer)
field(:changes, :map)
diff --git a/lib/mobilizon/application.ex b/lib/mobilizon/application.ex
index 22bf699d8..294831bba 100644
--- a/lib/mobilizon/application.ex
+++ b/lib/mobilizon/application.ex
@@ -54,6 +54,21 @@ defmodule Mobilizon.Application do
],
id: :cache_ics
),
+ worker(
+ Cachex,
+ [
+ :statistics,
+ [
+ limit: 10,
+ expiration:
+ expiration(
+ default: :timer.minutes(60),
+ interval: :timer.seconds(60)
+ )
+ ]
+ ],
+ id: :cache_statistics
+ ),
worker(
Cachex,
[
diff --git a/lib/mobilizon/events/event_options.ex b/lib/mobilizon/events/event_options.ex
index c6a02fafd..7c552d822 100644
--- a/lib/mobilizon/events/event_options.ex
+++ b/lib/mobilizon/events/event_options.ex
@@ -42,6 +42,7 @@ defmodule Mobilizon.Events.EventOptions do
}
@primary_key false
+ @derive Jason.Encoder
embedded_schema do
field(:maximum_attendee_capacity, :integer)
field(:remaining_attendee_capacity, :integer)
diff --git a/lib/mobilizon/reports.ex b/lib/mobilizon/reports.ex
index 1bfaac7ac..6a00dc70e 100644
--- a/lib/mobilizon/reports.ex
+++ b/lib/mobilizon/reports.ex
@@ -30,16 +30,28 @@ defmodule Mobilizon.Reports do
"""
@spec list_reports(integer(), integer(), atom(), atom()) :: list(Report.t())
- def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
+ def list_reports(
+ page \\ nil,
+ limit \\ nil,
+ sort \\ :updated_at,
+ direction \\ :desc,
+ status \\ :open
+ ) do
from(
r in Report,
- preload: [:reported, :reporter, :manager, :event, :comments, :notes]
+ preload: [:reported, :reporter, :manager, :event, :comments, :notes],
+ where: r.status == ^status
)
|> paginate(page, limit)
|> sort(sort, direction)
|> Repo.all()
end
+ def count_opened_reports() do
+ query = from(r in Report, where: r.status == ^:open)
+ Repo.aggregate(query, :count, :id)
+ end
+
@doc """
Gets a single report.
diff --git a/lib/mobilizon/reports/note.ex b/lib/mobilizon/reports/note.ex
index a7d8ad30e..56d1993c7 100644
--- a/lib/mobilizon/reports/note.ex
+++ b/lib/mobilizon/reports/note.ex
@@ -7,6 +7,7 @@ defmodule Mobilizon.Reports.Note do
alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Report
+ @timestamps_opts [type: :utc_datetime]
@attrs [:content, :moderator_id, :report_id]
@derive {Jason.Encoder, only: [:content]}
diff --git a/lib/mobilizon/reports/report.ex b/lib/mobilizon/reports/report.ex
index 11d55e824..2d5b5b0cc 100644
--- a/lib/mobilizon/reports/report.ex
+++ b/lib/mobilizon/reports/report.ex
@@ -17,6 +17,8 @@ defmodule Mobilizon.Reports.Report do
alias Mobilizon.Actors.Actor
alias Mobilizon.Reports.Note
+ @timestamps_opts [type: :utc_datetime]
+
@derive {Jason.Encoder, only: [:status, :uri]}
schema "reports" do
field(:content, :string)
@@ -48,7 +50,7 @@ defmodule Mobilizon.Reports.Report do
def changeset(report, attrs) do
report
|> cast(attrs, [:content, :status, :uri, :reported_id, :reporter_id, :manager_id, :event_id])
- |> validate_required([:content, :uri, :reported_id, :reporter_id])
+ |> validate_required([:uri, :reported_id, :reporter_id])
end
def creation_changeset(report, attrs) do
diff --git a/lib/mobilizon_web/api/events.ex b/lib/mobilizon_web/api/events.ex
index f841d2597..6a9493a25 100644
--- a/lib/mobilizon_web/api/events.ex
+++ b/lib/mobilizon_web/api/events.ex
@@ -135,4 +135,13 @@ defmodule MobilizonWeb.API.Events do
}
end
end
+
+ @doc """
+ Trigger the deletion of an event
+
+ If the event is deleted by
+ """
+ def delete_event(%Event{} = event, federate \\ true) do
+ ActivityPub.delete(event, federate)
+ end
end
diff --git a/lib/mobilizon_web/api/reports.ex b/lib/mobilizon_web/api/reports.ex
index 3010c6ecb..0560c80f4 100644
--- a/lib/mobilizon_web/api/reports.ex
+++ b/lib/mobilizon_web/api/reports.ex
@@ -21,21 +21,17 @@ defmodule MobilizonWeb.API.Reports do
def report(
%{
reporter_actor_id: reporter_actor_id,
- reported_actor_id: reported_actor_id,
- event_id: event_id,
- comments_ids: comments_ids,
- report_content: report_content
+ reported_actor_id: reported_actor_id
} = args
) do
with {:reporter, %Actor{url: reporter_url} = _reporter_actor} <-
{:reporter, Actors.get_actor!(reporter_actor_id)},
{:reported, %Actor{url: reported_actor_url} = reported_actor} <-
{:reported, Actors.get_actor!(reported_actor_id)},
- {:ok, content} <- make_report_content_html(report_content),
- {:ok, event} <-
- if(event_id, do: Events.get_event(event_id), else: {:ok, nil}),
+ {:ok, content} <- args |> Map.get(:content, nil) |> make_report_content_text(),
+ {:ok, event} <- args |> Map.get(:event_id, nil) |> get_event(),
{:get_report_comments, comments_urls} <-
- get_report_comments(reported_actor, comments_ids),
+ get_report_comments(reported_actor, Map.get(args, :comments_ids, [])),
{:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} <-
{:make_activity,
ActivityPub.flag(%{
@@ -49,6 +45,7 @@ defmodule MobilizonWeb.API.Reports do
})} do
{:ok, activity, report}
else
+ {:make_activity, err} -> {:error, err}
{:error, err} -> {:error, err}
{:actor_id, %{}} -> {:error, "Valid `actor_id` required"}
{:reporter, nil} -> {:error, "Reporter Actor not found"}
@@ -56,6 +53,9 @@ defmodule MobilizonWeb.API.Reports do
end
end
+ defp get_event(nil), do: {:ok, nil}
+ defp get_event(event_id), do: Events.get_event(event_id)
+
@doc """
Update the state of a report
"""
diff --git a/lib/mobilizon_web/api/utils.ex b/lib/mobilizon_web/api/utils.ex
index 9de90689b..d54a1de22 100644
--- a/lib/mobilizon_web/api/utils.ex
+++ b/lib/mobilizon_web/api/utils.ex
@@ -122,9 +122,9 @@ defmodule MobilizonWeb.API.Utils do
# |> Formatter.html_escape("text/html")
# end
- def make_report_content_html(nil), do: {:ok, {nil, [], []}}
+ def make_report_content_text(nil), do: {:ok, nil}
- def make_report_content_html(comment) do
+ def make_report_content_text(comment) do
max_size = Mobilizon.CommonConfig.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do
diff --git a/lib/mobilizon_web/controllers/node_info_controller.ex b/lib/mobilizon_web/controllers/node_info_controller.ex
index 31bf6bc18..aad33df9f 100644
--- a/lib/mobilizon_web/controllers/node_info_controller.ex
+++ b/lib/mobilizon_web/controllers/node_info_controller.ex
@@ -6,8 +6,8 @@
defmodule MobilizonWeb.NodeInfoController do
use MobilizonWeb, :controller
- alias Mobilizon.{Events, Users}
alias Mobilizon.CommonConfig
+ alias Mobilizon.Service.Statistics
@instance Application.get_env(:mobilizon, :instance)
@node_info_supported_versions ["2.0", "2.1"]
@@ -34,7 +34,7 @@ defmodule MobilizonWeb.NodeInfoController do
response = %{
version: version,
software: %{
- name: "mobilizon",
+ name: "Mobilizon",
version: Keyword.get(@instance, :version)
},
protocols: ["activitypub"],
@@ -45,10 +45,10 @@ defmodule MobilizonWeb.NodeInfoController do
openRegistrations: CommonConfig.registrations_open?(),
usage: %{
users: %{
- total: Users.count_users()
+ total: Statistics.get_cached_value(:local_users)
},
- localPosts: Events.count_local_events(),
- localComments: Events.count_local_comments()
+ localPosts: Statistics.get_cached_value(:local_events),
+ localComments: Statistics.get_cached_value(:local_comments)
},
metadata: %{
nodeName: CommonConfig.instance_name(),
diff --git a/lib/mobilizon_web/resolvers/admin.ex b/lib/mobilizon_web/resolvers/admin.ex
index 4f9972428..944b50ddb 100644
--- a/lib/mobilizon_web/resolvers/admin.ex
+++ b/lib/mobilizon_web/resolvers/admin.ex
@@ -2,10 +2,13 @@ defmodule MobilizonWeb.Resolvers.Admin do
@moduledoc """
Handles the report-related GraphQL calls
"""
+ alias Mobilizon.Events
alias Mobilizon.Users.User
import Mobilizon.Users.Guards
alias Mobilizon.Admin.ActionLog
alias Mobilizon.Reports.{Report, Note}
+ alias Mobilizon.Events.Event
+ alias Mobilizon.Service.Statistics
def list_action_logs(_parent, %{page: page, limit: limit}, %{
context: %{current_user: %User{role: role}}
@@ -17,14 +20,19 @@ defmodule MobilizonWeb.Resolvers.Admin do
target_type: target_type,
action: action,
actor: actor,
- id: id
+ id: id,
+ inserted_at: inserted_at
} = action_log ->
- transform_action_log(target_type, action, action_log)
- |> Map.merge(%{
- actor: actor,
- id: id
- })
+ with data when is_map(data) <-
+ transform_action_log(String.to_existing_atom(target_type), action, action_log) do
+ Map.merge(data, %{
+ actor: actor,
+ id: id,
+ inserted_at: inserted_at
+ })
+ end
end)
+ |> Enum.filter(& &1)
{:ok, action_logs}
end
@@ -35,38 +43,87 @@ defmodule MobilizonWeb.Resolvers.Admin do
end
defp transform_action_log(
- "Elixir.Mobilizon.Reports.Report",
- "update",
+ Report,
+ :update,
%ActionLog{} = action_log
) do
- with %Report{status: status} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
+ with %Report{} = report <- Mobilizon.Reports.get_report(action_log.target_id) do
+ action =
+ case action_log do
+ %ActionLog{changes: %{"status" => "closed"}} -> :report_update_closed
+ %ActionLog{changes: %{"status" => "open"}} -> :report_update_opened
+ %ActionLog{changes: %{"status" => "resolved"}} -> :report_update_resolved
+ end
+
%{
- action: "report_update_" <> to_string(status),
+ action: action,
object: report
}
end
end
- defp transform_action_log("Elixir.Mobilizon.Reports.Note", "create", %ActionLog{
+ defp transform_action_log(Note, :create, %ActionLog{
changes: changes
}) do
%{
- action: "note_creation",
+ action: :note_creation,
object: convert_changes_to_struct(Note, changes)
}
end
- defp transform_action_log("Elixir.Mobilizon.Reports.Note", "delete", %ActionLog{
+ defp transform_action_log(Note, :delete, %ActionLog{
changes: changes
}) do
%{
- action: "note_deletion",
+ action: :note_deletion,
object: convert_changes_to_struct(Note, changes)
}
end
+ defp transform_action_log(Event, :delete, %ActionLog{
+ changes: changes
+ }) do
+ %{
+ action: :event_deletion,
+ object: convert_changes_to_struct(Event, changes)
+ }
+ end
+
# 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
+ with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}),
+ data <- Map.put(data, :report, Mobilizon.Reports.get_report(data.report_id)) do
+ struct(struct, data)
+ end
+ end
+
defp convert_changes_to_struct(struct, changes) do
- struct(struct, for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}))
+ with data <- for({key, val} <- changes, into: %{}, do: {String.to_atom(key), val}) do
+ struct(struct, data)
+ end
+ end
+
+ def get_dashboard(_parent, _args, %{
+ context: %{current_user: %User{role: role}}
+ })
+ when is_admin(role) do
+ last_public_event_published =
+ case Events.list_events(1, 1, :inserted_at, :desc) do
+ [event | _] -> event
+ _ -> nil
+ end
+
+ {:ok,
+ %{
+ number_of_users: Statistics.get_cached_value(:local_users),
+ number_of_events: Statistics.get_cached_value(:local_events),
+ number_of_comments: Statistics.get_cached_value(:local_comments),
+ number_of_reports: Mobilizon.Reports.count_opened_reports(),
+ last_public_event_published: last_public_event_published
+ }}
+ end
+
+ def get_dashboard(_parent, _args, _resolution) do
+ {:error, "You need to be logged-in and an administrator to access dashboard statistics"}
end
end
diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex
index 869a91e93..d5c4734fd 100644
--- a/lib/mobilizon_web/resolvers/event.ex
+++ b/lib/mobilizon_web/resolvers/event.ex
@@ -9,7 +9,10 @@ defmodule MobilizonWeb.Resolvers.Event do
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Media.Picture
alias Mobilizon.Users.User
+ alias Mobilizon.Actors
+ alias Mobilizon.Actors.Actor
alias MobilizonWeb.Resolvers.Person
+ import Mobilizon.Service.Admin.ActionLogService
# We limit the max number of events that can be retrieved
@event_max_limit 100
@@ -328,28 +331,43 @@ defmodule MobilizonWeb.Resolvers.Event do
%{event_id: event_id, actor_id: actor_id},
%{
context: %{
- current_user: user
+ current_user: %User{role: role} = user
}
}
) do
- with {:ok, %Event{} = event} <- Mobilizon.Events.get_event(event_id),
- {:is_owned, true, _} <- User.owns_actor(user, actor_id),
- {:event_can_be_managed, true} <- Event.can_event_be_managed_by(event, actor_id),
- event <- Mobilizon.Events.delete_event!(event) do
- {:ok, %{id: event.id}}
+ with {:ok, %Event{local: is_local} = event} <- Mobilizon.Events.get_event_full(event_id),
+ {actor_id, ""} <- Integer.parse(actor_id),
+ {:is_owned, true, _} <- User.owns_actor(user, actor_id) do
+ cond do
+ Event.can_event_be_managed_by(event, actor_id) == {:event_can_be_managed, true} ->
+ do_delete_event(event)
+
+ role in [:moderator, :administrator] ->
+ with {:ok, res} <- do_delete_event(event, !is_local),
+ %Actor{} = actor <- Actors.get_actor(actor_id) do
+ log_action(actor, "delete", event)
+ {:ok, res}
+ end
+
+ true ->
+ {:error, "You cannot delete this event"}
+ end
else
{:error, :event_not_found} ->
{:error, "Event not found"}
{:is_owned, false} ->
{:error, "Actor id is not owned by authenticated user"}
-
- {:event_can_be_managed, false} ->
- {:error, "You cannot delete this event"}
end
end
def delete_event(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete an event"}
end
+
+ defp do_delete_event(event, federate \\ true) when is_boolean(federate) do
+ with {:ok, _activity, event} <- MobilizonWeb.API.Events.delete_event(event) do
+ {:ok, %{id: event.id}}
+ end
+ end
end
diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex
index 84239788a..2fcb0d44c 100644
--- a/lib/mobilizon_web/resolvers/group.ex
+++ b/lib/mobilizon_web/resolvers/group.ex
@@ -89,7 +89,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
- with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
+ with {actor_id, ""} <- Integer.parse(actor_id),
+ {group_id, ""} <- Integer.parse(group_id),
+ {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:is_owned, true, _} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Member.get_member(actor_id, group.id),
{:is_admin, true} <- Member.is_administrator(member),
@@ -126,7 +128,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
- with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
+ with {actor_id, ""} <- Integer.parse(actor_id),
+ {group_id, ""} <- Integer.parse(group_id),
+ {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:error, :member_not_found} <- Member.get_member(actor.id, group.id),
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
@@ -180,7 +184,9 @@ defmodule MobilizonWeb.Resolvers.Group do
}
}
) do
- with {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
+ with {actor_id, ""} <- Integer.parse(actor_id),
+ {group_id, ""} <- Integer.parse(group_id),
+ {:is_owned, true, actor} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Member.get_member(actor.id, group_id),
{:only_administrator, false} <-
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
diff --git a/lib/mobilizon_web/resolvers/report.ex b/lib/mobilizon_web/resolvers/report.ex
index dd3655050..51f815513 100644
--- a/lib/mobilizon_web/resolvers/report.ex
+++ b/lib/mobilizon_web/resolvers/report.ex
@@ -10,11 +10,11 @@ defmodule MobilizonWeb.Resolvers.Report do
alias MobilizonWeb.API.Reports, as: ReportsAPI
import Mobilizon.Users.Guards
- def list_reports(_parent, %{page: page, limit: limit}, %{
+ def list_reports(_parent, %{page: page, limit: limit, status: status}, %{
context: %{current_user: %User{role: role}}
})
when is_moderator(role) do
- {:ok, Mobilizon.Reports.list_reports(page, limit)}
+ {:ok, Mobilizon.Reports.list_reports(page, limit, :updated_at, :desc, status)}
end
def list_reports(_parent, _args, _resolution) do
@@ -25,7 +25,13 @@ defmodule MobilizonWeb.Resolvers.Report do
context: %{current_user: %User{role: role}}
})
when is_moderator(role) do
- {:ok, Mobilizon.Reports.get_report(id)}
+ case Mobilizon.Reports.get_report(id) do
+ %Report{} = report ->
+ {:ok, report}
+
+ nil ->
+ {:error, "Report not found"}
+ end
end
def get_report(_parent, _args, _resolution) do
diff --git a/lib/mobilizon_web/router.ex b/lib/mobilizon_web/router.ex
index 2ecd44af4..3375341d3 100644
--- a/lib/mobilizon_web/router.ex
+++ b/lib/mobilizon_web/router.ex
@@ -78,6 +78,9 @@ defmodule MobilizonWeb.Router do
get("/events/create", PageController, :index)
get("/events/list", PageController, :index)
get("/events/:uuid/edit", PageController, :index)
+
+ # This is a hack to ease link generation into emails
+ get("/moderation/reports/:id", PageController, :index, as: "moderation_report")
end
scope "/", MobilizonWeb do
diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex
index c3819ab75..5445b49f8 100644
--- a/lib/mobilizon_web/schema.ex
+++ b/lib/mobilizon_web/schema.ex
@@ -4,7 +4,7 @@ defmodule MobilizonWeb.Schema do
"""
use Absinthe.Schema
- alias Mobilizon.{Actors, Events, Users, Addresses, Media}
+ alias Mobilizon.{Actors, Events, Users, Addresses, Media, Reports}
alias Mobilizon.Actors.{Actor, Follower, Member}
alias Mobilizon.Events.{Event, Comment, Participant}
@@ -26,7 +26,7 @@ defmodule MobilizonWeb.Schema do
@desc "A struct containing the id of the deleted object"
object :deleted_object do
- field(:id, :integer)
+ field(:id, :id)
end
@desc "A JWT and the associated user ID"
@@ -44,7 +44,7 @@ defmodule MobilizonWeb.Schema do
Represents a notification for an user
"""
object :notification do
- field(:id, :integer, description: "The notification ID")
+ field(:id, :id, description: "The notification ID")
field(:user, :user, description: "The user to transmit the notification to")
field(:actor, :actor, description: "The notification target profile")
@@ -94,6 +94,7 @@ defmodule MobilizonWeb.Schema do
|> Dataloader.add_source(Events, Events.data())
|> Dataloader.add_source(Addresses, Addresses.data())
|> Dataloader.add_source(Media, Media.data())
+ |> Dataloader.add_source(Reports, Reports.data())
Map.put(ctx, :loader, loader)
end
diff --git a/lib/mobilizon_web/schema/actor.ex b/lib/mobilizon_web/schema/actor.ex
index 7509c3a3b..89ea0cee5 100644
--- a/lib/mobilizon_web/schema/actor.ex
+++ b/lib/mobilizon_web/schema/actor.ex
@@ -13,7 +13,7 @@ defmodule MobilizonWeb.Schema.ActorInterface do
@desc "An ActivityPub actor"
interface :actor do
- field(:id, :integer, description: "Internal ID for this actor")
+ field(:id, :id, description: "Internal ID for this actor")
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")
diff --git a/lib/mobilizon_web/schema/actors/group.ex b/lib/mobilizon_web/schema/actors/group.ex
index 30226039d..dcf5929d4 100644
--- a/lib/mobilizon_web/schema/actors/group.ex
+++ b/lib/mobilizon_web/schema/actors/group.ex
@@ -14,7 +14,7 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
object :group do
interfaces([:actor])
- field(:id, :integer, description: "Internal ID for this group")
+ field(:id, :id, description: "Internal ID for this group")
field(:url, :string, description: "The ActivityPub actor's URL")
field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
field(:name, :string, description: "The actor's displayed name")
@@ -96,9 +96,7 @@ defmodule MobilizonWeb.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(:integer),
- description: "The identity that creates 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: "")
@@ -118,8 +116,8 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
@desc "Delete a group"
field :delete_group, :deleted_object do
- arg(:group_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:group_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Group.delete_group/3)
end
diff --git a/lib/mobilizon_web/schema/actors/member.ex b/lib/mobilizon_web/schema/actors/member.ex
index 95bb75b6b..920138897 100644
--- a/lib/mobilizon_web/schema/actors/member.ex
+++ b/lib/mobilizon_web/schema/actors/member.ex
@@ -24,16 +24,16 @@ defmodule MobilizonWeb.Schema.Actors.MemberType do
object :member_mutations do
@desc "Join a group"
field :join_group, :member do
- arg(:group_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:group_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Resolvers.Group.join_group/3)
end
@desc "Leave an event"
field :leave_group, :deleted_member do
- arg(:group_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:group_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Resolvers.Group.leave_group/3)
end
diff --git a/lib/mobilizon_web/schema/actors/person.ex b/lib/mobilizon_web/schema/actors/person.ex
index a8e278456..ef7f8b4b7 100644
--- a/lib/mobilizon_web/schema/actors/person.ex
+++ b/lib/mobilizon_web/schema/actors/person.ex
@@ -15,7 +15,7 @@ defmodule MobilizonWeb.Schema.Actors.PersonType do
"""
object :person do
interfaces([:actor])
- field(:id, :integer, description: "Internal ID for this person")
+ field(:id, :id, description: "Internal ID for this person")
field(:user, :user, description: "The user this actor is associated to")
field(:member_of, list_of(:member), description: "The list of groups this person is member of")
diff --git a/lib/mobilizon_web/schema/address.ex b/lib/mobilizon_web/schema/address.ex
index 71f69a951..9a9006f83 100644
--- a/lib/mobilizon_web/schema/address.ex
+++ b/lib/mobilizon_web/schema/address.ex
@@ -15,7 +15,7 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:country, :string)
field(:description, :string)
field(:url, :string)
- field(:id, :integer)
+ field(:id, :id)
field(:origin_id, :string)
end
@@ -40,7 +40,7 @@ defmodule MobilizonWeb.Schema.AddressType do
field(:country, :string)
field(:description, :string)
field(:url, :string)
- field(:id, :integer)
+ field(:id, :id)
field(:origin_id, :string)
end
diff --git a/lib/mobilizon_web/schema/admin.ex b/lib/mobilizon_web/schema/admin.ex
index fa73ec0a9..715d7fdda 100644
--- a/lib/mobilizon_web/schema/admin.ex
+++ b/lib/mobilizon_web/schema/admin.ex
@@ -5,13 +5,25 @@ defmodule MobilizonWeb.Schema.AdminType do
use Absinthe.Schema.Notation
alias MobilizonWeb.Resolvers.Admin
alias Mobilizon.Reports.{Report, Note}
+ alias Mobilizon.Events.Event
@desc "An action log"
object :action_log do
field(:id, :id, description: "Internal ID for this comment")
field(:actor, :actor, description: "The actor that acted")
field(:object, :action_log_object, description: "The object that was acted upon")
- field(:action, :string, description: "The action that was done")
+ field(:action, :action_log_action, description: "The action that was done")
+ field(:inserted_at, :datetime, description: "The time when the action was performed")
+ end
+
+ enum :action_log_action do
+ value(:report_update_closed)
+ value(:report_update_opened)
+ value(:report_update_resolved)
+ value(:note_creation)
+ value(:note_deletion)
+ value(:event_deletion)
+ value(:event_update)
end
@desc "The objects that can be in an action log"
@@ -25,11 +37,22 @@ defmodule MobilizonWeb.Schema.AdminType do
%Note{}, _ ->
:report_note
+ %Event{}, _ ->
+ :event
+
_, _ ->
nil
end)
end
+ object :dashboard do
+ field(:last_public_event_published, :event, description: "Last public event publish")
+ field(:number_of_users, :integer, description: "The number of local users")
+ field(:number_of_events, :integer, description: "The number of local events")
+ field(:number_of_comments, :integer, description: "The number of local comments")
+ field(:number_of_reports, :integer, description: "The number of current opened reports")
+ end
+
object :admin_queries do
@desc "Get the list of action logs"
field :action_logs, type: list_of(:action_log) do
@@ -37,5 +60,9 @@ defmodule MobilizonWeb.Schema.AdminType do
arg(:limit, :integer, default_value: 10)
resolve(&Admin.list_action_logs/3)
end
+
+ field :dashboard, type: :dashboard do
+ resolve(&Admin.get_dashboard/3)
+ end
end
end
diff --git a/lib/mobilizon_web/schema/event.ex b/lib/mobilizon_web/schema/event.ex
index e1c15161b..c2f2fbfe3 100644
--- a/lib/mobilizon_web/schema/event.ex
+++ b/lib/mobilizon_web/schema/event.ex
@@ -12,7 +12,8 @@ defmodule MobilizonWeb.Schema.EventType do
@desc "An event"
object :event do
- field(:id, :integer, description: "Internal ID for this event")
+ interfaces([:action_log_object])
+ field(:id, :id, description: "Internal ID for this event")
field(:uuid, :uuid, description: "The Event UUID")
field(:url, :string, description: "The ActivityPub Event URL")
field(:local, :boolean, description: "Whether the event is local or not")
@@ -261,8 +262,8 @@ defmodule MobilizonWeb.Schema.EventType do
@desc "Delete an event"
field :delete_event, :deleted_object do
- arg(:event_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:event_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Event.delete_event/3)
end
diff --git a/lib/mobilizon_web/schema/events/feed_token.ex b/lib/mobilizon_web/schema/events/feed_token.ex
index 7be904907..15cf80e63 100644
--- a/lib/mobilizon_web/schema/events/feed_token.ex
+++ b/lib/mobilizon_web/schema/events/feed_token.ex
@@ -36,7 +36,7 @@ defmodule MobilizonWeb.Schema.Events.FeedTokenType do
object :feed_token_mutations do
@desc "Create a Feed Token"
field :create_feed_token, :feed_token do
- arg(:actor_id, :integer)
+ arg(:actor_id, :id)
resolve(&Resolvers.FeedToken.create_feed_token/3)
end
diff --git a/lib/mobilizon_web/schema/events/participant.ex b/lib/mobilizon_web/schema/events/participant.ex
index 75f9c8954..6f0e2e2e8 100644
--- a/lib/mobilizon_web/schema/events/participant.ex
+++ b/lib/mobilizon_web/schema/events/participant.ex
@@ -46,16 +46,16 @@ defmodule MobilizonWeb.Schema.Events.ParticipantType do
object :participant_mutations do
@desc "Join an event"
field :join_event, :participant do
- arg(:event_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:event_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Resolvers.Event.actor_join_event/3)
end
@desc "Leave an event"
field :leave_event, :deleted_participant do
- arg(:event_id, non_null(:integer))
- arg(:actor_id, non_null(:integer))
+ arg(:event_id, non_null(:id))
+ arg(:actor_id, non_null(:id))
resolve(&Resolvers.Event.actor_leave_event/3)
end
diff --git a/lib/mobilizon_web/schema/report.ex b/lib/mobilizon_web/schema/report.ex
index ea3a5cdaf..7e9acec4e 100644
--- a/lib/mobilizon_web/schema/report.ex
+++ b/lib/mobilizon_web/schema/report.ex
@@ -3,6 +3,8 @@ defmodule MobilizonWeb.Schema.ReportType do
Schema representation for User
"""
use Absinthe.Schema.Notation
+ import Absinthe.Resolution.Helpers, only: [dataloader: 1]
+ alias Mobilizon.Reports
alias MobilizonWeb.Resolvers.Report
@@ -17,6 +19,14 @@ defmodule MobilizonWeb.Schema.ReportType do
field(:reporter, :actor, description: "The actor that created the report")
field(:event, :event, description: "The event that is being reported")
field(:comments, list_of(:comment), description: "The comments that are reported")
+
+ field(:notes, list_of(:report_note),
+ description: "The notes made on the event",
+ resolve: dataloader(Reports)
+ )
+
+ field(:inserted_at, :datetime, description: "When the report was created")
+ field(:updated_at, :datetime, description: "When the report was updated")
end
@desc "A report note object"
@@ -24,8 +34,14 @@ defmodule MobilizonWeb.Schema.ReportType do
interfaces([:action_log_object])
field(:id, :id, description: "The internal ID of the report note")
field(:content, :string, description: "The content of the note")
- field(:moderator, :actor, description: "The moderator who added the note")
+
+ field(:moderator, :actor,
+ description: "The moderator who added the note",
+ resolve: dataloader(Reports)
+ )
+
field(:report, :report, description: "The report on which this note is added")
+ field(:inserted_at, :datetime, description: "When the report note was created")
end
@desc "The list of possible statuses for a report object"
@@ -40,6 +56,7 @@ defmodule MobilizonWeb.Schema.ReportType do
field :reports, list_of(:report) do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
+ arg(:status, :report_status, default_value: :open)
resolve(&Report.list_reports/3)
end
@@ -53,7 +70,7 @@ defmodule MobilizonWeb.Schema.ReportType do
object :report_mutations do
@desc "Create a report"
field :create_report, type: :report do
- arg(:report_content, :string)
+ arg(:content, :string)
arg(:reporter_actor_id, non_null(:id))
arg(:reported_actor_id, non_null(:id))
arg(:event_id, :id, default_value: nil)
diff --git a/lib/mobilizon_web/schema/user.ex b/lib/mobilizon_web/schema/user.ex
index 1b3a525e6..f1725b382 100644
--- a/lib/mobilizon_web/schema/user.ex
+++ b/lib/mobilizon_web/schema/user.ex
@@ -43,6 +43,14 @@ defmodule MobilizonWeb.Schema.UserType do
resolve: dataloader(Events),
description: "A list of the feed tokens for this user"
)
+
+ field(:role, :user_role, description: "The role for the user")
+ end
+
+ enum :user_role do
+ value(:administrator)
+ value(:moderator)
+ value(:user)
end
@desc "Token"
diff --git a/lib/mobilizon_web/templates/email/report.html.eex b/lib/mobilizon_web/templates/email/report.html.eex
index 3740eea67..870122bbc 100644
--- a/lib/mobilizon_web/templates/email/report.html.eex
+++ b/lib/mobilizon_web/templates/email/report.html.eex
@@ -1,15 +1,15 @@
-<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %>
+<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter.preferred_username, instance: @instance %>
<% if @report.event do %>
-<%= gettext "Event: %{event}", event: @report.event %>
+<%= gettext "Event: %{event}", event: @report.event.title %>
<% end %>
<%= for comment <- @report.comments do %>
<%= gettext "Comment: %{comment}", comment: comment %>
<% end %>
-<% if @content do %>
+<% if @report.content do %>
<%= gettext "Reason: %{content}", event: @report.content %>
<% end %>
-<%= link "View the report", to: MobilizonWeb.Endpoint.url() <> "/reports/#{@report.id}", target: "_blank" %>
+<%= link "View the report", to: moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id), target: "_blank" %>
\ No newline at end of file
diff --git a/lib/mobilizon_web/templates/email/report.text.eex b/lib/mobilizon_web/templates/email/report.text.eex
index af4233ece..ddd066e34 100644
--- a/lib/mobilizon_web/templates/email/report.text.eex
+++ b/lib/mobilizon_web/templates/email/report.text.eex
@@ -1,19 +1,19 @@
-<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter, instance: @instance %>
+<%= gettext "New report from %{reporter} on %{instance}", reporter: @report.reporter.preferred_username, instance: @instance %>
--
<% if @report.event do %>
- <%= gettext "Event: %{event}", event: @report.event %>
+ <%= gettext "Event: %{event}", event: @report.event.title %>
<% end %>
<%= for comment <- @report.comments do %>
-<%= gettext "Comment: %{comment}", comment: comment %>
+<%= gettext "Comment: %{comment}", comment: comment.text %>
<% end %>
-<% if @content do %>
+<% if @report.content do %>
<%= gettext "Reason: %{content}", event: @report.content %>
<% end %>
-<%= link "View the report", to: MobilizonWeb.Endpoint.url() <> "/reports/#{@report.id}", target: "_blank" %>
+View the report: <%= moderation_report_url(MobilizonWeb.Endpoint, :index, @report.id) %>
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
index 6cad45aab..e111dac0f 100644
--- a/lib/service/activity_pub/activity_pub.ex
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -335,9 +335,8 @@ defmodule Mobilizon.Service.ActivityPub do
with {:ok, _} <- Events.delete_event(event),
{:ok, activity} <- create_activity(data, local),
- {:ok, object} <- insert_full_object(data),
:ok <- maybe_federate(activity) do
- {:ok, activity, object}
+ {:ok, activity, event}
end
end
@@ -521,7 +520,8 @@ defmodule Mobilizon.Service.ActivityPub do
public = is_public?(activity)
- if public && Mobilizon.CommonConfig.get([:instance, :allow_relay]) do
+ if public && is_delete_activity?(activity) == false &&
+ Mobilizon.CommonConfig.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Mobilizon.Service.ActivityPub.Relay.publish(activity)
end
@@ -552,6 +552,9 @@ defmodule Mobilizon.Service.ActivityPub do
end)
end
+ defp is_delete_activity?(%Activity{data: %{"type" => "Delete"}}), do: true
+ defp is_delete_activity?(_), do: false
+
@doc """
Publish an activity to a specific inbox
"""
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
index d68c62287..b0c40d748 100644
--- a/lib/service/activity_pub/utils.ex
+++ b/lib/service/activity_pub/utils.ex
@@ -164,14 +164,14 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
{:ok, %Report{} = report} <- Reports.create_report(data) do
Enum.each(Users.list_moderators(), fn moderator ->
moderator
- |> Mobilizon.Email.Admin.report(moderator, report)
+ |> Mobilizon.Email.Admin.report(report)
|> Mobilizon.Mailer.deliver_later()
end)
{:ok, report}
else
err ->
- Logger.error("Error while inserting a remote comment inside database")
+ Logger.error("Error while inserting report inside database")
Logger.debug(inspect(err))
{:error, err}
end
diff --git a/lib/service/admin/action_log_service.ex b/lib/service/admin/action_log_service.ex
index b1a636853..6f82ead2f 100644
--- a/lib/service/admin/action_log_service.ex
+++ b/lib/service/admin/action_log_service.ex
@@ -22,9 +22,19 @@ defmodule Mobilizon.Service.Admin.ActionLogService do
"target_type" => to_string(target.__struct__),
"target_id" => target.id,
"action" => action,
- "changes" => Map.from_struct(target) |> Map.take([:status, :uri, :content])
+ "changes" => stringify_struct(target)
}) do
{:ok, create_action_log}
end
end
+
+ defp stringify_struct(%_{} = struct) do
+ association_fields = struct.__struct__.__schema__(:associations)
+
+ struct
+ |> Map.from_struct()
+ |> Map.drop(association_fields ++ [:__meta__])
+ end
+
+ defp stringify_struct(struct), do: struct
end
diff --git a/lib/service/statistics.ex b/lib/service/statistics.ex
new file mode 100644
index 000000000..9d1f07ee8
--- /dev/null
+++ b/lib/service/statistics.ex
@@ -0,0 +1,31 @@
+defmodule Mobilizon.Service.Statistics do
+ @moduledoc """
+ A module that provides cached statistics
+ """
+ alias Mobilizon.Events
+ alias Mobilizon.Users
+
+ def get_cached_value(key) do
+ case Cachex.fetch(:statistics, key, fn key ->
+ case create_cache(key) do
+ value when not is_nil(value) -> {:commit, value}
+ err -> {:ignore, err}
+ end
+ end) do
+ {status, value} when status in [:ok, :commit] -> value
+ _err -> nil
+ end
+ end
+
+ defp create_cache(:local_users) do
+ Users.count_users()
+ end
+
+ defp create_cache(:local_events) do
+ Events.count_local_events()
+ end
+
+ defp create_cache(:local_comments) do
+ Events.count_local_comments()
+ end
+end
diff --git a/schema.graphql b/schema.graphql
index b0951bde2..df2395faf 100644
--- a/schema.graphql
+++ b/schema.graphql
@@ -1,5 +1,5 @@
# source: http://localhost:4000/api
-# timestamp: Mon Sep 09 2019 11:37:31 GMT+0200 (heure d’été d’Europe centrale)
+# timestamp: Mon Sep 09 2019 20:33:17 GMT+0200 (GMT+02:00)
schema {
query: RootQueryType
@@ -9,7 +9,7 @@ schema {
"""An action log"""
type ActionLog {
"""The action that was done"""
- action: String
+ action: ActionLogAction
"""The actor that acted"""
actor: Actor
@@ -17,10 +17,23 @@ type ActionLog {
"""Internal ID for this comment"""
id: ID
+ """The time when the action was performed"""
+ insertedAt: DateTime
+
"""The object that was acted upon"""
object: ActionLogObject
}
+enum ActionLogAction {
+ EVENT_DELETION
+ EVENT_UPDATE
+ NOTE_CREATION
+ NOTE_DELETION
+ REPORT_UPDATE_CLOSED
+ REPORT_UPDATE_OPENED
+ REPORT_UPDATE_RESOLVED
+}
+
"""The objects that can be in an action log"""
interface ActionLogObject {
"""Internal ID for this object"""
@@ -51,7 +64,7 @@ interface Actor {
followingCount: Int
"""Internal ID for this actor"""
- id: Int
+ id: ID
"""The actors RSA Keys"""
keys: String
@@ -111,7 +124,7 @@ type Address {
"""The geocoordinates for the point where this address is"""
geom: Point
- id: Int
+ id: ID
"""The address's locality"""
locality: String
@@ -133,7 +146,7 @@ input AddressInput {
"""The geocoordinates for the point where this address is"""
geom: Point
- id: Int
+ id: ID
"""The address's locality"""
locality: String
@@ -185,6 +198,23 @@ type Config {
registrationsOpen: Boolean
}
+type Dashboard {
+ """Last public event publish"""
+ lastPublicEventPublished: Event
+
+ """The number of local comments"""
+ numberOfComments: Int
+
+ """The number of local events"""
+ numberOfEvents: Int
+
+ """The number of current opened reports"""
+ numberOfReports: Int
+
+ """The number of local users"""
+ numberOfUsers: Int
+}
+
"""
The `DateTime` scalar type represents a date and time in the UTC
timezone. The DateTime appears in a JSON response as an ISO8601 formatted
@@ -207,7 +237,7 @@ type DeletedMember {
"""A struct containing the id of the deleted object"""
type DeletedObject {
- id: Int
+ id: ID
}
"""Represents a deleted participant"""
@@ -217,7 +247,7 @@ type DeletedParticipant {
}
"""An event"""
-type Event {
+type Event implements ActionLogObject {
"""Who the event is attributed to (often a group)"""
attributedTo: Actor
@@ -237,7 +267,7 @@ type Event {
endsOn: DateTime
"""Internal ID for this event"""
- id: Int
+ id: ID
"""Whether the event is local or not"""
local: Boolean
@@ -501,7 +531,7 @@ type Group implements Actor {
followingCount: Int
"""Internal ID for this group"""
- id: Int
+ id: ID
"""The actors RSA Keys"""
keys: String
@@ -651,7 +681,7 @@ type Person implements Actor {
goingToEvents: [Event]
"""Internal ID for this person"""
- id: Int
+ id: ID
"""The actors RSA Keys"""
keys: String
@@ -763,6 +793,12 @@ type Report implements ActionLogObject {
"""The internal ID of the report"""
id: ID
+ """When the report was created"""
+ insertedAt: DateTime
+
+ """The notes made on the event"""
+ notes: [ReportNote]
+
"""The actor that is being reported"""
reported: Actor
@@ -772,6 +808,9 @@ type Report implements ActionLogObject {
"""Whether the report is still active"""
status: ReportStatus
+ """When the report was updated"""
+ updatedAt: DateTime
+
"""The URI of the report"""
uri: String
}
@@ -784,6 +823,9 @@ type ReportNote implements ActionLogObject {
"""The internal ID of the report note"""
id: ID
+ """When the report note was created"""
+ insertedAt: DateTime
+
"""The moderator who added the note"""
moderator: Actor
@@ -827,7 +869,7 @@ type RootMutationType {
"""
picture: PictureInput
publishAt: DateTime
- status: Int
+ status: EventStatus
"""The list of tags associated to the event"""
tags: [String] = [""]
@@ -836,7 +878,7 @@ type RootMutationType {
): Event
"""Create a Feed Token"""
- createFeedToken(actorId: Int): FeedToken
+ createFeedToken(actorId: ID): FeedToken
"""Create a group"""
createGroup(
@@ -851,7 +893,7 @@ type RootMutationType {
banner: PictureInput
"""The identity that creates the group"""
- creatorActorId: Int!
+ creatorActorId: ID!
"""The displayed name for the group"""
name: String
@@ -884,7 +926,7 @@ type RootMutationType {
): Person
"""Create a report"""
- createReport(commentsIds: [ID] = [""], eventId: ID, reportContent: String, reportedActorId: ID!, reporterActorId: ID!): Report
+ createReport(commentsIds: [ID] = [""], content: String, eventId: ID, reportedActorId: ID!, reporterActorId: ID!): Report
"""Create a note on a report"""
createReportNote(content: String, moderatorId: ID!, reportId: ID!): ReportNote
@@ -893,29 +935,29 @@ type RootMutationType {
createUser(email: String!, password: String!): User
"""Delete an event"""
- deleteEvent(actorId: Int!, eventId: Int!): DeletedObject
+ deleteEvent(actorId: ID!, eventId: ID!): DeletedObject
"""Delete a feed token"""
deleteFeedToken(token: String!): DeletedFeedToken
"""Delete a group"""
- deleteGroup(actorId: Int!, groupId: Int!): DeletedObject
+ deleteGroup(actorId: ID!, groupId: ID!): DeletedObject
"""Delete an identity"""
deletePerson(preferredUsername: String!): Person
deleteReportNote(moderatorId: ID!, noteId: ID!): DeletedObject
"""Join an event"""
- joinEvent(actorId: Int!, eventId: Int!): Participant
+ joinEvent(actorId: ID!, eventId: ID!): Participant
"""Join a group"""
- joinGroup(actorId: Int!, groupId: Int!): Member
+ joinGroup(actorId: ID!, groupId: ID!): Member
"""Leave an event"""
- leaveEvent(actorId: Int!, eventId: Int!): DeletedParticipant
+ leaveEvent(actorId: ID!, eventId: ID!): DeletedParticipant
"""Leave an event"""
- leaveGroup(actorId: Int!, groupId: Int!): DeletedMember
+ leaveGroup(actorId: ID!, groupId: ID!): DeletedMember
"""Login an user"""
login(email: String!, password: String!): Login
@@ -1019,6 +1061,7 @@ type RootQueryType {
"""Get the instance config"""
config: Config
+ dashboard: Dashboard
"""Get an event by uuid"""
event(uuid: UUID!): Event
@@ -1054,7 +1097,7 @@ type RootQueryType {
report(id: ID!): Report
"""Get all reports"""
- reports(limit: Int = 10, page: Int = 1): [Report]
+ reports(limit: Int = 10, page: Int = 1, status: ReportStatus = OPEN): [Report]
"""Reverse geocode coordinates"""
reverseGeocode(latitude: Float!, longitude: Float!): [Address]
@@ -1144,6 +1187,15 @@ type User {
"""The token sent when requesting password token"""
resetPasswordToken: String
+
+ """The role for the user"""
+ role: UserRole
+}
+
+enum UserRole {
+ ADMINISTRATOR
+ MODERATOR
+ USER
}
"""Users list"""
diff --git a/test/mobilizon/service/activity_pub/activity_pub_test.exs b/test/mobilizon/service/activity_pub/activity_pub_test.exs
index c744d3d68..ff3c0d018 100644
--- a/test/mobilizon/service/activity_pub/activity_pub_test.exs
+++ b/test/mobilizon/service/activity_pub/activity_pub_test.exs
@@ -15,6 +15,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
alias Mobilizon.Service.HTTPSignatures.Signature
alias Mobilizon.Service.ActivityPub
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
+ import Mock
setup_all do
HTTPoison.start()
@@ -111,7 +112,6 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
end
describe "deletion" do
- # TODO: The delete activity it relayed and fetched once again (and then not found /o\)
test "it creates a delete activity and deletes the original event" do
event = insert(:event)
event = Events.get_event_full_by_url!(event.url)
@@ -124,6 +124,25 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
assert Events.get_event_by_url(event.url) == nil
end
+ test "it deletes the original event but only locally if needed" do
+ with_mock ActivityPub.Utils,
+ maybe_federate: fn _ -> :ok end,
+ lazy_put_activity_defaults: fn args -> args end do
+ event = insert(:event)
+ event = Events.get_event_full_by_url!(event.url)
+ {:ok, delete, _} = ActivityPub.delete(event, false)
+
+ assert delete.data["type"] == "Delete"
+ assert delete.data["actor"] == event.organizer_actor.url
+ assert delete.data["object"] == event.url
+ assert delete.local == false
+
+ assert Events.get_event_by_url(event.url) == nil
+
+ assert_called(ActivityPub.Utils.maybe_federate(delete))
+ end
+ end
+
test "it creates a delete activity and deletes the original comment" do
comment = insert(:comment)
comment = Events.get_comment_full_from_url!(comment.url)
diff --git a/test/mobilizon/service/admin/action_log_service_test.exs b/test/mobilizon/service/admin/action_log_service_test.exs
index ccdfba86b..b96a3eefc 100644
--- a/test/mobilizon/service/admin/action_log_service_test.exs
+++ b/test/mobilizon/service/admin/action_log_service_test.exs
@@ -22,7 +22,7 @@ defmodule Mobilizon.Service.Admin.ActionLogServiceTest do
%ActionLog{
target_type: "Elixir.Mobilizon.Reports.Report",
target_id: report_id,
- action: "update",
+ action: :update,
actor: moderator
}} = log_action(moderator, "update", report)
end
@@ -35,7 +35,7 @@ defmodule Mobilizon.Service.Admin.ActionLogServiceTest do
%ActionLog{
target_type: "Elixir.Mobilizon.Reports.Note",
target_id: note_id,
- action: "create",
+ action: :create,
actor: moderator
}} = log_action(moderator, "create", report)
end
diff --git a/test/mobilizon_web/api/report_test.exs b/test/mobilizon_web/api/report_test.exs
index fa547024b..e757d00cd 100644
--- a/test/mobilizon_web/api/report_test.exs
+++ b/test/mobilizon_web/api/report_test.exs
@@ -25,7 +25,7 @@ defmodule MobilizonWeb.API.ReportTest do
Reports.report(%{
reporter_actor_id: reporter_id,
reported_actor_id: reported_id,
- report_content: comment,
+ content: comment,
event_id: event_id,
comments_ids: []
})
@@ -58,7 +58,7 @@ defmodule MobilizonWeb.API.ReportTest do
Reports.report(%{
reporter_actor_id: reporter_id,
reported_actor_id: reported_id,
- report_content: comment,
+ content: comment,
event_id: nil,
comments_ids: [comment_1_id, comment_2_id]
})
@@ -92,7 +92,7 @@ defmodule MobilizonWeb.API.ReportTest do
Reports.report(%{
reporter_actor_id: reporter_id,
reported_actor_id: reported_id,
- report_content: comment,
+ content: comment,
event_id: nil,
comments_ids: [comment_1_id, comment_2_id],
forward: true
@@ -121,7 +121,7 @@ defmodule MobilizonWeb.API.ReportTest do
Reports.report(%{
reporter_actor_id: reporter_id,
reported_actor_id: reported_id,
- report_content: "This is not a nice thing",
+ content: "This is not a nice thing",
event_id: nil,
comments_ids: [comment_1_id],
forward: true
@@ -147,7 +147,7 @@ defmodule MobilizonWeb.API.ReportTest do
Reports.report(%{
reporter_actor_id: reporter_id,
reported_actor_id: reported_id,
- report_content: "This is not a nice thing",
+ content: "This is not a nice thing",
event_id: nil,
comments_ids: [comment_1_id],
forward: true
diff --git a/test/mobilizon_web/controllers/nodeinfo_controller_test.exs b/test/mobilizon_web/controllers/nodeinfo_controller_test.exs
index 44592c05c..5bd4f8303 100644
--- a/test/mobilizon_web/controllers/nodeinfo_controller_test.exs
+++ b/test/mobilizon_web/controllers/nodeinfo_controller_test.exs
@@ -31,8 +31,9 @@ defmodule MobilizonWeb.NodeInfoControllerTest do
end
test "Get node info", %{conn: conn} do
+ # We clear the cache because it might have been initialized by other tests
+ Cachex.clear(:statistics)
conn = get(conn, node_info_path(conn, :nodeinfo, "2.1"))
-
resp = json_response(conn, 200)
assert resp == %{
@@ -44,7 +45,7 @@ defmodule MobilizonWeb.NodeInfoControllerTest do
"protocols" => ["activitypub"],
"services" => %{"inbound" => [], "outbound" => ["atom1.0"]},
"software" => %{
- "name" => "mobilizon",
+ "name" => "Mobilizon",
"version" => Keyword.get(@instance, :version),
"repository" => Keyword.get(@instance, :repository)
},
diff --git a/test/mobilizon_web/resolvers/admin_resolver_test.exs b/test/mobilizon_web/resolvers/admin_resolver_test.exs
index 150cb7bc4..a1452d488 100644
--- a/test/mobilizon_web/resolvers/admin_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/admin_resolver_test.exs
@@ -3,6 +3,7 @@ defmodule MobilizonWeb.Resolvers.AdminResolverTest do
use MobilizonWeb.ConnCase
import Mobilizon.Factory
+ alias Mobilizon.Events.Event
alias Mobilizon.Actors.Actor
alias Mobilizon.Users.User
alias Mobilizon.Reports.{Report, Note}
@@ -62,21 +63,60 @@ defmodule MobilizonWeb.Resolvers.AdminResolverTest do
assert json_response(res, 200)["data"]["actionLogs"] == [
%{
- "action" => "report_update_resolved",
+ "action" => "NOTE_DELETION",
+ "actor" => %{"preferredUsername" => moderator_2.preferred_username},
+ "object" => %{"content" => @note_content}
+ },
+ %{
+ "action" => "NOTE_CREATION",
+ "actor" => %{"preferredUsername" => moderator_2.preferred_username},
+ "object" => %{"content" => @note_content}
+ },
+ %{
+ "action" => "REPORT_UPDATE_RESOLVED",
"actor" => %{"preferredUsername" => moderator.preferred_username},
"object" => %{"id" => to_string(report.id), "status" => "RESOLVED"}
- },
- %{
- "action" => "note_creation",
- "actor" => %{"preferredUsername" => moderator_2.preferred_username},
- "object" => %{"content" => @note_content}
- },
- %{
- "action" => "note_deletion",
- "actor" => %{"preferredUsername" => moderator_2.preferred_username},
- "object" => %{"content" => @note_content}
}
]
end
end
+
+ describe "Resolver: Get the dashboard statistics" do
+ test "get_dashboard/3 gets dashboard information", %{conn: conn} do
+ %Event{title: title} = insert(:event)
+
+ %User{} = user_admin = insert(:user, role: :administrator)
+
+ query = """
+ {
+ dashboard {
+ lastPublicEventPublished {
+ title
+ }
+ numberOfUsers,
+ numberOfComments,
+ numberOfEvents,
+ numberOfReports
+ }
+ }
+ """
+
+ res =
+ conn
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs"))
+
+ assert json_response(res, 200)["errors"] |> hd |> Map.get("message") ==
+ "You need to be logged-in and an administrator to access dashboard statistics"
+
+ res =
+ conn
+ |> auth_conn(user_admin)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs"))
+
+ assert json_response(res, 200)["errors"] == nil
+
+ assert json_response(res, 200)["data"]["dashboard"]["lastPublicEventPublished"]["title"] ==
+ title
+ end
+ end
end
diff --git a/test/mobilizon_web/resolvers/event_resolver_test.exs b/test/mobilizon_web/resolvers/event_resolver_test.exs
index b6ce817d2..dd5752ca4 100644
--- a/test/mobilizon_web/resolvers/event_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/event_resolver_test.exs
@@ -731,7 +731,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["deleteEvent"]["id"] == event.id
+ assert json_response(res, 200)["data"]["deleteEvent"]["id"] == to_string(event.id)
res =
conn
@@ -815,6 +815,72 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete"
end
+ test "delete_event/3 allows a event being deleted by a moderator and creates a entry in actionLogs",
+ %{
+ conn: conn,
+ user: _user,
+ actor: _actor
+ } do
+ user_moderator = insert(:user, role: :moderator)
+ actor_moderator = insert(:actor, user: user_moderator)
+
+ actor2 = insert(:actor)
+ event = insert(:event, organizer_actor: actor2)
+
+ mutation = """
+ mutation {
+ deleteEvent(
+ actor_id: #{actor_moderator.id},
+ event_id: #{event.id}
+ ) {
+ id
+ }
+ }
+ """
+
+ res =
+ conn
+ |> auth_conn(user_moderator)
+ |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+
+ assert json_response(res, 200)["data"]["deleteEvent"]["id"] == to_string(event.id)
+
+ query = """
+ {
+ actionLogs {
+ action,
+ actor {
+ preferredUsername
+ },
+ object {
+ ... on Report {
+ id,
+ status
+ },
+ ... on ReportNote {
+ content
+ }
+ ... on Event {
+ id,
+ title
+ }
+ }
+ }
+ }
+ """
+
+ res =
+ conn
+ |> auth_conn(user_moderator)
+ |> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs"))
+
+ assert hd(json_response(res, 200)["data"]["actionLogs"]) == %{
+ "action" => "EVENT_DELETION",
+ "actor" => %{"preferredUsername" => actor_moderator.preferred_username},
+ "object" => %{"title" => event.title, "id" => to_string(event.id)}
+ }
+ end
+
test "list_related_events/3 should give related events", %{
conn: conn,
actor: actor
diff --git a/test/mobilizon_web/resolvers/feed_token_resolver_test.exs b/test/mobilizon_web/resolvers/feed_token_resolver_test.exs
index 8c5d122c7..01b277e26 100644
--- a/test/mobilizon_web/resolvers/feed_token_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/feed_token_resolver_test.exs
@@ -43,7 +43,8 @@ defmodule MobilizonWeb.Resolvers.FeedTokenResolverTest do
assert json_response(res, 200)["data"]["createFeedToken"]["user"]["id"] ==
to_string(user.id)
- assert json_response(res, 200)["data"]["createFeedToken"]["actor"]["id"] == actor2.id
+ assert json_response(res, 200)["data"]["createFeedToken"]["actor"]["id"] ==
+ to_string(actor2.id)
# The token is present for the user
query = """
@@ -209,8 +210,12 @@ defmodule MobilizonWeb.Resolvers.FeedTokenResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["deleteFeedToken"]["user"]["id"] == user.id
- assert json_response(res, 200)["data"]["deleteFeedToken"]["actor"]["id"] == actor.id
+
+ assert json_response(res, 200)["data"]["deleteFeedToken"]["user"]["id"] ==
+ to_string(user.id)
+
+ assert json_response(res, 200)["data"]["deleteFeedToken"]["actor"]["id"] ==
+ to_string(actor.id)
query = """
{
diff --git a/test/mobilizon_web/resolvers/group_resolver_test.exs b/test/mobilizon_web/resolvers/group_resolver_test.exs
index 7373b2f14..784aa0de0 100644
--- a/test/mobilizon_web/resolvers/group_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/group_resolver_test.exs
@@ -163,7 +163,7 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["deleteGroup"]["id"] == group.id
+ assert json_response(res, 200)["data"]["deleteGroup"]["id"] == to_string(group.id)
res =
conn
diff --git a/test/mobilizon_web/resolvers/member_resolver_test.exs b/test/mobilizon_web/resolvers/member_resolver_test.exs
index 413d04e78..cda9c1818 100644
--- a/test/mobilizon_web/resolvers/member_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/member_resolver_test.exs
@@ -40,8 +40,8 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["joinGroup"]["role"] == "not_approved"
- assert json_response(res, 200)["data"]["joinGroup"]["parent"]["id"] == group.id
- assert json_response(res, 200)["data"]["joinGroup"]["actor"]["id"] == actor.id
+ assert json_response(res, 200)["data"]["joinGroup"]["parent"]["id"] == to_string(group.id)
+ assert json_response(res, 200)["data"]["joinGroup"]["actor"]["id"] == to_string(actor.id)
mutation = """
mutation {
@@ -167,8 +167,8 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["leaveGroup"]["parent"]["id"] == group.id
- assert json_response(res, 200)["data"]["leaveGroup"]["actor"]["id"] == actor.id
+ assert json_response(res, 200)["data"]["leaveGroup"]["parent"]["id"] == to_string(group.id)
+ assert json_response(res, 200)["data"]["leaveGroup"]["actor"]["id"] == to_string(actor.id)
end
test "leave_group/3 should check if the member is the only administrator", %{
diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs
index c2a3bbb48..dc2b287fb 100644
--- a/test/mobilizon_web/resolvers/participant_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs
@@ -50,8 +50,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["joinEvent"]["role"] == "participant"
- assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == event.id
- assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == actor.id
+ assert json_response(res, 200)["data"]["joinEvent"]["event"]["id"] == to_string(event.id)
+ assert json_response(res, 200)["data"]["joinEvent"]["actor"]["id"] == to_string(actor.id)
mutation = """
mutation {
@@ -119,7 +119,7 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert hd(json_response(res, 200)["errors"])["message"] ==
- "Event with this ID 1042 doesn't exist"
+ "Event with this ID \"1042\" doesn't exist"
end
test "actor_leave_event/3 should delete a participant from an event", %{
@@ -153,8 +153,10 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["leaveEvent"]["event"]["id"] == event.id
- assert json_response(res, 200)["data"]["leaveEvent"]["actor"]["id"] == participant.actor.id
+ assert json_response(res, 200)["data"]["leaveEvent"]["event"]["id"] == to_string(event.id)
+
+ assert json_response(res, 200)["data"]["leaveEvent"]["actor"]["id"] ==
+ to_string(participant.actor.id)
query = """
{
diff --git a/test/mobilizon_web/resolvers/report_resolver_test.exs b/test/mobilizon_web/resolvers/report_resolver_test.exs
index 915ccbbe0..5a4c24a7a 100644
--- a/test/mobilizon_web/resolvers/report_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/report_resolver_test.exs
@@ -21,7 +21,7 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
reporter_actor_id: #{reporter.id},
reported_actor_id: #{reported.id},
event_id: #{event.id},
- report_content: "This is an issue"
+ content: "This is an issue"
) {
content,
reporter {
@@ -43,8 +43,10 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["createReport"]["content"] == "This is an issue"
assert json_response(res, 200)["data"]["createReport"]["status"] == "OPEN"
- assert json_response(res, 200)["data"]["createReport"]["event"]["id"] == event.id
- assert json_response(res, 200)["data"]["createReport"]["reporter"]["id"] == reporter.id
+ assert json_response(res, 200)["data"]["createReport"]["event"]["id"] == to_string(event.id)
+
+ assert json_response(res, 200)["data"]["createReport"]["reporter"]["id"] ==
+ to_string(reporter.id)
end
test "create_report/3 without being connected doesn't create any report", %{conn: conn} do
@@ -55,7 +57,7 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
createReport(
reported_actor_id: #{reported.id},
reporter_actor_id: 5,
- report_content: "This is an issue"
+ content: "This is an issue"
) {
content
}
@@ -109,7 +111,7 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
assert json_response(res, 200)["data"]["updateReportStatus"]["status"] == "RESOLVED"
assert json_response(res, 200)["data"]["updateReportStatus"]["reporter"]["id"] ==
- report.reporter.id
+ to_string(report.reporter.id)
end
test "create_report/3 without being connected doesn't create any report", %{conn: conn} do
@@ -172,9 +174,14 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
test "get a list of reports", %{conn: conn} do
%User{} = user_moderator = insert(:user, role: :moderator)
- %Report{id: report_1_id} = insert(:report)
- %Report{id: report_2_id} = insert(:report)
- %Report{id: report_3_id} = insert(:report)
+ # Report don't hold millisecond information so we need to wait a bit
+ # between each insert to keep order
+ %Report{id: report_1_id} = insert(:report, content: "My content 1")
+ Process.sleep(1000)
+ %Report{id: report_2_id} = insert(:report, content: "My content 2")
+ Process.sleep(1000)
+ %Report{id: report_3_id} = insert(:report, content: "My content 3")
+ %Report{} = insert(:report, status: :closed)
query = """
{
@@ -182,7 +189,9 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
id,
reported {
preferredUsername
- }
+ },
+ content,
+ updatedAt
}
}
"""
@@ -196,7 +205,7 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
assert json_response(res, 200)["data"]["reports"]
|> Enum.map(fn report -> Map.get(report, "id") end) ==
- Enum.map([report_1_id, report_2_id, report_3_id], &to_string/1)
+ Enum.map([report_3_id, report_2_id, report_1_id], &to_string/1)
query = """
{
@@ -360,7 +369,9 @@ defmodule MobilizonWeb.Resolvers.ReportResolverTest do
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert json_response(res, 200)["errors"] == nil
- assert json_response(res, 200)["data"]["deleteReportNote"]["id"] == report_note_id
+
+ assert json_response(res, 200)["data"]["deleteReportNote"]["id"] ==
+ to_string(report_note_id)
end
end
end