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 73839d96..cf47cef3 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 00000000..e0bc9d46
--- /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 00000000..5a25e677
--- /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 79dbe270..e815c8b1 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 00000000..89b14947
--- /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 00000000..89a7267a
--- /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 7dc92ca2..88d99de4 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 00000000..d646bb56
--- /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 58023619..438c46c1 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 0c4af8f6..d6e98cff 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 e43c11ca..eeb17f5c 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 00000000..9cd58c2f
--- /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 b23b5da3..5365ab60 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 25a5e606..7878e960 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 00000000..58ed37e6
--- /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 cca5e372..44534e84 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 00000000..52834524
--- /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 00000000..226b2fb5
--- /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 0c1f6277..0bafaac5 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 00000000..b1cea059
--- /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 29698bf7..75ec0a83 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 00000000..7628d1e6
--- /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 658bdaa7..eb21b4a8 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 00000000..aae6f4c9
--- /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 00000000..07c84bce
--- /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 00000000..5aa09f15
--- /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 8778b5ab..49ae7d5c 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 fba5ee05..33c2286e 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 2b3f80f7..62977e4e 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 22bf699d..294831bb 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 c6a02faf..7c552d82 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 1bfaac7a..6a00dc70 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 a7d8ad30..56d1993c 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 11d55e82..2d5b5b0c 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 f841d259..6a9493a2 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 3010c6ec..0560c80f 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 9de90689..d54a1de2 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 31bf6bc1..aad33df9 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 4f997242..944b50dd 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 869a91e9..d5c4734f 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 84239788..2fcb0d44 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 dd365505..51f81551 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 2ecd44af..3375341d 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 c3819ab7..5445b49f 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 7509c3a3..89ea0cee 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 30226039..dcf5929d 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 95bb75b6..92013889 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 a8e27845..ef7f8b4b 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 71f69a95..9a9006f8 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 fa73ec0a..715d7fdd 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 e1c15161..c2f2fbfe 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 7be90490..15cf80e6 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 75f9c895..6f0e2e2e 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 ea3a5cda..7e9acec4 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 1b3a525e..f1725b38 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 3740eea6..870122bb 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 af4233ec..ddd066e3 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 6cad45aa..e111dac0 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 d68c6228..b0c40d74 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 b1a63685..6f82ead2 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 00000000..9d1f07ee
--- /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 b0951bde..df2395fa 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 c744d3d6..ff3c0d01 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 ccdfba86..b96a3eef 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 fa547024..e757d00c 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 44592c05..5bd4f830 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 150cb7bc..a1452d48 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 b6ce817d..dd5752ca 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 8c5d122c..01b277e2 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 7373b2f1..784aa0de 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 413d04e7..cda9c181 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 c2a3bbb4..dc2b287f 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 915ccbbe..5a4c24a7 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