From b40b4ba3b438f3baaffda91816e7ffd9b2836652 Mon Sep 17 00:00:00 2001 From: lesion Date: Mon, 18 Mar 2024 12:25:36 +0100 Subject: [PATCH] feat: new Report / Moderation feature, fix #221, fix #350, fix #220 --- components/EventAdmin.vue | 44 +------ components/EventModeration.vue | 103 +++++++++++++++++ components/admin/Settings.vue | 13 ++- locales/email/en.json | 4 + locales/en.json | 23 ++-- middleware/isAdmin.js | 2 +- middleware/isAdminOrEditor.js | 2 +- pages/event/_slug.vue | 56 ++++++--- pages/export.vue | 17 ++- server/api/controller/event.js | 107 +++++++++++++++++- server/api/controller/settings.js | 2 +- server/api/controller/user.js | 14 --- server/api/index.js | 6 +- server/api/models/index.js | 6 +- server/api/models/message.js | 27 +++++ server/api/models/models.js | 1 + server/emails/message/html.pug | 3 - server/emails/message/subject.pug | 1 - server/emails/report/html.pug | 3 + server/emails/report/subject.pug | 1 + server/helpers.js | 2 +- .../20240310211804-event_messages.js | 44 +++++++ store/index.js | 2 +- 23 files changed, 385 insertions(+), 98 deletions(-) create mode 100644 components/EventModeration.vue create mode 100644 server/api/models/message.js delete mode 100644 server/emails/message/html.pug delete mode 100644 server/emails/message/subject.pug create mode 100644 server/emails/report/html.pug create mode 100644 server/emails/report/subject.pug create mode 100644 server/migrations/20240310211804-event_messages.js diff --git a/components/EventAdmin.vue b/components/EventAdmin.vue index 4891d495..28e09059 100644 --- a/components/EventAdmin.vue +++ b/components/EventAdmin.vue @@ -27,14 +27,6 @@ span v-list-item-content v-list-item-title(v-text="$t('common.clone')") - //- Contact user - v-list-item(v-if='event.userId && event.userId !== $auth.user.id && settings.allow_message_users' @click='openContactUser = true') - v-list-item-icon - v-icon(v-text='mdiMessageTextOutline') - v-list-item-content - v-list-item-title(v-text="$t('common.contact_user')") - - //- Remove v-list-item(v-if='!event.parentId' @click='remove(false)') v-list-item-icon @@ -42,6 +34,12 @@ span v-list-item-content v-list-item-title(v-text="$t('common.remove')") + //- Moderation + v-list-item(v-if='settings.enable_moderation' @click='$emit("openModeration")') + v-list-item-icon + v-icon(v-text='mdiMessageTextOutline') + v-list-item-content + v-list-item-title(v-text="$t('common.moderation')") template(v-if='event.parentId') v-list-item.text-overline(v-html="$t('common.recurring_event_actions')") @@ -68,18 +66,6 @@ span v-list-item-content v-list-item-title(v-text="$t('common.remove')") - v-dialog(v-model='openContactUser' :fullscreen="$vuetify.breakpoint.xsOnly" width='1000px') - v-card - v-card-title {{$t('common.contact_user')}} - v-card-text - v-textarea(type='text' - @input='v => message=v' - :value='value.message' - label='Message') - br - v-card-actions.justify-space-between - v-btn(text @click='openContactUser=false' color='warning') Cancel - v-btn(text color='primary' @click='sendMessageToUser') Send diff --git a/components/EventModeration.vue b/components/EventModeration.vue new file mode 100644 index 00000000..cb470d34 --- /dev/null +++ b/components/EventModeration.vue @@ -0,0 +1,103 @@ + + + \ No newline at end of file diff --git a/components/admin/Settings.vue b/components/admin/Settings.vue index 8baa8fdf..17f32880 100644 --- a/components/admin/Settings.vue +++ b/components/admin/Settings.vue @@ -60,9 +60,12 @@ v-container inset :label="$t('admin.allow_geolocation')") - v-switch.mt-1(v-model='allow_message_users' + v-switch.mt-1(v-model='enable_moderation' inset - :label="$t('admin.allow_message_users')") + persistent-hint + :hint="$t('admin.enable_moderation_hint')" + :label="$t('admin.enable_moderation')") + v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly') SMTP(@close='showSMTP = false') @@ -138,9 +141,9 @@ export default { get () { return this.settings.allow_online_event }, set (value) { this.setSetting({ key: 'allow_online_event', value }) } }, - allow_message_users: { - get () { return this.settings.allow_message_users }, - set (value) { this.setSetting({ key: 'allow_message_users', value }) } + enable_moderation: { + get () { return this.settings.enable_moderation }, + set (value) { this.setSetting({ key: 'enable_moderation', value }) } }, filteredTimezones () { const current_timezone = DateTime.local().zoneName diff --git a/locales/email/en.json b/locales/email/en.json index e63845b0..54cb5697 100644 --- a/locales/email/en.json +++ b/locales/email/en.json @@ -25,5 +25,9 @@ "test": { "subject": "Your SMTP configuration is working", "content": "This is a test email, if you are reading this your configuration is working." + }, + "report": { + "subject": "Report event [{{event.title}}] from {{author}}", + "content": "{{author}} reported the event {{event.title}} with the following message:
{{message}}


Open moderation" } } diff --git a/locales/en.json b/locales/en.json index 6f932a78..972115a7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -112,7 +112,8 @@ "actors": "Node", "collection_in_home": "Show a collection in home", "my_events": "My Events", - "contact_user": "Contact user" + "contact_user": "Contact user", + "report": "Report event", }, "login": { "description": "By logging in you can publish new events.", @@ -199,23 +200,31 @@ "online_locations": "Online locations", "online_locations_help": "For instance an url to a videconference room and a fallback url (max. 3)", "online_locations_fallback_urls": "Fallback links", - "address_geocoded_disclaimer": "If you cannot find the street address or the housenumber you are looking for in the geocoding results, you can manually insert them in the 'Address' field without loose the coordinates. Consider also that the OpenStreetMap project is open to contributions. If you have Android, we recommend StreetComplete " + "address_geocoded_disclaimer": "If you cannot find the street address or the housenumber you are looking for in the geocoding results, you can manually insert them in the 'Address' field without loose the coordinates. Consider also that the OpenStreetMap project is open to contributions. If you have Android, we recommend StreetComplete ", + "message": "Message", + "message_hint": "You could send a message to other admins and optionally to the person who authored the event.", + "report_message_confirmation": "Do you think this event should not be here? Write to us why", + "send_to_admins": "Send to admins", + "send_to_author_too": "Send to admins and author", + "disable_author": "Disable the event author" }, "admin": { "place_description": "If you have gotten the place or address wrong, you can change it.
All current and past events associated with this place will change address.", "event_confirm_description": "You can confirm events entered by anonymous users here", "delete_user": "Remove", "remove_admin": "Remove admin", - "disable_user_confirm": "Are you sure you want to disable {user}?", - "delete_user_confirm": "Are you sure you want to remove {user}?", - "disable_admin_user_confirm": "Are you sure to remove admin permissions from {user}?", - "enable_admin_user_confirm": "Are you sure to add admin permissions to {user}?", + "change_role_confirm": "Are you sure to change {user} role from {from_role} to {to_role}", + "disable_user_confirm": "Are you sure you want to disable {user}?", + "delete_user_confirm": "Are you sure you want to remove {user}?", + "disable_admin_user_confirm": "Are you sure to remove admin permissions from {user}?", + "enable_admin_user_confirm": "Are you sure to add admin permissions to {user}?", "user_remove_ok": "User removed", "user_create_ok": "User created", "event_remove_ok": "Event removed", "allow_registration_description": "Allow open registrations?", "allow_anon_event": "Allow anonymous events (has to be confirmed)?", - "allow_message_users": "Allow admin to message users", + "enable_moderation": "Enable moderation", + "enable_moderation_hint": "Anyone could report an event, admins are notified and could talk each other optionally including event's author", "allow_multidate_event": "Allow multi-day events", "allow_recurrent_event": "Allow recurring events", "allow_online_event": "Allow online events", diff --git a/middleware/isAdmin.js b/middleware/isAdmin.js index 2acf012f..f66caed2 100644 --- a/middleware/isAdmin.js +++ b/middleware/isAdmin.js @@ -1,5 +1,5 @@ export default async function ({ redirect, $auth }) { - if (!$auth.user.is_admin) { + if (!$auth?.user?.is_admin) { return redirect('/') } } \ No newline at end of file diff --git a/middleware/isAdminOrEditor.js b/middleware/isAdminOrEditor.js index 63b359c9..9d12355c 100644 --- a/middleware/isAdminOrEditor.js +++ b/middleware/isAdminOrEditor.js @@ -1,5 +1,5 @@ export default async function ({ redirect, $auth }) { - if (!$auth.user.is_editor && !$auth.user.is_admin) { + if (!$auth?.user?.is_editor && !$auth?.user?.is_admin) { return redirect('/') } } \ No newline at end of file diff --git a/pages/event/_slug.vue b/pages/event/_slug.vue index c1172e19..57096550 100644 --- a/pages/event/_slug.vue +++ b/pages/event/_slug.vue @@ -28,7 +28,7 @@ .font-weight-light.p-street-address(v-if='event?.place?.name !=="online"' itemprop='address') {{event?.place?.address}} //- a.d-block(v-if='event.ap_object?.url' :href="event.ap_object?.url") {{ event.ap_object?.url }} - a(v-if='event?.ap_user' :href="event?.ap_user?.object?.url ?? event?.ap_user?.ap_id") @{{event.ap_user?.object?.preferredUsername}}@{{ event.ap_user?.instanceDomain }} + a(v-if='event?.original_url' :href="event?.original_url") {{event.original_url}} //- tags, hashtags v-container.pt-0(v-if='event?.tags?.length') @@ -61,13 +61,6 @@ v-list-item-content v-list-item-title(v-text="$t('common.show_map')") - //- embed - v-list-item(@click='showEmbed=true') - v-list-item-icon - v-icon(v-text='mdiCodeTags') - v-list-item-content - v-list-item-title(v-text="$t('common.embed')") - //- calendar v-list-item(:href='`/api/event/detail/${event.slug || event.id}.ics`') v-list-item-icon @@ -75,6 +68,13 @@ v-list-item-content v-list-item-title(v-text="$t('common.add_to_calendar')") + //- Report + v-list-item(v-if='settings.enable_moderation && !showModeration' @click='report') + v-list-item-icon + v-icon(v-text='mdiMessageTextOutline') + v-list-item-content + v-list-item-title(v-text="$t('common.report')") + //- download flyer v-list-item(v-if='hasMedia' :href='$helper.mediaURL(event, "download")') v-list-item-icon @@ -82,11 +82,18 @@ v-list-item-content v-list-item-title(v-text="$t('event.download_flyer')") + //- embed + v-list-item(@click='showEmbed=true') + v-list-item-icon + v-icon(v-text='mdiCodeTags') + v-list-item-content + v-list-item-title(v-text="$t('common.embed')") + + //- admin actions template(v-if='can_edit') - v-divider - EventAdmin(:event='event') + EventAdmin(:event='event' @openModeration='openModeration=true') //- resources from fediverse EventResource#resources.mt-3(:event='event' v-if='showResources') @@ -106,10 +113,12 @@ v-dialog(v-show='settings.allow_geolocation && event.place?.latitude && event.place?.longitude' v-model='mapModal' :fullscreen='$vuetify.breakpoint.xsOnly' destroy-on-close) EventMapDialog(:place='event.place' @close='mapModal=false') + v-navigation-drawer(v-model='openModeration' :fullscreen='$vuetify.breakpoint.xsOnly' fixed top right width=400 temporary) + EventModeration(:event='event' v-if='openModeration' @close='openModeration=false') +