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",
"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": {
"name": "lesion"
@ -73,6 +85,15 @@
}
],
"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": {
"name": "lesion"
@ -85,18 +106,6 @@
],
"testing": [],
"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": {
"name": "lesion"
@ -168,6 +177,24 @@
"references": [],
"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": {
"name": "lesion"
@ -214,15 +241,6 @@
"references": [],
"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": {
"name": "lesion"
@ -259,10 +277,10 @@
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-05-27T20:42:22.581Z",
"id": "24",
"creation_time": "2019-05-29T13:10:04.463Z",
"id": "26",
"references": [],
"title": "copy to clipboard"
"title": "v-calendar colori e eventi multidays..."
}
]
}

View file

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

View file

@ -1,9 +1,14 @@
<template lang="pug">
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
no-ssr
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')
a(:id='event.newDay' v-if='event.newDay')
.d-block.d-sm-none
@ -20,14 +25,13 @@
import { mapGetters } from 'vuex'
import Event from '@/components/Event'
import Calendar from '@/components/Calendar'
import Search from '@/components/Search'
export default {
name: 'Home',
data () {
return { }
},
components: { Calendar, Event, Search },
components: { Calendar, Event },
computed: mapGetters(['filteredEvents']),
}
</script>
@ -36,5 +40,19 @@ section {
width: 100%;
max-width: 1500px;
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>

View file

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

View file

@ -1,3 +1,3 @@
<template lang="pug">
nuxt
<template>
<nuxt/>
</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",
"sharp": "^0.22.0",
"sqlite3": "^4.0.6",
"v-calendar": "^1.0.0-beta.10",
"v-calendar": "^1.0.0-beta.13",
"vue-awesome": "^3.5.1",
"vue-custom-element": "^3.2.6",
"vue-i18n": "^8.10.0",

View file

@ -26,10 +26,9 @@
filterable allow-create
default-first-option
)
el-option(v-for='place in places_name' :label='place' :value='place' :key='place.id')
br
br
div {{$t("common.address")}} {{event.place.name}}
el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id')
span {{place.name}} - {{place.weigth}}
div {{$t("common.address")}}
el-input.mb-3(ref='address' v-model='event.place.address'
:disabled='places_name.indexOf(event.place.name)>-1'
@keydown.native.enter='next')
@ -89,9 +88,9 @@
:on-change='uploadedFile'
:multiple='false'
:file-list="fileList"
)
i.el-icon-upload
div.el-upload__text {{$t('event.media_description')}}
)
i.el-icon-upload
div.el-upload__text {{$t('event.media_description')}}
el-button.float-right(@click='done' :disabled='!couldProceed') {{edit?$t('common.edit'):$t('common.send')}}
</template>
@ -153,7 +152,7 @@ export default {
computed: {
...mapState({
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,
user: state => state.user,
events: state => state.events

View file

@ -27,7 +27,7 @@
template(slot='label')
v-icon(name='map-marker-alt')
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-item(:label="$t('common.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}`)
this.loading = false
Message({
message: this.$t('common.event_confirmed'),
message: this.$t('event.confirmed'),
type: 'success'
})
this.events = this.events.filter(e => e.id !== id)

View file

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

View file

@ -13,9 +13,9 @@
h5.text-center {{event.title}}
div.nextprev
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}`')
el-button(icon='el-icon-arrow-right' round size='small' plain type='success')
el-button(icon='el-icon-arrow-right' round type='success')
//- image
img(:src='imgPath' v-if='event.image_path')

View file

@ -1,11 +1,11 @@
<template lang="pug">
el-dialog(:title='$t("common.export")' visible :before-close='close')
p {{$t('export.intro')}}
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}}
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}}
Search
//- 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}}
//- 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-tabs.mt-2(v-model='type')
el-tab-pane.pt-1(label='email' name='email')
@ -55,13 +55,15 @@ import { mapState, mapGetters } from 'vuex'
import path from 'path'
import Calendar from '@/components/Calendar'
import List from '@/components/List'
import Search from '@/components/Search'
import {intersection} from 'lodash'
import { Message } from 'element-ui'
const { SHARED_CONF } = require('@/config')
export default {
name: 'Export',
components: { List },
components: { List, Search },
data () {
return {
type: 'email',
@ -96,6 +98,11 @@ export default {
if (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>`
},
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("/")
} catch (e) {
console.log('DENTRO CATCH!!!', e)
const error = e && e.response && e.response.data && e.response.data.errors[0].message || e
console.error(error)
console.error(e)
Message({
message: this.$t('register.error') + error,
message: this.$t('register.error') + this.$t(error),
type: 'error'
})
}

View file

@ -4,7 +4,7 @@ import 'dayjs/locale/it'
moment.locale('it')
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('short_datetime', value => moment(value).format('D/MM 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('event_when', event => {
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 {
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')

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()
},
async isAuth(req, res, next) {
console.error('ma sono dentro auth ?!?!', req.user)
if (!req.user) {
return res
.status(403)

View file

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

View file

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

View file

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

View file

@ -74,17 +74,14 @@ const userController = {
async addEvent(req, res) {
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 = {
title: body.title,
description,
description: body.description.replace(/(<([^>]+)>)/ig, ''),
multidate: body.multidate,
start_datetime: body.start_datetime,
end_datetime: body.end_datetime,
// publish this event if authenticated
is_visible: !!req.user
}
@ -94,7 +91,7 @@ const userController = {
let event = await Event.create(eventDetails)
// create place
// create place if needs to
let place
try {
place = await Place.findOrCreate({ where: { name: body.place_name },
@ -140,7 +137,7 @@ const userController = {
body.description = body.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)
let place

View file

@ -19,7 +19,8 @@ const Event = db.define('event', {
})
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 }
})
@ -43,6 +44,7 @@ const Notification = db.define('notification', {
const Place = db.define('place', {
name: { type: Sequelize.STRING, unique: true, index: true },
weigth: { type: Sequelize.INTEGER, defaultValue: 0 },
address: { type: Sequelize.STRING }
})

View file

@ -5,7 +5,7 @@ const db = require('../db')
const User = db.define('user', {
email: {
type: Sequelize.STRING,
unique: { msg: 'Email already exists' },
unique: { msg: 'err.register_error' },
index: true,
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 app = express()
const cors = require('cors')
const notifier = require('./notifier')
const corsConfig = {
allowedHeaders: ['Authorization'],
exposeHeaders: ['Authorization']
}
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
@ -47,3 +50,4 @@ async function 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) {
const b = bot.post(settings.mastodon_auth, event).then(b => {
event.activitypub_id = b.data.id
// event.activitypub_ids.push(b.data.id)
return event.save()
})
promises.push(b)
@ -29,7 +30,8 @@ async function sendNotification (notification, event, eventNotification) {
return Promise.all(promises)
}
async function loop () {
async function notify() {
console.error('dentro il loop di notify')
settings = await settingsController.settings()
// get all event notification in queue
const eventNotifications = await EventNotification.findAll({ where: { status: 'new' } })
@ -51,5 +53,14 @@ async function loop () {
return Promise.all(promises)
}
setInterval(loop, 260000)
loop()
let interval
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 = {
token: state => state.token,
// filter current + future events only
// plus, filter matches search tag/place
filteredEvents: (state) => {
let events = state.events
@ -50,7 +50,6 @@ export const getters = {
let lastDay = null
events = map(events, e => {
const currentDay = moment(e.start_datetime).date()
console.log(currentDay)
e.newDay = (!lastDay || lastDay!==currentDay) && currentDay
lastDay = currentDay
return e
@ -88,22 +87,9 @@ export const mutations = {
state.tags = tags
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) {
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) {
state.filters.places = places
},
@ -122,26 +108,23 @@ export const actions = {
commit('update', { tags, places })
},
async addEvent({ commit }, formData) {
const event = await this.$axios.$post('/user/event', formData) // .addEvent(formData)
commit('addEvent', event)
const event = await this.$axios.$post('/user/event', formData)
if (event.user) {
commit('addEvent', event)
}
},
async updateEvent({ commit }, formData) {
const event = await this.$axios.$put('/user/event', formData)
commit('updateEvent', event)
if (event.user) {
commit('updateEvent', event)
}
},
delEvent({ commit }, eventId) {
commit('delEvent', eventId)
},
// search
addSearchTag({ commit }, tag) {
commit('addSearchTag', tag)
},
setSearchTags({ commit }, tags) {
commit('setSearchTags', tags)
},
addSearchPlace({ commit }, place) {
commit('addSearchPlace', place)
},
setSearchPlaces({ commit }, 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;
}