mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
parent
0e3e045d3f
commit
b40b4ba3b4
23 changed files with 385 additions and 98 deletions
|
@ -27,14 +27,6 @@ span
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('common.clone')")
|
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
|
//- Remove
|
||||||
v-list-item(v-if='!event.parentId' @click='remove(false)')
|
v-list-item(v-if='!event.parentId' @click='remove(false)')
|
||||||
v-list-item-icon
|
v-list-item-icon
|
||||||
|
@ -42,6 +34,12 @@ span
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('common.remove')")
|
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')
|
template(v-if='event.parentId')
|
||||||
v-list-item.text-overline(v-html="$t('common.recurring_event_actions')")
|
v-list-item.text-overline(v-html="$t('common.recurring_event_actions')")
|
||||||
|
@ -68,18 +66,6 @@ span
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('common.remove')")
|
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
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -90,8 +76,6 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
mdiChevronUp, mdiRepeat, mdiDelete, mdiCalendarEdit, mdiEyeOff, mdiEye, mdiPause, mdiPlay, mdiDeleteForever, mdiScanner, mdiMessageTextOutline,
|
mdiChevronUp, mdiRepeat, mdiDelete, mdiCalendarEdit, mdiEyeOff, mdiEye, mdiPause, mdiPlay, mdiDeleteForever, mdiScanner, mdiMessageTextOutline,
|
||||||
openContactUser: false,
|
|
||||||
message: this.value.message || ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -99,11 +83,6 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings'])
|
...mapState(['settings'])
|
||||||
|
@ -131,17 +110,6 @@ export default {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async sendMessageToUser () {
|
|
||||||
try {
|
|
||||||
await this.$axios.$post('/user/send_message', {
|
|
||||||
subject: this.event.title,
|
|
||||||
message: this.message,
|
|
||||||
userId: this.event.userId
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
103
components/EventModeration.vue
Normal file
103
components/EventModeration.vue
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<template>
|
||||||
|
<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-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>
|
||||||
|
</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-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-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>
|
||||||
|
<script>
|
||||||
|
import { mdiMessageTextOutline, mdiSend, mdiChevronRight } from '@mdi/js'
|
||||||
|
import TBtn from '../components/TBtn.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EventModeration',
|
||||||
|
components: { TBtn },
|
||||||
|
props: {
|
||||||
|
event: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
mdiMessageTextOutline, mdiSend, mdiChevronRight,
|
||||||
|
message: '',
|
||||||
|
messages: [],
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.messages = await this.$axios.$get(`/event/messages/${this.event.id}`)
|
||||||
|
},
|
||||||
|
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
|
||||||
|
await this.$axios.$post(`/event/messages/${this.event.id}`, {
|
||||||
|
is_author_visible,
|
||||||
|
message: this.message,
|
||||||
|
})
|
||||||
|
this.message = ''
|
||||||
|
this.loading = false
|
||||||
|
this.messages = await this.$axios.$get(`/event/messages/${this.event.id}`)
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.$message(e, { color: 'warning' })
|
||||||
|
this.loading = false
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.eventModeration {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventModeration .messageList {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventModeration .v-textarea {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eventModeration .messageList .v-list-item {
|
||||||
|
border-top: 1px solid rgba(100,100,100,.3);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -60,9 +60,12 @@ v-container
|
||||||
inset
|
inset
|
||||||
:label="$t('admin.allow_geolocation')")
|
:label="$t('admin.allow_geolocation')")
|
||||||
|
|
||||||
v-switch.mt-1(v-model='allow_message_users'
|
v-switch.mt-1(v-model='enable_moderation'
|
||||||
inset
|
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')
|
v-dialog(v-model='showSMTP' destroy-on-close max-width='700px' :fullscreen='$vuetify.breakpoint.xsOnly')
|
||||||
SMTP(@close='showSMTP = false')
|
SMTP(@close='showSMTP = false')
|
||||||
|
@ -138,9 +141,9 @@ export default {
|
||||||
get () { return this.settings.allow_online_event },
|
get () { return this.settings.allow_online_event },
|
||||||
set (value) { this.setSetting({ key: 'allow_online_event', value }) }
|
set (value) { this.setSetting({ key: 'allow_online_event', value }) }
|
||||||
},
|
},
|
||||||
allow_message_users: {
|
enable_moderation: {
|
||||||
get () { return this.settings.allow_message_users },
|
get () { return this.settings.enable_moderation },
|
||||||
set (value) { this.setSetting({ key: 'allow_message_users', value }) }
|
set (value) { this.setSetting({ key: 'enable_moderation', value }) }
|
||||||
},
|
},
|
||||||
filteredTimezones () {
|
filteredTimezones () {
|
||||||
const current_timezone = DateTime.local().zoneName
|
const current_timezone = DateTime.local().zoneName
|
||||||
|
|
|
@ -25,5 +25,9 @@
|
||||||
"test": {
|
"test": {
|
||||||
"subject": "Your SMTP configuration is working",
|
"subject": "Your SMTP configuration is working",
|
||||||
"content": "This is a test email, if you are reading this your 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:<br/><pre>{{message}}</pre><br/><br/><a href='{{url}}'>Open moderation</a>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,8 @@
|
||||||
"actors": "Node",
|
"actors": "Node",
|
||||||
"collection_in_home": "Show a collection in home",
|
"collection_in_home": "Show a collection in home",
|
||||||
"my_events": "My Events",
|
"my_events": "My Events",
|
||||||
"contact_user": "Contact user"
|
"contact_user": "Contact user",
|
||||||
|
"report": "Report event",
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "By logging in you can publish new events.",
|
"description": "By logging in you can publish new events.",
|
||||||
|
@ -199,23 +200,31 @@
|
||||||
"online_locations": "Online locations",
|
"online_locations": "Online locations",
|
||||||
"online_locations_help": "For instance an url to a videconference room and a fallback url (max. 3)",
|
"online_locations_help": "For instance an url to a videconference room and a fallback url (max. 3)",
|
||||||
"online_locations_fallback_urls": "Fallback links",
|
"online_locations_fallback_urls": "Fallback links",
|
||||||
"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> "
|
"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",
|
||||||
|
"send_to_admins": "Send to admins",
|
||||||
|
"send_to_author_too": "Send to admins and author",
|
||||||
|
"disable_author": "Disable the event author"
|
||||||
},
|
},
|
||||||
"admin": {
|
"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.",
|
"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.",
|
||||||
"event_confirm_description": "You can confirm events entered by anonymous users here",
|
"event_confirm_description": "You can confirm events entered by anonymous users here",
|
||||||
"delete_user": "Remove",
|
"delete_user": "Remove",
|
||||||
"remove_admin": "Remove admin",
|
"remove_admin": "Remove admin",
|
||||||
"disable_user_confirm": "Are you sure you want to disable {user}?",
|
"change_role_confirm": "Are you sure to change <strong>{user}</strong> role from <strong>{from_role}</strong> to <strong>{to_role}</strong>",
|
||||||
"delete_user_confirm": "Are you sure you want to remove {user}?",
|
"disable_user_confirm": "Are you sure you want to disable <strong>{user}</strong>?",
|
||||||
"disable_admin_user_confirm": "Are you sure to remove admin permissions from {user}?",
|
"delete_user_confirm": "Are you sure you want to remove <strong>{user}</strong>?",
|
||||||
"enable_admin_user_confirm": "Are you sure to add admin permissions to {user}?",
|
"disable_admin_user_confirm": "Are you sure to remove admin permissions from <strong>{user}</strong>?",
|
||||||
|
"enable_admin_user_confirm": "Are you sure to add admin permissions to <strong>{user}</strong>?",
|
||||||
"user_remove_ok": "User removed",
|
"user_remove_ok": "User removed",
|
||||||
"user_create_ok": "User created",
|
"user_create_ok": "User created",
|
||||||
"event_remove_ok": "Event removed",
|
"event_remove_ok": "Event removed",
|
||||||
"allow_registration_description": "Allow open registrations?",
|
"allow_registration_description": "Allow open registrations?",
|
||||||
"allow_anon_event": "Allow anonymous events (has to be confirmed)?",
|
"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_multidate_event": "Allow multi-day events",
|
||||||
"allow_recurrent_event": "Allow recurring events",
|
"allow_recurrent_event": "Allow recurring events",
|
||||||
"allow_online_event": "Allow online events",
|
"allow_online_event": "Allow online events",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default async function ({ redirect, $auth }) {
|
export default async function ({ redirect, $auth }) {
|
||||||
if (!$auth.user.is_admin) {
|
if (!$auth?.user?.is_admin) {
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
export default async function ({ redirect, $auth }) {
|
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('/')
|
return redirect('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@
|
||||||
.font-weight-light.p-street-address(v-if='event?.place?.name !=="online"' itemprop='address') {{event?.place?.address}}
|
.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.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
|
//- tags, hashtags
|
||||||
v-container.pt-0(v-if='event?.tags?.length')
|
v-container.pt-0(v-if='event?.tags?.length')
|
||||||
|
@ -61,13 +61,6 @@
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('common.show_map')")
|
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
|
//- calendar
|
||||||
v-list-item(:href='`/api/event/detail/${event.slug || event.id}.ics`')
|
v-list-item(:href='`/api/event/detail/${event.slug || event.id}.ics`')
|
||||||
v-list-item-icon
|
v-list-item-icon
|
||||||
|
@ -75,6 +68,13 @@
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('common.add_to_calendar')")
|
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
|
//- download flyer
|
||||||
v-list-item(v-if='hasMedia' :href='$helper.mediaURL(event, "download")')
|
v-list-item(v-if='hasMedia' :href='$helper.mediaURL(event, "download")')
|
||||||
v-list-item-icon
|
v-list-item-icon
|
||||||
|
@ -82,11 +82,18 @@
|
||||||
v-list-item-content
|
v-list-item-content
|
||||||
v-list-item-title(v-text="$t('event.download_flyer')")
|
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
|
//- admin actions
|
||||||
template(v-if='can_edit')
|
template(v-if='can_edit')
|
||||||
v-divider
|
EventAdmin(:event='event' @openModeration='openModeration=true')
|
||||||
EventAdmin(:event='event')
|
|
||||||
|
|
||||||
//- resources from fediverse
|
//- resources from fediverse
|
||||||
EventResource#resources.mt-3(:event='event' v-if='showResources')
|
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)
|
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')
|
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')
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import get from 'lodash/get'
|
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import clipboard from '../../assets/clipboard'
|
import clipboard from '../../assets/clipboard'
|
||||||
import MyPicture from '~/components/MyPicture'
|
import MyPicture from '~/components/MyPicture'
|
||||||
|
@ -117,8 +126,9 @@ import EventAdmin from '@/components/EventAdmin'
|
||||||
import EventResource from '@/components/EventResource'
|
import EventResource from '@/components/EventResource'
|
||||||
import EmbedEvent from '@/components/embedEvent'
|
import EmbedEvent from '@/components/embedEvent'
|
||||||
import EventMapDialog from '@/components/EventMapDialog'
|
import EventMapDialog from '@/components/EventMapDialog'
|
||||||
|
import EventModeration from '@/components/EventModeration'
|
||||||
|
|
||||||
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose, mdiMap,
|
import { mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiClose, mdiMap, mdiMessageTextOutline,
|
||||||
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline, mdiShareAll,
|
mdiEye, mdiEyeOff, mdiDelete, mdiRepeat, mdiLock, mdiFileDownloadOutline, mdiShareAll,
|
||||||
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp, mdiMonitorAccount, mdiBookmark, mdiStar } from '@mdi/js'
|
mdiCalendarExport, mdiCalendar, mdiContentCopy, mdiMapMarker, mdiChevronUp, mdiMonitorAccount, mdiBookmark, mdiStar } from '@mdi/js'
|
||||||
|
|
||||||
|
@ -128,6 +138,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
EventAdmin,
|
EventAdmin,
|
||||||
EventResource,
|
EventResource,
|
||||||
|
EventModeration,
|
||||||
EmbedEvent,
|
EmbedEvent,
|
||||||
MyPicture,
|
MyPicture,
|
||||||
EventMapDialog
|
EventMapDialog
|
||||||
|
@ -142,12 +153,13 @@ export default {
|
||||||
},
|
},
|
||||||
data ({$store}) {
|
data ({$store}) {
|
||||||
return {
|
return {
|
||||||
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline,
|
mdiArrowLeft, mdiArrowRight, mdiDotsVertical, mdiCodeTags, mdiCalendarExport, mdiCalendar, mdiFileDownloadOutline, mdiMessageTextOutline,
|
||||||
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiMap, mdiChevronUp, mdiMonitorAccount, mdiBookmark, mdiStar, mdiShareAll,
|
mdiMapMarker, mdiContentCopy, mdiClose, mdiDelete, mdiEye, mdiEyeOff, mdiRepeat, mdiMap, mdiChevronUp, mdiMonitorAccount, mdiBookmark, mdiStar, mdiShareAll,
|
||||||
currentAttachment: 0,
|
currentAttachment: 0,
|
||||||
event: {},
|
event: {},
|
||||||
showEmbed: false,
|
showEmbed: false,
|
||||||
mapModal: false
|
mapModal: false,
|
||||||
|
openModeration: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
head () {
|
head () {
|
||||||
|
@ -231,6 +243,9 @@ export default {
|
||||||
hasOnlineLocations () {
|
hasOnlineLocations () {
|
||||||
return this.event.online_locations && this.event.online_locations.length
|
return this.event.online_locations && this.event.online_locations.length
|
||||||
},
|
},
|
||||||
|
showModeration () {
|
||||||
|
return this.settings.enable_moderation && this.$auth?.user && (this.event.isMine || this.$auth?.user?.is_admin || this.$auth?.user?.is_editor)
|
||||||
|
},
|
||||||
showMap () {
|
showMap () {
|
||||||
return this.settings.allow_geolocation && this.event.place?.latitude && this.event.place?.longitude
|
return this.settings.allow_geolocation && this.event.place?.latitude && this.event.place?.longitude
|
||||||
},
|
},
|
||||||
|
@ -261,6 +276,19 @@ export default {
|
||||||
window.removeEventListener('keydown', this.keyDown)
|
window.removeEventListener('keydown', this.keyDown)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async report () {
|
||||||
|
const message = await this.$root.$prompt(this.$t('event.report_message_confirmation'), { title: this.$t('common.report')})
|
||||||
|
if (!message) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$axios.$post(`/event/messages/${this.event.id}`, { message })
|
||||||
|
this.$root.$message('common.sent', { color: 'success' })
|
||||||
|
} catch (e) {
|
||||||
|
this.$root.$message(e, { color: 'warning' })
|
||||||
|
}
|
||||||
|
},
|
||||||
keyDown (ev) {
|
keyDown (ev) {
|
||||||
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) { return }
|
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) { return }
|
||||||
if (ev.key === 'ArrowRight' && this.event.next) {
|
if (ev.key === 'ArrowRight' && this.event.next) {
|
||||||
|
|
|
@ -93,12 +93,17 @@ export default {
|
||||||
Search
|
Search
|
||||||
},
|
},
|
||||||
mixins: [clipboard],
|
mixins: [clipboard],
|
||||||
async asyncData ({ $axios, params, store, $api, $time }) {
|
async asyncData ({ $api, $time }) {
|
||||||
const events = await $api.getEvents({
|
try {
|
||||||
start: $time.currentTimestamp(),
|
const events = await $api.getEvents({
|
||||||
show_recurrent: false
|
start: $time.currentTimestamp(),
|
||||||
})
|
show_recurrent: false
|
||||||
return { events }
|
})
|
||||||
|
return { events }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return { events: [] }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data ({ $store }) {
|
data ({ $store }) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -11,7 +11,7 @@ const Col = helpers.col
|
||||||
const notifier = require('../../notifier')
|
const notifier = require('../../notifier')
|
||||||
const { htmlToText } = require('html-to-text')
|
const { htmlToText } = require('html-to-text')
|
||||||
|
|
||||||
const { Event, Resource, Tag, Place, Notification, APUser, Collection, EventNotification } = require('../models/models')
|
const { Event, Resource, Tag, Place, Notification, APUser, EventNotification, Message, User } = require('../models/models')
|
||||||
|
|
||||||
|
|
||||||
const exportController = require('./export')
|
const exportController = require('./export')
|
||||||
|
@ -121,7 +121,7 @@ const eventController = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: ['createdAt', 'updatedAt', 'placeId']
|
exclude: ['createdAt', 'updatedAt', 'placeId', 'ap_id', 'apUserApId']
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
|
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
|
||||||
|
@ -134,7 +134,6 @@ const eventController = {
|
||||||
attributes: ['id', 'activitypub_id', 'data', 'hidden']
|
attributes: ['id', 'activitypub_id', 'data', 'hidden']
|
||||||
},
|
},
|
||||||
{ model: Event, required: false, as: 'parent', attributes: ['id', 'recurrent', 'is_visible', 'start_datetime'] },
|
{ model: Event, required: false, as: 'parent', attributes: ['id', 'recurrent', 'is_visible', 'start_datetime'] },
|
||||||
{ model: APUser, required: false }
|
|
||||||
],
|
],
|
||||||
order: [[Resource, 'id', 'DESC']]
|
order: [[Resource, 'id', 'DESC']]
|
||||||
})
|
})
|
||||||
|
@ -185,6 +184,9 @@ const eventController = {
|
||||||
if (event && (event.is_visible || is_admin)) {
|
if (event && (event.is_visible || is_admin)) {
|
||||||
event = event.get()
|
event = event.get()
|
||||||
event.isMine = event.userId === req.user?.id
|
event.isMine = event.userId === req.user?.id
|
||||||
|
event.isAnon = event.userId === null
|
||||||
|
event.original_url = event?.ap_object?.url || event?.ap_object?.id
|
||||||
|
delete event.ap_object
|
||||||
delete event.userId
|
delete event.userId
|
||||||
event.next = next && (next.slug || next.id)
|
event.next = next && (next.slug || next.id)
|
||||||
event.prev = prev && (prev.slug || prev.id)
|
event.prev = prev && (prev.slug || prev.id)
|
||||||
|
@ -206,6 +208,105 @@ const eventController = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async disableAuthor (req, res) {
|
||||||
|
const eventId = Number(req.params.event_id)
|
||||||
|
if (!res.locals.settings.enable_moderation) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await Event.findByPk(eventId, { include: [ User ]})
|
||||||
|
if (!event) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.user) {
|
||||||
|
await event.user.update({ is_active: false })
|
||||||
|
res.sendStatus(200)
|
||||||
|
} else {
|
||||||
|
res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// get all event moderation messages if we are admin || editor
|
||||||
|
// get mine and to_author moderation messages if I'm the event author
|
||||||
|
async getMessages (req, res) {
|
||||||
|
|
||||||
|
if (!res.locals.settings.enable_moderation) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventId = Number(req.params.event_id)
|
||||||
|
|
||||||
|
// in case we are admin or editor return all moderation messages related to this event
|
||||||
|
if (req.user.is_admin || req.user.is_editor) {
|
||||||
|
const messages = await Message.findAll({ where: { eventId }, order: [['createdAt', 'DESC']]})
|
||||||
|
return res.json(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await Event.findByPk(eventId)
|
||||||
|
if (!event) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.userId === req.user.id) {
|
||||||
|
const messages = await Message.findAll({ where: { eventId, is_author_visible: true }, order: [['createdAt', 'DESC']]})
|
||||||
|
return res.json(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug('userId: %s event ud %s', event, req.user.id)
|
||||||
|
return res.sendStatus(400)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async report (req, res) {
|
||||||
|
const mail = require('../mail')
|
||||||
|
if (!res.locals.settings.enable_moderation) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventId = Number(req.params.event_id)
|
||||||
|
const event = await Event.findByPk(eventId)
|
||||||
|
if (!event) {
|
||||||
|
log.warn(`Trying to ... ${eventId}`)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = req.body
|
||||||
|
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)
|
||||||
|
|
||||||
|
const author = isAdminOrEditor ? 'ADMIN' : isMine ? 'AUTHOR' : 'ANON'
|
||||||
|
try {
|
||||||
|
const message = await Message.create({
|
||||||
|
eventId,
|
||||||
|
message: body.message,
|
||||||
|
is_author_visible: body.is_author_visible || isMine,
|
||||||
|
author
|
||||||
|
})
|
||||||
|
|
||||||
|
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))
|
||||||
|
log.info('[EVENT] Report event to %s', emails)
|
||||||
|
mail.send(emails, 'report', { event, message: body.message, author })
|
||||||
|
|
||||||
|
return res.json(message)
|
||||||
|
} catch (e) {
|
||||||
|
log.warn(`[EVENT] ${e}`)
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
/** confirm an anonymous event
|
/** confirm an anonymous event
|
||||||
* and send related notifications
|
* and send related notifications
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,7 +27,7 @@ const defaultSettings = {
|
||||||
allow_multidate_event: true,
|
allow_multidate_event: true,
|
||||||
allow_recurrent_event: false,
|
allow_recurrent_event: false,
|
||||||
allow_online_event: true,
|
allow_online_event: true,
|
||||||
allow_message_users: true,
|
enable_moderation: true,
|
||||||
recurrent_event_visible: false,
|
recurrent_event_visible: false,
|
||||||
allow_geolocation: false,
|
allow_geolocation: false,
|
||||||
geocoding_provider_type: 'Nominatim',
|
geocoding_provider_type: 'Nominatim',
|
||||||
|
|
|
@ -9,20 +9,6 @@ const linkify = require('linkifyjs')
|
||||||
|
|
||||||
const userController = {
|
const userController = {
|
||||||
|
|
||||||
async sendMessage (req, res) {
|
|
||||||
if (!settingsController.settings.allow_message_users) { return res.sendStatus(404) }
|
|
||||||
const subject = req.body.subject
|
|
||||||
const message = req.body.message
|
|
||||||
const user = await User.findByPk(req.body.userId)
|
|
||||||
|
|
||||||
if (!user) { return res.status(404).json({ success: false, message: 'User not found!' }) }
|
|
||||||
|
|
||||||
mail.send(user.email, 'message', { subject, message }, res.locals.settings.locale)
|
|
||||||
|
|
||||||
res.status(200).send()
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
async forgotPassword (req, res) {
|
async forgotPassword (req, res) {
|
||||||
const email = req.body.email
|
const email = req.body.email
|
||||||
const user = await User.findOne({ where: { email, is_active: true } })
|
const user = await User.findOne({ where: { email, is_active: true } })
|
||||||
|
|
|
@ -81,7 +81,6 @@ module.exports = () => {
|
||||||
api.post('/user/recover', SPAMProtectionApiRateLimiter, userController.forgotPassword)
|
api.post('/user/recover', SPAMProtectionApiRateLimiter, userController.forgotPassword)
|
||||||
api.post('/user/check_recover_code', userController.checkRecoverCode)
|
api.post('/user/check_recover_code', userController.checkRecoverCode)
|
||||||
api.post('/user/recover_password', SPAMProtectionApiRateLimiter, userController.updatePasswordWithRecoverCode)
|
api.post('/user/recover_password', SPAMProtectionApiRateLimiter, userController.updatePasswordWithRecoverCode)
|
||||||
api.post('/user/send_message', isAdmin, userController.sendMessage)
|
|
||||||
|
|
||||||
// register and add users
|
// register and add users
|
||||||
api.post('/user/register', SPAMProtectionApiRateLimiter, userController.register)
|
api.post('/user/register', SPAMProtectionApiRateLimiter, userController.register)
|
||||||
|
@ -169,12 +168,17 @@ module.exports = () => {
|
||||||
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
||||||
api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings)
|
api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings)
|
||||||
|
|
||||||
|
// moderation
|
||||||
|
api.post('/event/messages/:event_id', SPAMProtectionApiRateLimiter, eventController.report)
|
||||||
|
api.get('/event/messages/:event_id', isAuth, eventController.getMessages)
|
||||||
|
|
||||||
// get unconfirmed events
|
// get unconfirmed events
|
||||||
api.get('/event/unconfirmed', isAdminOrEditor, eventController.getUnconfirmed)
|
api.get('/event/unconfirmed', isAdminOrEditor, eventController.getUnconfirmed)
|
||||||
|
|
||||||
// [un]confirm event
|
// [un]confirm event
|
||||||
api.put('/event/confirm/:event_id', isAuth, eventController.confirm)
|
api.put('/event/confirm/:event_id', isAuth, eventController.confirm)
|
||||||
api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm)
|
api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm)
|
||||||
|
api.put('/event/disable_author/:event_id', isAdminOrEditor, eventController.disableAuthor)
|
||||||
|
|
||||||
// get event
|
// get event
|
||||||
api.get('/event/detail/:event_slug.:format?', cors, eventController.get)
|
api.get('/event/detail/:event_slug.:format?', cors, eventController.get)
|
||||||
|
|
|
@ -24,6 +24,7 @@ const models = {
|
||||||
Setting: require('./setting'),
|
Setting: require('./setting'),
|
||||||
Tag: require('./tag'),
|
Tag: require('./tag'),
|
||||||
User: require('./user'),
|
User: require('./user'),
|
||||||
|
Message: require('./message')
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
|
@ -37,7 +38,7 @@ const db = {
|
||||||
},
|
},
|
||||||
associates () {
|
associates () {
|
||||||
const { Filter, Collection, APUser, Instance, User, Event, EventNotification, Tag,
|
const { Filter, Collection, APUser, Instance, User, Event, EventNotification, Tag,
|
||||||
OAuthCode, OAuthClient, OAuthToken, Resource, Place, Notification } = DB
|
OAuthCode, OAuthClient, OAuthToken, Resource, Place, Notification, Message } = DB
|
||||||
|
|
||||||
Filter.belongsTo(Collection)
|
Filter.belongsTo(Collection)
|
||||||
Collection.hasMany(Filter)
|
Collection.hasMany(Filter)
|
||||||
|
@ -57,6 +58,9 @@ const db = {
|
||||||
Event.belongsTo(Place)
|
Event.belongsTo(Place)
|
||||||
Place.hasMany(Event)
|
Place.hasMany(Event)
|
||||||
|
|
||||||
|
Message.belongsTo(Event)
|
||||||
|
Event.hasMany(Message)
|
||||||
|
|
||||||
Event.belongsTo(User)
|
Event.belongsTo(User)
|
||||||
User.hasMany(Event)
|
User.hasMany(Event)
|
||||||
|
|
||||||
|
|
27
server/api/models/message.js
Normal file
27
server/api/models/message.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
|
sequelize.define('message', {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: DataTypes.ENUM,
|
||||||
|
values: ['AUTHOR', 'ADMIN', 'ANON', 'REGISTERED']
|
||||||
|
},
|
||||||
|
is_author_visible: DataTypes.BOOLEAN, // is this message visible to the author?
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Moderation
|
||||||
|
*
|
||||||
|
* - new global settings to enable/disable this feature (enabled by default)
|
||||||
|
* - every user could report an event
|
||||||
|
* - admins will receive an mail notification about the report
|
||||||
|
* - admin could reply to report (optional adding author as destination)
|
||||||
|
* - admin could always interact with event moderation (hide, confirm, remove)
|
||||||
|
* - admin could disable the author
|
||||||
|
*/
|
|
@ -8,6 +8,7 @@
|
||||||
// Filter: require('./filter'),
|
// Filter: require('./filter'),
|
||||||
// Instance: require('./instance'),
|
// Instance: require('./instance'),
|
||||||
// Notification: require('./notification'),
|
// Notification: require('./notification'),
|
||||||
|
// Message: require('./message'),
|
||||||
// OAuthClient: require('./oauth_client'),
|
// OAuthClient: require('./oauth_client'),
|
||||||
// OAuthCode: require('./oauth_code'),
|
// OAuthCode: require('./oauth_code'),
|
||||||
// OAuthToken: require('./oauth_token'),
|
// OAuthToken: require('./oauth_token'),
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
extends ../layout.pug
|
|
||||||
block content
|
|
||||||
p #{message}
|
|
|
@ -1 +0,0 @@
|
||||||
| [#{config.title}] #{subject}
|
|
3
server/emails/report/html.pug
Normal file
3
server/emails/report/html.pug
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
extends ../layout.pug
|
||||||
|
block content
|
||||||
|
p !{t('report.content', {config, author, event, message, url: `${config.baseurl}/event/${event.slug || event.id}`})}
|
1
server/emails/report/subject.pug
Normal file
1
server/emails/report/subject.pug
Normal file
|
@ -0,0 +1 @@
|
||||||
|
| [#{config.title}] #{t('report.subject', { config, event, author })}
|
|
@ -77,7 +77,7 @@ module.exports = {
|
||||||
allow_registration: settings.allow_registration,
|
allow_registration: settings.allow_registration,
|
||||||
allow_anon_event: settings.allow_anon_event,
|
allow_anon_event: settings.allow_anon_event,
|
||||||
allow_recurrent_event: settings.allow_recurrent_event,
|
allow_recurrent_event: settings.allow_recurrent_event,
|
||||||
allow_message_users: settings.allow_message_users,
|
enable_moderation: settings.enable_moderation,
|
||||||
allow_multidate_event: settings.allow_multidate_event,
|
allow_multidate_event: settings.allow_multidate_event,
|
||||||
allow_online_event: settings.allow_online_event,
|
allow_online_event: settings.allow_online_event,
|
||||||
recurrent_event_visible: settings.recurrent_event_visible,
|
recurrent_event_visible: settings.recurrent_event_visible,
|
||||||
|
|
44
server/migrations/20240310211804-event_messages.js
Normal file
44
server/migrations/20240310211804-event_messages.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up (queryInterface, Sequelize) {
|
||||||
|
return queryInterface.createTable('messages',
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: Sequelize.TEXT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
eventId: {
|
||||||
|
type: Sequelize.INTEGER,
|
||||||
|
references: {
|
||||||
|
model: 'events',
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'SET NULL'
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
type: Sequelize.ENUM,
|
||||||
|
values: ['AUTHOR', 'ADMIN', 'ANON', 'REGISTERED']
|
||||||
|
},
|
||||||
|
is_author_visible: Sequelize.BOOLEAN,
|
||||||
|
createdAt: {
|
||||||
|
allowNull: false,
|
||||||
|
type: Sequelize.DATE
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
allowNull: false,
|
||||||
|
type: Sequelize.DATE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async down (queryInterface, Sequelize) {
|
||||||
|
return queryInterface.dropTable('messages')
|
||||||
|
}
|
||||||
|
};
|
|
@ -8,7 +8,7 @@ export const state = () => ({
|
||||||
instance_name: '',
|
instance_name: '',
|
||||||
allow_registration: true,
|
allow_registration: true,
|
||||||
allow_anon_event: true,
|
allow_anon_event: true,
|
||||||
allow_message_users: false,
|
enable_moderation: true,
|
||||||
allow_multidate_event: true,
|
allow_multidate_event: true,
|
||||||
allow_recurrent_event: true,
|
allow_recurrent_event: true,
|
||||||
allow_online_event: true,
|
allow_online_event: true,
|
||||||
|
|
Loading…
Reference in a new issue