mirror of
https://framagit.org/les/gancio.git
synced 2025-01-31 16:42:22 +01:00
Merge branch 'master' into gh
This commit is contained in:
commit
c8cc5c6c97
74 changed files with 1330 additions and 876 deletions
|
@ -1,5 +1,9 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
- models initialization refactored, better dev xperience as backend hmr is working
|
||||||
|
|
||||||
### 1.6.1 - 15 dec '22
|
### 1.6.1 - 15 dec '22
|
||||||
- allow edit tags in admin panel, fix #170
|
- allow edit tags in admin panel, fix #170
|
||||||
- fix header / fallback image upload, fix #222
|
- fix header / fallback image upload, fix #222
|
||||||
|
|
|
@ -8,7 +8,7 @@ export function attributesFromEvents(_events) {
|
||||||
const key = dayjs.unix(e.start_datetime).tz().format('MMDD') // Math.floor(e.start_datetime/(3600*24)) // dayjs.unix(e.start_datetime).tz().format('YYYYMMDD')
|
const key = dayjs.unix(e.start_datetime).tz().format('MMDD') // Math.floor(e.start_datetime/(3600*24)) // dayjs.unix(e.start_datetime).tz().format('YYYYMMDD')
|
||||||
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
|
const c = (e.end_datetime || e.start_datetime) < now ? 'vc-past' : ''
|
||||||
|
|
||||||
if (e.multidate) {
|
if (e.multidate === true) {
|
||||||
attributes.push({
|
attributes.push({
|
||||||
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) },
|
dates: { start: new Date(e.start_datetime * 1000), end: new Date(e.end_datetime * 1000) },
|
||||||
highlight: {
|
highlight: {
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<nav>
|
<nav>
|
||||||
|
<NavHeader />
|
||||||
<NavHeader/>
|
|
||||||
|
|
||||||
<!-- title -->
|
<!-- title -->
|
||||||
<div class='text-center'>
|
<div class="text-center">
|
||||||
<nuxt-link id='title' v-text='settings.title' to='/' />
|
<nuxt-link id="title" v-text="settings.title" to="/" />
|
||||||
<div class='text-body-1 font-weight-light' v-text='settings.description' />
|
<div
|
||||||
|
class="text-body-1 font-weight-light"
|
||||||
|
v-text="settings.description"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavSearch />
|
<NavSearch />
|
||||||
|
|
||||||
<NavBar />
|
<NavBar />
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
@ -27,18 +25,24 @@ import NavSearch from './NavSearch.vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'Appbar',
|
name: 'Appbar',
|
||||||
components: { NavHeader, NavBar, NavSearch },
|
components: { NavHeader, NavBar, NavSearch },
|
||||||
computed: mapState(['settings'])
|
computed: mapState(['settings']),
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
nav {
|
nav {
|
||||||
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.7)), url(/headerimage.png);
|
background-image: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.7)),
|
||||||
|
url(/headerimage.png);
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--light nav {
|
.theme--light nav {
|
||||||
background-image: linear-gradient(to bottom, rgba(230,230,230,.95), rgba(250,250,250,.95)), url(/headerimage.png);
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(230, 230, 230, 0.95),
|
||||||
|
rgba(250, 250, 250, 0.95)
|
||||||
|
),
|
||||||
|
url(/headerimage.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
#title {
|
#title {
|
||||||
|
@ -46,5 +50,4 @@ nav {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
</style>
|
|
||||||
|
|
|
@ -15,13 +15,14 @@
|
||||||
aria-label='Calendar'
|
aria-label='Calendar'
|
||||||
is-expanded
|
is-expanded
|
||||||
is-inline)
|
is-inline)
|
||||||
template(v-slot="{ inputValue, inputEvents }")
|
//- template(v-slot="{ inputValue, inputEvents }")
|
||||||
v-btn#calendarButton(v-on='inputEvents' text tile :color='selectedDate ? "primary" : "" ') {{inputValue || $t('common.calendar')}}
|
v-btn#calendarButton(v-on='inputEvents' text tile :color='selectedDate ? "primary" : "" ') {{inputValue || $t('common.calendar')}}
|
||||||
v-icon(v-if='selectedDate' v-text='mdiClose' right small icon @click.prevent.stop='selectedDate = null')
|
v-icon(v-if='selectedDate' v-text='mdiClose' right small icon @click.prevent.stop='selectedDate = null')
|
||||||
v-icon(v-else v-text='mdiChevronDown' right small icon)
|
v-icon(v-else v-text='mdiChevronDown' right small icon)
|
||||||
template(v-slot:placeholder)
|
.calh.d-flex.justify-center.align-center(slot='placeholder')
|
||||||
v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
v-progress-circular(indeterminate)
|
||||||
v-icon(v-text='mdiChevronDown' right small icon)
|
//- v-btn#calendarButton(text tile) {{$t('common.calendar')}}
|
||||||
|
//- v-icon(v-text='mdiChevronDown' right small icon)
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ v-dialog(v-model='show'
|
||||||
@keydown.esc='cancel')
|
@keydown.esc='cancel')
|
||||||
v-card
|
v-card
|
||||||
v-card-title {{ title }}
|
v-card-title {{ title }}
|
||||||
v-card-text(v-show='!!message') {{ message }}
|
v-card-text(v-show='!!message' v-html='message')
|
||||||
v-card-actions
|
v-card-actions
|
||||||
v-spacer
|
v-spacer
|
||||||
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
|
v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}}
|
||||||
|
|
|
@ -24,8 +24,9 @@ v-col(cols=12)
|
||||||
is-inline
|
is-inline
|
||||||
is-expanded
|
is-expanded
|
||||||
:min-date='type !== "recurrent" && new Date()')
|
:min-date='type !== "recurrent" && new Date()')
|
||||||
template(#placeholder)
|
//- template(#placeholder)
|
||||||
span.calc Loading
|
.d-flex.calh.justify-center(slot='placeholder')
|
||||||
|
v-progress-circular(indeterminate)
|
||||||
|
|
||||||
div.text-center.mb-2(v-if='type === "recurrent"')
|
div.text-center.mb-2(v-if='type === "recurrent"')
|
||||||
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }}
|
span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }}
|
||||||
|
@ -94,7 +95,7 @@ v-col(cols=12)
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { mapState } from 'vuex'
|
import { mapState, mapActions } from 'vuex'
|
||||||
import List from '@/components/List'
|
import List from '@/components/List'
|
||||||
import { attributesFromEvents } from '../assets/helper'
|
import { attributesFromEvents } from '../assets/helper'
|
||||||
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline, mdiClose } from '@mdi/js'
|
import { mdiClockTimeFourOutline, mdiClockTimeEightOutline, mdiClose } from '@mdi/js'
|
||||||
|
@ -113,7 +114,6 @@ export default {
|
||||||
menuFromHour: false,
|
menuFromHour: false,
|
||||||
menuDueHour: false,
|
menuDueHour: false,
|
||||||
type: this.value.type || 'normal',
|
type: this.value.type || 'normal',
|
||||||
events: [],
|
|
||||||
frequencies: [
|
frequencies: [
|
||||||
{ value: '1w', text: this.$t('event.each_week') },
|
{ value: '1w', text: this.$t('event.each_week') },
|
||||||
{ value: '2w', text: this.$t('event.each_2w') },
|
{ value: '2w', text: this.$t('event.each_2w') },
|
||||||
|
@ -122,7 +122,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings']),
|
...mapState(['settings', 'events']),
|
||||||
fromDate () {
|
fromDate () {
|
||||||
if (this.value.from) {
|
if (this.value.from) {
|
||||||
if (this.value.multidate) {
|
if (this.value.multidate) {
|
||||||
|
@ -138,7 +138,7 @@ export default {
|
||||||
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
return this.events.filter(e => e.start_datetime >= start && e.start_datetime <= end)
|
||||||
},
|
},
|
||||||
attributes() {
|
attributes() {
|
||||||
return attributesFromEvents(this.events)
|
return attributesFromEvents(this.events.filter(e => e.id !== this.event.id))
|
||||||
},
|
},
|
||||||
whenPatterns() {
|
whenPatterns() {
|
||||||
if (!this.value.from) { return }
|
if (!this.value.from) { return }
|
||||||
|
@ -192,13 +192,12 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.type = 'normal'
|
this.type = 'normal'
|
||||||
}
|
}
|
||||||
this.events = await this.$api.getEvents({
|
if (!this.events) {
|
||||||
start: dayjs().unix(),
|
this.getEvents()
|
||||||
show_recurrent: true
|
}
|
||||||
})
|
|
||||||
this.events = this.events.filter(e => e.id !== this.event.id)
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions(['getEvents']),
|
||||||
updateRecurrent(value) {
|
updateRecurrent(value) {
|
||||||
this.$emit('input', { ...this.value, recurrent: value || null })
|
this.$emit('input', { ...this.value, recurrent: value || null })
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,19 +1,51 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar')
|
#navsearch.mt-2.mt-sm-4(v-if='showCollectionsBar || showSearchBar || showCalendar')
|
||||||
v-text-field.mx-2(v-if='showSearchBar' outlined dense hide-details :placeholder='$t("common.search")' :append-icon='mdiMagnify' @input='search' clearable :clear-icon='mdiClose')
|
|
||||||
template(v-slot:prepend-inner)
|
div.mx-2
|
||||||
Calendar(v-if='!settings.hide_calendar')
|
client-only(v-if='showSearchBar')
|
||||||
v-btn.ml-2.mt-2.gap-2(v-if='showCollectionsBar' small outlined v-for='collection in collections' color='primary' :key='collection.id' :to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
v-menu(offset-y :close-on-content-click='false' tile)
|
||||||
|
template(v-slot:activator="{on ,attrs}")
|
||||||
|
v-text-field(hide-details outlined
|
||||||
|
:placeholder='$t("common.search")'
|
||||||
|
@input="v => setFilter(['query', v])" clearable :clear-icon='mdiClose')
|
||||||
|
template(v-slot:append)
|
||||||
|
v-icon(v-text='mdiCog' v-bind='attrs' v-on='on')
|
||||||
|
v-card(outlined :rounded='"0"')
|
||||||
|
v-card-text
|
||||||
|
v-row(dense)
|
||||||
|
v-col(v-if='settings.allow_recurrent_event')
|
||||||
|
v-switch.mt-0(v-model='show_recurrent' @change="v => setFilter(['show_recurrent', v])"
|
||||||
|
hide-details :label="$t('event.show_recurrent')" inset)
|
||||||
|
v-col(v-if='settings.allow_multidate_event')
|
||||||
|
v-switch.mt-0(v-model='show_multidate' @change="v => setFilter(['show_multidate', v])"
|
||||||
|
hide-details :label="$t('event.show_multidate')" inset)
|
||||||
|
v-row(v-if='!showCalendar')
|
||||||
|
v-col
|
||||||
|
Calendar.mt-2
|
||||||
|
v-text-field(slot='placeholder' outlined hide-details :placeholder="$t('common.search')" :append-icon='mdiCog')
|
||||||
|
|
||||||
|
span(v-if='showCollectionsBar')
|
||||||
|
v-btn.mr-2.mt-2(small outlined v-for='collection in collections'
|
||||||
|
color='primary' :key='collection.id'
|
||||||
|
:to='`/collection/${encodeURIComponent(collection.name)}`') {{collection.name}}
|
||||||
|
|
||||||
|
Calendar.mt-2(v-if='showCalendar')
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState, mapActions } from 'vuex'
|
||||||
import Calendar from '@/components/Calendar'
|
import Calendar from '@/components/Calendar'
|
||||||
import { mdiMagnify, mdiClose } from '@mdi/js'
|
import { mdiClose, mdiCog } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: () => ({
|
data: ({ $store }) => ({
|
||||||
mdiMagnify, mdiClose,
|
oldRoute: '',
|
||||||
collections: []
|
mdiClose, mdiCog,
|
||||||
|
collections: [],
|
||||||
|
show_recurrent: $store.state.settings.recurrent_event_visible,
|
||||||
|
show_multidate: true,
|
||||||
|
query: ''
|
||||||
}),
|
}),
|
||||||
async fetch () {
|
async fetch () {
|
||||||
this.collections = await this.$axios.$get('collections').catch(_e => [])
|
this.collections = await this.$axios.$get('collections').catch(_e => [])
|
||||||
|
@ -23,21 +55,27 @@ export default {
|
||||||
showSearchBar () {
|
showSearchBar () {
|
||||||
return this.$route.name === 'index'
|
return this.$route.name === 'index'
|
||||||
},
|
},
|
||||||
showCollectionsBar () {
|
showCalendar () {
|
||||||
return ['index', 'collection-collection'].includes(this.$route.name)
|
return (!this.settings.hide_calendar && this.$route.name === 'index')
|
||||||
},
|
},
|
||||||
...mapState(['settings'])
|
showCollectionsBar () {
|
||||||
|
const show = ['index', 'collection-collection'].includes(this.$route.name)
|
||||||
|
if (show && this.oldRoute !== this.$route.name) {
|
||||||
|
this.oldRoute = this.$route.name
|
||||||
|
this.$fetch()
|
||||||
|
}
|
||||||
|
return show
|
||||||
|
},
|
||||||
|
...mapState(['settings', 'filter'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
search (ev) {
|
...mapActions(['setFilter']),
|
||||||
this.$root.$emit('search', ev)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
#navsearch {
|
#navsearch {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 800px;
|
max-width: 700px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -125,9 +125,14 @@ export default {
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick( () => {
|
||||||
|
this.search()
|
||||||
|
})
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
search: debounce(async function(ev) {
|
search: debounce(async function(ev) {
|
||||||
const search = ev.target.value.trim().toLowerCase()
|
const search = ev ? ev.target.value.trim().toLowerCase() : ''
|
||||||
this.places = await this.$axios.$get(`place?search=${search}`)
|
this.places = await this.$axios.$get(`place?search=${search}`)
|
||||||
if (!search && this.places.length) { return this.places }
|
if (!search && this.places.length) { return this.places }
|
||||||
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
const matches = this.places.find(p => search === p.name.toLocaleLowerCase())
|
||||||
|
@ -256,7 +261,7 @@ export default {
|
||||||
this.addressList = []
|
this.addressList = []
|
||||||
}
|
}
|
||||||
} else if (this.geocoding_provider_type == "Photon") {
|
} else if (this.geocoding_provider_type == "Photon") {
|
||||||
let photon_properties = ['housenumber', 'street', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
let photon_properties = ['housenumber', 'street', 'locality', 'district', 'city', 'county', 'state', 'postcode', 'country']
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
this.addressList = ret.features.map(v => {
|
this.addressList = ret.features.map(v => {
|
||||||
|
|
|
@ -69,7 +69,7 @@ v-container
|
||||||
//- v-list-item-subtitle(v-text='item.address')
|
//- v-list-item-subtitle(v-text='item.address')
|
||||||
|
|
||||||
v-col(cols=2)
|
v-col(cols=2)
|
||||||
v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
v-btn(color='primary' :loading='loading' text @click='addFilter' :disabled='loading || !collection.id || !filterPlaces.length && !filterTags.length') add <v-icon v-text='mdiPlus'></v-icon>
|
||||||
|
|
||||||
v-data-table(
|
v-data-table(
|
||||||
:headers='filterHeaders'
|
:headers='filterHeaders'
|
||||||
|
@ -110,6 +110,9 @@ v-container
|
||||||
<script>
|
<script>
|
||||||
import get from 'lodash/get'
|
import get from 'lodash/get'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
|
import isEqual from 'lodash/isEqual'
|
||||||
|
import sortBy from 'lodash/sortBy'
|
||||||
|
|
||||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle, mdiChevronDown } from '@mdi/js'
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiPlus, mdiTagMultiple, mdiMapMarker, mdiDeleteForever, mdiCloseCircle, mdiChevronDown } from '@mdi/js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -147,7 +150,7 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
searchTags: debounce(async function (ev) {
|
searchTags: debounce(async function (ev) {
|
||||||
this.tags = await this.$axios.$get(`/tag?search=${ev.target.value}`)
|
this.tags = await this.$axios.$get(`/tag?search=${encodeURIComponent(ev.target.value)}`)
|
||||||
}, 100),
|
}, 100),
|
||||||
searchPlaces: debounce(async function (ev) {
|
searchPlaces: debounce(async function (ev) {
|
||||||
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
|
this.places = await this.$axios.$get(`/place?search=${ev.target.value}`)
|
||||||
|
@ -163,9 +166,20 @@ export default {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const tags = this.filterTags
|
const tags = this.filterTags
|
||||||
const places = this.filterPlaces.map(p => ({ id: p.id, name: p.name }))
|
const places = this.filterPlaces.map(p => ({ id: p.id, name: p.name }))
|
||||||
const filter = await this.$axios.$post('/filter', { collectionId: this.collection.id, tags, places })
|
|
||||||
|
const filter = { collectionId: this.collection.id, tags, places }
|
||||||
|
|
||||||
|
// tags and places are JSON field and there's no way to use them inside a unique constrain
|
||||||
|
//
|
||||||
|
const alreadyExists = this.filters.find(f =>
|
||||||
|
isEqual(sortBy(f.places, 'id'), sortBy(filter.places, 'id')) && isEqual(sortBy(f.tags), sortBy(filter.tags))
|
||||||
|
)
|
||||||
|
|
||||||
|
if (alreadyExists) return
|
||||||
|
|
||||||
|
const ret = await this.$axios.$post('/filter', filter )
|
||||||
this.$fetch()
|
this.$fetch()
|
||||||
this.filters.push(filter)
|
this.filters.push(ret)
|
||||||
this.filterTags = []
|
this.filterTags = []
|
||||||
this.filterPlaces = []
|
this.filterPlaces = []
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
|
@ -54,11 +54,10 @@ v-container
|
||||||
<script>
|
<script>
|
||||||
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag } from '@mdi/js'
|
import { mdiPencil, mdiChevronLeft, mdiChevronRight, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag } from '@mdi/js'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import debounce from 'lodash/debounce'
|
|
||||||
import get from 'lodash/get'
|
import get from 'lodash/get'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data( {$store} ) {
|
data() {
|
||||||
return {
|
return {
|
||||||
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag,
|
mdiPencil, mdiChevronRight, mdiChevronLeft, mdiMagnify, mdiEye, mdiMapSearch, mdiChevronDown, mdiDeleteForever, mdiTag,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
|
@ -106,7 +106,7 @@ docker-compose pull
|
||||||
See [Requirements](#requirements) about downloading the `.osm.pbf` files
|
See [Requirements](#requirements) about downloading the `.osm.pbf` files
|
||||||
```bash
|
```bash
|
||||||
cd docs/docker/nominatim/
|
cd docs/docker/nominatim/
|
||||||
wget https://download.geofabrik.de/europe/italy/nord-ovest-updates/nord-ovest-latest.osm.pbf \
|
wget https://download.geofabrik.de/europe/italy/nord-ovest-latest.osm.pbf \
|
||||||
./nominatim/data/default.osm.pbf
|
./nominatim/data/default.osm.pbf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0486d0852db569b7064535f0d52709e06e9ecd1f
|
Subproject commit 12640461481cc39cdee8efa05e7fd02a6a60e99c
|
|
@ -101,7 +101,8 @@
|
||||||
"about": "Sobre aquesta agenda",
|
"about": "Sobre aquesta agenda",
|
||||||
"content": "Contingut",
|
"content": "Contingut",
|
||||||
"admin_actions": "Accions d'administració",
|
"admin_actions": "Accions d'administració",
|
||||||
"recurring_event_actions": "Accions d'activitats recorrents"
|
"recurring_event_actions": "Accions d'activitats recorrents",
|
||||||
|
"tag": "Etiqueta"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "Amb la sessió iniciada pots afegir activitats noves.",
|
"description": "Amb la sessió iniciada pots afegir activitats noves.",
|
||||||
|
@ -278,7 +279,29 @@
|
||||||
"domain": "Domini",
|
"domain": "Domini",
|
||||||
"known_users": "Usuàries conegudes",
|
"known_users": "Usuàries conegudes",
|
||||||
"created_at": "Creada",
|
"created_at": "Creada",
|
||||||
"default_images_help": "<a href='/admin?tab=theme'>Actualitza la pàgina</a> per veure els canvis."
|
"default_images_help": "<a href='/admin?tab=theme'>Actualitza la pàgina</a> per veure els canvis.",
|
||||||
|
"geocoding_countrycodes": "Codis d'estats",
|
||||||
|
"admin_email_help": "L'adreça que es posa de remitent per enviar correus. També és l'adreça a la qual s'envien els correus d'administració",
|
||||||
|
"delete_collection_confirm": "Segur que vols eliminar la coŀlecció <u>{collection}</u>?",
|
||||||
|
"geocoding_provider_type_help": "Per defecte es resolen les adreces amb Nominatim",
|
||||||
|
"geocoding_provider": "Proveïdora de geocodificació",
|
||||||
|
"geocoding_provider_type": "Programari de geocodificació",
|
||||||
|
"geocoding_provider_help": "Per defecte es geocodifiquen les adreces amb Nominatim",
|
||||||
|
"geocoding_countrycodes_help": "Permet aplicar un filtre per cercar només dins de les fronteres de l'estat",
|
||||||
|
"geocoding_test_button": "Prova la geocodificació",
|
||||||
|
"geocoding_test_success": "La geocodificació de {service_name} funciona",
|
||||||
|
"geocoding_test_error": "El servei de geocodificació de {service_name} no està funcionant",
|
||||||
|
"tilelayer_provider": "Proveïdora de tesseŀles del mapa base",
|
||||||
|
"tilelayer_provider_help": "Per defecte les tesseŀles del mapa es baixen del servidor central d'OpenStreetMap",
|
||||||
|
"tilelayer_test_button": "Prova el mapa base",
|
||||||
|
"tilelayer_test_success": "El servei de tesseŀles de {service_name} està funcionant",
|
||||||
|
"tilelayer_test_error": "El servei de tesseŀles de {service_name} no està funcionant",
|
||||||
|
"geolocation": "Geolocalització",
|
||||||
|
"edit_tag_help": "Pots canviar l'etiqueta reanomenant-la o bé fusionant-la amb una d'existent. S'actualitzaran les {n} activitats que la fan servir.",
|
||||||
|
"allow_multidate_event": "Permet activitats de diversos dies",
|
||||||
|
"delete_tag_confirm": "Segur que vols eliminar l'etiqueta \"{tag}\"? S'esborrarà del sistema i de {n} activitats.",
|
||||||
|
"edit_tag": "Canvia l'etiqueta",
|
||||||
|
"tilelayer_provider_attribution": "Atribució"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Encara no s'ha confirmat…",
|
"not_confirmed": "Encara no s'ha confirmat…",
|
||||||
|
|
29
locales/email/ru.json
Normal file
29
locales/email/ru.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"recover": {
|
||||||
|
"subject": "Восстановление пароля",
|
||||||
|
"content": "Здравствуйте, вы запросили восстановление пароля на {{config.title}}. <a href='{{config.baseurl}}/recover/{{user.recover_code}}'>Нажмите здесь</a> для подтверждения."
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"subject": "Теперь вы можете начать публиковать события",
|
||||||
|
"content": "Здравствуйте, ваша учетная запись <a href='{{config.baseurl}}'>{{config.title}}</a> была подтверждена. Пишите нам по адресу {{config.admin_email}} для получения любой информации."
|
||||||
|
},
|
||||||
|
"admin_register": {
|
||||||
|
"subject": "Новая регистрация",
|
||||||
|
"content": "{{user.email}} запросил регистрацию на {{config.title}}: <br/><pre>{{user.description}}</pre><br/> Подтвердите это <a href='{{config.baseurl}}/admin'>здесь</a>."
|
||||||
|
},
|
||||||
|
"user_confirm": {
|
||||||
|
"subject": "Теперь вы можете начать публиковать события",
|
||||||
|
"content": "Здравствуйте, ваша учетная запись <a href='{{config.baseurl}}'>{{config.title}}</a> была создана. <a href='{{config.baseurl}}/user_confirm/{{user.recover_code}}'>Подтвердите это и измените пароль.</a>."
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"content": "Мы получили запрос на регистрацию. Мы подтвердим его как можно скорее.",
|
||||||
|
"subject": "Запрос на регистрацию получен"
|
||||||
|
},
|
||||||
|
"event_confirm": {
|
||||||
|
"content": "Вы можете подтвердить это событие по адресу <a href='{{url}}'>на этой странице</a>"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"subject": "Ваша конфигурация SMTP работает",
|
||||||
|
"content": "Это тестовое письмо, если вы читаете его, ваша конфигурация работает."
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,7 @@
|
||||||
"recurrent": "Recurring",
|
"recurrent": "Recurring",
|
||||||
"edit_recurrent": "Edit recurring event:",
|
"edit_recurrent": "Edit recurring event:",
|
||||||
"show_recurrent": "recurring events",
|
"show_recurrent": "recurring events",
|
||||||
|
"show_multidate": "multidate events",
|
||||||
"show_past": "also prior events",
|
"show_past": "also prior events",
|
||||||
"only_future": "only upcoming events",
|
"only_future": "only upcoming events",
|
||||||
"recurrent_description": "Choose frequency and select days",
|
"recurrent_description": "Choose frequency and select days",
|
||||||
|
@ -264,6 +265,7 @@
|
||||||
"new_collection": "New collection",
|
"new_collection": "New collection",
|
||||||
"collections_description": "Collections are groupings of events by tags and places. They will be displayed on the home page",
|
"collections_description": "Collections are groupings of events by tags and places. They will be displayed on the home page",
|
||||||
"edit_collection": "Edit Collection",
|
"edit_collection": "Edit Collection",
|
||||||
|
"delete_collection_confirm": "Are you sure you want to remove the collection <u>{collection}</u>?",
|
||||||
"config_plugin": "Plugin configuration",
|
"config_plugin": "Plugin configuration",
|
||||||
"plugins_description": "",
|
"plugins_description": "",
|
||||||
"fallback_image": "Fallback image",
|
"fallback_image": "Fallback image",
|
||||||
|
|
|
@ -101,7 +101,8 @@
|
||||||
"calendar": "Calendario",
|
"calendar": "Calendario",
|
||||||
"content": "Contenido",
|
"content": "Contenido",
|
||||||
"admin_actions": "Acciones de administrador",
|
"admin_actions": "Acciones de administrador",
|
||||||
"recurring_event_actions": "Acciones de eventos recurrentes"
|
"recurring_event_actions": "Acciones de eventos recurrentes",
|
||||||
|
"tag": "Etiqueta"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"description": "Entrando podrás publicar nuevos eventos.",
|
"description": "Entrando podrás publicar nuevos eventos.",
|
||||||
|
@ -281,7 +282,25 @@
|
||||||
"admin_email_help": "La dirección que usamos como remitente para enviar emails. Esta es también la dirección a la cual los emails de administración son enviados",
|
"admin_email_help": "La dirección que usamos como remitente para enviar emails. Esta es también la dirección a la cual los emails de administración son enviados",
|
||||||
"allow_multidate_event": "Permitir eventos de múltiples días",
|
"allow_multidate_event": "Permitir eventos de múltiples días",
|
||||||
"geocoding_countrycodes": "Códigos de país",
|
"geocoding_countrycodes": "Códigos de país",
|
||||||
"geocoding_provider_help": "El proveedor por defecto es Nominatim"
|
"geocoding_provider_help": "El proveedor por defecto es Nominatim",
|
||||||
|
"geocoding_provider_type": "Software de geocodificación",
|
||||||
|
"geocoding_provider_type_help": "El programa por defecto es Nominatim",
|
||||||
|
"geocoding_countrycodes_help": "Permite establecer un filtro para las búsquedas basadas en códigos de área",
|
||||||
|
"geocoding_test_button": "Prueba de geocodificación",
|
||||||
|
"delete_collection_confirm": "¿Estás seguro de que quieres eliminar la colección <u>{collection}</u>?",
|
||||||
|
"geocoding_test_success": "El servicio de geocodificación de {service_name} funciona",
|
||||||
|
"tilelayer_provider": "Proveedor de mosaicos de mapas",
|
||||||
|
"tilelayer_provider_help": "El proveedor por defecto es OpenStreetMap",
|
||||||
|
"tilelayer_provider_attribution": "Atribución",
|
||||||
|
"tilelayer_test_button": "Capa de mosaicos de prueba",
|
||||||
|
"tilelayer_test_success": "El servicio de mosaicos en {service_name} está funcionando",
|
||||||
|
"tilelayer_test_error": "No se puede acceder al servicio de mosaico en {service_name}",
|
||||||
|
"geolocation": "Geolocalización",
|
||||||
|
"delete_tag_confirm": "¿Está seguro de que desea eliminar la etiqueta \"{tag}\"? La etiqueta se eliminará de {n} eventos.",
|
||||||
|
"edit_tag": "Editar la etiqueta",
|
||||||
|
"edit_tag_help": "Puede cambiar la etiqueta sustituyéndola por una nueva o fusionándola con una existente. Los {n} eventos asociados también se modificarán.",
|
||||||
|
"geolocation_description": "<b>1. Defina un proveedor para el servicio de codificación geográfica</b>.<br>Actualmente, entre los enumerados en el <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap </a>, hay soporte para el software <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> y <a href=\"https://github.com/komoot /photon\">Photon</a>.<br>Puede usar una de las demostraciones oficiales relacionadas copiando el enlace en el campo 'Proveedor de codificación geográfica':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Términos de servicio</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\"> Condiciones de servicio</a>)</li></ul><br><b>2. Defina un proveedor para las capas del mapa.</b><br>Puede encontrar una lista de ellos aquí: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||||
|
"geocoding_test_error": "No se puede acceder al servicio de geocodificación en {service_name}"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Todavía no hemos confirmado este email…",
|
"not_confirmed": "Todavía no hemos confirmado este email…",
|
||||||
|
|
|
@ -101,7 +101,8 @@
|
||||||
"about": "Acerca de",
|
"about": "Acerca de",
|
||||||
"content": "Contido",
|
"content": "Contido",
|
||||||
"admin_actions": "Accións de Admin",
|
"admin_actions": "Accións de Admin",
|
||||||
"recurring_event_actions": "Accións de eventos recurrentes"
|
"recurring_event_actions": "Accións de eventos recurrentes",
|
||||||
|
"tag": "Etiqueta"
|
||||||
},
|
},
|
||||||
"recover": {
|
"recover": {
|
||||||
"not_valid_code": "Algo fallou."
|
"not_valid_code": "Algo fallou."
|
||||||
|
@ -142,7 +143,7 @@
|
||||||
"interact_with_me": "Sígueme",
|
"interact_with_me": "Sígueme",
|
||||||
"remove_recurrent_confirmation": "Tes a certeza de querer eliminar este evento recurrente?\nOs eventos pasados permanecerán, pero non se crearán novos eventos.",
|
"remove_recurrent_confirmation": "Tes a certeza de querer eliminar este evento recurrente?\nOs eventos pasados permanecerán, pero non se crearán novos eventos.",
|
||||||
"import_URL": "Importar desde URL",
|
"import_URL": "Importar desde URL",
|
||||||
"anon": "Anon",
|
"anon": "Anónimo",
|
||||||
"anon_description": "Podes engadir un evento sen rexistrarte nen acceder, pero deberás agardar a que alguén o lea e\nconfirme que é un evento axeitado. Non será posible modificalo.<br/><br/>\nPodes tamén <a href='/login'>acceder</a> ou <a href='/register'>crear unha conta</a>. Mais podes continuar e agardar a que se comprobe. ",
|
"anon_description": "Podes engadir un evento sen rexistrarte nen acceder, pero deberás agardar a que alguén o lea e\nconfirme que é un evento axeitado. Non será posible modificalo.<br/><br/>\nPodes tamén <a href='/login'>acceder</a> ou <a href='/register'>crear unha conta</a>. Mais podes continuar e agardar a que se comprobe. ",
|
||||||
"same_day": "no mesmo día",
|
"same_day": "no mesmo día",
|
||||||
"tag_description": "Cancelo",
|
"tag_description": "Cancelo",
|
||||||
|
@ -288,7 +289,11 @@
|
||||||
"geolocation": "Xeolocalización",
|
"geolocation": "Xeolocalización",
|
||||||
"allow_multidate_event": "Permitir eventos de varios días",
|
"allow_multidate_event": "Permitir eventos de varios días",
|
||||||
"admin_email_help": "O enderezo que se usará como remitente para os emails. Tamén é o enderezo ao que se envían emails de administración",
|
"admin_email_help": "O enderezo que se usará como remitente para os emails. Tamén é o enderezo ao que se envían emails de administración",
|
||||||
"geolocation_description": "<b>1. Define un provedor para o servizo geocoding</b>.<br>Actualmente, entre os que aparecen na <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap</a>, temos soporte para <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> e <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Podes usar unha das demos oficiais indicadas copiando a ligazón no campo 'Provedor Geocoding':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Termos do Servizo</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Termos do Servizo</a>)</li></ul><br><b>2. Define un provedor para capas do mapa.</b><br>Aquí hai unha lista: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>"
|
"geolocation_description": "<b>1. Define un provedor para o servizo geocoding</b>.<br>Actualmente, entre os que aparecen na <a href=\"https://wiki.openstreetmap.org/wiki/Nominatim#Alternatives_.2F_Third-party_providers\">wiki de OpenStreetMap</a>, temos soporte para <a href=\"https://github.com/osm-search/Nominatim\">Nominatim</a> e <a href=\"https://github.com/komoot/photon\">Photon</a>.<br>Podes usar unha das demos oficiais indicadas copiando a ligazón no campo 'Provedor Geocoding':<ul><li>https://nominatim.openstreetmap.org/search (<a href=\"https://operations.osmfoundation.org/policies/nominatim/\">Termos do Servizo</a>)</li><li>https://photon.komoot.io/api/ (<a href=\"https://photon.komoot.io/\">Termos do Servizo</a>)</li></ul><br><b>2. Define un provedor para capas do mapa.</b><br>Aquí hai unha lista: <a href=\"https://leaflet-extras.github.io/leaflet-providers/preview/\">https://leaflet-extras.github.io/leaflet-providers/preview/</a>",
|
||||||
|
"edit_tag": "Editar etiqueta",
|
||||||
|
"edit_tag_help": "Podes cambiar a etiqueta substituíndoa por unha nova ou fusionándoa cunha existente. Tamén se cambiarán os {n} eventos asociados.",
|
||||||
|
"delete_tag_confirm": "Estás seguro de que queres eliminar a etiqueta \"{tag}\"? A etiqueta eliminarase de {n} eventos.",
|
||||||
|
"delete_collection_confirm": "Estás seguro de que queres eliminar a colección <u>{collection}</u>?"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"not_confirmed": "Aínda non foi confirmado…",
|
"not_confirmed": "Aínda non foi confirmado…",
|
||||||
|
|
|
@ -10,6 +10,7 @@ module.exports = {
|
||||||
nb: 'Norwegian Bokmål',
|
nb: 'Norwegian Bokmål',
|
||||||
pl: 'Polski',
|
pl: 'Polski',
|
||||||
pt: 'Português',
|
pt: 'Português',
|
||||||
|
ru: 'Русский',
|
||||||
sk: 'Slovak',
|
sk: 'Slovak',
|
||||||
zh: '中国'
|
zh: '中国'
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,7 @@
|
||||||
"recurrent": "Ricorrente",
|
"recurrent": "Ricorrente",
|
||||||
"edit_recurrent": "Modifica evento ricorrente:",
|
"edit_recurrent": "Modifica evento ricorrente:",
|
||||||
"show_recurrent": "appuntamenti ricorrenti",
|
"show_recurrent": "appuntamenti ricorrenti",
|
||||||
|
"show_multidate": "eventi di più giorni",
|
||||||
"show_past": "eventi passati",
|
"show_past": "eventi passati",
|
||||||
"recurrent_description": "Scegli la frequenza e seleziona i giorni",
|
"recurrent_description": "Scegli la frequenza e seleziona i giorni",
|
||||||
"multidate_description": "Un festival o una tre giorni? Scegli quando comincia e quando finisce",
|
"multidate_description": "Un festival o una tre giorni? Scegli quando comincia e quando finisce",
|
||||||
|
|
|
@ -189,7 +189,10 @@
|
||||||
"known_users": "Usuários conhecidos",
|
"known_users": "Usuários conhecidos",
|
||||||
"created_at": "Criado em",
|
"created_at": "Criado em",
|
||||||
"hide_calendar": "Ocultar calendário",
|
"hide_calendar": "Ocultar calendário",
|
||||||
"blocked": "Bloqueado"
|
"blocked": "Bloqueado",
|
||||||
|
"admin_email": "E-mail do admin",
|
||||||
|
"tilelayer_provider_attribution": "Atribuição",
|
||||||
|
"geolocation": "Geolocalização"
|
||||||
},
|
},
|
||||||
"event": {
|
"event": {
|
||||||
"follow_me_description": "Uma das maneiras de se manter atualizado com os eventos publicados aqui em {title},\né seguir a conta <u>{account}</u> no Fediverso, por exemplo via Mastodon, e possivelmente adicionar recursos para um evento a partir de lá.<br/><br/>\nSe você nunca ouviu falar sobre Mastodon ou do Fediverso nós recomendamos ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Entre com sua instância abaixo (e.g. mastodon.social)",
|
"follow_me_description": "Uma das maneiras de se manter atualizado com os eventos publicados aqui em {title},\né seguir a conta <u>{account}</u> no Fediverso, por exemplo via Mastodon, e possivelmente adicionar recursos para um evento a partir de lá.<br/><br/>\nSe você nunca ouviu falar sobre Mastodon ou do Fediverso nós recomendamos ler <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>este artigo</a>.<br/><br/>Entre com sua instância abaixo (e.g. mastodon.social)",
|
||||||
|
|
333
locales/ru.json
Normal file
333
locales/ru.json
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"send": "Отправить",
|
||||||
|
"where": "Где",
|
||||||
|
"address": "Адрес",
|
||||||
|
"when": "Когда",
|
||||||
|
"what": "Что",
|
||||||
|
"media": "Медиа",
|
||||||
|
"login": "Логин",
|
||||||
|
"email": "E-mail",
|
||||||
|
"password": "Пароль",
|
||||||
|
"register": "Зарегистрироваться",
|
||||||
|
"description": "Описание",
|
||||||
|
"remove": "Удалить",
|
||||||
|
"search": "Поиск",
|
||||||
|
"edit": "Ред.",
|
||||||
|
"info": "Инфо",
|
||||||
|
"add_event": "Доб. событие",
|
||||||
|
"hide": "Скрыть",
|
||||||
|
"confirm": "Подтвердить",
|
||||||
|
"admin": "Администратор",
|
||||||
|
"users": "Пользователи",
|
||||||
|
"events": "События",
|
||||||
|
"places": "Места",
|
||||||
|
"settings": "Опции",
|
||||||
|
"actions": "Действия",
|
||||||
|
"deactivate": "Выключить",
|
||||||
|
"remove_admin": "Удалить администратора",
|
||||||
|
"activate": "Активировать",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"preview": "Предв. просмотр",
|
||||||
|
"logout": "Выйти",
|
||||||
|
"share": "Поделиться",
|
||||||
|
"name": "Имя",
|
||||||
|
"edit_event": "Ред. событие",
|
||||||
|
"related": "Связанные",
|
||||||
|
"add": "Доб.",
|
||||||
|
"logout_ok": "Вышел из системы",
|
||||||
|
"copy": "Копировать",
|
||||||
|
"recover_password": "Восстановить пароль",
|
||||||
|
"new_password": "Новый пароль",
|
||||||
|
"new_user": "Новый пользователь",
|
||||||
|
"ok": "Ок",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"enable": "Включить",
|
||||||
|
"disable": "Отключить",
|
||||||
|
"me": "Вы",
|
||||||
|
"password_updated": "Пароль изменен.",
|
||||||
|
"resources": "Ресурсы",
|
||||||
|
"n_resources": "нет ресурсов|ресурс|{n} ресурсы",
|
||||||
|
"activate_user": "Подтверждено",
|
||||||
|
"send_via_mail": "Отправить e-mail письмо",
|
||||||
|
"add_to_calendar": "Доб. в календарь",
|
||||||
|
"copied": "Скопировано",
|
||||||
|
"embed": "Встроить",
|
||||||
|
"embed_title": "Вставьте это событие на свой сайт",
|
||||||
|
"embed_help": "Скопируйте следующий код на свой сайт, и событие будет отображаться как здесь",
|
||||||
|
"feed": "RSS-канал",
|
||||||
|
"feed_url_copied": "Откройте скопированный URL канала в программе чтения RSS-каналов",
|
||||||
|
"follow_me_title": "Следите за обновлениями от fediverse",
|
||||||
|
"follow": "Подписаться",
|
||||||
|
"moderation": "Модерация",
|
||||||
|
"authorize": "Авторизация",
|
||||||
|
"filter": "Фильтр",
|
||||||
|
"start": "Начало",
|
||||||
|
"fediverse": "Fediverse",
|
||||||
|
"skip": "Пропустить",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"import": "Импорт",
|
||||||
|
"max_events": "N. максимум событий",
|
||||||
|
"label": "Этикетка",
|
||||||
|
"close": "Закрыть",
|
||||||
|
"plugins": "Плагины",
|
||||||
|
"home": "Главная",
|
||||||
|
"content": "Содержание",
|
||||||
|
"admin_actions": "Действия админа",
|
||||||
|
"instances": "Экземпляры",
|
||||||
|
"title": "Название",
|
||||||
|
"set_password": "Установите пароль",
|
||||||
|
"copy_link": "Копировать ссылку",
|
||||||
|
"show_map": "Показать карту",
|
||||||
|
"about": "О сайте",
|
||||||
|
"event": "Событие",
|
||||||
|
"url": "URL",
|
||||||
|
"tags": "Теги",
|
||||||
|
"theme": "Тема",
|
||||||
|
"reset": "Сброс",
|
||||||
|
"collections": "Коллекции",
|
||||||
|
"help_translate": "Помогите перевести",
|
||||||
|
"displayname": "Отображаемое имя",
|
||||||
|
"federation": "Федерация",
|
||||||
|
"announcements": "Объявления",
|
||||||
|
"place": "Место",
|
||||||
|
"user": "Пользователь",
|
||||||
|
"pause": "Пауза",
|
||||||
|
"tag": "Тег",
|
||||||
|
"calendar": "Календарь",
|
||||||
|
"next": "След.",
|
||||||
|
"export": "Экспорт"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"description": "Войдя в систему, вы можете публиковать новые события.",
|
||||||
|
"forgot_password": "Забыли пароль?",
|
||||||
|
"insert_email": "Введите свой адрес электронной почты",
|
||||||
|
"check_email": "Проверьте свой почтовый ящик и спам.",
|
||||||
|
"not_registered": "Не зарегистрированы?",
|
||||||
|
"error": "Не удалось войти в систему. Проверьте информацию для входа.",
|
||||||
|
"ok": "Вошел в систему"
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"each_2w": "Каждую вторую неделю",
|
||||||
|
"due": "до",
|
||||||
|
"from": "От",
|
||||||
|
"image_too_big": "Изображение не может быть больше 4 МБ",
|
||||||
|
"interact_with_me_at": "Общайтесь со мной на fediverse по адресу",
|
||||||
|
"each_month": "Каждый месяц",
|
||||||
|
"anon": "Анон",
|
||||||
|
"same_day": "в тот же день",
|
||||||
|
"what_description": "Название",
|
||||||
|
"description_description": "Описание",
|
||||||
|
"tag_description": "Тег",
|
||||||
|
"media_description": "Вы можете добавить флаер (необязательно)",
|
||||||
|
"added": "Событие добавлено",
|
||||||
|
"saved": "Событие сохранено",
|
||||||
|
"added_anon": "Событие добавлено, но еще не подтверждено.",
|
||||||
|
"updated": "Событие обновлено",
|
||||||
|
"where_description": "Где проходит мероприятие? Если его нет, вы можете его создать.",
|
||||||
|
"address_description": "Какой адрес?",
|
||||||
|
"address_description_osm": "Какой адрес? (<a href='http://osm.org/copyright'>OpenStreetMap</a> участники)",
|
||||||
|
"confirmed": "Событие одобрено",
|
||||||
|
"not_found": "Не удалось найти событие",
|
||||||
|
"remove_confirmation": "Вы уверены, что хотите удалить это событие?",
|
||||||
|
"recurrent": "Повторяющийся",
|
||||||
|
"edit_recurrent": "Ред. повтор. события:",
|
||||||
|
"show_recurrent": "повторяющ. события",
|
||||||
|
"show_past": "также предшеств. события",
|
||||||
|
"only_future": "только предстоящие события",
|
||||||
|
"recurrent_description": "Выберите частоту и выберите дни",
|
||||||
|
"multidate_description": "Это фестиваль? Выберите время начала и окончания",
|
||||||
|
"multidate": "Больше дней",
|
||||||
|
"normal": "Нормальный",
|
||||||
|
"normal_description": "Выберите день.",
|
||||||
|
"recurrent_1w_days": "Каждый {days}",
|
||||||
|
"interact_with_me": "Подпишись",
|
||||||
|
"remove_recurrent_confirmation": "Вы уверены, что хотите удалить это повторяющееся событие?\nPast events will be maintained, but no further events will be created.",
|
||||||
|
"import_URL": "Импорт из URL",
|
||||||
|
"import_ICS": "Импорт из ICS",
|
||||||
|
"ics": "ICS",
|
||||||
|
"alt_text_description": "Описание для людей с нарушениями зрения",
|
||||||
|
"choose_focal_point": "Выберите фокусную точку",
|
||||||
|
"remove_media_confirmation": "Вы подтверждаете удаление изображения?",
|
||||||
|
"download_flyer": "Скачать флаер",
|
||||||
|
"anon_description": "Вы можете добавить событие, не регистрируясь и не входя в систему, но вам придется подождать, пока кто-то его прочитает,\nподтверждение того, что это подходящее мероприятие. Изменить его будет невозможно.<br/><br/>\nВместо этого вы можете <a href='/login'>войти</a> или <a href='/register'>зарегистрироваться</a>. В противном случае действуйте и получите ответ как можно скорее. ",
|
||||||
|
"each_week": "Каждую неделю",
|
||||||
|
"follow_me_description": "Один из способов оставаться в курсе событий, публикуемых здесь на {title},\nследит за аккаунтом<u>{account}</u>из fediverse, например, через Mastodon, и, возможно, добавляет ресурсы в событие оттуда.<br/><br/>\nЕсли вы никогда не слышали о Mastodon и fediverse, рекомендуем прочитать <a href='https://www.savjee.be/videos/simply-explained/mastodon-and-fediverse-explained/'>эта статья</a>.<br/><br/>Введите ниже свой экземпляр (например, mastodon.social)",
|
||||||
|
"import_description": "Вы можете импортировать события из других платформ и других инстанций с помощью стандартных форматов (ics и h-event)",
|
||||||
|
"recurrent_1m_ordinal": "{n} {days} каждого месяца",
|
||||||
|
"recurrent_2w_days": "Каждый второй {days}",
|
||||||
|
"recurrent_1m_days": "Каждое {days} число месяца"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"delete_resource_confirm": "Вы уверены, что хотите удалить этот ресурс?",
|
||||||
|
"delete_tag_confirm": "Вы уверены, что хотите удалить тег \"{tag}\"? Метка будет удалена из {n} событий.",
|
||||||
|
"block_user": "Заблок. пользователя",
|
||||||
|
"filter_instances": "Фильтр экземпляров",
|
||||||
|
"filter_users": "Фильтр пользователей",
|
||||||
|
"user_blocked": "Пользователь {user} заблокирован",
|
||||||
|
"user_block_confirm": "Вы уверены, что хотите заблокировать пользователя {user}?",
|
||||||
|
"enable_resources_help": "Позволяет добавлять ресурсы в событие из федерации",
|
||||||
|
"place_description": "Если вы ошиблись с местом или адресом, вы можете изменить его.<br/>Все текущие и прошлые события, связанные с этим местом, изменят адрес.",
|
||||||
|
"hide_boost_bookmark": "Скрывает буст/закладки",
|
||||||
|
"favicon": "Логотип",
|
||||||
|
"resources": "Ресурсы",
|
||||||
|
"disable_admin_user_confirm": "Вы уверены, что удалили права администратора у {user}?",
|
||||||
|
"allow_anon_event": "Разрешить анонимные мероприятия (должно быть подтверждение)?",
|
||||||
|
"recurrent_event_visible": "Показывать повторяющиеся события по умолчанию",
|
||||||
|
"select_instance_timezone": "Часовой пояс",
|
||||||
|
"event_confirm_description": "Вы можете подтвердить события, введенные анонимными пользователями, здесь",
|
||||||
|
"delete_user": "Удалить",
|
||||||
|
"remove_admin": "Удалить админа",
|
||||||
|
"disable_user_confirm": "Вы уверены, что хотите отключить {user}?",
|
||||||
|
"delete_user_confirm": "Вы уверены, что хотите удалить {user}?",
|
||||||
|
"enable_admin_user_confirm": "Вы уверены, добавить права администратора для {user}?",
|
||||||
|
"user_remove_ok": "Пользователь удален",
|
||||||
|
"user_create_ok": "Пользователь создан",
|
||||||
|
"event_remove_ok": "Событие удалено",
|
||||||
|
"allow_registration_description": "Разрешить открытую регистрацию?",
|
||||||
|
"allow_multidate_event": "Разрешить многодневные мероприятия",
|
||||||
|
"allow_recurrent_event": "Разрешить повторяющиеся события",
|
||||||
|
"allow_geolocation": "Разрешить геолокацию событий",
|
||||||
|
"federation": "",
|
||||||
|
"enable_federation": "Включить федерацию",
|
||||||
|
"enable_federation_help": "За этим экземпляром можно будет следить из федиверса",
|
||||||
|
"add_instance": "Доб. экземпляр",
|
||||||
|
"enable_resources": "Включить ресурсы",
|
||||||
|
"unblock": "Разблокировать",
|
||||||
|
"block": "Блокировать",
|
||||||
|
"user_add_help": "Новому пользователю будет отправлено электронное письмо с инструкциями по подтверждению подписки и выбору пароля",
|
||||||
|
"instance_name": "Имя экземпляра",
|
||||||
|
"show_resource": "Показать ресурс",
|
||||||
|
"hide_resource": "Скрыть ресурс",
|
||||||
|
"delete_resource": "Удалить ресурс",
|
||||||
|
"delete_announcement_confirm": "Вы уверены, что хотите удалить объявление?",
|
||||||
|
"instance_locale": "Язык по умолчанию",
|
||||||
|
"domain": "Домен",
|
||||||
|
"known_users": "Известные пользователи",
|
||||||
|
"delete_footer_link_confirm": "Вы хотите удалить эту ссылку?",
|
||||||
|
"new_collection": "Новая коллекция",
|
||||||
|
"default_images_help": "Вы должны <a href='/admin?tab=theme'>обновить</a> страницу, чтобы увидеть изменения.",
|
||||||
|
"delete_collection_confirm": "Вы уверены, что хотите удалить коллекцию <u>{collection}</u>?",
|
||||||
|
"geocoding_provider_type_help": "Программное обеспечение по умолчанию - Nominatim",
|
||||||
|
"created_at": "Создан в",
|
||||||
|
"instance_block_confirm": "Вы уверены, что хотите блокировать экземпляр {instance}?",
|
||||||
|
"announcement_remove_ok": "Объявление удалено",
|
||||||
|
"description_description": "Появляется в заголовке рядом с названием",
|
||||||
|
"trusted_instances_label_default": "Дружественные экземпляры",
|
||||||
|
"blocked": "Блокирован",
|
||||||
|
"geocoding_provider_type": "Программа для геокодирования",
|
||||||
|
"geocoding_provider": "Провайдер геокодирования",
|
||||||
|
"geocoding_provider_help": "Поставщиком по умолчанию является Nominatim",
|
||||||
|
"geolocation": "Геолокация",
|
||||||
|
"title_description": "Он используется в заголовке страницы, в теме письма для экспорта RSS- и ICS-каналов.",
|
||||||
|
"instance_place": "Ориентировочное местонахождение данного экземпляра",
|
||||||
|
"instance_name_help": "Аккаунт ActivityPub, для подписки",
|
||||||
|
"enable_trusted_instances": "Включите дружественные экземпляры",
|
||||||
|
"trusted_instances_label": "Навигационная метка для дружественных экземпляров",
|
||||||
|
"trusted_instances_label_help": "Метка по умолчанию в 'Дружеств. экземплярах'",
|
||||||
|
"add_trusted_instance": "Доб. дружественный экземпляр",
|
||||||
|
"instance_place_help": "Метка для отображения в других экземплярах",
|
||||||
|
"is_dark": "Темная тема",
|
||||||
|
"add_link": "Добавить ссылку",
|
||||||
|
"footer_links": "Ссылки в нижнем колонтитуле",
|
||||||
|
"edit_place": "Ред. место",
|
||||||
|
"edit_tag": "Ред. тег",
|
||||||
|
"new_announcement": "Новое объявление",
|
||||||
|
"show_smtp_setup": "Настройки электронной почты",
|
||||||
|
"smtp_hostname": "Имя хоста SMTP",
|
||||||
|
"smtp_port": "Порт SMTP",
|
||||||
|
"smtp_test_success": "Тестовое письмо отправлено на {admin_email}, пожалуйста, проверьте свой почтовый ящик",
|
||||||
|
"smtp_test_button": "Отправьте тестовое электронное письмо",
|
||||||
|
"smtp_use_sendmail": "Используйте sendmail",
|
||||||
|
"admin_email": "Электронная почта администратора",
|
||||||
|
"admin_email_help": "Адрес, который мы используем в качестве отправителя для отправки электронных писем. Это также адрес, на который отправляются электронные письма администраторов",
|
||||||
|
"widget": "Виджет",
|
||||||
|
"wrong_domain_warning": "Baseurl, настроенный в config.json <b>({baseurl})</b> отличается от того, который вы посещаете <b>({url})</b>",
|
||||||
|
"collections_description": "Коллекции - это группировка событий по тегам и местам. Они будут отображаться на главной странице",
|
||||||
|
"edit_collection": "Ред. коллекцию",
|
||||||
|
"config_plugin": "Конфигурация плагина",
|
||||||
|
"header_image": "Изображение заголовка",
|
||||||
|
"hide_thumbs": "Скрыть превью",
|
||||||
|
"hide_calendar": "Скрыть календарь",
|
||||||
|
"default_images": "Изображения по умолчанию",
|
||||||
|
"geocoding_countrycodes": "Коды стран",
|
||||||
|
"geocoding_countrycodes_help": "Позволяет установить фильтр для поиска на основе кодов городов",
|
||||||
|
"geocoding_test_button": "Тест геокодирования",
|
||||||
|
"geocoding_test_success": "Служба геокодирования на {service_name} работает",
|
||||||
|
"geocoding_test_error": "Служба геокодирования недоступна по адресу {service_name}",
|
||||||
|
"tilelayer_provider_attribution": "Атрибуция",
|
||||||
|
"announcement_description": "В этом разделе вы можете вставлять объявления, которые будут оставаться на главной странице",
|
||||||
|
"delete_trusted_instance_confirm": "Вы действительно хотите удалить этот пункт из меню дружественных экземпляров?",
|
||||||
|
"edit_tag_help": "Вы можете изменить тег, заменив его новым или объединив с существующим. Связанные с ним {n} события также будут изменены.",
|
||||||
|
"instance_timezone_description": "Gancio предназначен для сбора событий определенного места, например, города. Все события в этом месте будут отображаться в выбранном для него часовом поясе.",
|
||||||
|
"instance_locale_description": "Предпочитаемый язык пользователя для страниц. Иногда сообщения должны отображаться на одном языке для всех (например, при публикации через ActivityPub или при отправке некоторых электронных писем). В этих случаях будет использоваться язык, выбранный выше.",
|
||||||
|
"trusted_instances_help": "Список дружественных экземпляров будет показан в заголовке",
|
||||||
|
"smtp_description": "<ul><li>Администратор должен получать электронное письмо при добавлении события анонса (если включено).</li><li>Администратор должен получить электронное письмо с запросом на регистрацию (если включено).</li><li>Пользователь должен получить электронное письмо с запросом на регистрацию.</li><li>Пользователь должен получить электронное письмо с подтверждением регистрации.</li><li>Пользователь должен получить подтверждение по электронной почте при подписке непосредственно администратором.</li><li>Пользователи должны получать электронное письмо для восстановления пароля, если они его забыли</li></ul>",
|
||||||
|
"hide_boost_bookmark_help": "Скрывает маленькие иконки, показывающие количество бустов и закладок, поступающих из fediverse"
|
||||||
|
},
|
||||||
|
"recover": {
|
||||||
|
"not_valid_code": "Что-то пошло не так."
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"email_description": "Вы можете получать интересующие вас события на электронной почте.",
|
||||||
|
"insert_your_address": "Введите свой адрес электронной почты",
|
||||||
|
"ical_description": "Компьютеры и смартфоны обычно оснащены приложением календаря, способным импортировать календарь (ical файл).",
|
||||||
|
"list_description": "Если у вас есть веб-сайт и вы хотите показать список событий, используйте следующий код",
|
||||||
|
"intro": "В отличие от несоциальных платформ, которые делают все, чтобы удержать пользователей и данные о них, мы считаем, что информация, как и люди, должна быть свободной. Для этого вы можете оставаться в курсе нужных вам событий, без обязательного посещения этого сайта.",
|
||||||
|
"feed_description": "Чтобы следить за обновлениями с компьютера или смартфона без необходимости периодически открывать этот сайт, используйте RSS-каналы.\n\n<p> С помощью RSS-каналов вы используете специальное приложение для получения обновлений с интересующих вас сайтов. Это хороший способ быстро следить за многими сайтами, без необходимости создавать учетную запись или других сложностей. </p>\n\n<li> Если у вас Android, мы рекомендуем <a href=\"https://f-droid.org/en/packages/net.frju.flym/\">Flym</a> или Feeder </li>\n<li> Для iPhone / iPad вы можете использовать <a href=\"https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8\"> Feed4U </a> </li>\n<li> Для настольных компьютеров / ноутбуков мы рекомендуем Feedbro, который должен быть установлен на <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/\"> Firefox </a> или <a href=\"https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa\"> Chrome </a>. </li>\n<br/>\nДобавив эту ссылку в свой RSS-канал, вы всегда будете в курсе последних событий."
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"error": "Ошибка: ",
|
||||||
|
"complete": "Регистрация должна быть подтверждена.",
|
||||||
|
"first_user": "Администратор создан",
|
||||||
|
"description": "Общественные движения должны организовываться и самофинансироваться.<br/>\n<br/>Перед публикацией, <strong> аккаунт должен быть подтвержден</strong>, подумайте о том, что <strong> на этом сайте вы найдете реальных людей</strong>, поэтому напишите две строчки, чтобы сообщить нам, какие события вы хотели бы опубликовать."
|
||||||
|
},
|
||||||
|
"setup": {
|
||||||
|
"copy_password_dialog": "Да, вы можете скопировать пароль!",
|
||||||
|
"https_warning": "Вы посещаете сайт с HTTP, не забудьте изменить baseurl в config.json, если вы перейдете на HTTPS!",
|
||||||
|
"start": "Начало",
|
||||||
|
"completed_description": "<p>Вы можете войти в систему под следующим пользователем:<br/><br/>Логин: <b>{email}</b><br/>Пароль: <b>{password}<b/></p>",
|
||||||
|
"completed": "Установка завершена"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Подтверждение пользователя",
|
||||||
|
"not_valid": "Что-то пошло не так.",
|
||||||
|
"valid": "Ваша учетная запись подтверждена, теперь вы можете <a href=\"/login\">войти</a>"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"password_updated": "Пароль изменен.",
|
||||||
|
"update_confirm": "Хотите ли вы сохранить свою изменение?",
|
||||||
|
"change_password": "Изменить пароль",
|
||||||
|
"danger_section": "Опасная опция",
|
||||||
|
"remove_account": "При нажатии следующей кнопки ваша учетная запись пользователя будет удалена. Опубликованные вами события не будут удалены.",
|
||||||
|
"remove_account_confirm": "Вы собираетесь навсегда удалить свой аккаунт"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"email_taken": "Эта электронная почта уже используется.",
|
||||||
|
"nick_taken": "Это имя пользователя уже используется."
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"not_confirmed": "Не подтверждено…",
|
||||||
|
"fail": "Не удалось войти в систему. Вы уверены, что пароль правильный?"
|
||||||
|
},
|
||||||
|
"validators": {
|
||||||
|
"required": "{fieldName} заполните",
|
||||||
|
"email": "Доб. действующий адрес электронной почты"
|
||||||
|
},
|
||||||
|
"oauth": {
|
||||||
|
"authorization_request": "Приложение <code>{app}</code> запрашивает следующую авторизацию на <code>{instance_name}</code>:",
|
||||||
|
"scopes": {
|
||||||
|
"event:write": "Добавляйте и редактируйте свои события"
|
||||||
|
},
|
||||||
|
"redirected_to": "После подтверждения вы будете перенаправлены на <code>{url}</code>"
|
||||||
|
},
|
||||||
|
"about": "\n <p><a href='https://gancio.org'>Gancio</a> это афиша для местных сообществ.</p>\n ",
|
||||||
|
"ordinal": {
|
||||||
|
"1": "первый",
|
||||||
|
"2": "второй",
|
||||||
|
"3": "третий",
|
||||||
|
"4": "четвертый",
|
||||||
|
"5": "пятый",
|
||||||
|
"-1": "последний"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ const config = require('./server/config.js')
|
||||||
const minifyTheme = require('minify-css-string').default
|
const minifyTheme = require('minify-css-string').default
|
||||||
const locales = require('./locales/index')
|
const locales = require('./locales/index')
|
||||||
|
|
||||||
import { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, zhHans } from 'vuetify/lib/locale'
|
import { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } from 'vuetify/lib/locale'
|
||||||
|
|
||||||
const isDev = (process.env.NODE_ENV !== 'production')
|
const isDev = (process.env.NODE_ENV !== 'production')
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -141,7 +141,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
buildModules: ['@nuxtjs/vuetify'],
|
buildModules: ['@nuxtjs/vuetify'],
|
||||||
vuetify: {
|
vuetify: {
|
||||||
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, zhHans } },
|
lang: { locales: { ca, de, en, es, eu, fr, gl, it, nb, pl, pt, sk, ru, zhHans } },
|
||||||
treeShake: true,
|
treeShake: true,
|
||||||
theme: {
|
theme: {
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "gancio",
|
"name": "gancio",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"description": "A shared agenda for local communities",
|
"description": "A shared agenda for local communities",
|
||||||
"author": "lesion",
|
"author": "lesion",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -75,7 +75,7 @@
|
||||||
"passport-oauth2-client-password": "^0.1.2",
|
"passport-oauth2-client-password": "^0.1.2",
|
||||||
"passport-oauth2-client-public": "^0.0.1",
|
"passport-oauth2-client-public": "^0.0.1",
|
||||||
"pg": "^8.8.0",
|
"pg": "^8.8.0",
|
||||||
"sequelize": "^6.27.0",
|
"sequelize": "^6.28.0",
|
||||||
"sequelize-slugify": "^1.6.2",
|
"sequelize-slugify": "^1.6.2",
|
||||||
"sharp": "^0.27.2",
|
"sharp": "^0.27.2",
|
||||||
"sqlite3": "^5.1.4",
|
"sqlite3": "^5.1.4",
|
||||||
|
|
|
@ -179,7 +179,6 @@ export default {
|
||||||
filteredTags() {
|
filteredTags() {
|
||||||
if (!this.tagName) { return this.tags.slice(0, 10).map(t => t.tag) }
|
if (!this.tagName) { return this.tags.slice(0, 10).map(t => t.tag) }
|
||||||
const tagName = this.tagName.trim().toLowerCase()
|
const tagName = this.tagName.trim().toLowerCase()
|
||||||
console.log(tagName)
|
|
||||||
return this.tags.filter(t => t.tag.toLowerCase().includes(tagName)).map(t => t.tag)
|
return this.tags.filter(t => t.tag.toLowerCase().includes(tagName)).map(t => t.tag)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -245,6 +244,8 @@ export default {
|
||||||
if (this.date.dueHour) {
|
if (this.date.dueHour) {
|
||||||
[hour, minute] = this.date.dueHour.split(':')
|
[hour, minute] = this.date.dueHour.split(':')
|
||||||
formData.append('end_datetime', dayjs(this.date.due).hour(Number(hour)).minute(Number(minute)).second(0).unix())
|
formData.append('end_datetime', dayjs(this.date.due).hour(Number(hour)).minute(Number(minute)).second(0).unix())
|
||||||
|
} else if (!!this.date.multidate) {
|
||||||
|
formData.append('end_datetime', dayjs(this.date.due).hour(24).minute(0).second(0).unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.edit) {
|
if (this.edit) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
||||||
async asyncData ({ $axios, params, error }) {
|
async asyncData ({ $axios, params, error }) {
|
||||||
try {
|
try {
|
||||||
const collection = params.collection
|
const collection = params.collection
|
||||||
const events = await $axios.$get(`/collections/${collection}`)
|
const events = await $axios.$get(`/collections/${encodeURIComponent(collection)}`)
|
||||||
return { events, collection }
|
return { events, collection }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
@ -3,7 +3,7 @@ v-container#event.pa-0.pa-sm-2
|
||||||
//- EVENT PAGE
|
//- EVENT PAGE
|
||||||
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
//- gancio supports microformats (http://microformats.org/wiki/h-event)
|
||||||
//- and microdata https://schema.org/Event
|
//- and microdata https://schema.org/Event
|
||||||
v-card.h-event(itemscope itemtype="https://schema.org/Event")
|
v-card.h-event(itemscope itemtype="https://schema.org/Event" v-touch="{ left: goNext, right: goPrev }")
|
||||||
|
|
||||||
v-card-text
|
v-card-text
|
||||||
v-row
|
v-row
|
||||||
|
@ -318,12 +318,22 @@ export default {
|
||||||
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) {
|
||||||
this.$router.replace(`/event/${this.event.next}`)
|
this.goNext()
|
||||||
}
|
}
|
||||||
if (ev.key === 'ArrowLeft' && this.event.prev) {
|
if (ev.key === 'ArrowLeft' && this.event.prev) {
|
||||||
|
this.goPrev()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goPrev () {
|
||||||
|
if (this.event.prev) {
|
||||||
this.$router.replace(`/event/${this.event.prev}`)
|
this.$router.replace(`/event/${this.event.prev}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
goNext () {
|
||||||
|
if (this.event.next) {
|
||||||
|
this.$router.replace(`/event/${this.event.next}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
showResource (resource) {
|
showResource (resource) {
|
||||||
this.showResources = true
|
this.showResources = true
|
||||||
this.selectedResource = resource
|
this.selectedResource = resource
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
v-container.px-2.px-sm-6.pt-0
|
v-container.px-2.px-sm-6.pt-0
|
||||||
|
|
||||||
//- Announcements
|
//- Announcements
|
||||||
#announcements.mt-2.mt-sm-4(v-if='announcements.length')
|
#announcements.mt-2.mt-sm-4(v-if='announcements.length')
|
||||||
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
Announcement(v-for='announcement in announcements' :key='`a_${announcement.id}`' :announcement='announcement')
|
||||||
|
@ -41,7 +40,8 @@ export default {
|
||||||
searching: false,
|
searching: false,
|
||||||
tmpEvents: [],
|
tmpEvents: [],
|
||||||
selectedDay: null,
|
selectedDay: null,
|
||||||
show_recurrent: $store.state.settings.recurrent_event_visible,
|
storeUnsubscribe: null
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
head () {
|
head () {
|
||||||
|
@ -63,53 +63,60 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['settings', 'announcements', 'events']),
|
...mapState(['settings', 'announcements', 'events', 'filter']),
|
||||||
visibleEvents () {
|
visibleEvents () {
|
||||||
if (this.searching) {
|
if (this.filter.query && this.filter.query.length > 2) {
|
||||||
return this.tmpEvents
|
return this.tmpEvents
|
||||||
}
|
}
|
||||||
const now = dayjs().unix()
|
const now = dayjs().unix()
|
||||||
if (this.selectedDay) {
|
if (this.selectedDay) {
|
||||||
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
|
const min = dayjs.tz(this.selectedDay).startOf('day').unix()
|
||||||
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
|
const max = dayjs.tz(this.selectedDay).endOf('day').unix()
|
||||||
return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.show_recurrent || !e.parentId))
|
return this.events.filter(e => (e.start_datetime <= max && (e.end_datetime || e.start_datetime) >= min) && (this.filter.show_recurrent || !e.parentId))
|
||||||
} else if (this.isCurrentMonth) {
|
} else if (this.isCurrentMonth) {
|
||||||
return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.show_recurrent || !e.parentId)))
|
return this.events.filter(e => ((e.end_datetime ? e.end_datetime > now : e.start_datetime + 3 * 60 * 60 > now) && (this.filter.show_recurrent || !e.parentId)))
|
||||||
} else {
|
} else {
|
||||||
return this.events.filter(e => this.show_recurrent || !e.parentId)
|
return this.events.filter(e => this.filter.show_recurrent || !e.parentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
mounted () {
|
||||||
this.$root.$on('dayclick', this.dayChange)
|
this.$root.$on('dayclick', this.dayChange)
|
||||||
this.$root.$on('monthchange', this.monthChange)
|
this.$root.$on('monthchange', this.monthChange)
|
||||||
this.$root.$on('search', debounce(this.search, 100))
|
this.storeUnsubscribe = this.$store.subscribeAction( { after: (action, state) => {
|
||||||
|
if (action.type === 'setFilter') {
|
||||||
|
if (this.filter.query && this.filter.query.length > 2) {
|
||||||
|
this.search()
|
||||||
|
} else {
|
||||||
|
this.updateEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}})
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
this.$root.$off('dayclick')
|
this.$root.$off('dayclick')
|
||||||
this.$root.$off('monthchange')
|
this.$root.$off('monthchange')
|
||||||
this.$root.$off('search')
|
if (typeof this.storeUnsubscribe === 'function') {
|
||||||
|
this.storeUnsubscribe()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['getEvents']),
|
...mapActions(['getEvents']),
|
||||||
async search (query) {
|
search: debounce(async function() {
|
||||||
if (query) {
|
this.tmpEvents = await this.$api.getEvents({
|
||||||
this.tmpEvents = await this.$axios.$get(`/event/search?search=${query}`)
|
start: 0,
|
||||||
this.searching = true
|
show_recurrent: this.filter.show_recurrent,
|
||||||
} else {
|
show_multidate: this.filter.show_multidate,
|
||||||
this.tmpEvents = null
|
query: this.filter.query
|
||||||
this.searching = false
|
})
|
||||||
}
|
}, 100),
|
||||||
},
|
|
||||||
updateEvents () {
|
updateEvents () {
|
||||||
return this.getEvents({
|
return this.getEvents({
|
||||||
start: this.start,
|
start: this.start,
|
||||||
end: this.end,
|
end: this.end
|
||||||
show_recurrent: true
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async monthChange ({ year, month }) {
|
async monthChange ({ year, month }) {
|
||||||
|
|
||||||
this.$nuxt.$loading.start()
|
this.$nuxt.$loading.start()
|
||||||
this.$nextTick( async () => {
|
this.$nextTick( async () => {
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,15 @@ export default ({ $axios }, inject) => {
|
||||||
try {
|
try {
|
||||||
const events = await $axios.$get('/events', {
|
const events = await $axios.$get('/events', {
|
||||||
params: {
|
params: {
|
||||||
start: params.start,
|
...params,
|
||||||
end: params.end,
|
// start: params.start,
|
||||||
|
// end: params.end,
|
||||||
places: params.places && params.places.join(','),
|
places: params.places && params.places.join(','),
|
||||||
tags: params.tags && params.tags.join(','),
|
tags: params.tags && params.tags.join(','),
|
||||||
show_recurrent: !!params.show_recurrent,
|
// ...(params.show_recurrent !== && {show_recurrent: !!params.show_recurrent}),
|
||||||
max: params.maxs
|
// show_multidate: !!params.show_multidate,
|
||||||
|
// query: params.query,
|
||||||
|
// max: params.maxs
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return events.map(e => Object.freeze(e))
|
return events.map(e => Object.freeze(e))
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'dayjs/locale/fr'
|
||||||
import 'dayjs/locale/de'
|
import 'dayjs/locale/de'
|
||||||
import 'dayjs/locale/gl'
|
import 'dayjs/locale/gl'
|
||||||
import 'dayjs/locale/sk'
|
import 'dayjs/locale/sk'
|
||||||
|
import 'dayjs/locale/ru'
|
||||||
import 'dayjs/locale/pt'
|
import 'dayjs/locale/pt'
|
||||||
import 'dayjs/locale/zh'
|
import 'dayjs/locale/zh'
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ rm -fr node_modules
|
||||||
yarn
|
yarn
|
||||||
yarn build
|
yarn build
|
||||||
yarn pack
|
yarn pack
|
||||||
# yarn publish
|
yarn publish
|
||||||
gpg --pinentry-mode loopback --passphrase `pass underscore/pgp` --detach-sign --local-user 5DAC477D5441B7A15ACBF680BBEB4DD39AC6CCA9 gancio-$RELEASE.tgz
|
gpg --pinentry-mode loopback --passphrase `pass underscore/pgp` --detach-sign --local-user 5DAC477D5441B7A15ACBF680BBEB4DD39AC6CCA9 gancio-$RELEASE.tgz
|
||||||
cp gancio-$RELEASE.tgz releases/
|
cp gancio-$RELEASE.tgz releases/
|
||||||
mv gancio-$RELEASE.tgz releases/latest.tgz
|
mv gancio-$RELEASE.tgz releases/latest.tgz
|
||||||
|
@ -12,4 +12,4 @@ cp gancio-$RELEASE.tgz.sig releases/
|
||||||
mv gancio-$RELEASE.tgz.sig releases/latest.tgz.sig
|
mv gancio-$RELEASE.tgz.sig releases/latest.tgz.sig
|
||||||
yarn doc
|
yarn doc
|
||||||
rsync -a docs/_site/ --chown=www-data:www-data cisti.web:/var/www/gancio/
|
rsync -a docs/_site/ --chown=www-data:www-data cisti.web:/var/www/gancio/
|
||||||
cd docs
|
cd docs
|
|
@ -1,4 +1,5 @@
|
||||||
const Announcement = require('../models/announcement')
|
const { Announcement } = require('../models/models')
|
||||||
|
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
|
|
||||||
const announceController = {
|
const announceController = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const APUser = require('../models/ap_user')
|
const { APUser } = require('../models/models')
|
||||||
|
|
||||||
const apUserController = {
|
const apUserController = {
|
||||||
async toggleBlock (req, res) {
|
async toggleBlock (req, res) {
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
const Collection = require('../models/collection')
|
const { Collection, Filter, Event, Tag, Place } = require('../models/models')
|
||||||
const Filter = require('../models/filter')
|
|
||||||
const Event = require('../models/event')
|
|
||||||
const Tag = require('../models/tag')
|
|
||||||
const Place = require('../models/place')
|
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const { col: Col } = require('../../helpers')
|
const { col: Col } = require('../../helpers')
|
||||||
|
@ -114,7 +111,7 @@ const collectionController = {
|
||||||
res.json(collection)
|
res.json(collection)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(`Create collection failed ${e}`)
|
log.error(`Create collection failed ${e}`)
|
||||||
res.sendStatus(400)
|
res.status(400).send(e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -138,15 +135,14 @@ const collectionController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async addFilter (req, res) {
|
async addFilter (req, res) {
|
||||||
const collectionId = req.body.collectionId
|
const { collectionId, tags, places } = req.body
|
||||||
const tags = req.body.tags
|
|
||||||
const places = req.body.places
|
|
||||||
try {
|
try {
|
||||||
const filter = await Filter.create({ collectionId, tags, places })
|
filter = await Filter.create({ collectionId, tags, places })
|
||||||
return res.json(filter)
|
return res.json(filter)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(String(e))
|
log.error(String(e))
|
||||||
return res.status(500)
|
return res.sendStatus(400)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -170,6 +166,4 @@ const collectionController = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = collectionController
|
module.exports = collectionController
|
|
@ -3,18 +3,15 @@ const path = require('path')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const { Op } = require('sequelize')
|
const { Op } = require('sequelize')
|
||||||
const intersection = require('lodash/intersection')
|
|
||||||
const linkifyHtml = require('linkify-html')
|
const linkifyHtml = require('linkify-html')
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const helpers = require('../../helpers')
|
const helpers = require('../../helpers')
|
||||||
const Col = helpers.col
|
const Col = helpers.col
|
||||||
const Event = require('../models/event')
|
const notifier = require('../../notifier')
|
||||||
const Resource = require('../models/resource')
|
|
||||||
const Tag = require('../models/tag')
|
const { Event, Resource, Tag, Place, Notification, APUser } = require('../models/models')
|
||||||
const Place = require('../models/place')
|
|
||||||
const Notification = require('../models/notification')
|
|
||||||
const APUser = require('../models/ap_user')
|
|
||||||
|
|
||||||
const exportController = require('./export')
|
const exportController = require('./export')
|
||||||
const tagController = require('./tag')
|
const tagController = require('./tag')
|
||||||
|
@ -89,99 +86,71 @@ const eventController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async search(req, res) {
|
// async search(req, res) {
|
||||||
const search = req.query.search.trim().toLocaleLowerCase()
|
// const search = req.query.search.trim().toLocaleLowerCase()
|
||||||
const show_recurrent = req.query.show_recurrent || false
|
// const show_recurrent = req.query.show_recurrent || false
|
||||||
const end = req.query.end
|
// const end = req.query.end
|
||||||
const replacements = []
|
// const replacements = []
|
||||||
|
|
||||||
const where = {
|
// const where = {
|
||||||
// do not include parent recurrent event
|
// // do not include parent recurrent event
|
||||||
recurrent: null,
|
// recurrent: null,
|
||||||
|
|
||||||
// confirmed event only
|
// // confirmed event only
|
||||||
is_visible: true,
|
// is_visible: true,
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!show_recurrent) {
|
// if (!show_recurrent) {
|
||||||
where.parentId = null
|
// where.parentId = null
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (end) {
|
// if (end) {
|
||||||
where.start_datetime = { [Op.lte]: end }
|
// where.start_datetime = { [Op.lte]: end }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (search) {
|
// if (search) {
|
||||||
replacements.push(search)
|
// replacements.push(search)
|
||||||
where[Op.or] =
|
// where[Op.or] =
|
||||||
[
|
// [
|
||||||
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
|
// { title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
|
||||||
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
|
// Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
|
||||||
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
// Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
const events = await Event.findAll({
|
// const events = await Event.findAll({
|
||||||
where,
|
// where,
|
||||||
attributes: {
|
// attributes: {
|
||||||
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
// exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'updatedAt', 'description', 'resources']
|
||||||
},
|
// },
|
||||||
order: [['start_datetime', 'DESC']],
|
// order: [['start_datetime', 'DESC']],
|
||||||
include: [
|
// include: [
|
||||||
{
|
// {
|
||||||
model: Tag,
|
// model: Tag,
|
||||||
// order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
// // order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
||||||
attributes: ['tag'],
|
// attributes: ['tag'],
|
||||||
through: { attributes: [] }
|
// through: { attributes: [] }
|
||||||
},
|
// },
|
||||||
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
|
// { model: Place, required: true, attributes: ['id', 'name', 'address', 'latitude', 'longitude'] }
|
||||||
],
|
// ],
|
||||||
replacements,
|
// replacements,
|
||||||
limit: 30,
|
// limit: 30,
|
||||||
}).catch(e => {
|
// }).catch(e => {
|
||||||
log.error('[EVENT]', e)
|
// log.error('[EVENT]', e)
|
||||||
return res.json([])
|
// return res.json([])
|
||||||
})
|
// })
|
||||||
|
|
||||||
const ret = events.map(e => {
|
// const ret = events.map(e => {
|
||||||
e = e.get()
|
// e = e.get()
|
||||||
e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
// e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
||||||
return e
|
// return e
|
||||||
})
|
// })
|
||||||
|
|
||||||
return res.json(ret)
|
// return res.json(ret)
|
||||||
|
|
||||||
},
|
// },
|
||||||
|
|
||||||
async getNotifications(event, action) {
|
|
||||||
log.debug(`getNotifications ${event.title} ${action}`)
|
|
||||||
function match(event, filters) {
|
|
||||||
// matches if no filter specified
|
|
||||||
if (!filters) { return true }
|
|
||||||
|
|
||||||
// check for visibility
|
|
||||||
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
|
|
||||||
|
|
||||||
if (!filters.tags && !filters.places) { return true }
|
|
||||||
if (!filters.tags.length && !filters.places.length) { return true }
|
|
||||||
if (filters.tags.length) {
|
|
||||||
const m = intersection(event.tags.map(t => t.tag), filters.tags)
|
|
||||||
if (m.length > 0) { return true }
|
|
||||||
}
|
|
||||||
if (filters.places.length) {
|
|
||||||
if (filters.places.find(p => p === event.place.name)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
|
|
||||||
|
|
||||||
// get notification that matches with selected event
|
|
||||||
return notifications.filter(notification => match(event, notification.filters))
|
|
||||||
},
|
|
||||||
|
|
||||||
async _get(slug) {
|
async _get(slug) {
|
||||||
// retrocompatibility, old events URL does not use slug, use id as fallback
|
// retrocompatibility, old events URL does not use slug, use id as fallback
|
||||||
|
@ -317,7 +286,6 @@ const eventController = {
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
|
||||||
// send notification
|
// send notification
|
||||||
const notifier = require('../../notifier')
|
|
||||||
notifier.notifyEvent('Create', event.id)
|
notifier.notifyEvent('Create', event.id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('[EVENT]', e)
|
log.error('[EVENT]', e)
|
||||||
|
@ -631,9 +599,11 @@ const eventController = {
|
||||||
async _select({
|
async _select({
|
||||||
start = dayjs().unix(),
|
start = dayjs().unix(),
|
||||||
end,
|
end,
|
||||||
|
query,
|
||||||
tags,
|
tags,
|
||||||
places,
|
places,
|
||||||
show_recurrent,
|
show_recurrent,
|
||||||
|
show_multidate,
|
||||||
limit,
|
limit,
|
||||||
page,
|
page,
|
||||||
older }) {
|
older }) {
|
||||||
|
@ -656,6 +626,10 @@ const eventController = {
|
||||||
where.parentId = null
|
where.parentId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!show_multidate) {
|
||||||
|
where.multidate = { [Op.not]: true }
|
||||||
|
}
|
||||||
|
|
||||||
if (end) {
|
if (end) {
|
||||||
where.start_datetime = { [older ? Op.gte : Op.lte]: end }
|
where.start_datetime = { [older ? Op.gte : Op.lte]: end }
|
||||||
}
|
}
|
||||||
|
@ -679,6 +653,16 @@ const eventController = {
|
||||||
where.placeId = places.split(',')
|
where.placeId = places.split(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query) {
|
||||||
|
replacements.push(query)
|
||||||
|
where[Op.or] =
|
||||||
|
[
|
||||||
|
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + query + '%') },
|
||||||
|
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + query + '%'),
|
||||||
|
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
let pagination = {}
|
let pagination = {}
|
||||||
if (limit) {
|
if (limit) {
|
||||||
pagination = {
|
pagination = {
|
||||||
|
@ -723,17 +707,21 @@ const eventController = {
|
||||||
const settings = res.locals.settings
|
const settings = res.locals.settings
|
||||||
const start = req.query.start || dayjs().unix()
|
const start = req.query.start || dayjs().unix()
|
||||||
const end = req.query.end
|
const end = req.query.end
|
||||||
|
const query = req.query.query
|
||||||
const tags = req.query.tags
|
const tags = req.query.tags
|
||||||
const places = req.query.places
|
const places = req.query.places
|
||||||
const limit = Number(req.query.max) || 0
|
const limit = Number(req.query.max) || 0
|
||||||
const page = Number(req.query.page) || 0
|
const page = Number(req.query.page) || 0
|
||||||
const older = req.query.older || false
|
const older = req.query.older || false
|
||||||
|
|
||||||
|
const show_multidate = settings.allow_multidate_event &&
|
||||||
|
typeof req.query.show_multidate !== 'undefined' ? req.query.show_multidate !== 'false' : true
|
||||||
|
|
||||||
const show_recurrent = settings.allow_recurrent_event &&
|
const show_recurrent = settings.allow_recurrent_event &&
|
||||||
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
|
typeof req.query.show_recurrent !== 'undefined' ? req.query.show_recurrent === 'true' : settings.recurrent_event_visible
|
||||||
|
|
||||||
res.json(await eventController._select({
|
res.json(await eventController._select({
|
||||||
start, end, places, tags, show_recurrent, limit, page, older
|
start, end, query, places, tags, show_recurrent, show_multidate, limit, page, older
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const Event = require('../models/event')
|
const { Event, Place, Tag } = require('../models/models')
|
||||||
const Place = require('../models/place')
|
|
||||||
const Tag = require('../models/tag')
|
|
||||||
|
|
||||||
const { htmlToText } = require('html-to-text')
|
const { htmlToText } = require('html-to-text')
|
||||||
const { Op, literal } = require('sequelize')
|
const { Op, literal } = require('sequelize')
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const APUser = require('../models/ap_user')
|
const { APUser, Instance, Resource } = require('../models/models')
|
||||||
const Instance = require('../models/instance')
|
|
||||||
const Resource = require('../models/resource')
|
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
|
|
||||||
const instancesController = {
|
const instancesController = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const User = require('../models/user')
|
const User = require('../models/modles')
|
||||||
|
|
||||||
const metrics = {
|
const metrics = {
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,9 @@ const bodyParser = require('body-parser')
|
||||||
const cookieParser = require('cookie-parser')
|
const cookieParser = require('cookie-parser')
|
||||||
const session = require('cookie-session')
|
const session = require('cookie-session')
|
||||||
|
|
||||||
const OAuthClient = require('../models/oauth_client')
|
const { OAuthClient, OAuthToken, OAuthCode, User } = require('../models/models')
|
||||||
const OAuthToken = require('../models/oauth_token')
|
|
||||||
const OAuthCode = require('../models/oauth_code')
|
|
||||||
|
|
||||||
const helpers = require('../../helpers.js')
|
const helpers = require('../../helpers.js')
|
||||||
const User = require('../models/user')
|
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
|
|
||||||
const get = require('lodash/get')
|
const get = require('lodash/get')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const Place = require('../models/place')
|
const { Place, Event } = require('../models/models')
|
||||||
const Event = require('../models/event')
|
|
||||||
const eventController = require('./event')
|
const eventController = require('./event')
|
||||||
const exportController = require('./export')
|
const exportController = require('./export')
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@ const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
const settingsController = require('./settings')
|
||||||
|
const notifier = require('../../notifier')
|
||||||
|
|
||||||
const pluginController = {
|
const pluginController = {
|
||||||
plugins: [],
|
plugins: [],
|
||||||
getAll(_req, res) {
|
getAll(_req, res) {
|
||||||
const settingsController = require('./settings')
|
|
||||||
// return plugins and inner settings
|
// return plugins and inner settings
|
||||||
const plugins = pluginController.plugins.map( ({ configuration }) => {
|
const plugins = pluginController.plugins.map( ({ configuration }) => {
|
||||||
if (settingsController.settings['plugin_' + configuration.name]) {
|
if (settingsController.settings['plugin_' + configuration.name]) {
|
||||||
|
@ -18,7 +19,6 @@ const pluginController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePlugin(req, res) {
|
togglePlugin(req, res) {
|
||||||
const settingsController = require('./settings')
|
|
||||||
const pluginName = req.params.plugin
|
const pluginName = req.params.plugin
|
||||||
const pluginSettings = settingsController.settings['plugin_' + pluginName]
|
const pluginSettings = settingsController.settings['plugin_' + pluginName]
|
||||||
if (!pluginSettings) { return res.sendStatus(404) }
|
if (!pluginSettings) { return res.sendStatus(404) }
|
||||||
|
@ -33,7 +33,6 @@ const pluginController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
unloadPlugin(pluginName) {
|
unloadPlugin(pluginName) {
|
||||||
const settingsController = require('./settings')
|
|
||||||
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
||||||
const settings = settingsController.settings['plugin_' + pluginName]
|
const settings = settingsController.settings['plugin_' + pluginName]
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
|
@ -59,14 +58,12 @@ const pluginController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPlugin(pluginName) {
|
loadPlugin(pluginName) {
|
||||||
const settingsController = require('./settings')
|
|
||||||
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
|
||||||
const settings = settingsController.settings['plugin_' + pluginName]
|
const settings = settingsController.settings['plugin_' + pluginName]
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
log.warn(`Plugin ${pluginName} not found`)
|
log.warn(`Plugin ${pluginName} not found`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const notifier = require('../../notifier')
|
|
||||||
log.info('Load plugin ' + pluginName)
|
log.info('Load plugin ' + pluginName)
|
||||||
if (typeof plugin.onEventCreate === 'function') {
|
if (typeof plugin.onEventCreate === 'function') {
|
||||||
notifier.emitter.on('Create', plugin.onEventCreate)
|
notifier.emitter.on('Create', plugin.onEventCreate)
|
||||||
|
@ -88,7 +85,6 @@ const pluginController = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_load() {
|
_load() {
|
||||||
const settingsController = require('./settings')
|
|
||||||
// load custom plugins
|
// load custom plugins
|
||||||
const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins')
|
const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins')
|
||||||
log.info(`Loading plugin ${plugins_path}`)
|
log.info(`Loading plugin ${plugins_path}`)
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const Resource = require('../models/resource')
|
const { Resource, APUser, Event } = require('../models/models')
|
||||||
const APUser = require('../models/ap_user')
|
|
||||||
const Event = require('../models/event')
|
|
||||||
const get = require('lodash/get')
|
const get = require('lodash/get')
|
||||||
|
|
||||||
const resourceController = {
|
const resourceController = {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const URL = require('url')
|
const URL = require('url')
|
||||||
const fs = require('fs')
|
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
const sharp = require('sharp')
|
const sharp = require('sharp')
|
||||||
|
@ -9,7 +8,7 @@ const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
// const locales = require('../../../locales/index')
|
// const locales = require('../../../locales/index')
|
||||||
const escape = require('lodash/escape')
|
const escape = require('lodash/escape')
|
||||||
const pluginController = require('./plugins')
|
const DB = require('../models/models')
|
||||||
|
|
||||||
let defaultHostname
|
let defaultHostname
|
||||||
try {
|
try {
|
||||||
|
@ -30,7 +29,7 @@ const defaultSettings = {
|
||||||
allow_multidate_event: true,
|
allow_multidate_event: true,
|
||||||
allow_recurrent_event: false,
|
allow_recurrent_event: false,
|
||||||
recurrent_event_visible: false,
|
recurrent_event_visible: false,
|
||||||
allow_geolocation: true,
|
allow_geolocation: false,
|
||||||
geocoding_provider_type: 'Nominatim',
|
geocoding_provider_type: 'Nominatim',
|
||||||
geocoding_provider: 'https://nominatim.openstreetmap.org/search',
|
geocoding_provider: 'https://nominatim.openstreetmap.org/search',
|
||||||
geocoding_countrycodes: [],
|
geocoding_countrycodes: [],
|
||||||
|
@ -74,8 +73,7 @@ const settingsController = {
|
||||||
// initialize instance settings from db
|
// initialize instance settings from db
|
||||||
// note that this is done only once when the server starts
|
// note that this is done only once when the server starts
|
||||||
// and not for each request
|
// and not for each request
|
||||||
const Setting = require('../models/setting')
|
const settings = await DB.Setting.findAll()
|
||||||
const settings = await Setting.findAll()
|
|
||||||
settingsController.settings = defaultSettings
|
settingsController.settings = defaultSettings
|
||||||
settings.forEach(s => {
|
settings.forEach(s => {
|
||||||
if (s.is_secret) {
|
if (s.is_secret) {
|
||||||
|
@ -117,15 +115,14 @@ const settingsController = {
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
|
const pluginController = require('./plugins')
|
||||||
pluginController._load()
|
pluginController._load()
|
||||||
},
|
},
|
||||||
|
|
||||||
async set (key, value, is_secret = false) {
|
async set (key, value, is_secret = false) {
|
||||||
const Setting = require('../models/setting')
|
|
||||||
log.info(`SET ${key} ${is_secret ? '*****' : value}`)
|
log.info(`SET ${key} ${is_secret ? '*****' : value}`)
|
||||||
try {
|
try {
|
||||||
const [setting, created] = await Setting.findOrCreate({
|
const [setting, created] = await DB.Setting.findOrCreate({
|
||||||
where: { key },
|
where: { key },
|
||||||
defaults: { value, is_secret }
|
defaults: { value, is_secret }
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,8 @@ const settingsController = require('./settings')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const escape = require('lodash/escape')
|
const escape = require('lodash/escape')
|
||||||
|
|
||||||
|
const DB = require('../models/models')
|
||||||
|
|
||||||
const setupController = {
|
const setupController = {
|
||||||
|
|
||||||
async _setupDb (dbConf) {
|
async _setupDb (dbConf) {
|
||||||
|
@ -23,7 +25,10 @@ const setupController = {
|
||||||
|
|
||||||
// try to connect
|
// try to connect
|
||||||
dbConf.logging = false
|
dbConf.logging = false
|
||||||
await db.connect(dbConf)
|
db.connect(dbConf)
|
||||||
|
db.loadModels()
|
||||||
|
db.associates()
|
||||||
|
await db.sequelize.authenticate()
|
||||||
|
|
||||||
// is empty ?
|
// is empty ?
|
||||||
const isEmpty = await db.isEmpty()
|
const isEmpty = await db.isEmpty()
|
||||||
|
@ -69,8 +74,7 @@ const setupController = {
|
||||||
// create admin
|
// create admin
|
||||||
const password = helpers.randomString()
|
const password = helpers.randomString()
|
||||||
const email = `admin`
|
const email = `admin`
|
||||||
const User = require('../models/user')
|
await DB.User.create({
|
||||||
await User.create({
|
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
is_admin: true,
|
is_admin: true,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const Tag = require('../models/tag')
|
const { Tag, Event } = require('../models/models')
|
||||||
const Event = require('../models/event')
|
|
||||||
const uniq = require('lodash/uniq')
|
const uniq = require('lodash/uniq')
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
|
|
||||||
|
@ -82,29 +81,35 @@ module.exports = {
|
||||||
return res.json(tags.map(t => t.tag))
|
return res.json(tags.map(t => t.tag))
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateTag (req, res) {
|
// async updateTag (req, res) {
|
||||||
const tag = await Tag.findByPk(req.body.tag)
|
// const tag = await Tag.findByPk(req.body.tag)
|
||||||
await tag.update(req.body)
|
// await tag.update(req.body)
|
||||||
res.json(place)
|
// res.json(place)
|
||||||
},
|
// },
|
||||||
|
|
||||||
async updateTag (req, res) {
|
async updateTag (req, res) {
|
||||||
const oldtag = await Tag.findByPk(req.body.tag)
|
try {
|
||||||
const newtag = await Tag.findByPk(req.body.newTag)
|
const oldtag = await Tag.findByPk(req.body.tag)
|
||||||
|
const newtag = await Tag.findByPk(req.body.newTag)
|
||||||
|
|
||||||
// if the new tag does not exists, just rename the old one
|
// if the new tag does not exists, just rename the old one
|
||||||
if (!newtag) {
|
if (!newtag) {
|
||||||
oldtag.tag = req.body.newTag
|
log.info(`Rename tag ${oldtag.tag} to ${req.body.newTag}`)
|
||||||
await oldtag.update({ tag: req.body.newTag })
|
await Tag.update({ tag: req.body.newTag }, { where: { tag: req.body.tag }, raw: true })
|
||||||
} else {
|
|
||||||
// in case it exists:
|
} else {
|
||||||
// - search for events with old tag
|
// in case it exists:
|
||||||
const events = await oldtag.getEvents()
|
// - search for events with old tag
|
||||||
// - substitute it with the new one
|
const events = await oldtag.getEvents()
|
||||||
await oldtag.removeEvents(events)
|
// - substitute it with the new one
|
||||||
await newtag.addEvents(events)
|
await oldtag.removeEvents(events)
|
||||||
|
await newtag.addEvents(events)
|
||||||
|
}
|
||||||
|
res.sendStatus(200)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
res.sendStatus(400)
|
||||||
}
|
}
|
||||||
res.sendStatus(200)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async remove (req, res) {
|
async remove (req, res) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ const crypto = require('crypto')
|
||||||
const { Op } = require('sequelize')
|
const { Op } = require('sequelize')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const mail = require('../mail')
|
const mail = require('../mail')
|
||||||
const User = require('../models/user')
|
const { User } = require('../models/models')
|
||||||
const settingsController = require('./settings')
|
const settingsController = require('./settings')
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const linkify = require('linkifyjs')
|
const linkify = require('linkifyjs')
|
||||||
|
|
|
@ -5,219 +5,224 @@ const cors = require('cors')()
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
const log = require('../log')
|
const log = require('../log')
|
||||||
|
|
||||||
const api = express.Router()
|
const collectionController = require('./controller/collection')
|
||||||
api.use(express.urlencoded({ extended: false }))
|
const setupController = require('./controller/setup')
|
||||||
api.use(express.json())
|
const settingsController = require('./controller/settings')
|
||||||
|
const eventController = require('./controller/event')
|
||||||
|
const placeController = require('./controller/place')
|
||||||
|
const tagController = require('./controller/tag')
|
||||||
|
const exportController = require('./controller/export')
|
||||||
|
const userController = require('./controller/user')
|
||||||
|
const instanceController = require('./controller/instance')
|
||||||
|
const apUserController = require('./controller/ap_user')
|
||||||
|
const resourceController = require('./controller/resource')
|
||||||
|
const oauthController = require('./controller/oauth')
|
||||||
|
const announceController = require('./controller/announce')
|
||||||
|
const pluginController = require('./controller/plugins')
|
||||||
|
const helpers = require('../helpers')
|
||||||
|
const storage = require('./storage')
|
||||||
|
|
||||||
|
|
||||||
if (config.status !== 'READY') {
|
module.exports = () => {
|
||||||
|
|
||||||
const setupController = require('./controller/setup')
|
const api = express.Router()
|
||||||
const settingsController = require('./controller/settings')
|
api.use(express.urlencoded({ extended: false }))
|
||||||
api.post('/settings', settingsController.setRequest)
|
api.use(express.json())
|
||||||
api.post('/setup/db', setupController.setupDb)
|
|
||||||
api.post('/setup/restart', setupController.restart)
|
|
||||||
api.post('/settings/smtp', settingsController.testSMTP)
|
if (config.status !== 'READY') {
|
||||||
|
|
||||||
} else {
|
api.post('/settings', settingsController.setRequest)
|
||||||
|
api.post('/setup/db', setupController.setupDb)
|
||||||
const { isAuth, isAdmin } = require('./auth')
|
api.post('/setup/restart', setupController.restart)
|
||||||
const eventController = require('./controller/event')
|
api.post('/settings/smtp', settingsController.testSMTP)
|
||||||
const placeController = require('./controller/place')
|
|
||||||
const tagController = require('./controller/tag')
|
} else {
|
||||||
const settingsController = require('./controller/settings')
|
|
||||||
const exportController = require('./controller/export')
|
const { isAuth, isAdmin } = require('./auth')
|
||||||
const userController = require('./controller/user')
|
const upload = multer({ storage })
|
||||||
const instanceController = require('./controller/instance')
|
|
||||||
const apUserController = require('./controller/ap_user')
|
/**
|
||||||
const resourceController = require('./controller/resource')
|
* Get current authenticated user
|
||||||
const oauthController = require('./controller/oauth')
|
* @category User
|
||||||
const announceController = require('./controller/announce')
|
* @name /api/user
|
||||||
const collectionController = require('./controller/collection')
|
* @type GET
|
||||||
const pluginController = require('./controller/plugins')
|
* @example **Response**
|
||||||
const helpers = require('../helpers')
|
* ```json
|
||||||
const storage = require('./storage')
|
{
|
||||||
const upload = multer({ storage })
|
"description" : null,
|
||||||
|
"recover_code" : "",
|
||||||
/**
|
"id" : 1,
|
||||||
* Get current authenticated user
|
"createdAt" : "2020-01-29T18:10:16.630Z",
|
||||||
* @category User
|
"updatedAt" : "2020-01-30T22:42:14.789Z",
|
||||||
* @name /api/user
|
"is_active" : true,
|
||||||
* @type GET
|
"settings" : "{}",
|
||||||
* @example **Response**
|
"email" : "eventi@cisti.org",
|
||||||
* ```json
|
"is_admin" : true
|
||||||
{
|
}
|
||||||
"description" : null,
|
```
|
||||||
"recover_code" : "",
|
*/
|
||||||
"id" : 1,
|
api.get('/ping', (_req, res) => res.sendStatus(200))
|
||||||
"createdAt" : "2020-01-29T18:10:16.630Z",
|
api.get('/user', isAuth, (req, res) => res.json(req.user))
|
||||||
"updatedAt" : "2020-01-30T22:42:14.789Z",
|
|
||||||
"is_active" : true,
|
|
||||||
"settings" : "{}",
|
api.post('/user/recover', userController.forgotPassword)
|
||||||
"email" : "eventi@cisti.org",
|
api.post('/user/check_recover_code', userController.checkRecoverCode)
|
||||||
"is_admin" : true
|
api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
|
||||||
|
|
||||||
|
// register and add users
|
||||||
|
api.post('/user/register', userController.register)
|
||||||
|
api.post('/user', isAdmin, userController.create)
|
||||||
|
|
||||||
|
// update user
|
||||||
|
api.put('/user', isAuth, userController.update)
|
||||||
|
|
||||||
|
// delete user
|
||||||
|
api.delete('/user/:id', isAdmin, userController.remove)
|
||||||
|
api.delete('/user', isAuth, userController.remove)
|
||||||
|
|
||||||
|
// get all users
|
||||||
|
api.get('/users', isAdmin, userController.getAll)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get events
|
||||||
|
* @category Event
|
||||||
|
* @name /api/events
|
||||||
|
* @type GET
|
||||||
|
* @param {integer} [start] - start timestamp (default: now)
|
||||||
|
* @param {integer} [end] - end timestamp (optional)
|
||||||
|
* @param {string} [query] - search for this string
|
||||||
|
* @param {array} [tags] - List of tags
|
||||||
|
* @param {array} [places] - List of places id
|
||||||
|
* @param {integer} [max] - Limit events
|
||||||
|
* @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings)
|
||||||
|
* @param {integer} [page] - Pagination
|
||||||
|
* @param {boolean} [older] - select <= start instead of >=
|
||||||
|
* @example ***Example***
|
||||||
|
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
|
||||||
|
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
||||||
|
*/
|
||||||
|
|
||||||
|
api.get('/events', cors, eventController.select)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new event
|
||||||
|
* @category Event
|
||||||
|
* @name /api/event
|
||||||
|
* @type POST
|
||||||
|
* @info `Content-Type` has to be `multipart/form-data` to support image upload
|
||||||
|
* @param {string} title - event's title
|
||||||
|
* @param {string} description - event's description (html accepted and sanitized)
|
||||||
|
* @param {string} place_name - the name of the place
|
||||||
|
* @param {string} [place_address] - the address of the place
|
||||||
|
* @param {float} [place_latitude] - the latitude of the place
|
||||||
|
* @param {float} [place_longitude] - the longitude of the place
|
||||||
|
* @param {integer} start_datetime - start timestamp
|
||||||
|
* @param {integer} multidate - is a multidate event?
|
||||||
|
* @param {array} tags - List of tags
|
||||||
|
* @param {object} [recurrent] - Recurrent event details
|
||||||
|
* @param {string} [recurrent.frequency] - could be `1w` or `2w`
|
||||||
|
* @param {array} [recurrent.days] - array of days
|
||||||
|
* @param {image} [image] - Image
|
||||||
|
*/
|
||||||
|
|
||||||
|
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
|
||||||
|
api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add)
|
||||||
|
|
||||||
|
// api.get('/event/search', eventController.search)
|
||||||
|
|
||||||
|
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
||||||
|
api.get('/event/import', isAuth, helpers.importURL)
|
||||||
|
|
||||||
|
// remove event
|
||||||
|
api.delete('/event/:id', isAuth, eventController.remove)
|
||||||
|
|
||||||
|
// get tags/places
|
||||||
|
api.get('/event/meta', eventController.searchMeta)
|
||||||
|
|
||||||
|
// add event notification TODO
|
||||||
|
api.post('/event/notification', eventController.addNotification)
|
||||||
|
api.delete('/event/notification/:code', eventController.delNotification)
|
||||||
|
|
||||||
|
api.post('/settings', isAdmin, settingsController.setRequest)
|
||||||
|
api.get('/settings', isAdmin, settingsController.getAll)
|
||||||
|
api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo)
|
||||||
|
api.post('/settings/fallbackImage', isAdmin, multer({ dest: config.upload_path }).single('fallbackImage'), settingsController.setFallbackImage)
|
||||||
|
api.post('/settings/headerImage', isAdmin, multer({ dest: config.upload_path }).single('headerImage'), settingsController.setHeaderImage)
|
||||||
|
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
||||||
|
api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings)
|
||||||
|
|
||||||
|
// get unconfirmed events
|
||||||
|
api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed)
|
||||||
|
|
||||||
|
// [un]confirm event
|
||||||
|
api.put('/event/confirm/:event_id', isAuth, eventController.confirm)
|
||||||
|
api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm)
|
||||||
|
|
||||||
|
// get event
|
||||||
|
api.get('/event/:event_slug.:format?', cors, eventController.get)
|
||||||
|
|
||||||
|
// export events (rss/ics)
|
||||||
|
api.get('/export/:type', cors, exportController.export)
|
||||||
|
|
||||||
|
|
||||||
|
// - PLACES
|
||||||
|
api.get('/places', isAdmin, placeController.getAll)
|
||||||
|
api.get('/place/:placeName', cors, placeController.getEvents)
|
||||||
|
api.get('/place', cors, placeController.search)
|
||||||
|
api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim)
|
||||||
|
api.get('/placeOSM/Photon/:place_details', cors, placeController._photon)
|
||||||
|
api.put('/place', isAdmin, placeController.updatePlace)
|
||||||
|
|
||||||
|
// - TAGS
|
||||||
|
api.get('/tags', isAdmin, tagController.getAll)
|
||||||
|
api.get('/tag', cors, tagController.search)
|
||||||
|
api.get('/tag/:tag', cors, tagController.getEvents)
|
||||||
|
api.delete('/tag/:tag', isAdmin, tagController.remove)
|
||||||
|
api.put('/tag', isAdmin, tagController.updateTag)
|
||||||
|
|
||||||
|
|
||||||
|
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
||||||
|
api.get('/instances', isAdmin, instanceController.getAll)
|
||||||
|
api.get('/instances/:instance_domain', isAdmin, instanceController.get)
|
||||||
|
api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock)
|
||||||
|
api.post('/instances/toggle_user_block', isAdmin, apUserController.toggleBlock)
|
||||||
|
api.put('/resources/:resource_id', isAdmin, resourceController.hide)
|
||||||
|
api.delete('/resources/:resource_id', isAdmin, resourceController.remove)
|
||||||
|
api.get('/resources', isAdmin, resourceController.getAll)
|
||||||
|
|
||||||
|
// - ADMIN ANNOUNCEMENTS
|
||||||
|
api.get('/announcements', isAdmin, announceController.getAll)
|
||||||
|
api.post('/announcements', isAdmin, announceController.add)
|
||||||
|
api.put('/announcements/:announce_id', isAdmin, announceController.update)
|
||||||
|
api.delete('/announcements/:announce_id', isAdmin, announceController.remove)
|
||||||
|
|
||||||
|
// - COLLECTIONS
|
||||||
|
api.get('/collections/:name', cors, collectionController.getEvents)
|
||||||
|
api.get('/collections', collectionController.getAll)
|
||||||
|
api.post('/collections', isAdmin, collectionController.add)
|
||||||
|
api.delete('/collection/:id', isAdmin, collectionController.remove)
|
||||||
|
api.get('/filter/:collection_id', isAdmin, collectionController.getFilters)
|
||||||
|
api.post('/filter', isAdmin, collectionController.addFilter)
|
||||||
|
api.delete('/filter/:id', isAdmin, collectionController.removeFilter)
|
||||||
|
|
||||||
|
// - PLUGINS
|
||||||
|
api.get('/plugins', isAdmin, pluginController.getAll)
|
||||||
|
api.put('/plugin/:plugin', isAdmin, pluginController.togglePlugin)
|
||||||
|
|
||||||
|
// OAUTH
|
||||||
|
api.get('/clients', isAuth, oauthController.getClients)
|
||||||
|
api.get('/client/:client_id', isAuth, oauthController.getClient)
|
||||||
|
api.post('/client', oauthController.createClient)
|
||||||
}
|
}
|
||||||
```
|
|
||||||
*/
|
api.use((_req, res) => res.sendStatus(404))
|
||||||
api.get('/ping', (_req, res) => res.sendStatus(200))
|
|
||||||
api.get('/user', isAuth, (req, res) => res.json(req.user))
|
// Handle 500
|
||||||
|
api.use((error, _req, res, _next) => {
|
||||||
|
log.error('[API ERROR]', error)
|
||||||
|
res.status(500).send('500: Internal Server Error')
|
||||||
|
})
|
||||||
|
|
||||||
|
return api
|
||||||
api.post('/user/recover', userController.forgotPassword)
|
|
||||||
api.post('/user/check_recover_code', userController.checkRecoverCode)
|
|
||||||
api.post('/user/recover_password', userController.updatePasswordWithRecoverCode)
|
|
||||||
|
|
||||||
// register and add users
|
|
||||||
api.post('/user/register', userController.register)
|
|
||||||
api.post('/user', isAdmin, userController.create)
|
|
||||||
|
|
||||||
// update user
|
|
||||||
api.put('/user', isAuth, userController.update)
|
|
||||||
|
|
||||||
// delete user
|
|
||||||
api.delete('/user/:id', isAdmin, userController.remove)
|
|
||||||
api.delete('/user', isAuth, userController.remove)
|
|
||||||
|
|
||||||
// get all users
|
|
||||||
api.get('/users', isAdmin, userController.getAll)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get events
|
|
||||||
* @category Event
|
|
||||||
* @name /api/events
|
|
||||||
* @type GET
|
|
||||||
* @param {integer} [start] - start timestamp (default: now)
|
|
||||||
* @param {integer} [end] - end timestamp (optional)
|
|
||||||
* @param {array} [tags] - List of tags
|
|
||||||
* @param {array} [places] - List of places id
|
|
||||||
* @param {integer} [max] - Limit events
|
|
||||||
* @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings)
|
|
||||||
* @param {integer} [page] - Pagination
|
|
||||||
* @param {boolean} [older] - select <= start instead of >=
|
|
||||||
* @example ***Example***
|
|
||||||
* [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events)
|
|
||||||
* [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42)
|
|
||||||
*/
|
|
||||||
|
|
||||||
api.get('/events', cors, eventController.select)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new event
|
|
||||||
* @category Event
|
|
||||||
* @name /api/event
|
|
||||||
* @type POST
|
|
||||||
* @info `Content-Type` has to be `multipart/form-data` to support image upload
|
|
||||||
* @param {string} title - event's title
|
|
||||||
* @param {string} description - event's description (html accepted and sanitized)
|
|
||||||
* @param {string} place_name - the name of the place
|
|
||||||
* @param {string} [place_address] - the address of the place
|
|
||||||
* @param {float} [place_latitude] - the latitude of the place
|
|
||||||
* @param {float} [place_longitude] - the longitude of the place
|
|
||||||
* @param {integer} start_datetime - start timestamp
|
|
||||||
* @param {integer} multidate - is a multidate event?
|
|
||||||
* @param {array} tags - List of tags
|
|
||||||
* @param {object} [recurrent] - Recurrent event details
|
|
||||||
* @param {string} [recurrent.frequency] - could be `1w` or `2w`
|
|
||||||
* @param {array} [recurrent.days] - array of days
|
|
||||||
* @param {image} [image] - Image
|
|
||||||
*/
|
|
||||||
|
|
||||||
// allow anyone to add an event (anon event has to be confirmed, TODO: flood protection)
|
|
||||||
api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add)
|
|
||||||
|
|
||||||
api.get('/event/search', eventController.search)
|
|
||||||
|
|
||||||
api.put('/event', isAuth, upload.single('image'), eventController.update)
|
|
||||||
api.get('/event/import', isAuth, helpers.importURL)
|
|
||||||
|
|
||||||
// remove event
|
|
||||||
api.delete('/event/:id', isAuth, eventController.remove)
|
|
||||||
|
|
||||||
// get tags/places
|
|
||||||
api.get('/event/meta', eventController.searchMeta)
|
|
||||||
|
|
||||||
// add event notification TODO
|
|
||||||
api.post('/event/notification', eventController.addNotification)
|
|
||||||
api.delete('/event/notification/:code', eventController.delNotification)
|
|
||||||
|
|
||||||
api.post('/settings', isAdmin, settingsController.setRequest)
|
|
||||||
api.get('/settings', isAdmin, settingsController.getAll)
|
|
||||||
api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo)
|
|
||||||
api.post('/settings/fallbackImage', isAdmin, multer({ dest: config.upload_path }).single('fallbackImage'), settingsController.setFallbackImage)
|
|
||||||
api.post('/settings/headerImage', isAdmin, multer({ dest: config.upload_path }).single('headerImage'), settingsController.setHeaderImage)
|
|
||||||
api.post('/settings/smtp', isAdmin, settingsController.testSMTP)
|
|
||||||
api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings)
|
|
||||||
|
|
||||||
// get unconfirmed events
|
|
||||||
api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed)
|
|
||||||
|
|
||||||
// [un]confirm event
|
|
||||||
api.put('/event/confirm/:event_id', isAuth, eventController.confirm)
|
|
||||||
api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm)
|
|
||||||
|
|
||||||
// get event
|
|
||||||
api.get('/event/:event_slug.:format?', cors, eventController.get)
|
|
||||||
|
|
||||||
// export events (rss/ics)
|
|
||||||
api.get('/export/:type', cors, exportController.export)
|
|
||||||
|
|
||||||
|
|
||||||
// - PLACES
|
|
||||||
api.get('/places', isAdmin, placeController.getAll)
|
|
||||||
api.get('/place/:placeName', cors, placeController.getEvents)
|
|
||||||
api.get('/place', cors, placeController.search)
|
|
||||||
api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim)
|
|
||||||
api.get('/placeOSM/Photon/:place_details', cors, placeController._photon)
|
|
||||||
api.put('/place', isAdmin, placeController.updatePlace)
|
|
||||||
|
|
||||||
// - TAGS
|
|
||||||
api.get('/tags', isAdmin, tagController.getAll)
|
|
||||||
api.get('/tag', cors, tagController.search)
|
|
||||||
api.get('/tag/:tag', cors, tagController.getEvents)
|
|
||||||
api.delete('/tag/:tag', isAdmin, tagController.remove)
|
|
||||||
api.put('/tag', isAdmin, tagController.updateTag)
|
|
||||||
|
|
||||||
|
|
||||||
// - FEDIVERSE INSTANCES, MODERATION, RESOURCES
|
|
||||||
api.get('/instances', isAdmin, instanceController.getAll)
|
|
||||||
api.get('/instances/:instance_domain', isAdmin, instanceController.get)
|
|
||||||
api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock)
|
|
||||||
api.post('/instances/toggle_user_block', isAdmin, apUserController.toggleBlock)
|
|
||||||
api.put('/resources/:resource_id', isAdmin, resourceController.hide)
|
|
||||||
api.delete('/resources/:resource_id', isAdmin, resourceController.remove)
|
|
||||||
api.get('/resources', isAdmin, resourceController.getAll)
|
|
||||||
|
|
||||||
// - ADMIN ANNOUNCEMENTS
|
|
||||||
api.get('/announcements', isAdmin, announceController.getAll)
|
|
||||||
api.post('/announcements', isAdmin, announceController.add)
|
|
||||||
api.put('/announcements/:announce_id', isAdmin, announceController.update)
|
|
||||||
api.delete('/announcements/:announce_id', isAdmin, announceController.remove)
|
|
||||||
|
|
||||||
// - COLLECTIONS
|
|
||||||
api.get('/collections/:name', cors, collectionController.getEvents)
|
|
||||||
api.get('/collections', collectionController.getAll)
|
|
||||||
api.post('/collections', isAdmin, collectionController.add)
|
|
||||||
api.delete('/collection/:id', isAdmin, collectionController.remove)
|
|
||||||
api.get('/filter/:collection_id', isAdmin, collectionController.getFilters)
|
|
||||||
api.post('/filter', isAdmin, collectionController.addFilter)
|
|
||||||
api.delete('/filter/:id', isAdmin, collectionController.removeFilter)
|
|
||||||
|
|
||||||
// - PLUGINS
|
|
||||||
api.get('/plugins', isAdmin, pluginController.getAll)
|
|
||||||
api.put('/plugin/:plugin', isAdmin, pluginController.togglePlugin)
|
|
||||||
|
|
||||||
// OAUTH
|
|
||||||
api.get('/clients', isAuth, oauthController.getClients)
|
|
||||||
api.get('/client/:client_id', isAuth, oauthController.getClient)
|
|
||||||
api.post('/client', oauthController.createClient)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
api.use((_req, res) => res.sendStatus(404))
|
|
||||||
|
|
||||||
// Handle 500
|
|
||||||
api.use((error, _req, res, _next) => {
|
|
||||||
log.error('[API ERROR]', error)
|
|
||||||
res.status(500).send('500: Internal Server Error')
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = api
|
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
const sequelize = require('./index').sequelize
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const { Model, DataTypes } = require('sequelize')
|
sequelize.define('announcement', {
|
||||||
|
title: DataTypes.STRING,
|
||||||
class Announcement extends Model {}
|
announcement: DataTypes.STRING,
|
||||||
|
visible: DataTypes.BOOLEAN
|
||||||
Announcement.init({
|
})
|
||||||
title: DataTypes.STRING,
|
|
||||||
announcement: DataTypes.STRING,
|
|
||||||
visible: DataTypes.BOOLEAN
|
|
||||||
}, { sequelize, modelName: 'announcement' })
|
|
||||||
|
|
||||||
module.exports = Announcement
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
const sequelize = require('./index').sequelize
|
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
|
|
||||||
class APUser extends Model {}
|
module.exports = (sequelize, DataTypes) =>
|
||||||
|
sequelize.define('ap_user', {
|
||||||
APUser.init({
|
|
||||||
ap_id: {
|
ap_id: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
|
@ -11,6 +8,4 @@ APUser.init({
|
||||||
follower: DataTypes.BOOLEAN,
|
follower: DataTypes.BOOLEAN,
|
||||||
blocked: DataTypes.BOOLEAN,
|
blocked: DataTypes.BOOLEAN,
|
||||||
object: DataTypes.JSON
|
object: DataTypes.JSON
|
||||||
}, { sequelize, modelName: 'ap_user' })
|
})
|
||||||
|
|
||||||
module.exports = APUser
|
|
||||||
|
|
|
@ -1,28 +1,20 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('collection', {
|
||||||
|
id: {
|
||||||
class Collection extends Model {}
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
// TODO: slugify!
|
primaryKey: true,
|
||||||
Collection.init({
|
},
|
||||||
id: {
|
name: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.STRING,
|
||||||
autoIncrement: true,
|
unique: true,
|
||||||
primaryKey: true,
|
index: true,
|
||||||
},
|
allowNull: false
|
||||||
name: {
|
},
|
||||||
type: DataTypes.STRING,
|
isActor: {
|
||||||
unique: true,
|
type: DataTypes.BOOLEAN
|
||||||
index: true,
|
},
|
||||||
allowNull: false
|
isTop: {
|
||||||
},
|
type: DataTypes.BOOLEAN
|
||||||
isActor: {
|
}
|
||||||
type: DataTypes.BOOLEAN
|
}, { timestamps: false })
|
||||||
},
|
|
||||||
isTop: {
|
|
||||||
type: DataTypes.BOOLEAN
|
|
||||||
}
|
|
||||||
}, { sequelize, modelName: 'collection', timestamps: false })
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Collection
|
|
||||||
|
|
|
@ -1,18 +1,5 @@
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const { htmlToText } = require('html-to-text')
|
const { htmlToText } = require('html-to-text')
|
||||||
|
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
const SequelizeSlugify = require('sequelize-slugify')
|
|
||||||
|
|
||||||
const sequelize = require('./index').sequelize
|
|
||||||
|
|
||||||
const Resource = require('./resource')
|
|
||||||
const Notification = require('./notification')
|
|
||||||
const EventNotification = require('./eventnotification')
|
|
||||||
const Place = require('./place')
|
|
||||||
const User = require('./user')
|
|
||||||
const Tag = require('./tag')
|
|
||||||
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
@ -20,108 +7,88 @@ const utc = require('dayjs/plugin/utc')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
|
||||||
class Event extends Model {}
|
// class Event extends Model {}
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
Event.init({
|
const Event = sequelize.define('event', {
|
||||||
id: {
|
id: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
},
|
},
|
||||||
title: DataTypes.STRING,
|
title: DataTypes.STRING,
|
||||||
slug: {
|
slug: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
index: true,
|
index: true,
|
||||||
unique: true
|
unique: true
|
||||||
},
|
},
|
||||||
description: DataTypes.TEXT,
|
description: DataTypes.TEXT,
|
||||||
multidate: DataTypes.BOOLEAN,
|
multidate: DataTypes.BOOLEAN,
|
||||||
start_datetime: {
|
start_datetime: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
index: true
|
index: true
|
||||||
},
|
},
|
||||||
end_datetime: {
|
end_datetime: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
index: true
|
index: true
|
||||||
},
|
},
|
||||||
image_path: DataTypes.STRING,
|
image_path: DataTypes.STRING,
|
||||||
media: DataTypes.JSON,
|
media: DataTypes.JSON,
|
||||||
is_visible: DataTypes.BOOLEAN,
|
is_visible: DataTypes.BOOLEAN,
|
||||||
recurrent: DataTypes.JSON,
|
recurrent: DataTypes.JSON,
|
||||||
likes: { type: DataTypes.JSON, defaultValue: [] },
|
likes: { type: DataTypes.JSON, defaultValue: [] },
|
||||||
boost: { type: DataTypes.JSON, defaultValue: [] }
|
boost: { type: DataTypes.JSON, defaultValue: [] }
|
||||||
}, { sequelize, modelName: 'event' })
|
})
|
||||||
|
|
||||||
Event.belongsTo(Place)
|
Event.prototype.toAP = function (username, locale, to = []) {
|
||||||
Place.hasMany(Event)
|
const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_'))
|
||||||
|
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
|
||||||
Event.belongsTo(User)
|
const content = `
|
||||||
User.hasMany(Event)
|
|
||||||
|
|
||||||
Event.belongsToMany(Tag, { through: 'event_tags' })
|
|
||||||
Tag.belongsToMany(Event, { through: 'event_tags' })
|
|
||||||
|
|
||||||
Event.belongsToMany(Notification, { through: EventNotification })
|
|
||||||
Notification.belongsToMany(Event, { through: EventNotification })
|
|
||||||
|
|
||||||
Event.hasMany(Resource)
|
|
||||||
Resource.belongsTo(Event)
|
|
||||||
|
|
||||||
Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' })
|
|
||||||
Event.belongsTo(Event, { as: 'parent' })
|
|
||||||
|
|
||||||
SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false })
|
|
||||||
|
|
||||||
Event.prototype.toAP = function (username, locale, to = []) {
|
|
||||||
const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_'))
|
|
||||||
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
|
|
||||||
const content = `
|
|
||||||
📍 ${this.place && this.place.name}
|
📍 ${this.place && this.place.name}
|
||||||
📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')}
|
📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')}
|
||||||
|
|
||||||
${plainDescription}
|
${plainDescription}
|
||||||
`
|
`
|
||||||
|
|
||||||
const attachment = []
|
const attachment = []
|
||||||
if (this.media && this.media.length) {
|
if (this.media && this.media.length) {
|
||||||
attachment.push({
|
attachment.push({
|
||||||
type: 'Document',
|
type: 'Document',
|
||||||
mediaType: 'image/jpeg',
|
mediaType: 'image/jpeg',
|
||||||
url: `${config.baseurl}/media/${this.media[0].url}`,
|
url: `${config.baseurl}/media/${this.media[0].url}`,
|
||||||
name: this.media[0].name || this.title || '',
|
name: this.media[0].name || this.title || '',
|
||||||
blurHash: null,
|
blurHash: null,
|
||||||
focalPoint: this.media[0].focalPoint || [0, 0]
|
focalPoint: this.media[0].focalPoint || [0, 0]
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `${config.baseurl}/federation/m/${this.id}`,
|
||||||
|
name: this.title,
|
||||||
|
url: `${config.baseurl}/event/${this.slug || this.id}`,
|
||||||
|
type: 'Event',
|
||||||
|
startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(),
|
||||||
|
endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null,
|
||||||
|
location: {
|
||||||
|
name: this.place.name,
|
||||||
|
address: this.place.address,
|
||||||
|
latitude: this.place.latitude,
|
||||||
|
longitude: this.place.longitude
|
||||||
|
},
|
||||||
|
attachment,
|
||||||
|
tag: tags && tags.map(tag => ({
|
||||||
|
type: 'Hashtag',
|
||||||
|
name: '#' + tag,
|
||||||
|
href: `${config.baseurl}/tag/${tag}`
|
||||||
|
})),
|
||||||
|
published: dayjs(this.createdAt).utc().format(),
|
||||||
|
attributedTo: `${config.baseurl}/federation/u/${username}`,
|
||||||
|
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||||
|
cc: [`${config.baseurl}/federation/u/${username}/followers`],
|
||||||
|
content,
|
||||||
|
summary: content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Event
|
||||||
|
}
|
||||||
return {
|
|
||||||
id: `${config.baseurl}/federation/m/${this.id}`,
|
|
||||||
name: this.title,
|
|
||||||
url: `${config.baseurl}/event/${this.slug || this.id}`,
|
|
||||||
type: 'Event',
|
|
||||||
startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(),
|
|
||||||
endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null,
|
|
||||||
location: {
|
|
||||||
name: this.place.name,
|
|
||||||
address: this.place.address,
|
|
||||||
latitude: this.place.latitude,
|
|
||||||
longitude: this.place.longitude
|
|
||||||
},
|
|
||||||
attachment,
|
|
||||||
tag: tags && tags.map(tag => ({
|
|
||||||
type: 'Hashtag',
|
|
||||||
name: '#' + tag,
|
|
||||||
href: `${config.baseurl}/tag/${tag}`
|
|
||||||
})),
|
|
||||||
published: dayjs(this.createdAt).utc().format(),
|
|
||||||
attributedTo: `${config.baseurl}/federation/u/${username}`,
|
|
||||||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
cc: [`${config.baseurl}/federation/u/${username}/followers`],
|
|
||||||
content,
|
|
||||||
summary: content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Event
|
|
|
@ -1,15 +1,9 @@
|
||||||
const sequelize = require('./index').sequelize
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const { Model, DataTypes } = require('sequelize')
|
sequelize.define('event_notification', {
|
||||||
|
|
||||||
class EventNotification extends Model {}
|
|
||||||
|
|
||||||
EventNotification.init({
|
|
||||||
status: {
|
status: {
|
||||||
type: DataTypes.ENUM,
|
type: DataTypes.ENUM,
|
||||||
values: ['new', 'sent', 'error', 'sending'],
|
values: ['new', 'sent', 'error', 'sending'],
|
||||||
defaultValue: 'new',
|
defaultValue: 'new',
|
||||||
index: true
|
index: true
|
||||||
}
|
}
|
||||||
}, { sequelize, modelName: 'event_notification' })
|
})
|
||||||
|
|
||||||
module.exports = EventNotification
|
|
|
@ -1,24 +1,20 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const Collection = require('./collection')
|
sequelize.define('filter',
|
||||||
const sequelize = require('./index').sequelize
|
{
|
||||||
|
id: {
|
||||||
class Filter extends Model {}
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
Filter.init({
|
autoIncrement: true,
|
||||||
id: {
|
},
|
||||||
type: DataTypes.INTEGER,
|
tags: {
|
||||||
primaryKey: true,
|
type: DataTypes.JSON,
|
||||||
autoIncrement: true,
|
},
|
||||||
},
|
places: {
|
||||||
tags: {
|
type: DataTypes.JSON,
|
||||||
type: DataTypes.JSON,
|
}
|
||||||
},
|
}, {
|
||||||
places: {
|
indexes: [
|
||||||
type: DataTypes.JSON,
|
{ fields: ['collectionId', 'tags', 'places'], unique: true }
|
||||||
}
|
],
|
||||||
}, { sequelize, modelName: 'filter', timestamps: false })
|
timestamps: false
|
||||||
|
})
|
||||||
Filter.belongsTo(Collection)
|
|
||||||
Collection.hasMany(Filter)
|
|
||||||
|
|
||||||
module.exports = Filter
|
|
|
@ -4,10 +4,77 @@ const Umzug = require('umzug')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
const log = require('../../log')
|
const log = require('../../log')
|
||||||
const settingsController = require('../controller/settings')
|
const SequelizeSlugify = require('sequelize-slugify')
|
||||||
|
const DB = require('./models')
|
||||||
|
|
||||||
|
const models = {
|
||||||
|
Announcement: require('./announcement'),
|
||||||
|
APUser: require('./ap_user'),
|
||||||
|
Collection: require('./collection'),
|
||||||
|
Event: require('./event'),
|
||||||
|
EventNotification: require('./eventnotification'),
|
||||||
|
Filter: require('./filter'),
|
||||||
|
Instance: require('./instance'),
|
||||||
|
Notification: require('./notification'),
|
||||||
|
OAuthClient: require('./oauth_client'),
|
||||||
|
OAuthCode: require('./oauth_code'),
|
||||||
|
OAuthToken: require('./oauth_token'),
|
||||||
|
Place: require('./place'),
|
||||||
|
Resource: require('./resource'),
|
||||||
|
Setting: require('./setting'),
|
||||||
|
Tag: require('./tag'),
|
||||||
|
User: require('./user'),
|
||||||
|
}
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
sequelize: null,
|
sequelize: null,
|
||||||
|
loadModels () {
|
||||||
|
for (const modelName in models) {
|
||||||
|
const m = models[modelName](db.sequelize, Sequelize.DataTypes)
|
||||||
|
DB[modelName] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
associates () {
|
||||||
|
const { Filter, Collection, APUser, Instance, User, Event, EventNotification, Tag,
|
||||||
|
OAuthCode, OAuthClient, OAuthToken, Resource, Place, Notification } = DB
|
||||||
|
|
||||||
|
Filter.belongsTo(Collection)
|
||||||
|
Collection.hasMany(Filter)
|
||||||
|
|
||||||
|
Instance.hasMany(APUser)
|
||||||
|
APUser.belongsTo(Instance)
|
||||||
|
|
||||||
|
OAuthCode.belongsTo(User)
|
||||||
|
OAuthCode.belongsTo(OAuthClient, { as: 'client' })
|
||||||
|
|
||||||
|
OAuthToken.belongsTo(User)
|
||||||
|
OAuthToken.belongsTo(OAuthClient, { as: 'client' })
|
||||||
|
|
||||||
|
APUser.hasMany(Resource)
|
||||||
|
Resource.belongsTo(APUser)
|
||||||
|
|
||||||
|
Event.belongsTo(Place)
|
||||||
|
Place.hasMany(Event)
|
||||||
|
|
||||||
|
Event.belongsTo(User)
|
||||||
|
User.hasMany(Event)
|
||||||
|
|
||||||
|
Event.belongsToMany(Tag, { through: 'event_tags' })
|
||||||
|
Tag.belongsToMany(Event, { through: 'event_tags' })
|
||||||
|
|
||||||
|
Event.belongsToMany(Notification, { through: EventNotification })
|
||||||
|
Notification.belongsToMany(Event, { through: EventNotification })
|
||||||
|
|
||||||
|
Event.hasMany(Resource)
|
||||||
|
Resource.belongsTo(Event)
|
||||||
|
|
||||||
|
Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' })
|
||||||
|
Event.belongsTo(Event, { as: 'parent' })
|
||||||
|
|
||||||
|
SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false })
|
||||||
|
|
||||||
|
},
|
||||||
close() {
|
close() {
|
||||||
if (db.sequelize) {
|
if (db.sequelize) {
|
||||||
return db.sequelize.close()
|
return db.sequelize.close()
|
||||||
|
@ -28,7 +95,6 @@ const db = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.sequelize = new Sequelize(dbConf)
|
db.sequelize = new Sequelize(dbConf)
|
||||||
return db.sequelize.authenticate()
|
|
||||||
},
|
},
|
||||||
async isEmpty() {
|
async isEmpty() {
|
||||||
try {
|
try {
|
||||||
|
@ -57,13 +123,12 @@ const db = {
|
||||||
})
|
})
|
||||||
return umzug.up()
|
return umzug.up()
|
||||||
},
|
},
|
||||||
async initialize() {
|
initialize() {
|
||||||
if (config.status === 'CONFIGURED') {
|
if (config.status === 'CONFIGURED') {
|
||||||
try {
|
try {
|
||||||
await db.connect()
|
db.connect()
|
||||||
log.debug('Running migrations')
|
db.loadModels()
|
||||||
await db.runMigrations()
|
db.associates()
|
||||||
return settingsController.load()
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`)
|
log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('instance', {
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
const APUser = require('./ap_user')
|
|
||||||
|
|
||||||
class Instance extends Model {}
|
|
||||||
|
|
||||||
Instance.init({
|
|
||||||
domain: {
|
domain: {
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
@ -14,9 +8,4 @@ Instance.init({
|
||||||
name: DataTypes.STRING,
|
name: DataTypes.STRING,
|
||||||
blocked: DataTypes.BOOLEAN,
|
blocked: DataTypes.BOOLEAN,
|
||||||
data: DataTypes.JSON
|
data: DataTypes.JSON
|
||||||
}, { sequelize, modelName: 'instance' })
|
})
|
||||||
|
|
||||||
Instance.hasMany(APUser)
|
|
||||||
APUser.belongsTo(Instance)
|
|
||||||
|
|
||||||
module.exports = Instance
|
|
2
server/api/models/models.js
Normal file
2
server/api/models/models.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// export default models
|
||||||
|
module.exports = {}
|
|
@ -1,10 +1,5 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('notification', {
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
|
|
||||||
class Notification extends Model {}
|
|
||||||
|
|
||||||
Notification.init({
|
|
||||||
filters: DataTypes.JSON,
|
filters: DataTypes.JSON,
|
||||||
email: DataTypes.STRING,
|
email: DataTypes.STRING,
|
||||||
remove_code: DataTypes.STRING,
|
remove_code: DataTypes.STRING,
|
||||||
|
@ -24,6 +19,4 @@ Notification.init({
|
||||||
unique: true,
|
unique: true,
|
||||||
fields: ['action', 'type']
|
fields: ['action', 'type']
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = Notification
|
|
|
@ -1,10 +1,5 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('oauth_client', {
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
|
|
||||||
class OAuthClient extends Model {}
|
|
||||||
|
|
||||||
OAuthClient.init({
|
|
||||||
id: {
|
id: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
|
@ -15,6 +10,4 @@ OAuthClient.init({
|
||||||
scopes: DataTypes.STRING,
|
scopes: DataTypes.STRING,
|
||||||
redirectUris: DataTypes.STRING,
|
redirectUris: DataTypes.STRING,
|
||||||
website: DataTypes.STRING
|
website: DataTypes.STRING
|
||||||
}, { sequelize, modelName: 'oauth_client' })
|
})
|
||||||
|
|
||||||
module.exports = OAuthClient
|
|
|
@ -1,13 +1,5 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('oauth_code', {
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
|
|
||||||
const User = require('./user')
|
|
||||||
const OAuthClient = require('./oauth_client')
|
|
||||||
|
|
||||||
class OAuthCode extends Model {}
|
|
||||||
|
|
||||||
OAuthCode.init({
|
|
||||||
authorizationCode: {
|
authorizationCode: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
|
@ -15,9 +7,4 @@ OAuthCode.init({
|
||||||
expiresAt: DataTypes.DATE,
|
expiresAt: DataTypes.DATE,
|
||||||
scope: DataTypes.STRING,
|
scope: DataTypes.STRING,
|
||||||
redirect_uri: DataTypes.STRING
|
redirect_uri: DataTypes.STRING
|
||||||
}, { sequelize, modelName: 'oauth_code' })
|
})
|
||||||
|
|
||||||
OAuthCode.belongsTo(User)
|
|
||||||
OAuthCode.belongsTo(OAuthClient, { as: 'client' })
|
|
||||||
|
|
||||||
module.exports = OAuthCode
|
|
|
@ -1,13 +1,5 @@
|
||||||
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('oauth_token', {
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
|
|
||||||
const User = require('./user')
|
|
||||||
const OAuthClient = require('./oauth_client')
|
|
||||||
|
|
||||||
class OAuthToken extends Model {}
|
|
||||||
|
|
||||||
OAuthToken.init({
|
|
||||||
accessToken: {
|
accessToken: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
@ -27,9 +19,4 @@ OAuthToken.init({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scope: DataTypes.STRING
|
scope: DataTypes.STRING
|
||||||
}, { sequelize, modelName: 'oauth_token' })
|
})
|
||||||
|
|
||||||
OAuthToken.belongsTo(User)
|
|
||||||
OAuthToken.belongsTo(OAuthClient, { as: 'client' })
|
|
||||||
|
|
||||||
module.exports = OAuthToken
|
|
|
@ -1,9 +1,5 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('place', {
|
||||||
|
|
||||||
class Place extends Model {}
|
|
||||||
|
|
||||||
Place.init({
|
|
||||||
name: {
|
name: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
unique: true,
|
unique: true,
|
||||||
|
@ -13,6 +9,4 @@ Place.init({
|
||||||
address: DataTypes.STRING,
|
address: DataTypes.STRING,
|
||||||
latitude: DataTypes.FLOAT,
|
latitude: DataTypes.FLOAT,
|
||||||
longitude: DataTypes.FLOAT,
|
longitude: DataTypes.FLOAT,
|
||||||
}, { sequelize, modelName: 'place' })
|
})
|
||||||
|
|
||||||
module.exports = Place
|
|
|
@ -1,11 +1,5 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('resource', {
|
||||||
|
|
||||||
const APUser = require('./ap_user')
|
|
||||||
|
|
||||||
class Resource extends Model {}
|
|
||||||
|
|
||||||
Resource.init({
|
|
||||||
activitypub_id: {
|
activitypub_id: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
index: true,
|
index: true,
|
||||||
|
@ -13,9 +7,4 @@ Resource.init({
|
||||||
},
|
},
|
||||||
hidden: DataTypes.BOOLEAN,
|
hidden: DataTypes.BOOLEAN,
|
||||||
data: DataTypes.JSON
|
data: DataTypes.JSON
|
||||||
}, { sequelize, modelName: 'resource' })
|
})
|
||||||
|
|
||||||
APUser.hasMany(Resource)
|
|
||||||
Resource.belongsTo(APUser)
|
|
||||||
|
|
||||||
module.exports = Resource
|
|
|
@ -1,9 +1,5 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('setting', {
|
||||||
|
|
||||||
class Setting extends Model {}
|
|
||||||
|
|
||||||
Setting.init({
|
|
||||||
key: {
|
key: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
|
@ -12,6 +8,4 @@ Setting.init({
|
||||||
},
|
},
|
||||||
value: DataTypes.JSON,
|
value: DataTypes.JSON,
|
||||||
is_secret: DataTypes.BOOLEAN
|
is_secret: DataTypes.BOOLEAN
|
||||||
}, { sequelize, modelName: 'setting' })
|
})
|
||||||
|
|
||||||
module.exports = Setting
|
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
const { Model, DataTypes } = require('sequelize')
|
module.exports = (sequelize, DataTypes) =>
|
||||||
const sequelize = require('./index').sequelize
|
sequelize.define('tag', {
|
||||||
|
|
||||||
class Tag extends Model {}
|
|
||||||
|
|
||||||
Tag.init({
|
|
||||||
tag: {
|
tag: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
index: true,
|
index: true,
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
}
|
}
|
||||||
}, { sequelize, modelName: 'tag' })
|
})
|
||||||
|
|
||||||
module.exports = Tag
|
|
|
@ -1,53 +1,49 @@
|
||||||
|
|
||||||
const bcrypt = require('bcryptjs')
|
const bcrypt = require('bcryptjs')
|
||||||
const { Model, DataTypes } = require('sequelize')
|
|
||||||
const sequelize = require('./index').sequelize
|
|
||||||
|
|
||||||
class User extends Model {}
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const User = sequelize.define('user', {
|
||||||
User.init({
|
settings: {
|
||||||
settings: {
|
type: DataTypes.JSON,
|
||||||
type: DataTypes.JSON,
|
defaultValue: []
|
||||||
defaultValue: []
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
unique: { msg: 'error.email_taken' },
|
|
||||||
validate: {
|
|
||||||
notEmpty: true
|
|
||||||
},
|
},
|
||||||
index: true,
|
email: {
|
||||||
allowNull: false
|
type: DataTypes.STRING,
|
||||||
},
|
unique: { msg: 'error.email_taken' },
|
||||||
description: DataTypes.TEXT,
|
validate: {
|
||||||
password: DataTypes.STRING,
|
notEmpty: true
|
||||||
recover_code: DataTypes.STRING,
|
},
|
||||||
is_admin: DataTypes.BOOLEAN,
|
index: true,
|
||||||
is_active: DataTypes.BOOLEAN
|
allowNull: false
|
||||||
}, {
|
|
||||||
sequelize,
|
|
||||||
modelName: 'user',
|
|
||||||
scopes: {
|
|
||||||
withoutPassword: {
|
|
||||||
attributes: { exclude: ['password', 'recover_code'] }
|
|
||||||
},
|
},
|
||||||
withRecover: {
|
description: DataTypes.TEXT,
|
||||||
attributes: { exclude: ['password'] }
|
password: DataTypes.STRING,
|
||||||
|
recover_code: DataTypes.STRING,
|
||||||
|
is_admin: DataTypes.BOOLEAN,
|
||||||
|
is_active: DataTypes.BOOLEAN
|
||||||
|
}, {
|
||||||
|
scopes: {
|
||||||
|
withoutPassword: {
|
||||||
|
attributes: { exclude: ['password', 'recover_code'] }
|
||||||
|
},
|
||||||
|
withRecover: {
|
||||||
|
attributes: { exclude: ['password'] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
User.prototype.comparePassword = async function (pwd) {
|
||||||
|
if (!this.password) { return false }
|
||||||
|
return bcrypt.compare(pwd, this.password)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
User.beforeSave(async (user, _options) => {
|
||||||
|
if (user.changed('password')) {
|
||||||
|
const salt = await bcrypt.genSalt(10)
|
||||||
|
const hash = await bcrypt.hash(user.password, salt)
|
||||||
|
user.password = hash
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
User.prototype.comparePassword = async function (pwd) {
|
return User
|
||||||
if (!this.password) { return false }
|
|
||||||
return bcrypt.compare(pwd, this.password)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User.beforeSave(async (user, _options) => {
|
|
||||||
if (user.changed('password')) {
|
|
||||||
const salt = await bcrypt.genSalt(10)
|
|
||||||
const hash = await bcrypt.hash(user.password, salt)
|
|
||||||
user.password = hash
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = User
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ function _initializeDB () {
|
||||||
async function modify (args) {
|
async function modify (args) {
|
||||||
await _initializeDB()
|
await _initializeDB()
|
||||||
const helpers = require('../helpers')
|
const helpers = require('../helpers')
|
||||||
const User = require('../api/models/user')
|
const { User } = require('../api/models/models')
|
||||||
const user = await User.findOne({ where: { email: args.account } })
|
const user = await User.findOne({ where: { email: args.account } })
|
||||||
console.log()
|
console.log()
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
// const request = require('request')
|
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const config = require('../config')
|
const config = require('../config')
|
||||||
const httpSignature = require('http-signature')
|
const httpSignature = require('http-signature')
|
||||||
const APUser = require('../api/models/ap_user')
|
|
||||||
const Instance = require('../api/models/instance')
|
const { APUser, Instance } = require('../api/models/models')
|
||||||
|
|
||||||
const url = require('url')
|
const url = require('url')
|
||||||
const settingsController = require('../api/controller/settings')
|
const settingsController = require('../api/controller/settings')
|
||||||
const log = require('../log')
|
const log = require('../log')
|
||||||
|
|
|
@ -270,9 +270,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async APRedirect(req, res, next) {
|
async APRedirect(req, res, next) {
|
||||||
|
const eventController = require('../server/api/controller/event')
|
||||||
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
|
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
|
||||||
if (acceptJson) {
|
if (acceptJson) {
|
||||||
const eventController = require('../server/api/controller/event')
|
|
||||||
const event = await eventController._get(req.params.slug)
|
const event = await eventController._get(req.params.slug)
|
||||||
if (event) {
|
if (event) {
|
||||||
return res.redirect(`/federation/m/${event.id}`)
|
return res.redirect(`/federation/m/${event.id}`)
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
const config = require('../server/config')
|
const config = require('../server/config')
|
||||||
|
const db = require('./api/models/index')
|
||||||
|
const log = require('../server/log')
|
||||||
|
|
||||||
|
db.initialize()
|
||||||
|
|
||||||
|
const settingsController = require('./api/controller/settings')
|
||||||
|
|
||||||
|
|
||||||
const initialize = {
|
const initialize = {
|
||||||
// close connections/port/unix socket
|
// close connections/port/unix socket
|
||||||
|
@ -19,14 +26,14 @@ const initialize = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
const log = require('../server/log')
|
|
||||||
const settingsController = require('./api/controller/settings')
|
|
||||||
const db = require('./api/models/index')
|
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
const timezone = require('dayjs/plugin/timezone')
|
const timezone = require('dayjs/plugin/timezone')
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
if (config.status == 'CONFIGURED') {
|
if (config.status == 'CONFIGURED') {
|
||||||
await db.initialize()
|
await db.sequelize.authenticate()
|
||||||
|
log.debug('Running migrations')
|
||||||
|
await db.runMigrations()
|
||||||
|
await settingsController.load()
|
||||||
config.status = 'READY'
|
config.status = 'READY'
|
||||||
} else {
|
} else {
|
||||||
if (process.env.GANCIO_DB_DIALECT) {
|
if (process.env.GANCIO_DB_DIALECT) {
|
||||||
|
|
|
@ -4,14 +4,10 @@ const mail = require('./api/mail')
|
||||||
const log = require('./log')
|
const log = require('./log')
|
||||||
const fediverseHelpers = require('./federation/helpers')
|
const fediverseHelpers = require('./federation/helpers')
|
||||||
|
|
||||||
const Event = require('./api/models/event')
|
|
||||||
const Notification = require('./api/models/notification')
|
|
||||||
const EventNotification = require('./api/models/eventnotification')
|
|
||||||
const User = require('./api/models/user')
|
|
||||||
const Place = require('./api/models/place')
|
|
||||||
const Tag = require('./api/models/tag')
|
|
||||||
|
|
||||||
const eventController = require('./api/controller/event')
|
const { Event, Notification, EventNotification, User, Place, Tag } = require('./api/models/models')
|
||||||
|
|
||||||
|
|
||||||
const settingsController = require('./api/controller/settings')
|
const settingsController = require('./api/controller/settings')
|
||||||
|
|
||||||
const notifier = {
|
const notifier = {
|
||||||
|
@ -37,7 +33,36 @@ const notifier = {
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getNotifications(event, action) {
|
||||||
|
log.debug(`getNotifications ${event.title} ${action}`)
|
||||||
|
function match(event, filters) {
|
||||||
|
// matches if no filter specified
|
||||||
|
if (!filters) { return true }
|
||||||
|
|
||||||
|
// check for visibility
|
||||||
|
if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false }
|
||||||
|
|
||||||
|
if (!filters.tags && !filters.places) { return true }
|
||||||
|
if (!filters.tags.length && !filters.places.length) { return true }
|
||||||
|
if (filters.tags.length) {
|
||||||
|
const m = intersection(event.tags.map(t => t.tag), filters.tags)
|
||||||
|
if (m.length > 0) { return true }
|
||||||
|
}
|
||||||
|
if (filters.places.length) {
|
||||||
|
if (filters.places.find(p => p === event.place.name)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notifications = await Notification.findAll({ where: { action }, include: [Event] })
|
||||||
|
|
||||||
|
// get notification that matches with selected event
|
||||||
|
return notifications.filter(notification => match(event, notification.filters))
|
||||||
|
},
|
||||||
|
|
||||||
async notifyEvent (action, eventId) {
|
async notifyEvent (action, eventId) {
|
||||||
|
|
||||||
const event = await Event.findByPk(eventId, {
|
const event = await Event.findByPk(eventId, {
|
||||||
include: [Tag, Place, Notification, User]
|
include: [Tag, Place, Notification, User]
|
||||||
})
|
})
|
||||||
|
@ -46,7 +71,7 @@ const notifier = {
|
||||||
log.debug(action, event.title)
|
log.debug(action, event.title)
|
||||||
|
|
||||||
// insert notifications
|
// insert notifications
|
||||||
const notifications = await eventController.getNotifications(event, action)
|
const notifications = await notifier.getNotifications(event, action)
|
||||||
await event.addNotifications(notifications)
|
await event.addNotifications(notifications)
|
||||||
const event_notifications = await event.getNotifications({ through: { where: { status: 'new' } } })
|
const event_notifications = await event.getNotifications({ through: { where: { status: 'new' } } })
|
||||||
|
|
||||||
|
|
|
@ -4,23 +4,22 @@ const initialize = require('./initialize.server')
|
||||||
|
|
||||||
const config = require('./config')
|
const config = require('./config')
|
||||||
const helpers = require('./helpers')
|
const helpers = require('./helpers')
|
||||||
|
const api = require('./api')
|
||||||
app.use([
|
|
||||||
helpers.initSettings,
|
|
||||||
helpers.logRequest,
|
|
||||||
helpers.serveStatic()
|
|
||||||
])
|
|
||||||
|
|
||||||
async function main () {
|
async function main () {
|
||||||
|
|
||||||
await initialize.start()
|
await initialize.start()
|
||||||
|
|
||||||
|
app.use([
|
||||||
|
helpers.initSettings,
|
||||||
|
helpers.logRequest,
|
||||||
|
helpers.serveStatic()
|
||||||
|
])
|
||||||
// const metricsController = require('./metrics')
|
// const metricsController = require('./metrics')
|
||||||
// const promBundle = require('express-prom-bundle')
|
// const promBundle = require('express-prom-bundle')
|
||||||
// const metricsMiddleware = promBundle({ includeMethod: true })
|
// const metricsMiddleware = promBundle({ includeMethod: true })
|
||||||
|
|
||||||
const log = require('./log')
|
const log = require('./log')
|
||||||
const api = require('./api')
|
|
||||||
|
|
||||||
app.enable('trust proxy')
|
app.enable('trust proxy')
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ async function main () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// api!
|
// api!
|
||||||
app.use('/api', api)
|
app.use('/api', api())
|
||||||
|
|
||||||
// // Handle 500
|
// // Handle 500
|
||||||
app.use((error, _req, res, _next) => {
|
app.use((error, _req, res, _next) => {
|
||||||
|
@ -87,8 +86,6 @@ if (process.env.NODE_ENV !== 'test') {
|
||||||
main()
|
main()
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.listen(13120)
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
main,
|
main,
|
||||||
handler: app,
|
handler: app,
|
||||||
|
|
|
@ -23,6 +23,11 @@ export const state = () => ({
|
||||||
trusted_instances_label: '',
|
trusted_instances_label: '',
|
||||||
footerLinks: []
|
footerLinks: []
|
||||||
},
|
},
|
||||||
|
filter: {
|
||||||
|
query: '',
|
||||||
|
show_recurrent: null,
|
||||||
|
show_multidate: null
|
||||||
|
},
|
||||||
announcements: [],
|
announcements: [],
|
||||||
events: []
|
events: []
|
||||||
})
|
})
|
||||||
|
@ -39,6 +44,9 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
setEvents (state, events) {
|
setEvents (state, events) {
|
||||||
state.events = events
|
state.events = events
|
||||||
|
},
|
||||||
|
setFilter (state, { type, value }) {
|
||||||
|
state.filter[type] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +55,9 @@ export const actions = {
|
||||||
// we use it to get configuration from db, set locale, etc...
|
// we use it to get configuration from db, set locale, etc...
|
||||||
nuxtServerInit ({ commit }, { _req, res }) {
|
nuxtServerInit ({ commit }, { _req, res }) {
|
||||||
commit('setSettings', res.locals.settings)
|
commit('setSettings', res.locals.settings)
|
||||||
|
commit('setFilter', { type: 'show_recurrent',
|
||||||
|
value: res.locals.settings.allow_recurrent_event && res.locals.settings.recurrent_event_visible })
|
||||||
|
|
||||||
if (res.locals.status === 'READY') {
|
if (res.locals.status === 'READY') {
|
||||||
commit('setAnnouncements', res.locals.announcements)
|
commit('setAnnouncements', res.locals.announcements)
|
||||||
}
|
}
|
||||||
|
@ -62,11 +73,15 @@ export const actions = {
|
||||||
await this.$axios.$post('/settings', setting)
|
await this.$axios.$post('/settings', setting)
|
||||||
commit('setSetting', setting)
|
commit('setSetting', setting)
|
||||||
},
|
},
|
||||||
|
setFilter ({ commit }, [type, value]) {
|
||||||
|
commit('setFilter', { type, value })
|
||||||
|
},
|
||||||
async getEvents ({ commit, state }, params = {}) {
|
async getEvents ({ commit, state }, params = {}) {
|
||||||
const events = await this.$api.getEvents({
|
const events = await this.$api.getEvents({
|
||||||
start: params.start || dayjs().startOf('month').unix(),
|
start: params.start || dayjs().startOf('month').unix(),
|
||||||
end: params.end || null,
|
end: params.end || null,
|
||||||
show_recurrent: params.show_recurrent || state.settings.recurrent_event_visible
|
show_recurrent: state.filter.show_recurrent,
|
||||||
|
show_multidate: state.filter.show_multidate
|
||||||
})
|
})
|
||||||
commit('setEvents', events)
|
commit('setEvents', events)
|
||||||
return events
|
return events
|
||||||
|
|
|
@ -21,16 +21,21 @@ beforeAll(async () => {
|
||||||
default:
|
default:
|
||||||
process.env.config_path = path.resolve(__dirname, './seeds/config.sqlite.json')
|
process.env.config_path = path.resolve(__dirname, './seeds/config.sqlite.json')
|
||||||
}
|
}
|
||||||
app = await require('../server/routes.js').main()
|
try {
|
||||||
const { sequelize } = require('../server/api/models/index')
|
app = await require('../server/routes.js').main()
|
||||||
await sequelize.query('DELETE FROM settings')
|
const { sequelize } = require('../server/api/models/index')
|
||||||
await sequelize.query('DELETE FROM events')
|
await sequelize.query('DELETE FROM settings')
|
||||||
await sequelize.query('DELETE FROM users')
|
await sequelize.query('DELETE FROM events')
|
||||||
await sequelize.query('DELETE FROM ap_users')
|
await sequelize.query('DELETE FROM user_followers')
|
||||||
await sequelize.query('DELETE FROM tags')
|
await sequelize.query('DELETE FROM users')
|
||||||
await sequelize.query('DELETE FROM places')
|
await sequelize.query('DELETE FROM ap_users')
|
||||||
await sequelize.query('DELETE FROM collections')
|
await sequelize.query('DELETE FROM tags')
|
||||||
await sequelize.query('DELETE FROM filters')
|
await sequelize.query('DELETE FROM places')
|
||||||
|
await sequelize.query('DELETE FROM filters')
|
||||||
|
await sequelize.query('DELETE FROM collections')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -10634,10 +10634,10 @@ ret@~0.1.10:
|
||||||
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
|
||||||
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
|
||||||
|
|
||||||
retry-as-promised@^6.1.0:
|
retry-as-promised@^7.0.3:
|
||||||
version "6.1.0"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-6.1.0.tgz#11eca9a0f97804d552ec8e74bc4eb839bd226dc4"
|
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-7.0.3.tgz#ca3c13b15525a7bfbf0f56d2996f0e75649d068b"
|
||||||
integrity sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA==
|
integrity sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q==
|
||||||
|
|
||||||
retry@^0.12.0:
|
retry@^0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
|
@ -10926,10 +10926,10 @@ sequelize-slugify@^1.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
sluglife "^0.9.8"
|
sluglife "^0.9.8"
|
||||||
|
|
||||||
sequelize@^6.27.0:
|
sequelize@^6.28.0:
|
||||||
version "6.27.0"
|
version "6.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.27.0.tgz#b267e76997df57842cc1e2c1c1d7e02405bcdb9c"
|
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.28.0.tgz#d6bc4e36647e8501635467c0777c45a33f5d5ba8"
|
||||||
integrity sha512-Rm7BM8HQekeABup0KdtSHriu8ppJuHj2TJWCxvZtzU6j8V1LVnBk2rs38P8r4gMWgdLKs5NYoLC4il95KLsv0w==
|
integrity sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/debug" "^4.1.7"
|
"@types/debug" "^4.1.7"
|
||||||
"@types/validator" "^13.7.1"
|
"@types/validator" "^13.7.1"
|
||||||
|
@ -10940,7 +10940,7 @@ sequelize@^6.27.0:
|
||||||
moment "^2.29.1"
|
moment "^2.29.1"
|
||||||
moment-timezone "^0.5.34"
|
moment-timezone "^0.5.34"
|
||||||
pg-connection-string "^2.5.0"
|
pg-connection-string "^2.5.0"
|
||||||
retry-as-promised "^6.1.0"
|
retry-as-promised "^7.0.3"
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
sequelize-pool "^7.1.0"
|
sequelize-pool "^7.1.0"
|
||||||
toposort-class "^1.0.1"
|
toposort-class "^1.0.1"
|
||||||
|
|
1
yunohost
Submodule
1
yunohost
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 0e3de77d5a7d5e40f3c5ae4c673b73b885a6e9c3
|
Loading…
Reference in a new issue