mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 08:32:23 +01:00
feat: refactoring event moderation, report, report notification
This commit is contained in:
parent
ca3ed0b0d1
commit
22c2735e93
13 changed files with 90 additions and 52 deletions
|
@ -2,21 +2,20 @@
|
|||
<v-card class="eventModeration">
|
||||
<v-card-title>{{$t('common.moderation')}} <v-spacer /><v-btn text label icon @click="$emit('close', false)" size='small'><v-icon v-text="mdiChevronRight"/></v-btn></v-card-title>
|
||||
<v-card-text class="d-flex flex-column flex-grow-1 overflow-auto">
|
||||
<v-textarea :label="$t('event.message')" :hint="$t('event.message_hint')" persistent-hint v-model='message' rows="2" class="mb-2"/>
|
||||
<template v-if="$auth.user.is_admin || $auth.user.is_editor">
|
||||
<v-textarea :label="$t('event.message')" :hint="$t(isAdmin ? 'event.message_hint' : 'event.message_author_hint')" persistent-hint v-model='message' rows="2" class="mb-2"/>
|
||||
<template v-if="isAdmin">
|
||||
<v-btn class='mb-1' small outlined :disabled='!message || loading' :loading='loading' @click="sendMessage(false)" color="primary">{{$t('event.send_to_admins')}}</v-btn>
|
||||
<v-btn class='mb-1' small outlined :disabled='!message || loading' :loading='loading' @click="sendMessage(true)" color="primary">{{$t('event.send_to_author_too')}}</v-btn>
|
||||
<v-btn v-if='!event.isAnon' class='mb-1' small outlined :disabled='!message || loading' :loading='loading' @click="sendMessage(true)" color="primary">{{$t('event.send_to_author_too')}}</v-btn>
|
||||
</template>
|
||||
<v-btn v-else small outlined :disabled='!message || loading' :loading='loading' @click="sendMessage(true)" color="primary">send</v-btn><br/>
|
||||
<v-list dense class='messageList'>
|
||||
<v-list-item v-for="(item, index) in messages" :key="index" class="px-2">
|
||||
<v-list-item v-for="(item, index) in messages" :key="index" class="px-2" :class="item.author">
|
||||
<v-list-item-content>
|
||||
<span v-if="item?.message">{{ item.message }}</span>
|
||||
<v-list-item-subtitle>{{ $time.format(item.createdAt, 'EEEE d MMMM HH:mm') }} / {{ item.author }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle class="font-weight-light">{{ $time.format(item.createdAt, 'EEEE d MMMM HH:mm') }} / {{ item.author }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-btn v-if='!event.isAnon' class='mb-1' small outlined :disabled='loading' :loading='loading' @click="disableAuthor" color="primary">{{$t('event.disable_author')}}</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
@ -44,20 +43,12 @@ export default {
|
|||
async mounted () {
|
||||
this.messages = await this.$axios.$get(`/event/messages/${this.event.id}`)
|
||||
},
|
||||
computed: {
|
||||
isAdmin () {
|
||||
return this.$auth.user.is_admin || this.$auth.user.is_editor
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async disableAuthor () {
|
||||
// ask confirmation only to disable
|
||||
const ret = await this.$root.$confirm('admin.disable_user_confirm')
|
||||
if (!ret) { return }
|
||||
this.loading = true
|
||||
try {
|
||||
await this.$axios.$put(`/event/disable_author/${this.event.id}`)
|
||||
this.$root.$message("Author disabled!", { color: 'success' })
|
||||
} catch (e) {
|
||||
this.$root.$message(e, { color: 'warning' })
|
||||
}
|
||||
this.loading = false
|
||||
},
|
||||
async sendMessage (is_author_visible) {
|
||||
try {
|
||||
this.loading = true
|
||||
|
@ -96,8 +87,19 @@ export default {
|
|||
}
|
||||
|
||||
.eventModeration .messageList .v-list-item {
|
||||
border-top: 1px solid rgba(100,100,100,.3);
|
||||
border-top: 1px solid rgba(100,100,100,.07);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.eventModeration .messageList .v-list-item.ADMIN {
|
||||
background-color: rgba(200,100,100,0.1);
|
||||
border-left: 2px solid rgba(255,69,0, 0.3);
|
||||
}
|
||||
|
||||
.eventModeration .messageList .v-list-item.AUTHOR {
|
||||
border-left: 2px solid lightblue;
|
||||
background-color: rgba(35, 193, 255, 0.1);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -58,6 +58,8 @@ v-container
|
|||
|
||||
v-switch.mt-1(v-model='allow_geolocation'
|
||||
inset
|
||||
persistent-hint
|
||||
:hint="$t('admin.allow_geolocation_hint')"
|
||||
:label="$t('admin.allow_geolocation')")
|
||||
|
||||
v-switch.mt-1(v-model='enable_moderation'
|
||||
|
@ -66,6 +68,12 @@ v-container
|
|||
:hint="$t('admin.enable_moderation_hint')"
|
||||
:label="$t('admin.enable_moderation')")
|
||||
|
||||
v-switch.mt-1(v-model='enable_report'
|
||||
v-if="enable_moderation"
|
||||
inset
|
||||
persistent-hint
|
||||
:hint="$t('admin.enable_report_hint')"
|
||||
:label="$t('admin.enable_report')")
|
||||
|
||||
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||
SMTP(@close='showSMTP = false')
|
||||
|
@ -145,6 +153,10 @@ export default {
|
|||
get () { return this.settings.enable_moderation },
|
||||
set (value) { this.setSetting({ key: 'enable_moderation', value }) }
|
||||
},
|
||||
enable_report: {
|
||||
get () { return this.settings.enable_report },
|
||||
set (value) { this.setSetting({ key: 'enable_report', value }) }
|
||||
},
|
||||
filteredTimezones () {
|
||||
const current_timezone = DateTime.local().zoneName
|
||||
tzNames.unshift(current_timezone)
|
||||
|
|
|
@ -27,7 +27,9 @@
|
|||
"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:<br/><pre>{{message}}</pre><br/><br/><a href='{{url}}'>Open moderation</a>"
|
||||
"subject": "Event moderation [{{event.title}}]",
|
||||
"content_ADMIN": "An admin commented about the event <strong>{{event.title}}</strong>:<br/><blockquote>{{message}}</blockquote><br/><br/><a href='{{url}}'>Open moderation</a>",
|
||||
"content_ANON": "A visitor reported the event <strong>{{event.title}}</strong>:<br/><blockquote>{{message}}</blockquote><br/><br/><a href='{{url}}'>Open moderation</a>",
|
||||
"content_AUTHOR": "The author of event <strong>{{event.title}}</strong> wrote:<br/><blockquote>{{message}}</blockquote><br/><br/><a href='{{url}}'>Open moderation</a>"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"next": "Next",
|
||||
"export": "Export",
|
||||
"send": "Send",
|
||||
"sent": "Sent",
|
||||
"where": "Where",
|
||||
"address": "Address",
|
||||
"when": "When",
|
||||
|
@ -204,10 +205,12 @@
|
|||
"address_geocoded_disclaimer": "If you cannot find the <strong>street address</strong> or the <strong>housenumber</strong> 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 <a target=\"_blank\" href=\"https://www.openstreetmap.org/\">OpenStreetMap</a> project is open to contributions. If you have Android, we recommend <a target=\"_blank\" href=\"https://f-droid.org/en/packages/de.westnordost.streetcomplete/\">StreetComplete</a> ",
|
||||
"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",
|
||||
"message_author_hint": "Write a message to admins",
|
||||
"report_message_confirmation": "Do you think this event should not be here? Write us a reason",
|
||||
"send_to_admins": "Send to admins",
|
||||
"send_to_author_too": "Send to admins and author",
|
||||
"disable_author": "Disable the event author"
|
||||
"disable_author": "Disable author",
|
||||
"disable_author_confirm": "Are you sure you want to disable the author of this event?"
|
||||
},
|
||||
"admin": {
|
||||
"place_description": "If you have gotten the place or address wrong, you can change it.<br/>All current and past events associated with this place will change address.",
|
||||
|
@ -225,12 +228,14 @@
|
|||
"allow_registration_description": "Allow open registrations?",
|
||||
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
||||
"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",
|
||||
"enable_moderation_hint": "Admins could talk each about events, optionally including event's author",
|
||||
"enable_report": "Enable report event",
|
||||
"enable_report_hint": "Any visitor could report an event, admins are notified via e-mail",
|
||||
"allow_multidate_event": "Allow multi-day events",
|
||||
"allow_recurrent_event": "Allow recurring events",
|
||||
"allow_online_event": "Allow online events",
|
||||
"allow_online_event_hint": "Ask for urls ",
|
||||
"allow_online_event": "Allow online events",
|
||||
"allow_geolocation": "Allow events geolocation",
|
||||
"allow_geolocation_hint": "Optionally set the exact location of an event on a geographic map",
|
||||
"recurrent_event_visible": "Show recurring events by default",
|
||||
"federation": "Federation / ActivityPub",
|
||||
"enable_federation": "Turn on federation",
|
||||
|
@ -238,7 +243,7 @@
|
|||
"add_instance": "Add instance",
|
||||
"select_instance_timezone": "Time zone",
|
||||
"enable_resources": "Turn on resources",
|
||||
"enable_resources_help": "Allows adding resources to the event from the fediverse",
|
||||
"enable_resources_help": "Allows adding audio, images and comments to the event from the fediverse",
|
||||
"hide_boost_bookmark": "Hides boost/bookmarks",
|
||||
"hide_boost_bookmark_help": "Hides the small icons showing the number of boosts and bookmarks coming from the fediverse",
|
||||
"block": "Block",
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
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(v-if='settings.enable_moderation && settings.enable_report && !showModeration' @click='report')
|
||||
v-list-item-icon
|
||||
v-icon(v-text='mdiMessageTextOutline')
|
||||
v-list-item-content
|
||||
|
@ -159,7 +159,8 @@ export default {
|
|||
event: {},
|
||||
showEmbed: false,
|
||||
mapModal: false,
|
||||
openModeration: false
|
||||
openModeration: false,
|
||||
reporting: false
|
||||
}
|
||||
},
|
||||
head () {
|
||||
|
@ -277,11 +278,12 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async report () {
|
||||
const message = await this.$root.$prompt(this.$t('event.report_message_confirmation'), { title: this.$t('common.report')})
|
||||
this.reporting = true
|
||||
const message = await this.$root.$prompt(this.$t('event.report_message_confirmation'), { title: this.$t('common.report') })
|
||||
if (!message) {
|
||||
return
|
||||
}
|
||||
|
||||
this.reporting = false
|
||||
try {
|
||||
await this.$axios.$post(`/event/messages/${this.event.id}`, { message })
|
||||
this.$root.$message('common.sent', { color: 'success' })
|
||||
|
@ -290,6 +292,9 @@ export default {
|
|||
}
|
||||
},
|
||||
keyDown (ev) {
|
||||
if (this.openModeration || this.reporting) {
|
||||
return
|
||||
}
|
||||
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) { return }
|
||||
if (ev.key === 'ArrowRight' && this.event.next) {
|
||||
this.goNext()
|
||||
|
|
|
@ -126,6 +126,7 @@ const eventController = {
|
|||
include: [
|
||||
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
|
||||
{ model: Place, attributes: ['name', 'address', 'latitude', 'longitude', 'id'] },
|
||||
{ model: User, required: false, attributes: ['is_active'] },
|
||||
{
|
||||
model: Resource,
|
||||
where: !is_admin && { hidden: false },
|
||||
|
@ -135,7 +136,7 @@ const eventController = {
|
|||
},
|
||||
{ model: Event, required: false, as: 'parent', attributes: ['id', 'recurrent', 'is_visible', 'start_datetime'] },
|
||||
],
|
||||
order: [[Resource, 'id', 'DESC']]
|
||||
order: [[Resource, 'id', 'DESC']],
|
||||
})
|
||||
} catch (e) {
|
||||
log.error('[EVENT]', e)
|
||||
|
@ -184,9 +185,10 @@ const eventController = {
|
|||
if (event && (event.is_visible || is_admin)) {
|
||||
event = event.get()
|
||||
event.isMine = event.userId === req.user?.id
|
||||
event.isAnon = event.userId === null
|
||||
event.isAnon = event.userId === null || !event?.user?.is_active
|
||||
event.original_url = event?.ap_object?.url || event?.ap_object?.id
|
||||
delete event.ap_object
|
||||
delete event.ap_object
|
||||
delete event.user
|
||||
delete event.userId
|
||||
event.next = next && (next.slug || next.id)
|
||||
event.prev = prev && (prev.slug || prev.id)
|
||||
|
@ -263,7 +265,6 @@ const eventController = {
|
|||
return res.json(messages)
|
||||
}
|
||||
|
||||
log.debug('userId: %s event ud %s', event, req.user.id)
|
||||
return res.sendStatus(400)
|
||||
|
||||
},
|
||||
|
@ -275,9 +276,9 @@ const eventController = {
|
|||
}
|
||||
|
||||
const eventId = Number(req.params.event_id)
|
||||
const event = await Event.findByPk(eventId)
|
||||
const event = await Event.findByPk(eventId, { include: [{ model: User, required: false }], raw: true })
|
||||
if (!event) {
|
||||
log.warn(`Trying to ... ${eventId}`)
|
||||
log.warn(`[REPORT] Event does not exists: ${eventId}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
|
@ -285,12 +286,10 @@ const eventController = {
|
|||
const isMine = req.user?.id === event.userId
|
||||
const isAdminOrEditor = req.user?.is_editor || req.user?.is_admin
|
||||
|
||||
// if (!isAdminOrEditor && !isMine) {
|
||||
// log.warn(`Someone not allowed is trying to report on an event -> "${event.title}" isMine: ${isMine} `)
|
||||
// return res.sendStatus(403)
|
||||
// }
|
||||
|
||||
// mail.send(user.email, 'message', { subject, message }, res.locals.settings.locale)
|
||||
if (!isAdminOrEditor && !isMine && !res.locals.settings.enable_report) {
|
||||
log.warn(`[REPORT] Someone not allowed is trying to report an event -> "${event.title}" isMine: ${isMine} `)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
const author = isAdminOrEditor ? 'ADMIN' : isMine ? 'AUTHOR' : 'ANON'
|
||||
try {
|
||||
|
@ -302,12 +301,18 @@ const eventController = {
|
|||
})
|
||||
|
||||
const admins = await User.findAll({ where: { role: ['admin', 'editor'], is_active: true }, attributes: ['email'], raw: true })
|
||||
console.error(admins)
|
||||
let emails = [res.locals.settings.admin_email]
|
||||
emails = emails.concat(admins.map(a => a.email))
|
||||
emails = emails.concat(admins?.map(a => a.email))
|
||||
log.info('[EVENT] Report event to %s', emails)
|
||||
|
||||
// notify admins
|
||||
mail.send(emails, 'report', { event, message: body.message, author })
|
||||
|
||||
// notify author
|
||||
if (event['user.email'] && body.is_author_visible && !isMine) {
|
||||
mail.send(event['user.email'], 'report', { event, message: body.message, author })
|
||||
}
|
||||
|
||||
return res.json(message)
|
||||
} catch (e) {
|
||||
log.warn(`[EVENT] ${e}`)
|
||||
|
|
|
@ -89,7 +89,7 @@ passport.use(new ClientPublicStrategy(verifyPublicClient))
|
|||
|
||||
async function verifyToken (req, accessToken, done) {
|
||||
const token = await OAuthToken.findByPk(accessToken,
|
||||
{ include: [{ model: User, attributes: { exclude: ['password'] } }, { model: OAuthClient, as: 'client' }] })
|
||||
{ include: [{ required: true, where: { is_active: true } , model: User, attributes: { exclude: ['password'] } }, { model: OAuthClient, as: 'client' }] })
|
||||
|
||||
if (!token) return done(null, false)
|
||||
if (token.userId) {
|
||||
|
|
|
@ -27,7 +27,8 @@ const defaultSettings = {
|
|||
allow_multidate_event: true,
|
||||
allow_recurrent_event: false,
|
||||
allow_online_event: true,
|
||||
enable_moderation: true,
|
||||
enable_moderation: false,
|
||||
enable_report: false,
|
||||
recurrent_event_visible: false,
|
||||
allow_geolocation: false,
|
||||
geocoding_provider_type: 'Nominatim',
|
||||
|
|
|
@ -120,6 +120,7 @@ const userController = {
|
|||
async remove (req, res) {
|
||||
try {
|
||||
let user
|
||||
// TODO: has to unset events first!
|
||||
if (req.user.is_admin && req.params.id) {
|
||||
user = await User.findByPk(req.params.id)
|
||||
} else {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
extends ../layout.pug
|
||||
block content
|
||||
p !{t('report.content', {config, author, event, message, url: `${config.baseurl}/event/${event.slug || event.id}`})}
|
||||
p !{t(`report.content_${author}`, {config, event, message, url: `${config.baseurl}/event/${event.slug || event.id}`})}
|
||||
|
|
|
@ -78,6 +78,7 @@ module.exports = {
|
|||
allow_anon_event: settings.allow_anon_event,
|
||||
allow_recurrent_event: settings.allow_recurrent_event,
|
||||
enable_moderation: settings.enable_moderation,
|
||||
enable_report: settings.enable_report,
|
||||
allow_multidate_event: settings.allow_multidate_event,
|
||||
allow_online_event: settings.allow_online_event,
|
||||
recurrent_event_visible: settings.recurrent_event_visible,
|
||||
|
|
|
@ -14,7 +14,7 @@ const notifier = {
|
|||
|
||||
emitter: new events.EventEmitter(),
|
||||
|
||||
sendNotification (notification, event) {
|
||||
async sendNotification (notification, event) {
|
||||
const promises = []
|
||||
log.info(`Send ${notification.type} notification ${notification.action}`)
|
||||
let p
|
||||
|
@ -22,7 +22,10 @@ const notifier = {
|
|||
// case 'mail': TODO: locale?
|
||||
// return mail.send(notification.email, 'event', { event, notification })
|
||||
case 'admin_email':
|
||||
p = mail.send(settingsController.settings.admin_email, 'event',
|
||||
const admins = await User.findAll({ where: { role: ['admin', 'editor'], is_active: true }, attributes: ['email'], raw: true })
|
||||
let emails = [settingsController.settings.admin_email]
|
||||
emails = emails.concat(admins?.map(a => a.email))
|
||||
p = mail.send(emails, 'event',
|
||||
{ event, to_confirm: !event.is_visible, notification })
|
||||
promises.push(p)
|
||||
break
|
||||
|
|
|
@ -8,7 +8,8 @@ export const state = () => ({
|
|||
instance_name: '',
|
||||
allow_registration: true,
|
||||
allow_anon_event: true,
|
||||
enable_moderation: true,
|
||||
enable_moderation: false,
|
||||
enable_report: false,
|
||||
allow_multidate_event: true,
|
||||
allow_recurrent_event: true,
|
||||
allow_online_event: true,
|
||||
|
|
Loading…
Reference in a new issue