This commit is contained in:
lesion
2019-05-30 12:12:51 +02:00
parent 6099d538c0
commit 745b9247c9
46 changed files with 543 additions and 181 deletions

View File

@@ -31,6 +31,18 @@
"title": "export page", "title": "export page",
"type": "bug" "type": "bug"
}, },
{
"assignedTo": {
"name": "lesion"
},
"category": "feature",
"creation_time": "2019-04-23T19:55:59.993Z",
"id": "10",
"prio": 1,
"references": [],
"title": "gestione errori form aggiungi evento",
"type": "bug"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@@ -73,6 +85,15 @@
} }
], ],
"in-progress": [ "in-progress": [
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-04-30T22:00:29.237Z",
"id": "17",
"references": [],
"title": "porcoddio la config arriva anche al client ovviamente, devo separare!"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@@ -85,18 +106,6 @@
], ],
"testing": [], "testing": [],
"todo": [ "todo": [
{
"assignedTo": {
"name": "lesion"
},
"category": "feature",
"creation_time": "2019-04-23T19:55:59.993Z",
"id": "10",
"prio": 1,
"references": [],
"title": "gestione errori form aggiungi evento",
"type": "bug"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@@ -168,6 +177,24 @@
"references": [], "references": [],
"title": "colori te prego!" "title": "colori te prego!"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-05-27T20:42:22.581Z",
"id": "24",
"references": [],
"title": "copy to clipboard"
},
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-05-29T13:08:20.887Z",
"id": "25",
"references": [],
"title": "creazione script di backup"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@@ -214,15 +241,6 @@
"references": [], "references": [],
"title": "popup sul calendario" "title": "popup sul calendario"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-04-30T22:00:29.237Z",
"id": "17",
"references": [],
"title": "porcoddio la config arriva anche al client ovviamente, devo separare!"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@@ -259,10 +277,10 @@
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
}, },
"creation_time": "2019-05-27T20:42:22.581Z", "creation_time": "2019-05-29T13:10:04.463Z",
"id": "24", "id": "26",
"references": [], "references": [],
"title": "copy to clipboard" "title": "v-calendar colori e eventi multidays..."
} }
] ]
} }

View File

@@ -7,7 +7,6 @@
:attributes='attributes' :attributes='attributes'
:from-page.sync='page' :from-page.sync='page'
is-expanded is-expanded
show-clear-margin
is-inline is-inline
@dayclick='click') @dayclick='click')
@@ -15,7 +14,7 @@
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex' import { mapState, mapActions, mapGetters } from 'vuex'
import moment from 'dayjs' import moment from 'dayjs'
import { intersection } from 'lodash' import { intersection, sample, get } from 'lodash'
export default { export default {
name: 'Calendar', name: 'Calendar',
@@ -45,18 +44,19 @@ export default {
order: event.start_datetime, order: event.start_datetime,
} }
const day = moment(event.start_datetime).date() const day = moment(event.start_datetime).date()
let color = event.tags && event.tags.length && event.tags[0].color ? event.tags[0].color : 'rgba(170,170,250,0.7)' let color = event.past ? 'rgba(200,200,200,0.5)' : get(event, 'tags[0].color') || 'rgba(170,170,250,0.7)'
if (event.past) color = 'rgba(200,200,200,0.5)'
console.error(color)
if (event.multidate) { if (event.multidate) {
e.dates = { e.dates = {
start: event.start_datetime, end: event.end_datetime start: event.start_datetime, end: event.end_datetime
} }
e.highlight = { backgroundColor: color, e.highlight = {
// borderColor: 'transparent', color: 'red' // : sample(['purple', 'red', 'green', 'blue']),
borderWidth: '4px' } }
} else { } else {
e.dates = event.start_datetime e.dates = event.start_datetime
e.dot = { backgroundColor: color, borderColor: color, borderWidth: '3px' } e.dot = { color: 'rgba(102,10,20)' }
} }
return e return e
} }
@@ -84,4 +84,11 @@ export default {
align-self: center; align-self: center;
} }
.vc-highlight {
/* color: red; */
height: 22px !important;
opacity: 0.4;
border-radius: 15px;
}
</style> </style>

View File

@@ -1,9 +1,14 @@
<template lang="pug"> <template lang="pug">
section section
a(href='#totop')
el-button.top.d-block.d-sm-none(icon='el-icon-top' circle type='primary' plain)
a.totop(name='totop')
.row.m-0 .row.m-0
no-ssr no-ssr
Calendar.col-sm-12.col-lg-8.col-xl-6 Calendar.col-sm-12.col-lg-8.col-xl-6
.p-0.col-sm-6.col-lg-4.col-xl-3(v-for='event in filteredEvents') .p-0.col-sm-6.col-lg-4.col-xl-3(v-for='event in filteredEvents')
a(:id='event.newDay' v-if='event.newDay') a(:id='event.newDay' v-if='event.newDay')
.d-block.d-sm-none .d-block.d-sm-none
@@ -20,14 +25,13 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Event from '@/components/Event' import Event from '@/components/Event'
import Calendar from '@/components/Calendar' import Calendar from '@/components/Calendar'
import Search from '@/components/Search'
export default { export default {
name: 'Home', name: 'Home',
data () { data () {
return { } return { }
}, },
components: { Calendar, Event, Search }, components: { Calendar, Event },
computed: mapGetters(['filteredEvents']), computed: mapGetters(['filteredEvents']),
} }
</script> </script>
@@ -36,5 +40,19 @@ section {
width: 100%; width: 100%;
max-width: 1500px; max-width: 1500px;
margin: 0 auto; margin: 0 auto;
.top {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 1;
opacity: 0.7;
font-size: 16px;
}
.totop {
position: absolute;
top: 0px;
}
} }
</style> </style>

View File

@@ -23,7 +23,6 @@
<script> <script>
import {mapState, mapActions} from 'vuex' import {mapState, mapActions} from 'vuex'
export default { export default {
data () { data () {
return { return {
@@ -35,10 +34,11 @@ export default {
methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents']), methods: mapActions(['setSearchPlaces', 'setSearchTags', 'showPastEvents']),
computed: { computed: {
...mapState(['tags', 'places', 'filters', 'show_past_events']), ...mapState(['tags', 'places', 'filters', 'show_past_events']),
// TOFIX: optimize
keywords () { keywords () {
const tags = this.tags.map( t => ({ value: 't' + t.tag, label: t.tag, count: +t.eventsCount })) const tags = this.tags.map( t => ({ value: 't' + t.tag, label: t.tag, weigth: t.weigth }))
const places = this.places.map( p => ({ value: 'p' + p.id, label: p.name, count: +p.eventsCount })) const places = this.places.map( p => ({ value: 'p' + p.id, label: p.name, weigth: p.weigth }))
return tags.concat(places) return tags.concat(places).sort((a, b) => b.weigth-a.weigth)
}, },
showPast : { showPast : {
set (value) { set (value) {
@@ -59,24 +59,6 @@ export default {
return this.filters.tags.map(t => 't' + t).concat(this.filters.places.map(p => 'p' + p)) return this.filters.tags.map(t => 't' + t).concat(this.filters.places.map(p => 'p' + p))
} }
}, },
filters_tags: {
set (value) {
this.setSearchTags(value)
},
get () {
return this.filters.tags
}
},
filters_places: {
set (value) {
this.setSearchPlaces(value)
},
get () {
return this.filters.places
}
},
} }
} }
</script> </script>
<style lang="less">
</style>

View File

@@ -1,3 +1,3 @@
<template lang="pug"> <template>
nuxt <nuxt/>
</template> </template>

3
locales/en.js Normal file
View File

@@ -0,0 +1,3 @@
{
"registration_email": "registration_email"
}

3
locales/es.js Normal file
View File

@@ -0,0 +1,3 @@
{
"registration_email": "registration_email"
}

125
locales/it.js Normal file
View File

@@ -0,0 +1,125 @@
const it = {
common: {
add_event: 'Nuovo evento',
next: 'Continua',
export: 'Esporta',
send: 'Invia',
where: 'Dove',
address: 'Indirizzo',
when: 'Quando',
what: 'Cosa',
media: 'Media',
login: 'Entra',
email: 'Email',
password: 'Password',
register: 'Registrati',
description: 'Descrizione',
remove: 'Elimina',
hide: 'Nascondi',
search: 'Cerca',
edit: 'Modifica',
info: 'Info',
confirm: 'Conferma',
admin: 'Amministra',
users: 'Utenti',
events: 'Eventi',
places: 'Luoghi',
settings: 'Opzioni',
actions: 'Azioni',
deactivate: 'Disattiva',
remove_admin: 'Rimuovi Admin',
activate: 'Attiva',
save: 'Salva',
preview: 'Anteprima',
logout: 'Esci',
share: 'Esporta',
name: 'Nome',
associate: 'Associa',
edit_event: 'Modifica evento',
related: 'Memoria storica',
add: 'Aggiungi',
logout_ok: 'Uscita correttamente',
copy: 'Copia'
},
login: {
description: `Entrando puoi pubblicare nuovi eventi.`,
check_email: 'Controlla la tua posta (anche lo spam)',
not_registered: 'Non sei registrata?',
forgot_password: 'Dimenticato la password?',
error: 'Errore: ',
insert_email: 'Inserisci la mail',
ok: 'Tutto rego'
},
export: {
intro: `Contrariamente alle piattaforme del capitalismo, che fanno di tutto per tenere
i dati e gli utenti al loro interno, crediamo che le informazioni, come le persone,
debbano essere libere. Per questo puoi rimanere aggiornata sugli eventi che vuoi, come meglio credi, senza necessariamente passare da questo sito.`,
email_description: `Puoi ricevere via mail gli eventi che ti interessano.`,
insert_your_address: 'Indirizzo email',
feed_description: `Per seguire gli aggiornamenti da computer o smartphone senza la necessità di aprire periodicamente il sito, il metodo consigliato è quello dei Feed RSS.</p>
<p>Con i feed rss utilizzi un'apposita applicazione per ricevere aggiornamenti dai siti che più ti interessano. È un buon metodo per seguire anche molti siti in modo molto rapido, senza necessità di creare un account o altre complicazioni.</p>
<li>Se hai Android, ti consigliamo <a href="https://play.google.com/store/apps/details?id=net.frju.flym">Flym</a> o Feeder</li>
<li>Per iPhone/iPad puoi usare <a href="https://itunes.apple.com/ua/app/feeds4u/id1038456442?mt=8">Feed4U</a></li>
<li>Per il computer fisso/portatile consigliamo Feedbro, da installare all'interno <a href="https://addons.mozilla.org/en-GB/firefox/addon/feedbroreader/">di Firefox </a>o <a href="https://chrome.google.com/webstore/detail/feedbro/mefgmmbdailogpfhfblcnnjfmnpnmdfa">di Chrome</a> e compatibile con tutti i principali sistemi operativi.</li>
<br/>
Aggiungendo questo link al tuo lettore di feed, rimarrai aggiornata.`,
ical_description: `I computer e gli smartphone sono comunemente attrezzati con un'applicazione per gestire un calendario. A questi programmi solitamente è possibile far importare un calendario remoto.`,
list_description: `Se hai un sito web e vuoi mostrare una lista di eventi, puoi usare il seguente codice`
},
register: {
description: `I movimenti hanno bisogno di organizzarsi e autofinanziarsi. <br/>Questo è un dono per voi, usatelo solo per eventi non commerciali e ovviamente antifascisti, antisessisti, antirazzisti.
<br/>Prima di poter pubblicare <strong>dobbiamo approvare l'account</strong>, considera che <strong>dietro questo sito ci sono delle persone</strong> di
carne e sangue, scrivici quindi due righe per farci capire che eventi vorresti pubblicare.`,
error: 'Errore: '
},
event: {
anon: 'Anonimo',
anon_description: `Puoi inserire un evento senza rigistrarti o fare il login,
ma in questo caso dovrai aspettare che qualcuno lo legga confermando che si
tratta di un evento adatto a questo spazio, delegando questa scelta.<br/><br/>
Puoi fare il <a href='/login'>login</a> o <a href='/registrarti'>registrarti</a>,
altrimenti vai avanti e riceverai una risposta il prima possibile.`,
multidate_description: 'tanti giorni',
date_description: `Quand'è il gancio?`,
dates_description: 'Che giorni?',
same_day: 'stesso giorno',
what_description: 'Nome evento',
description_description: 'Descrizione, dajene di copia/incolla',
tag_description: 'Tag...',
media_description: 'Puoi aggiungere un volantino',
time_start_description: 'Comincia alle',
time_end_description: 'Se vuoi puoi specificare un orario di fine.',
added: 'Evento aggiunto',
added_anon: 'Evento aggiunto, verrà confermato quanto prima.',
where_description: `Dov'è il gancio?<br/>Se il posto non è presente, scrivilo e premi invio. `,
confirmed: 'Evento confermato'
},
admin: {
mastodon_instance: 'Istanza',
mastodon_description: 'Puoi associare un account mastodon a questa istanza di gancio, ogni evento verrà pubblicato lì.',
place_description: `Nel caso in cui un luogo sia errato o cambi indirizzo, puoi modificarlo. <br/>Considera che tutti gli eventi associati a questo luogo cambieranno indirizzo (anche quelli passati!)`,
event_confirm_description: 'Puoi confermare qui gli eventi inseriti da utenti anonimi'
},
auth: {
not_confirmed: 'Non abbiamo ancora confermato questa mail...',
fail: 'Autenticazione fallita. Sicura la password è giusta? E la mail?'
},
settings: {
change_password: 'Cambia password'
},
err: {
register_error: 'Errore nella registrazione'
}
}
export default it

View File

@@ -0,0 +1,30 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
const a = queryInterface.addColumn('events', 'activitypub_ids', Sequelize.ARRAY(Sequelize.DOUBLE), { index: true })
const b = queryInterface.addColumn('comments', 'data', Sequelize.JSON)
return Promise.all([a, b])
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.dropTable('users');
*/
const b = queryInterface.removeColumn('comments', 'data')
const a = queryInterface.removeColumn('events', 'activitypub_ids')
return Promise.all([a, b])
}
};

View File

@@ -0,0 +1,27 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
const a = queryInterface.changeColumn('events', 'activitypub_id', { type: Sequelize.BIGINT, index: true })
const b = queryInterface.changeColumn('events', 'activitypub_ids', { type: Sequelize.ARRAY(Sequelize.BIGINT), index: true, defaultValue: [] })
const c = queryInterface.changeColumn('comments', 'activitypub_id', { type: Sequelize.BIGINT, index: true})
return Promise.all([a, b, c])
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.dropTable('users');
*/
}
};

View File

@@ -0,0 +1,29 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
await queryInterface.addColumn('tags', 'weigth', Sequelize.INTEGER)
await queryInterface.sequelize.query('update "tags" SET weigth=subquery.c from (SELECT COUNT(*) as c, "tagTag" from "tagEvent" group by "tagTag") as subquery where "subquery"."tagTag"="tags"."tag";')
await queryInterface.addColumn('places', 'weigth', Sequelize.INTEGER)
await queryInterface.sequelize.query('update "places" SET weigth=subquery.c from (SELECT COUNT(*) as c, "placeId" from "events" group by "placeId") as subquery where "subquery"."placeId"="places"."id";')
},
down: async (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.dropTable('users');
*/
await queryInterface.removeColumn('tags', 'weigth', Sequelize.INTEGER)
await queryInterface.removeColumn('places', 'weigth', Sequelize.INTEGER)
}
};

37
models/index.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

View File

@@ -41,7 +41,7 @@
"sequelize-cli": "^5.4.0", "sequelize-cli": "^5.4.0",
"sharp": "^0.22.0", "sharp": "^0.22.0",
"sqlite3": "^4.0.6", "sqlite3": "^4.0.6",
"v-calendar": "^1.0.0-beta.10", "v-calendar": "^1.0.0-beta.13",
"vue-awesome": "^3.5.1", "vue-awesome": "^3.5.1",
"vue-custom-element": "^3.2.6", "vue-custom-element": "^3.2.6",
"vue-i18n": "^8.10.0", "vue-i18n": "^8.10.0",

View File

@@ -26,10 +26,9 @@
filterable allow-create filterable allow-create
default-first-option default-first-option
) )
el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id') el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id')
br span {{place.name}} - {{place.weigth}}
br div {{$t("common.address")}}
div {{$t("common.address")}} {{event.place.name}}
el-input.mb-3(ref='address' v-model='event.place.address' el-input.mb-3(ref='address' v-model='event.place.address'
:disabled='places_name.indexOf(event.place.name)>-1' :disabled='places_name.indexOf(event.place.name)>-1'
@keydown.native.enter='next') @keydown.native.enter='next')
@@ -153,7 +152,7 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
tags: state => state.tags.map(t => t.tag ), tags: state => state.tags.map(t => t.tag ),
places_name: state => state.places.map(p => p.name ), places_name: state => state.places.map(p => p.name ).sort((a, b) => b.weigth-a.weigth),
places: state => state.places, places: state => state.places,
user: state => state.user, user: state => state.user,
events: state => state.events events: state => state.events

View File

@@ -27,7 +27,7 @@
template(slot='label') template(slot='label')
v-icon(name='map-marker-alt') v-icon(name='map-marker-alt')
span.ml-1 {{$t('common.places')}} span.ml-1 {{$t('common.places')}}
p {{$t('admin.place_description')}} p(v-html="$t('admin.place_description')")
el-form.mb-2(:inline='true' label-width='120px') el-form.mb-2(:inline='true' label-width='120px')
el-form-item(:label="$t('common.name')") el-form-item(:label="$t('common.name')")
el-input.mr-1(:placeholder='$t("common.name")' v-model='place.name') el-input.mr-1(:placeholder='$t("common.name")' v-model='place.name')
@@ -201,7 +201,7 @@ export default {
await this.$axios.$get(`/event/confirm/${id}`) await this.$axios.$get(`/event/confirm/${id}`)
this.loading = false this.loading = false
Message({ Message({
message: this.$t('common.event_confirmed'), message: this.$t('event.confirmed'),
type: 'success' type: 'success'
}) })
this.events = this.events.filter(e => e.id !== id) this.events = this.events.filter(e => e.id !== id)

View File

@@ -12,15 +12,18 @@ export default {
components: { List }, components: { List },
async asyncData ({ $axios, req, res }) { async asyncData ({ $axios, req, res }) {
const title = req.query.title || SHARED_CONF.title const title = req.query.title || SHARED_CONF.title
const show_tags = req.query.showtags
const tags = req.query.tags const tags = req.query.tags
const places = req.query.places const places = req.query.places
const now = new Date() const now = new Date()
// TODO: filter future events based on tags/places/userid let params = []
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`) if (places) params.push(`places=${places}`)
if (tags) params.push(`tags=${tags}`)
return { show_tags, events, title } params = params.length ? `?${params.join('&')}` : ''
const events = await $axios.$get(`/export/json${params}`)
return { events, title }
}, },
} }
</script> </script>

View File

@@ -13,9 +13,9 @@
h5.text-center {{event.title}} h5.text-center {{event.title}}
div.nextprev div.nextprev
nuxt-link(v-if='prev' :to='`/event/${prev.id}`') nuxt-link(v-if='prev' :to='`/event/${prev.id}`')
el-button(icon='el-icon-arrow-left' round size='small' type='success' plain) el-button(icon='el-icon-arrow-left' round type='success')
nuxt-link.float-right(v-if='next' :to='`/event/${next.id}`') nuxt-link.float-right(v-if='next' :to='`/event/${next.id}`')
el-button(icon='el-icon-arrow-right' round size='small' plain type='success') el-button(icon='el-icon-arrow-right' round type='success')
//- image //- image
img(:src='imgPath' v-if='event.image_path') img(:src='imgPath' v-if='event.image_path')

View File

@@ -1,11 +1,11 @@
<template lang="pug"> <template lang="pug">
el-dialog(:title='$t("common.export")' visible :before-close='close') el-dialog(:title='$t("common.export")' visible :before-close='close')
p {{$t('export.intro')}} p {{$t('export.intro')}}
Search
li(v-if='filters.tags.length') {{$t('common.tags')}}: //- li(v-if='filters.tags.length') {{$t('common.tags')}}:
el-tag.ml-1(size='mini' v-for='tag in filters.tags' :key='tag.tag') {{tag}} //- el-tag.ml-1(size='mini' v-for='tag in filters.tags' :key='tag.tag') {{tag}}
li(v-if='filters.places.length') {{$t('common.places')}}: //- li(v-if='filters.places.length') {{$t('common.places')}}:
el-tag.ml-1(size='mini' v-for='place in filters.places' :key='place.id') {{place}} //- el-tag.ml-1(size='mini' v-for='place in filters.places' :key='place.id') {{place}}
el-tabs.mt-2(v-model='type') el-tabs.mt-2(v-model='type')
el-tab-pane.pt-1(label='email' name='email') el-tab-pane.pt-1(label='email' name='email')
@@ -55,13 +55,15 @@ import { mapState, mapGetters } from 'vuex'
import path from 'path' import path from 'path'
import Calendar from '@/components/Calendar' import Calendar from '@/components/Calendar'
import List from '@/components/List' import List from '@/components/List'
import Search from '@/components/Search'
import {intersection} from 'lodash' import {intersection} from 'lodash'
import { Message } from 'element-ui' import { Message } from 'element-ui'
const { SHARED_CONF } = require('@/config') const { SHARED_CONF } = require('@/config')
export default { export default {
name: 'Export', name: 'Export',
components: { List }, components: { List, Search },
data () { data () {
return { return {
type: 'email', type: 'email',
@@ -96,6 +98,11 @@ export default {
if (this.list.title) { if (this.list.title) {
params.push(`title=${this.list.title}`) params.push(`title=${this.list.title}`)
} }
if (this.filters.places) {
params.push(`places=${this.filters.places}`)
}
return `<iframe src="${SHARED_CONF.baseurl}/embed/list?${params.join('&')}"></iframe>` return `<iframe src="${SHARED_CONF.baseurl}/embed/list?${params.join('&')}"></iframe>`
}, },
link () { link () {

52
pages/recover/_code.vue Normal file
View File

@@ -0,0 +1,52 @@
<template lang="pug">
el-dialog(visible)
template(slot='title')
h5 <img src='/favicon.ico'/> {{$t('common.recover_password')}}
div(v-if='valid')
el-form
el-form-item {{$t('common.new_password')}}
el-input(type='password', v-model='new_password')
el-button(plain type="success" icon='el-icon-send', @click='change_password') {{$t('common.send')}}
div(v-else) {{$t('recover.not_valid_code')}}
</template>
<script>
import { Message } from 'element-ui'
export default {
name: 'Recover',
data () {
return { new_password: '' }
},
async asyncData({ params, $axios }) {
const code = params.code
try {
const valid = await $axios.$post('/user/check_recover_code', { recover_code: code })
return { valid, code }
}
catch (e) {
return { valid: false }
}
},
methods: {
async change_password () {
try {
const res = await this.$axios.$post('/user/recover_password', { recover_code: this.code, password: this.new_password })
Message({
type: 'success',
message: this.$t('Password changed!')
})
} catch(e) {
Message({
type: 'warning',
message: e
})
}
}
}
}
</script>

View File

@@ -41,12 +41,9 @@ export default {
}) })
this.$router.replace("/") this.$router.replace("/")
} catch (e) { } catch (e) {
console.log('DENTRO CATCH!!!', e)
const error = e && e.response && e.response.data && e.response.data.errors[0].message || e const error = e && e.response && e.response.data && e.response.data.errors[0].message || e
console.error(error)
console.error(e)
Message({ Message({
message: this.$t('register.error') + error, message: this.$t('register.error') + this.$t(error),
type: 'error' type: 'error'
}) })
} }

View File

@@ -4,7 +4,7 @@ import 'dayjs/locale/it'
moment.locale('it') moment.locale('it')
export default (a) => { export default (a) => {
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>'))
Vue.filter('datetime', value => moment(value).format('ddd, D MMMM HH:mm')) Vue.filter('datetime', value => moment(value).format('ddd, D MMMM HH:mm'))
Vue.filter('short_datetime', value => moment(value).format('D/MM HH:mm')) Vue.filter('short_datetime', value => moment(value).format('D/MM HH:mm'))
Vue.filter('hour', value => moment(value).format('HH:mm')) Vue.filter('hour', value => moment(value).format('HH:mm'))
@@ -12,7 +12,7 @@ export default (a) => {
Vue.filter('month', value => moment(value).format('MMM')) Vue.filter('month', value => moment(value).format('MMM'))
Vue.filter('event_when', event => { Vue.filter('event_when', event => {
if (event.multidate) { if (event.multidate) {
return moment(event.start_datetime).format('dddd, D MMMM HH:mm') + ' - ' + moment(event.end_datetime).format('ddd, D MMMM') return moment(event.start_datetime).format('ddd, D MMMM HH:mm') + ' - ' + moment(event.end_datetime).format('ddd, D MMMM')
} else { } else {
if (event.end_datetime && event.end_datetime !== event.start_datetime) if (event.end_datetime && event.end_datetime !== event.start_datetime)
return moment(event.start_datetime).format('dddd, D MMMM HH:mm') + '-' + moment(event.end_datetime).format('HH:mm') return moment(event.start_datetime).format('dddd, D MMMM HH:mm') + '-' + moment(event.end_datetime).format('HH:mm')

9
plugins/initialize.js Normal file
View File

@@ -0,0 +1,9 @@
// TOFIX: not needed in any case (eg. embed)
export default async ({ store, $axios }) => {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
store.commit('setEvents', events)
const { tags, places } = await $axios.$get('/event/meta')
store.commit('update', { tags, places })
}

View File

@@ -11,6 +11,7 @@ const Auth = {
next() next()
}, },
async isAuth(req, res, next) { async isAuth(req, res, next) {
console.error('ma sono dentro auth ?!?!', req.user)
if (!req.user) { if (!req.user) {
return res return res
.status(403) .status(403)

View File

@@ -12,6 +12,7 @@ moment.locale('it')
const botController = { const botController = {
bot: null, bot: null,
async initialize () { async initialize () {
console.error('dentro bot inizialiteds')
const settings = await settingsController.settings() const settings = await settingsController.settings()
if (!settings.mastodon_auth || !settings.mastodon_auth.access_token) return if (!settings.mastodon_auth || !settings.mastodon_auth.access_token) return
const mastodon_auth = settings.mastodon_auth const mastodon_auth = settings.mastodon_auth

View File

@@ -21,24 +21,22 @@ const eventController = {
async getMeta(req, res) { async getMeta(req, res) {
const places = await Place.findAll({ const places = await Place.findAll({
group: ['place.id'], order: [[Sequelize.literal('weigth'), 'DESC']],
order: [[Sequelize.fn("COUNT", Sequelize.col('events.id')), 'DESC']],
attributes: { attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col('events.id')), 'eventsCount']], include: [[Sequelize.fn('count', Sequelize.col('events.placeId')) ,'weigth']], // <---- Here you will get the total count of user
exclude: ['createdAt', 'updatedAt'] exclude: ['weigth', 'createdAt', 'updatedAt']
}, },
include: { model: Event, attributes: [] } include: [{ model: Event, attributes: [] }],
group: ['place.id']
}) })
const tags = await Tag.findAll({ const tags = await Tag.findAll({
group: ['tag'], order: [['weigth' , 'DESC']],
order: [[Sequelize.fn("COUNT", Sequelize.col('events.id')), 'DESC']],
includeIgnoreAttributes: false, includeIgnoreAttributes: false,
attributes: { attributes: {
include: [[Sequelize.fn("COUNT", Sequelize.col('events.id')), 'eventsCount']],
exclude: ['createdAt', 'updatedAt'] exclude: ['createdAt', 'updatedAt']
}, }
include: { model: Event, attributes: [] }}) })
res.json({ tags, places }) res.json({ tags, places })
}, },
@@ -92,7 +90,7 @@ const eventController = {
Comment, Comment,
{ model: Place, attributes: ['name', 'address'] } { model: Place, attributes: ['name', 'address'] }
] , ] ,
order: [ [Comment, 'id', 'DESC'] ] order: [ [Comment, 'id', 'DESC'], [Tag, 'weigth', 'DESC'] ]
}) })
res.json(event) res.json(event)
}, },
@@ -177,15 +175,17 @@ const eventController = {
{ start_datetime: { [Op.lte]: end } } { start_datetime: { [Op.lte]: end } }
] ]
}, },
order: [['start_datetime', 'ASC']], order: [
['start_datetime', 'ASC'],
[Tag, 'weigth', 'DESC']
],
include: [ include: [
{ model: User, required: false }, // { model: User, required: false },
Comment, // { type: Comment, required: false, attributes: ['']
Tag, { model: Tag, required: false, attributes: ['tag', 'weigth','color'] },
{ model: Place, required: false } { model: Place, required: false, attributes: ['id', 'name', 'address'] }
] ]
}) })
// console.log(events)
res.json(events) res.json(events)
} }

View File

@@ -8,6 +8,7 @@ const exportController = {
async export (req, res) { async export (req, res) {
console.log('type ', req.params.type) console.log('type ', req.params.type)
console.error(req)
const type = req.params.type const type = req.params.type
const tags = req.query.tags const tags = req.query.tags
const places = req.query.places const places = req.query.places
@@ -18,14 +19,20 @@ const exportController = {
whereTag.tag = tags.split(',') whereTag.tag = tags.split(',')
} }
if (places) { if (places) {
wherePlace.name = places.split(',') wherePlace.id = places.split(',')
} }
console.error(places)
const events = await Event.findAll({ const events = await Event.findAll({
where: { is_visible: true, start_datetime: { [Op.gte]: yesterday } }, order: ['start_datetime'],
include: [Comment, { where: {
model: Tag, is_visible: true,
where: whereTag start_datetime: { [Op.gte]: yesterday },
}, { model: Place, where: wherePlace } ] placeId: places.split(',')
},
attributes: {
exclude: ['createdAt', 'updatedAt']
},
include: [{model: Place, attributes: ['name', 'id', 'address', 'weigth']}]
}) })
switch (type) { switch (type) {
case 'feed': case 'feed':

View File

@@ -74,17 +74,14 @@ const userController = {
async addEvent(req, res) { async addEvent(req, res) {
const body = req.body const body = req.body
// remove description tag and create anchor tags
const description = body.description
.replace(/(<([^>]+)>)/ig, '')
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>')
const eventDetails = { const eventDetails = {
title: body.title, title: body.title,
description, description: body.description.replace(/(<([^>]+)>)/ig, ''),
multidate: body.multidate, multidate: body.multidate,
start_datetime: body.start_datetime, start_datetime: body.start_datetime,
end_datetime: body.end_datetime, end_datetime: body.end_datetime,
// publish this event if authenticated
is_visible: !!req.user is_visible: !!req.user
} }
@@ -94,7 +91,7 @@ const userController = {
let event = await Event.create(eventDetails) let event = await Event.create(eventDetails)
// create place // create place if needs to
let place let place
try { try {
place = await Place.findOrCreate({ where: { name: body.place_name }, place = await Place.findOrCreate({ where: { name: body.place_name },
@@ -140,7 +137,7 @@ const userController = {
body.description = body.description body.description = body.description
.replace(/(<([^>]+)>)/ig, '') // remove all tags from description .replace(/(<([^>]+)>)/ig, '') // remove all tags from description
.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>') // add links // .replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</a>') // add links
await event.update(body) await event.update(body)
let place let place

View File

@@ -19,7 +19,8 @@ const Event = db.define('event', {
}) })
const Tag = db.define('tag', { const Tag = db.define('tag', {
tag: { type: Sequelize.STRING, index: true, unique: true, }, tag: { type: Sequelize.STRING, index: true, unique: true, primaryKey: true },
weigth: { type: Sequelize.INTEGER, defaultValue: 0 },
color: { type: Sequelize.STRING } color: { type: Sequelize.STRING }
}) })
@@ -43,6 +44,7 @@ const Notification = db.define('notification', {
const Place = db.define('place', { const Place = db.define('place', {
name: { type: Sequelize.STRING, unique: true, index: true }, name: { type: Sequelize.STRING, unique: true, index: true },
weigth: { type: Sequelize.INTEGER, defaultValue: 0 },
address: { type: Sequelize.STRING } address: { type: Sequelize.STRING }
}) })

View File

@@ -5,7 +5,7 @@ const db = require('../db')
const User = db.define('user', { const User = db.define('user', {
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: { msg: 'Email already exists' }, unique: { msg: 'err.register_error' },
index: true, index: true,
allowNull: false allowNull: false
}, },

View File

@@ -0,0 +1,4 @@
p= t('confirm_email')
hr
small #{config.baseurl}

View File

@@ -0,0 +1,18 @@
h3 #{event.title}
p Dove: #{event.place.name} - #{event.place.address}
p Quando: #{datetime(event.start_datetime)}
br
if event.image_path
<img style="width: 100%" src="#{config.apiurl}/uploads/#{event.image_path}" />
p #{event.description}
each tag in event.tags
span ##{tag.tag}
br
<a href="#{config.baseurl}/event/#{event.id}">#{config.baseurl}/event/#{event.id}</a>
hr
if to_confirm
p Puoi confermare questo evento <a href="#{config.baseurl}/admin/confirm/#{event.id}">qui</a>
else
p Puoi eliminare queste notifiche <a href="#{config.baseurl}/del_notification/#{notification.remove_code}">qui</a>
<a href="#{config.baseurl}">#{config.title} - #{config.description}</a>

View File

@@ -0,0 +1 @@
= `[${config.title}] ${event.title} @${event.place.name} ${datetime(event.start_datetime)}`

8
server/emails/mail.css Normal file
View File

@@ -0,0 +1,8 @@
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid #555;
}

View File

@@ -0,0 +1,3 @@
p= t('recover_email')
<a href="#{config.baseurl}/recover/#{user.recover_code}">#{t('press here')}</a>

View File

@@ -0,0 +1 @@
= `[Gancio] Richiesta password recovery`

View File

@@ -0,0 +1,6 @@
p= t('registration_email')
hr
small #{config.title} / #{config.description}
br
small #{config.baseurl}

View File

@@ -0,0 +1 @@
= `[Gancio] Richiesta registrazione`

View File

@@ -7,11 +7,14 @@ const path = require('path')
const { Nuxt, Builder } = require('nuxt') const { Nuxt, Builder } = require('nuxt')
const app = express() const app = express()
const cors = require('cors') const cors = require('cors')
const notifier = require('./notifier')
const corsConfig = { const corsConfig = {
allowedHeaders: ['Authorization'], allowedHeaders: ['Authorization'],
exposeHeaders: ['Authorization'] exposeHeaders: ['Authorization']
} }
// Import and Set Nuxt.js options // Import and Set Nuxt.js options
const config = require('../nuxt.config.js') const config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production') config.dev = !(process.env.NODE_ENV === 'production')
@@ -47,3 +50,4 @@ async function start() {
}) })
} }
start() start()
notifier.startLoop(20)

View File

@@ -21,6 +21,7 @@ async function sendNotification (notification, event, eventNotification) {
if (settings.mastodon_auth.instance && settings.mastodon_auth.access_token) { if (settings.mastodon_auth.instance && settings.mastodon_auth.access_token) {
const b = bot.post(settings.mastodon_auth, event).then(b => { const b = bot.post(settings.mastodon_auth, event).then(b => {
event.activitypub_id = b.data.id event.activitypub_id = b.data.id
// event.activitypub_ids.push(b.data.id)
return event.save() return event.save()
}) })
promises.push(b) promises.push(b)
@@ -29,7 +30,8 @@ async function sendNotification (notification, event, eventNotification) {
return Promise.all(promises) return Promise.all(promises)
} }
async function loop () { async function notify() {
console.error('dentro il loop di notify')
settings = await settingsController.settings() settings = await settingsController.settings()
// get all event notification in queue // get all event notification in queue
const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } }) const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
@@ -51,5 +53,14 @@ async function loop () {
return Promise.all(promises) return Promise.all(promises)
} }
setInterval(loop, 260000) let interval
loop() function startLoop(seconds) {
console.error('starting notifier loop')
interval = setInterval(notify, seconds*1000)
}
function stopLoop() {
stopInterval(interval)
}
module.exports = { startLoop, stopLoop }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/gancio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -20,9 +20,9 @@ export const state = () => ({
export const getters = { export const getters = {
token: state => state.token, token: state => state.token,
// filter current + future events only // filter current + future events only
// plus, filter matches search tag/place // plus, filter matches search tag/place
filteredEvents: (state) => { filteredEvents: (state) => {
let events = state.events let events = state.events
@@ -50,7 +50,6 @@ export const getters = {
let lastDay = null let lastDay = null
events = map(events, e => { events = map(events, e => {
const currentDay = moment(e.start_datetime).date() const currentDay = moment(e.start_datetime).date()
console.log(currentDay)
e.newDay = (!lastDay || lastDay!==currentDay) && currentDay e.newDay = (!lastDay || lastDay!==currentDay) && currentDay
lastDay = currentDay lastDay = currentDay
return e return e
@@ -88,22 +87,9 @@ export const mutations = {
state.tags = tags state.tags = tags
state.places = places state.places = places
}, },
// search
addSearchTag(state, tag) {
if (!state.filters.tags.find(t => t === tag.tag)) {
state.filters.tags.push(tag.tag)
} else {
state.filters.tags = state.filters.tags.filter(t => t !== tag.tag)
}
},
setSearchTags(state, tags) { setSearchTags(state, tags) {
state.filters.tags = tags state.filters.tags = tags
}, },
addSearchPlace(state, place) {
if (state.filters.places.find(p => p.name === place.name)) {
state.filters.places.push(place)
}
},
setSearchPlaces(state, places) { setSearchPlaces(state, places) {
state.filters.places = places state.filters.places = places
}, },
@@ -122,26 +108,23 @@ export const actions = {
commit('update', { tags, places }) commit('update', { tags, places })
}, },
async addEvent({ commit }, formData) { async addEvent({ commit }, formData) {
const event = await this.$axios.$post('/user/event', formData) // .addEvent(formData) const event = await this.$axios.$post('/user/event', formData)
if (event.user) {
commit('addEvent', event) commit('addEvent', event)
}
}, },
async updateEvent({ commit }, formData) { async updateEvent({ commit }, formData) {
const event = await this.$axios.$put('/user/event', formData) const event = await this.$axios.$put('/user/event', formData)
if (event.user) {
commit('updateEvent', event) commit('updateEvent', event)
}
}, },
delEvent({ commit }, eventId) { delEvent({ commit }, eventId) {
commit('delEvent', eventId) commit('delEvent', eventId)
}, },
// search
addSearchTag({ commit }, tag) {
commit('addSearchTag', tag)
},
setSearchTags({ commit }, tags) { setSearchTags({ commit }, tags) {
commit('setSearchTags', tags) commit('setSearchTags', tags)
}, },
addSearchPlace({ commit }, place) {
commit('addSearchPlace', place)
},
setSearchPlaces({ commit }, places) { setSearchPlaces({ commit }, places) {
commit('setSearchPlaces', places) commit('setSearchPlaces', places)
}, },

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gancio Widget Example</title>
<script src="https://unpkg.com/vue"></script>
<script src='dist/gancio-widget.min.js'></script>
<link rel="stylesheet" href="../list/style.css">
</head>
<body>
<gancio-widget minimal></gancio-widget>
</body>
</html>

View File

@@ -1,12 +0,0 @@
import Vue from 'vue'
import vueCustomElement from 'vue-custom-element'
import App from '../../components/List'
// import router from './router'
// import store from '../../store'
Vue.use(vueCustomElement)
// App.store = store
// App.router = router
Vue.customElement('gancio-widget', App)
export default App

View File

@@ -1,3 +0,0 @@
#gancio-widget {
border: 1px solid black;
}