This commit is contained in:
lesion 2019-06-06 23:54:32 +02:00
parent 745b9247c9
commit 3ca818f016
66 changed files with 989 additions and 532 deletions

3
.gitignore vendored
View file

@ -1,4 +1,7 @@
# Created by .ignore support plugin (hsz.mobi) # Created by .ignore support plugin (hsz.mobi)
### Gancio production configuration
server/config.js
### Node template ### Node template
# Logs # Logs
logs logs

7
.sequelizerc Normal file
View file

@ -0,0 +1,7 @@
const path = require('path')
module.exports = {
'config': path.resolve('server' ,'config.js'),
'migrations-path': path.resolve('server', 'migrations'),
'models-path': path.resolve('server', 'api', 'models')
}

View file

@ -150,6 +150,15 @@
"title": "risolvere le modali quando il js e' disabilitato", "title": "risolvere le modali quando il js e' disabilitato",
"type": "bug" "type": "bug"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-06-01T21:00:22.155Z",
"id": "28",
"references": [],
"title": "activitypub stream"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@ -168,6 +177,15 @@
"references": [], "references": [],
"title": "check password reset" "title": "check password reset"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-06-01T21:23:46.941Z",
"id": "30",
"references": [],
"title": "choose listening port"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@ -195,6 +213,15 @@
"references": [], "references": [],
"title": "creazione script di backup" "title": "creazione script di backup"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-06-01T21:00:07.431Z",
"id": "27",
"references": [],
"title": "eventi ricorrenti"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@ -250,6 +277,15 @@
"references": [], "references": [],
"title": "rifare il calendario o solo il popup" "title": "rifare il calendario o solo il popup"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-06-01T21:15:42.190Z",
"id": "29",
"references": [],
"title": "settings di istanza (default filter, eg, eventi ricorrenti)"
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@ -261,6 +297,15 @@
"references": [], "references": [],
"title": "traduzione in inglese" "title": "traduzione in inglese"
}, },
{
"assignedTo": {
"name": "lesion"
},
"creation_time": "2019-05-29T13:10:04.463Z",
"id": "26",
"references": [],
"title": "v-calendar colori e eventi multidays..."
},
{ {
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
@ -277,10 +322,10 @@
"assignedTo": { "assignedTo": {
"name": "lesion" "name": "lesion"
}, },
"creation_time": "2019-05-29T13:10:04.463Z", "creation_time": "2019-06-05T20:39:59.287Z",
"id": "26", "id": "31",
"references": [], "references": [],
"title": "v-calendar colori e eventi multidays..." "title": "scroll h su mobile"
} }
] ]
} }

View file

@ -1,6 +1,10 @@
@background: #222C32;
@success: #c7ffbc;
// @info
html, body { html, body {
margin: 0px; margin: 0px;
background-color: #222C32 !important; background-color: @background !important;
width: 100%; width: 100%;
overflow-x: hidden; overflow-x: hidden;
box-sizing: border-box; box-sizing: border-box;
@ -11,6 +15,20 @@ html, body {
box-sizing: border-box; box-sizing: border-box;
} }
.el-form-item {
margin-bottom: 5px;
}
.el-divider__text {
background-color: @background;
color: white;
border-radius: 5px;
}
.el-card {
max-width: 600px;
margin: 30px auto;
}
.el-dialog { .el-dialog {
margin-top: 0px !important; margin-top: 0px !important;
border-radius: 0px; border-radius: 0px;

View file

@ -52,17 +52,18 @@ export default {
start: event.start_datetime, end: event.end_datetime start: event.start_datetime, end: event.end_datetime
} }
e.highlight = { e.highlight = {
color: 'red' // : sample(['purple', 'red', 'green', 'blue']), color: sample(['purple', 'red', 'green', 'blue']),
} }
} else { } else {
e.dates = event.start_datetime e.dates = event.start_datetime
e.dot = { color: 'rgba(102,10,20)' } e.dot = { color: sample(['purple', 'red', 'green', 'blue']) }
} }
return e return e
} }
}, },
computed: { computed: {
...mapGetters(['filteredEvents']), ...mapGetters(['filteredEvents']),
...mapState(['events']),
attributes () { attributes () {
return [ return [
{ key: 'today', dates: new Date(), { key: 'today', dates: new Date(),

View file

@ -12,7 +12,7 @@
//- date / place //- date / place
.date .date
div {{event|event_when}} div {{event|event_when}}
div {{event.place.name}} div @{{event.place.name}}
//- p(v-if='showDescription') {{event.description}} //- p(v-if='showDescription') {{event.description}}
@ -54,6 +54,7 @@ export default {
} }
</script> </script>
<style lang='less'> <style lang='less'>
@import '../assets/style.less';
@media only screen and (min-width: 574px) { @media only screen and (min-width: 574px) {
.event { .event {
@ -83,21 +84,18 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
background-color: #222; background-color: #111214;
} }
.content-info { .content-info {
font-size: 12px;
font-size: 0.8em;
padding: 0.8em 1em; padding: 0.8em 1em;
max-height: 200px;
color: rgb(255, 122, 204);
h2 { h2 {
color: yellow; color: @success;
font-size: 16px; font-size: 16px;
font-size: 1.1rem; font-size: 1.2rem;
font-weight: 400;
margin: 0px; margin: 0px;
} }
@ -109,9 +107,10 @@ export default {
} }
.date { .date {
font-weight: 800; font-weight: 300;
font-size: 16px; font-size: 12px;
font-size: 1rem; font-size: 0.95rem;
color: #ff917a;
} }
} }
@ -124,7 +123,7 @@ export default {
justify-content: center; justify-content: center;
li { li {
background: #40484D; background: #1B1F21;
display: inline-block; display: inline-block;
padding: 2px 10px; padding: 2px 10px;
color: rgba(255,255,255,0.7); color: rgba(255,255,255,0.7);

View file

@ -4,10 +4,9 @@
a(href='#totop') a(href='#totop')
el-button.top.d-block.d-sm-none(icon='el-icon-top' circle type='primary' plain) el-button.top.d-block.d-sm-none(icon='el-icon-top' circle type='primary' plain)
a.totop(name='totop') a.totop(name='totop')
.row.m-0
no-ssr no-ssr
Calendar.col-sm-12.col-lg-8.col-xl-6 Calendar
.row.m-0
.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')

View file

@ -17,17 +17,16 @@ div#list
</template> </template>
<script> <script>
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
const { SHARED_CONF } = require('@/config')
export default { export default {
name: 'List', name: 'List',
data () { data () {
return { SHARED_CONF } return { }
}, },
props: { props: {
title: { title: {
type: String, type: String,
default: SHARED_CONF.title default: ''
}, },
events: { events: {
type: Array, type: Array,

View file

@ -1,6 +1,7 @@
<template lang="pug"> <template lang="pug">
el-menu.d-grid.nav(mode='horizontal' router background-color="#222C32") el-menu.d-grid.nav(mode='horizontal' router background-color="#222C32")
nuxt-link(to='/login')
el-menu-item(v-if='!$auth.loggedIn' index='/login' :title="$t('common.login')") el-menu-item(v-if='!$auth.loggedIn' index='/login' :title="$t('common.login')")
v-icon(color='lightgreen' name='user') v-icon(color='lightgreen' name='user')

BIN
db.sqlite

Binary file not shown.

View file

@ -80,11 +80,11 @@ const it = {
event: { event: {
anon: 'Anonimo', anon: 'Anonimo',
anon_description: `Puoi inserire un evento senza rigistrarti o fare il login, anon_description: `Puoi inserire un evento senza registrarti o fare il login,
ma in questo caso dovrai aspettare che qualcuno lo legga confermando che si 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/> tratta di un evento adatto a questo spazio, delegando questa scelta. Inoltre non sarà possibile modificarlo.<br/><br/>
Puoi fare il <a href='/login'>login</a> o <a href='/registrarti'>registrarti</a>, Puoi invece fare il <a href='/login'>login</a> o <a href='/registrarti'>registrarti</a>,
altrimenti vai avanti e riceverai una risposta il prima possibile.`, altrimenti vai avanti e riceverai una risposta il prima possibile. `,
multidate_description: 'tanti giorni', multidate_description: 'tanti giorni',
date_description: `Quand'è il gancio?`, date_description: `Quand'è il gancio?`,
dates_description: 'Che giorni?', dates_description: 'Che giorni?',
@ -97,7 +97,7 @@ const it = {
time_end_description: 'Se vuoi puoi specificare un orario di fine.', time_end_description: 'Se vuoi puoi specificare un orario di fine.',
added: 'Evento aggiunto', added: 'Evento aggiunto',
added_anon: 'Evento aggiunto, verrà confermato quanto prima.', added_anon: 'Evento aggiunto, verrà confermato quanto prima.',
where_description: `Dov'è il gancio?<br/>Se il posto non è presente, scrivilo e premi invio. `, where_description: `Dov'è il gancio? Se il posto non è presente, scrivilo e <b>premi invio</b>. `,
confirmed: 'Evento confermato' confirmed: 'Evento confermato'
}, },
@ -119,6 +119,13 @@ const it = {
err: { err: {
register_error: 'Errore nella registrazione' register_error: 'Errore nella registrazione'
},
firstrun: {
basic: `Inserisci titolo e descrizione della tua istanza di gancio.`,
database: `Gancio ha bisogno di un database postgresql!`,
smtp: `Inserisci un account SMTP relativo a questa istanza di gancio.`,
} }
} }

View file

@ -1,30 +0,0 @@
'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

@ -1,27 +0,0 @@
'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

@ -1,29 +0,0 @@
'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)
}
};

View file

@ -1,37 +0,0 @@
'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

@ -1,20 +1,20 @@
const { SHARED_CONF } = require('./config') const config = require('./server/config').SHARED_CONF
module.exports = { module.exports = {
mode: 'universal', mode: 'universal',
/* /*
** Headers of the page ** Headers of the page
*/ */
head: { head: {
title: SHARED_CONF.title, title: config.title,
meta: [ meta: [
{ charset: 'utf-8' }, { charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: SHARED_CONF.description } { hid: 'description', name: 'description', content: config.description }
], ],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }] link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
}, },
dev: (process.env.NODE_ENV !== 'production'),
serverMiddleware: [ serverMiddleware: [
{ path: '/api', handler: '@/server/api/index.js' } { path: '/api', handler: '@/server/api/index.js' }
@ -32,6 +32,7 @@ module.exports = {
'bootstrap/dist/css/bootstrap.css', 'bootstrap/dist/css/bootstrap.css',
'element-ui/lib/theme-chalk/index.css', 'element-ui/lib/theme-chalk/index.css',
], ],
/* /*
** Plugins to load before mounting the App ** Plugins to load before mounting the App
*/ */
@ -41,7 +42,6 @@ module.exports = {
'@/plugins/i18n', // localization plugin '@/plugins/i18n', // localization plugin
'@/plugins/vue-awesome', // icon '@/plugins/vue-awesome', // icon
{ src: '@/plugins/v-calendar', ssr: false }, // calendar, TO-REDO { src: '@/plugins/v-calendar', ssr: false }, // calendar, TO-REDO
'@/plugins/initialize'
], ],
/* /*
@ -56,8 +56,8 @@ module.exports = {
** Axios module configuration ** Axios module configuration
*/ */
axios: { axios: {
baseURL: SHARED_CONF.baseurl + '/api', baseURL: config.baseurl + '/api',
browserBaseURL: SHARED_CONF.baseurl + '/api', browserBaseURL: config.baseurl + '/api',
prefix: '/api', prefix: '/api',
// credentials: true // credentials: true
// See https://github.com/nuxt-community/axios-module#options // See https://github.com/nuxt-community/axios-module#options
@ -68,7 +68,7 @@ module.exports = {
endpoints: { endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'token' }, login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: false, logout: false,
user: { url: '/auth/user', method: 'get', propertyName: false } user: { url: '/auth/user', method: 'get', propertyName: false },
}, },
} }
} }
@ -79,6 +79,9 @@ module.exports = {
** Build configuration ** Build configuration
*/ */
build: { build: {
// babel: {
// presets: ['@nuxt/babel-preset-app']
// },
transpile: [/^element-ui/, /^vue-awesome/], transpile: [/^element-ui/, /^vue-awesome/],
splitChunks: { splitChunks: {
layouts: true layouts: true

View file

@ -13,19 +13,20 @@
"precommit": "npm run lint" "precommit": "npm run lint"
}, },
"dependencies": { "dependencies": {
"@nuxtjs/auth": "^4.5.3", "@nuxt/babel-preset-app": "^2.8.1",
"@nuxtjs/axios": "^5.4.1", "@nuxtjs/auth": "^4.6.5",
"axios": "^0.18.0", "@nuxtjs/axios": "^5.5.3",
"axios": "^0.19.0",
"bcrypt": "^3.0.5", "bcrypt": "^3.0.5",
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"cookie-parser": "^1.4.4", "cookie-parser": "^1.4.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"dayjs": "^1.8.11", "dayjs": "^1.8.14",
"element-ui": "^2.8.2", "element-ui": "^2.9.1",
"email-templates": "^5.0.4", "email-templates": "^5.1.0",
"express": "^4.16.4", "express": "^4.17.1",
"express-jwt": "^5.3.1", "express-jwt": "^5.3.1",
"ics": "^2.13.2", "ics": "^2.13.2",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
@ -35,36 +36,36 @@
"mastodon-api": "^1.3.0", "mastodon-api": "^1.3.0",
"morgan": "^1.9.1", "morgan": "^1.9.1",
"multer": "^1.4.1", "multer": "^1.4.1",
"nuxt": "^2.4.0", "nuxt": "^2.8.1",
"pg": "^7.10.0", "pg": "^7.11.0",
"sequelize": "^5.2.1", "sequelize": "^5.8.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.8",
"v-calendar": "^1.0.0-beta.13", "v-calendar": "^1.0.0-beta.14",
"vue-awesome": "^3.5.1", "vue-awesome": "^3.5.3",
"vue-custom-element": "^3.2.6", "vue-custom-element": "^3.2.7",
"vue-i18n": "^8.10.0", "vue-i18n": "^8.10.0",
"vuex-persist": "^2.0.0" "vuex-persist": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/eslint-config": "^0.0.1", "@nuxtjs/eslint-config": "^0.0.1",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"eslint": "^5.15.1", "eslint": "^5.15.1",
"eslint-config-prettier": "^4.1.0", "eslint-config-prettier": "^4.3.0",
"eslint-config-standard": ">=12.0.0", "eslint-config-standard": ">=12.0.0",
"eslint-loader": "^2.1.2", "eslint-loader": "^2.1.2",
"eslint-plugin-import": ">=2.16.0", "eslint-plugin-import": ">=2.17.3",
"eslint-plugin-jest": ">=22.3.0", "eslint-plugin-jest": ">=22.6.4",
"eslint-plugin-node": ">=8.0.1", "eslint-plugin-node": ">=9.1.0",
"eslint-plugin-nuxt": ">=0.4.2", "eslint-plugin-nuxt": ">=0.4.2",
"eslint-plugin-prettier": "^3.0.1", "eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-promise": ">=4.0.1", "eslint-plugin-promise": ">=4.0.1",
"eslint-plugin-standard": ">=4.0.0", "eslint-plugin-standard": ">=4.0.0",
"eslint-plugin-vue": "^5.2.2", "eslint-plugin-vue": "^5.2.2",
"nodemon": "^1.18.9", "nodemon": "^1.19.1",
"prettier": "^1.16.4", "prettier": "^1.17.1",
"pug-plain-loader": "^1.0.0", "pug-plain-loader": "^1.0.0",
"webpack-cli": "^3.3.1" "webpack-cli": "^3.3.2"
} }
} }

View file

@ -1,7 +1,10 @@
<template lang="pug"> <template lang="pug">
el-dialog(:title='$t("common.info")' visible el-card
:before-close='close')
h5 Chi siamo nuxt-link.float-right(to='/')
v-icon(name='times' color='red')
h3 {{$t('common.info')}}
p. p.
Gancio e' un progetto dell'<a href='https://autistici.org/underscore'>underscore hacklab</a> e uno dei Gancio e' un progetto dell'<a href='https://autistici.org/underscore'>underscore hacklab</a> e uno dei
servizi di <a href='https://cisti.org'>cisti.org</a>. servizi di <a href='https://cisti.org'>cisti.org</a>.

View file

@ -1,5 +1,9 @@
<template lang="pug"> <template lang="pug">
el-dialog(:before-close='close' :visible='open' :title="edit?$t('common.edit_event'):$t('common.add_event')") el-card
nuxt-link.float-right(to='/')
v-icon(name='times' color='red')
h5 {{edit?$t('common.edit_event'):$t('common.add_event')}}
el-form(v-loading='loading') el-form(v-loading='loading')
el-tabs.mb-2(v-model='activeTab') el-tabs.mb-2(v-model='activeTab')
@ -12,13 +16,6 @@
//- WHERE //- WHERE
el-tab-pane el-tab-pane
span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}} span(slot='label') <v-icon name='map-marker-alt'/> {{$t('common.where')}}
div {{$t('common.where')}}
el-popover(
placement="top-start"
width="400"
trigger="click")
v-icon(slot='reference' color='#ff9fc4' name='question-circle')
slot
p(v-html="$t('event.where_description')") p(v-html="$t('event.where_description')")
el-select.mb-3(v-model='event.place.name' el-select.mb-3(v-model='event.place.name'
@ -27,7 +24,7 @@
default-first-option default-first-option
) )
el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id') el-option(v-for='place in places' :label='place.name' :value='place.name' :key='place.id')
span {{place.name}} - {{place.weigth}} span {{place.name}}
div {{$t("common.address")}} div {{$t("common.address")}}
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'
@ -190,11 +187,6 @@ export default {
}, },
methods: { methods: {
...mapActions(['addEvent', 'updateEvent', 'updateMeta']), ...mapActions(['addEvent', 'updateEvent', 'updateMeta']),
close (done) {
this.$router.replace('/')
done()
},
eventToAttribute(event) { eventToAttribute(event) {
let e = { let e = {
key: event.id, key: event.id,

View file

@ -130,7 +130,8 @@ export default {
const users = await $axios.$get('/users') const users = await $axios.$get('/users')
const events = await $axios.$get('/event/unconfirmed') const events = await $axios.$get('/event/unconfirmed')
const settings = await $axios.$get('/settings') const settings = await $axios.$get('/settings')
return { users, events, settings, mastodon_instance: settings.mastodon_auth && settings.mastodon_auth.instance}
return { users, events, settings, mastodon_instance: settings && settings.mastodon_auth && settings.mastodon_auth.instance || ''}
} catch ( e ) { } catch ( e ) {
console.error(e) console.error(e)
} }

View file

@ -3,15 +3,15 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from 'vuex'
import { SHARED_CONF } from '../../config'
import List from '../../components/List' import List from '../../components/List'
import moment from 'dayjs' import moment from 'dayjs'
export default { export default {
layout: 'iframe', layout: 'iframe',
components: { List }, components: { List },
computed: mapState(['config']),
async asyncData ({ $axios, req, res }) { async asyncData ({ $axios, req, res }) {
const title = req.query.title || SHARED_CONF.title const title = req.query.title || config.title
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()

View file

@ -53,7 +53,6 @@
</template> </template>
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex' import { mapState, mapActions, mapGetters } from 'vuex'
import config from '@/config'
export default { export default {
name: 'Event', name: 'Event',

View file

@ -1,5 +1,11 @@
<template lang="pug"> <template lang="pug">
el-dialog(:title='$t("common.export")' visible :before-close='close') el-card
nuxt-link.float-right(to='/')
v-icon(name='times' color='red')
h5 {{$t('common.export')}}
p {{$t('export.intro')}} p {{$t('export.intro')}}
Search Search
//- li(v-if='filters.tags.length') {{$t('common.tags')}}: //- li(v-if='filters.tags.length') {{$t('common.tags')}}:
@ -59,7 +65,6 @@ 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')
export default { export default {
name: 'Export', name: 'Export',
@ -68,7 +73,7 @@ export default {
return { return {
type: 'email', type: 'email',
notification: { email: '' }, notification: { email: '' },
list: { title: SHARED_CONF.title }, list: { title: 'Gancio' },
} }
}, },
// filters, // filters,
@ -76,7 +81,7 @@ export default {
async add_notification () { async add_notification () {
if (!this.notification.email){ if (!this.notification.email){
Message({message:'Inserisci una mail', type: 'error'}) Message({message:'Inserisci una mail', type: 'error'})
return this.$refs.email.focus() // return this.$refs.email.focus()
} }
// await api.addNotification({ ...this.notification, filters: this.filters}) // await api.addNotification({ ...this.notification, filters: this.filters})
// this.$refs.modal.hide() // this.$refs.modal.hide()
@ -103,7 +108,7 @@ export default {
params.push(`places=${this.filters.places}`) params.push(`places=${this.filters.places}`)
} }
return `<iframe src="${SHARED_CONF.baseurl}/embed/list?${params.join('&')}"></iframe>` return `<iframe src="/embed/list?${params.join('&')}"></iframe>`
}, },
link () { link () {
const tags = this.filters.tags.join(',') const tags = this.filters.tags.join(',')
@ -119,7 +124,7 @@ export default {
} }
} }
return `${SHARED_CONF.baseurl}/api/export/${this.type}${query}` return `/api/export/${this.type}${query}`
}, },
showLink () { showLink () {
return (['feed', 'ics'].indexOf(this.type)>-1) return (['feed', 'ics'].indexOf(this.type)>-1)

View file

@ -2,13 +2,34 @@
#home #home
Nav Nav
Home Home
</template> </template>
<script> <script>
import Home from '~/components/Home.vue' import Home from '~/components/Home.vue'
import Nav from '~/components/Nav.vue' import Nav from '~/components/Nav.vue'
import { mapState } from 'vuex'
export default { export default {
name: 'Index', name: 'Index',
async asyncData ({ redirect, store }) {
// console.error('diocane', store.state.settings)
// const firstRun = store.state.settings.firstRun
// if (firstRun!==true) {
// redirect('/firstrun')
// }
},
async fetch ({ store, $axios }) {
const now = new Date()
const events = await $axios.$get(`/event/${now.getMonth()}/${now.getFullYear()}`)
console.error(events)
store.commit('setEvents', events)
const { tags, places } = await $axios.$get('/event/meta')
store.commit('update', { tags, places })
// const settings = await $axios.$get('/settings')
// store.commit('setSettings', settings)
},
computed: mapState(['events']),
components: { Nav, Home }, components: { Nav, Home },
} }
</script> </script>

View file

@ -1,17 +1,24 @@
<template lang='pug'> <template lang='pug'>
el-dialog(:title='$t("common.login")' :before-close='close' visible) el-card
el-form(v-loading='loading') nuxt-link.float-right(to='/')
v-icon(name='times' color='red')
h5 {{$t('common.login')}}
el-form(v-loading='loading' method='POST' action='/api/auth/login')
p(v-html="$t('login.description')") p(v-html="$t('login.description')")
el-input.mb-2(v-model='email' type='email' :placeholder='$t("common.email")' autocomplete='email' ref='email') el-input.mb-2(v-model='email' type='email' name='email'
i.el-icon-user(slot='prepend') :placeholder='$t("common.email")' autocomplete='email' ref='email')
v-icon(name='user' slot='prepend')
el-input.mb-1(v-model='password' @keyup.enter.native="submit" type='password' :placeholder='$t("common.password")') el-input.mb-1(v-model='password' @keyup.enter.native="submit" name='password'
i.el-icon-lock(slot='prepend') type='password' :placeholder='$t("common.password")')
v-icon(name='lock' slot='prepend')
el-button.mr-1(plain type="success" :disabled='!email || !password' @click='submit') {{$t('common.login')}} el-button.mr-1(plain type="success" native-type='submit'
:disabled='disabled' @click='submit') {{$t('common.login')}}
nuxt-link(to='/register') nuxt-link(to='/register')
el-button.mt-1(plain type="primary") {{$t('login.not_registered')}} el-button.mt-1(plain type="primary") {{$t('login.not_registered')}}
@ -35,6 +42,12 @@ export default {
loading: false loading: false
} }
}, },
computed: {
disabled () {
if (process.server) return false
return !this.email || !this.password
}
},
methods: { methods: {
close () { close () {
this.$router.replace('/') this.$router.replace('/')

View file

@ -1,18 +1,25 @@
<template lang='pug'> <template lang='pug'>
el-dialog(:title="$t('common.register')" visible :before-close='() => $router.replace("/")' @open='$refs.email.focus()') el-card
el-form
nuxt-link.float-right(to='/')
v-icon(name='times' color='red')
h5 {{$t('common.register')}}
el-form(method='POST' action='/api/user')
p(v-html="$t('register.description')") p(v-html="$t('register.description')")
el-input.mb-2(ref='email' v-model='user.email' type='email' required el-input.mb-2(ref='email' v-model='user.email' type='email' required
:placeholder='$t("common.email")' autocomplete='email') :placeholder='$t("common.email")' autocomplete='email' name='email')
span(slot='prepend') @ span(slot='prepend') @
el-input.mb-2(v-model='user.password' type="password" placeholder="Password") el-input.mb-2(v-model='user.password' type="password" placeholder="Password" name='password')
v-icon(name='lock' slot='prepend') v-icon(name='lock' slot='prepend')
el-input.mb-2(v-model='user.description' type="textarea" rows='3' :placeholder="$t('common.description')") el-input.mb-2(v-model='user.description' type="textarea" rows='3' :placeholder="$t('common.description')")
v-icon(name='envelope-open-text') v-icon(name='envelope-open-text')
el-button(plain type="success" icon='el-icon-arrow-right' :disabled='!user.password || !user.email || !user.description' @click='register') {{$t('common.send')}} el-button(plain type="success" native-type='submit'
:disabled='disabled'
@click='register') {{$t('common.send')}} <v-icon name='chevron-right'/>
</template> </template>
<script> <script>
@ -28,7 +35,10 @@ export default {
} }
}, },
computed: { computed: {
disabled () {
if (process.server) return false
return !this.user.password || !this.user.email || !this.user.description
}
}, },
methods: { methods: {
...mapActions(['login']), ...mapActions(['login']),

View file

@ -1,6 +1,6 @@
import Vue from 'vue' import Vue from 'vue'
import { Button, Select, Tag, Option, Table, FormItem, Card, Row, Col, Upload, Checkbox, import { Button, Select, Tag, Option, Table, FormItem, Card, Row, Col, Upload, Checkbox,
Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect, Badge, ButtonGroup, Divider, Form, Tabs, TabPane, Switch, Input, Loading, TimeSelect, Badge, ButtonGroup, Divider, Step, Steps,
TableColumn, ColorPicker, Pagination, Popover, Tooltip, Dialog, Calendar, Carousel, CarouselItem, TableColumn, ColorPicker, Pagination, Popover, Tooltip, Dialog, Calendar, Carousel, CarouselItem,
Container, Footer , Timeline, TimelineItem, Menu, MenuItem } from 'element-ui' Container, Footer , Timeline, TimelineItem, Menu, MenuItem } from 'element-ui'
import localeEn from 'element-ui/lib/locale/lang/en' import localeEn from 'element-ui/lib/locale/lang/en'
@ -11,6 +11,8 @@ locale.use(localeIt)
export default () => { export default () => {
Vue.use(Button) Vue.use(Button)
Vue.use(Divider) Vue.use(Divider)
Vue.use(Step)
Vue.use(Steps)
Vue.use(Checkbox) Vue.use(Checkbox)
Vue.use(Upload) Vue.use(Upload)
Vue.use(ButtonGroup) Vue.use(ButtonGroup)

View file

@ -3,21 +3,31 @@ import moment from 'dayjs'
import 'dayjs/locale/it' import 'dayjs/locale/it'
moment.locale('it') moment.locale('it')
function short_hour(datetime) {
if (datetime.minute() === 0) {
return 'h' + datetime.format('HH')
} else {
return 'h' + datetime.format('HH:mm')
}
}
export default (a) => { export default (a) => {
Vue.filter('linkify', value => value.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1">$1</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'))
Vue.filter('day', value => moment(value).format('ddd, D MMM')) Vue.filter('day', value => moment(value).format('dddd, D MMMM'))
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 => {
const start = moment(event.start_datetime)
const end = moment(event.end_datetime)
if (event.multidate) { if (event.multidate) {
return moment(event.start_datetime).format('ddd, D MMMM HH:mm') + ' - ' + moment(event.end_datetime).format('ddd, D MMMM') return `${start.format('ddd, D MMMM')} (${short_hour(start)}) - ${end.format('ddd, D MMMM')} (${short_hour(end)})`
} 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 `${start.format('ddd, D MMMM')} (${short_hour(start)}-${short_hour(end)}`
else else
return moment(event.start_datetime).format('dddd, D MMMM HH:mm') return `${start.format('dddd, D MMMM')} (${short_hour(start)})`
} }
}) })
} }

View file

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

View file

@ -24,6 +24,7 @@ import 'vue-awesome/icons/tags'
import 'vue-awesome/icons/chevron-right' import 'vue-awesome/icons/chevron-right'
import 'vue-awesome/icons/chevron-left' import 'vue-awesome/icons/chevron-left'
import 'vue-awesome/icons/search' import 'vue-awesome/icons/search'
import 'vue-awesome/icons/times'
import Icon from 'vue-awesome/components/Icon' import Icon from 'vue-awesome/components/Icon'

View file

@ -1,22 +1,25 @@
const { Op } = require('sequelize') const { Op } = require('sequelize')
const config = require('../../config') const { user: User } = require('./models')
const User = require('./models/user') const Settings = require('./controller/settings')
const Auth = { const Auth = {
async fillUser(req, res, next) { async fillUser(req, res, next) {
if (!req.user) return next() if (!req.user) return next()
req.user = await User.findOne({ req.user = await User.findOne({
where: { id: { [Op.eq]: req.user.id }, is_active: true } where: { id: { [Op.eq]: req.user.id }, is_active: true }
}).catch(e => {
res.sendStatus(404)
return next(false)
}) })
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)
.send({ message: 'Failed to authenticate token ' }) .send({ message: 'Failed to authenticate token ' })
} }
req.user = await User.findOne({ req.user = await User.findOne({
where: { id: { [Op.eq]: req.user.id }, is_active: true } where: { id: { [Op.eq]: req.user.id }, is_active: true }
}) })
@ -28,9 +31,22 @@ const Auth = {
next() next()
}, },
isAdmin(req, res, next) { isAdmin(req, res, next) {
if (!req.user) {
return res
.status(403)
.send({ message: 'Failed to authenticate token ' })
}
if (req.user.is_admin && req.user.is_active) return next() if (req.user.is_admin && req.user.is_active) return next()
return res.status(403).send({ message: 'Admin needed' }) return res.status(403).send({ message: 'Admin needed' })
},
async adminOrFirstRun(req, res, next) {
if (req.user && req.user.is_admin && req.user.is_active) return next()
const settings = await Settings.settings()
if (!settings.firstRun) {
return next()
} }
}
} }
module.exports = Auth module.exports = Auth

View file

@ -1,14 +0,0 @@
{
"development": {
"storage": "/home/les/dev/hacklab/gancio/db.sqlite",
"dialect": "sqlite",
"logging": false
},
"production": {
"username": "docker",
"password": "docker",
"database": "gancio",
"host": "db",
"dialect": "postgres"
}
}

View file

@ -1,12 +1,10 @@
const { User, Event, Comment, Tag } = require('../model')
const { SHARED_CONF } = require('../../../config')
const Mastodon = require('mastodon-api')
// const Sequelize = require('sequelize')
// const Op = Sequelize.Op
const settingsController = require('./settings')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const moment = require('moment') const moment = require('moment')
const { event: Event, comment: Comment, tag: Tag } = require('../model')
const config = require('../../config').SHARED_CONF
const Mastodon = require('mastodon-api')
const settingsController = require('./settings')
moment.locale('it') moment.locale('it')
const botController = { const botController = {
@ -47,7 +45,7 @@ const botController = {
const { access_token, instance } = mastodon_auth const { access_token, instance } = mastodon_auth
const bot = new Mastodon({ access_token, api_url: `https://${instance}/api/v1/` }) const bot = new Mastodon({ access_token, api_url: `https://${instance}/api/v1/` })
const status = `${event.title} @ ${event.place.name} ${moment(event.start_datetime).format('ddd, D MMMM HH:mm')} - const status = `${event.title} @ ${event.place.name} ${moment(event.start_datetime).format('ddd, D MMMM HH:mm')} -
${event.description.length > 200 ? event.description.substr(0, 200) + '...' : event.description} - ${event.tags.map(t => '#' + t.tag).join(' ')} ${SHARED_CONF.baseurl}/event/${event.id}` ${event.description.length > 200 ? event.description.substr(0, 200) + '...' : event.description} - ${event.tags.map(t => '#' + t.tag).join(' ')} ${config.baseurl}/event/${event.id}`
let media let media
if (event.image_path) { if (event.image_path) {
@ -58,9 +56,9 @@ ${event.description.length > 200 ? event.description.substr(0, 200) + '...' : ev
} }
return bot.post('statuses', { status, visibility: 'direct', media_ids: media ? [media.data.id] : [] }) return bot.post('statuses', { status, visibility: 'direct', media_ids: media ? [media.data.id] : [] })
}, },
// TOFIX: enable message deletion
async message (msg) { async message (msg) {
console.log(msg)
console.log(msg.data.accounts)
const replyid = msg.data.in_reply_to_id || msg.data.last_status.in_reply_to_id const replyid = msg.data.in_reply_to_id || msg.data.last_status.in_reply_to_id
if (!replyid) return if (!replyid) return
const event = await Event.findOne({ where: { activitypub_id: replyid } }) const event = await Event.findOne({ where: { activitypub_id: replyid } })
@ -71,9 +69,9 @@ ${event.description.length > 200 ? event.description.substr(0, 200) + '...' : ev
} }
const comment = await Comment.create({ const comment = await Comment.create({
activitypub_id: msg.data.last_status.id, activitypub_id: msg.data.last_status.id,
text: msg.data.last_status.content, // text: msg.data.last_status.content,
data: msg.data, data: msg.data,
author: msg.data.accounts[0].username // author: msg.data.accounts[0].username
}) })
event.addComment(comment) event.addComment(comment)
// const comment = await Comment.findOne( { where: {activitypub_id: msg.data.in_reply_to}} ) // const comment = await Comment.findOne( { where: {activitypub_id: msg.data.in_reply_to}} )
@ -93,5 +91,5 @@ ${event.description.length > 200 ? event.description.substr(0, 200) + '...' : ev
} }
} }
setTimeout(botController.initialize, 2000) // setTimeout(botController.initialize, 2000)
module.exports = botController module.exports = botController

View file

@ -2,7 +2,7 @@ const crypto = require('crypto')
const moment = require('moment') const moment = require('moment')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const lodash = require('lodash') const lodash = require('lodash')
const { User, Event, Comment, Tag, Place, Notification } = require('../model') const { event: Event, comment: Comment, tag: Tag, place: Place, notification: Notification } = require('../models')
const Sequelize = require('sequelize') const Sequelize = require('sequelize')
const eventController = { const eventController = {

View file

@ -1,6 +1,6 @@
const { Event, Comment, Tag, Place } = require('../model') const { event: Event, place: Place } = require('../models')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const config = require('../../../config') const config = require('../../config').SHARED_CONF
const moment = require('moment') const moment = require('moment')
const ics = require('ics') const ics = require('ics')
@ -65,7 +65,6 @@ const exportController = {
}) })
res.type('text/calendar; charset=UTF-8') res.type('text/calendar; charset=UTF-8')
const { error, value } = ics.createEvents(eventsMap) const { error, value } = ics.createEvents(eventsMap)
console.log(error, value)
res.send(value) res.send(value)
} }
} }

View file

@ -1,10 +1,11 @@
const { Settings } = require('../model')
const { SHARED_CONF } = require('../../../config')
const Mastodon = require('mastodon-api') const Mastodon = require('mastodon-api')
const { setting: Setting } = require('../models')
const config = require('../../config').SHARED_CONF
const settingsController = { const settingsController = {
async setAdminSetting (key, value) { async setAdminSetting (key, value) {
await Settings.findOrCreate({ where: { key }, await Setting.findOrCreate({ where: { key },
defaults: { value } }) defaults: { value } })
.spread((settings, created) => { .spread((settings, created) => {
if (!created) return settings.update({ value }) if (!created) return settings.update({ value })
@ -16,11 +17,15 @@ const settingsController = {
res.json(settings) res.json(settings)
}, },
async getConfig (req, res) {
res.json(config)
},
async getAuthURL(req, res) { async getAuthURL(req, res) {
const instance = req.body.instance const instance = req.body.instance
const callback = `${SHARED_CONF.baseurl}/api/settings/oauth` const callback = `${config.baseurl}/api/settings/oauth`
const { client_id, client_secret } = await Mastodon.createOAuthApp(`https://${instance}/api/v1/apps`, const { client_id, client_secret } = await Mastodon.createOAuthApp(`https://${instance}/api/v1/apps`,
SHARED_CONF.title, 'read write', callback) config.title, 'read write', callback)
const url = await Mastodon.getAuthorizationUrl(client_id, client_secret, const url = await Mastodon.getAuthorizationUrl(client_id, client_secret,
`https://${instance}`, 'read write', callback) `https://${instance}`, 'read write', callback)
@ -31,19 +36,16 @@ const settingsController = {
async code(req, res) { async code(req, res) {
const code = req.query.code const code = req.query.code
let client_id, client_secret, instance let client_id, client_secret, instance
const callback = `${SHARED_CONF.baseurl}/api/settings/oauth` const callback = `${config.baseurl}/api/settings/oauth`
console.error('sono dentro CODEEEEEEEEEE', code)
const settings = await settingsController.settings() const settings = await settingsController.settings()
console.log(settings);
({ client_id, client_secret, instance } = settings.mastodon_auth) ({ client_id, client_secret, instance } = settings.mastodon_auth)
try { try {
const token = await Mastodon.getAccessToken(client_id, client_secret, code, const token = await Mastodon.getAccessToken(client_id, client_secret, code,
`https://${instance}`, callback) `https://${instance}`, callback)
const mastodon_auth = { client_id, client_secret, access_token: token, instance } const mastodon_auth = { client_id, client_secret, access_token: token, instance }
console.error(mastodon_auth)
await settingsController.setAdminSetting('mastodon_auth', mastodon_auth) await settingsController.setAdminSetting('mastodon_auth', mastodon_auth)
res.redirect('/admin') res.redirect('/admin')
@ -53,13 +55,11 @@ const settingsController = {
}, },
async settings () { async settings () {
const settings = await Settings.findAll() console.error('ma sono dentro settings ?!?!')
const map = {} const settings = await Setting.findAll()
settings.forEach(setting => { return settings
map[setting.key] = setting.value },
})
return map
}
} }
module.exports = settingsController module.exports = settingsController

View file

@ -4,14 +4,14 @@ const crypto = require('crypto')
const jwt = require('jsonwebtoken') const jwt = require('jsonwebtoken')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const jsonwebtoken = require('jsonwebtoken') const jsonwebtoken = require('jsonwebtoken')
const User = require('../models/user') const { SECRET_CONF, SHARED_CONF } = require('../../config')
const { SECRET_CONF, SHARED_CONF } = require('../../../config')
const mail = require('../mail') const mail = require('../mail')
const { Event, Tag, Place } = require('../models/event') const { user: User, event: Event, tag: Tag, place: Place } = require('../models')
const eventController = require('./event') const eventController = require('./event')
const userController = { const userController = {
async login(req, res) { async login(req, res) {
// find the user // find the user
const user = await User.findOne({ where: { email: { [Op.eq]: req.body && req.body.email } } }) const user = await User.findOne({ where: { email: { [Op.eq]: req.body && req.body.email } } })
if (!user) { if (!user) {
@ -89,7 +89,9 @@ const userController = {
eventDetails.image_path = req.file.filename eventDetails.image_path = req.file.filename
} }
console.error('prima la creazione di evento')
let event = await Event.create(eventDetails) let event = await Event.create(eventDetails)
console.error('dopo la creazione di evento')
// create place if needs to // create place if needs to
let place let place
@ -195,7 +197,10 @@ const userController = {
}, },
async current(req, res) { async current(req, res) {
if (req.user)
res.json(req.user) res.json(req.user)
else
res.sendStatus(404)
}, },
async getAll(req, res) { async getAll(req, res) {
@ -219,7 +224,6 @@ const userController = {
}, },
async register(req, res) { async register(req, res) {
const n_users = await User.count() const n_users = await User.count()
try { try {
@ -234,14 +238,17 @@ const userController = {
try { try {
mail.send([user.email, SECRET_CONF.admin], 'register', { user, config: SHARED_CONF }) mail.send([user.email, SECRET_CONF.admin], 'register', { user, config: SHARED_CONF })
} catch (e) { } catch (e) {
console.error(e)
return res.status(400).json(e) return res.status(400).json(e)
} }
const payload = { email: user.email } const payload = {
id: user.id,
email: user.email,
scope: [user.is_admin ? 'admin' : 'user']
}
const token = jwt.sign(payload, SECRET_CONF.secret) const token = jwt.sign(payload, SECRET_CONF.secret)
res.json({ user, token }) res.json({ token })
// res.redirect('/')
} catch (e) { } catch (e) {
console.error(e)
res.status(404).json(e) res.status(404).json(e)
} }
} }

View file

@ -1,6 +0,0 @@
const Sequelize = require('sequelize')
const { SECRET_CONF } = require('../../config.js')
const db = new Sequelize(SECRET_CONF.db)
// db.sync()
module.exports = db

View file

@ -1,27 +1,16 @@
const express = require('express') const express = require('express')
const multer = require('multer') const multer = require('multer')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const expressJwt = require('express-jwt')
const { fillUser, isAuth, isAdmin } = require('./auth') const { fillUser, isAuth, isAdmin } = require('./auth')
const eventController = require('./controller/event') const eventController = require('./controller/event')
const exportController = require('./controller/export') const exportController = require('./controller/export')
const userController = require('./controller/user') const userController = require('./controller/user')
const settingsController = require('./controller/settings') const settingsController = require('./controller/settings')
const { SECRET_CONF } = require('../../config')
const cookieParser = require('cookie-parser')
const expressJwt = require('express-jwt') const { SECRET_CONF } = require('../config')
const jwt = expressJwt({
secret: SECRET_CONF.secret,
credentialsRequired: false,
getToken: req => {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.cookies && req.cookies['auth._token.local']) {
const tmp = req.cookies['auth._token.local'].split(' ');
return tmp[1]
}
return null
}
})
const storage = require('./storage')({ const storage = require('./storage')({
destination: 'uploads/' destination: 'uploads/'
@ -30,6 +19,24 @@ const storage = require('./storage')({
const upload = multer({ storage }) const upload = multer({ storage })
const api = express.Router() const api = express.Router()
api.use(cookieParser()) api.use(cookieParser())
api.use(bodyParser.urlencoded({ extended: false }))
api.use(bodyParser.json())
const jwt = expressJwt({
secret: SECRET_CONF.secret,
credentialsRequired: false,
// getToken: req => {
// // if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
// // return req.headers.authorization.split(' ')[1];
// if (req.cookies && req.cookies['token']) {
// console.error(req.cookies['token'])
// return req.cookies['token']
// }
// return null
// }
})
// AUTH // AUTH
api.post('/auth/login', userController.login) api.post('/auth/login', userController.login)
api.post('/auth/logout', userController.logout) api.post('/auth/logout', userController.logout)
@ -77,8 +84,9 @@ api.get('/event/unconfirmed', jwt, isAuth, isAdmin, eventController.getUnconfirm
api.post('/event/notification', eventController.addNotification) api.post('/event/notification', eventController.addNotification)
api.delete('/event/notification/:code', eventController.delNotification) api.delete('/event/notification/:code', eventController.delNotification)
api.get('/settings', settingsController.getAdminSettings) api.get('/config', settingsController.getConfig)
api.post('/settings', settingsController.setAdminSetting) api.get('/settings', jwt, fillUser, isAdmin, settingsController.getAdminSettings)
api.post('/settings', jwt, fillUser, isAdmin, settingsController.setAdminSetting)
// get event // get event
api.get('/event/:event_id', eventController.get) api.get('/event/:event_id', eventController.get)

View file

@ -1,8 +1,8 @@
const Email = require('email-templates') const Email = require('email-templates')
const path = require('path') const path = require('path')
const { SECRET_CONF, SHARED_CONF } = require('../../config')
const moment = require('moment') const moment = require('moment')
moment.locale(SHARED_CONF.locale) const config = require('../config')
moment.locale(config.SHARED_CONF.locale)
const mail = { const mail = {
send (addresses, template, locals) { send (addresses, template, locals) {
@ -16,25 +16,25 @@ const mail = {
} }
}, },
message: { message: {
from: `${SHARED_CONF.title} <${SECRET_CONF.smtp.auth.user}>` from: `${config.SHARED_CONF.title} <${config.SECRET_CONF.smtp.auth.user}>`
}, },
send: true, send: true,
i18n: { i18n: {
directory: path.join(__dirname, '..', '..', 'locales', 'email'), directory: path.join(__dirname, '..', '..', 'locales', 'email'),
defaultLocale: SHARED_CONF.locale defaultLocale: config.SHARED_CONF.locale
}, },
transport: SECRET_CONF.smtp transport: config.SECRET_CONF.smtp
}) })
return email.send({ return email.send({
template, template,
message: { message: {
to: addresses, to: addresses,
bcc: SECRET_CONF.admin bcc: config.SECRET_CONF.admin
}, },
locals: { locals: {
...locals, ...locals,
locale: SHARED_CONF.locale, locale: config.SHARED_CONF.locale,
config: SHARED_CONF, config: config.SHARED_CONF,
datetime: datetime => moment(datetime).format('ddd, D MMMM HH:mm') datetime: datetime => moment(datetime).format('ddd, D MMMM HH:mm')
} }
}) })

View file

@ -1,14 +0,0 @@
const User = require('./models/user')
const { Event, Comment, Tag, Place, Notification, EventNotification } = require('./models/event')
const Settings = require('./models/settings')
module.exports = {
User,
Event,
Comment,
Tag,
Place,
Notification,
EventNotification,
Settings
}

View file

@ -0,0 +1,13 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const comment = sequelize.define('comment', {
activitypub_id: DataTypes.BIGINT,
data: DataTypes.JSON
}, {});
comment.associate = function(models) {
comment.belongsTo(models.event)
// Event.hasMany(Comment)
// associations can be defined here
};
return comment;
};

View file

@ -1,81 +1,31 @@
const Sequelize = require('sequelize') 'use strict';
const db = require('../db') module.exports = (sequelize, DataTypes) => {
const User = require('./user') const event = sequelize.define('event', {
title: DataTypes.STRING,
const Event = db.define('event', { slug: DataTypes.STRING,
title: Sequelize.STRING, description: DataTypes.TEXT,
description: Sequelize.TEXT, multidate: DataTypes.BOOLEAN,
multidate: Sequelize.BOOLEAN, start_datetime: {
start_datetime: { type: Sequelize.DATE, index: true }, type: DataTypes.DATE,
end_datetime: { type: Sequelize.DATE, index: true }, index: true
image_path: Sequelize.STRING, },
is_visible: Sequelize.BOOLEAN, end_datetime: DataTypes.DATE,
activitypub_id: { type: Sequelize.BIGINT, index: true }, image_path: DataTypes.STRING,
activitypub_ids: { is_visible: DataTypes.BOOLEAN,
type: Sequelize.ARRAY(Sequelize.BIGINT), activitypub_id: {
index: true, type: DataTypes.BIGINT,
defaultValue: []
}
})
const Tag = db.define('tag', {
tag: { type: Sequelize.STRING, index: true, unique: true, primaryKey: true },
weigth: { type: Sequelize.INTEGER, defaultValue: 0 },
color: { type: Sequelize.STRING }
})
const Comment = db.define('comment', {
activitypub_id: { type: Sequelize.BIGINT, index: true },
data: Sequelize.JSON,
// url: Sequelize.STRING,
author: Sequelize.STRING,
text: Sequelize.STRING
})
const Notification = db.define('notification', {
filters: Sequelize.JSON,
email: Sequelize.STRING,
remove_code: Sequelize.STRING,
type: {
type: Sequelize.ENUM,
values: ['mail', 'admin_email', 'mastodon']
}
})
const Place = db.define('place', {
name: { type: Sequelize.STRING, unique: true, index: true },
weigth: { type: Sequelize.INTEGER, defaultValue: 0 },
address: { type: Sequelize.STRING }
})
Comment.belongsTo(Event)
Event.hasMany(Comment)
Event.belongsToMany(Tag, { through: 'tagEvent' })
Tag.belongsToMany(Event, { through: 'tagEvent' })
const EventNotification = db.define('EventNotification', {
status: {
type: Sequelize.ENUM,
values: ['new', 'sent', 'error'],
defaultValue: 'new',
index: true index: true
} }
}) }, {});
event.associate = function(models) {
Event.belongsToMany(Notification, { through: EventNotification }) event.belongsTo(models.place)
Notification.belongsToMany(Event, { through: EventNotification }) event.belongsTo(models.user)
event.belongsToMany(models.tag, { through: 'event_tags' })
Event.belongsTo(User) event.belongsToMany(models.notification, { through: 'event_notification' })
Event.belongsTo(Place) event.hasMany(models.comment)
// Tag.belongsToMany(Event, { through: 'tagEvent' })
User.hasMany(Event) // Event.hasMany(models.Tag)
Place.hasMany(Event) // associations can be defined here
};
async function init() { return event;
await Notification.findOrCreate({ where: { type: 'mastodon', filters: { is_visible: true } } }) };
// await Notification.findOrCreate({ where: { type: 'admin_email', filters: { is_visible: false } } })
}
init()
module.exports = { Event, Comment, Tag, Place, Notification, EventNotification }

View file

@ -0,0 +1,16 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const eventNotification = sequelize.define('eventNotification', {
status: {
type: DataTypes.ENUM,
values: ['new', 'sent', 'error'],
defaultValue: 'new',
index: true
}
}, {});
eventNotification.associate = function(models) {
// associations can be defined here
};
return eventNotification;
};

View file

@ -1,29 +1,31 @@
const fs = require('fs') 'use strict';
const path = require('path')
const Sequelize = require('sequelize')
const basename = path.basename(__filename)
const config = require('../../../config')
const db = {}
const sequelize = new Sequelize(config.db) const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = require(__dirname + '/../../config.js').SECRET_CONF.db
const db = {};
let sequelize = new Sequelize(config);
fs fs
.readdirSync(__dirname) .readdirSync(__dirname)
.filter(file => { .filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js') return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
}) })
.forEach(file => { .forEach(file => {
const model = sequelize['import'](path.join(__dirname, file)) const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model; db[model.name] = model;
}); });
Object.keys(db).forEach(modelName => { Object.keys(db).forEach(modelName => {
if (db[modelName].associate) { if (db[modelName].associate) {
db[modelName].associate(db) db[modelName].associate(db);
} }
}) });
db.sequelize = sequelize db.sequelize = sequelize;
db.Sequelize = Sequelize db.Sequelize = Sequelize;
module.exports = db; module.exports = db;

View file

@ -0,0 +1,17 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const notification = sequelize.define('notification', {
filters: DataTypes.JSON,
email: DataTypes.STRING,
remove_code: DataTypes.STRING,
type: {
type: DataTypes.ENUM,
values: ['mail', 'admin_email', 'mastodon']
}
}, {});
notification.associate = function(models) {
notification.belongsToMany(models.event, { through: 'event_notification' })
// associations can be defined here
};
return notification;
};

View file

@ -0,0 +1,15 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const place = sequelize.define('place', {
name: DataTypes.STRING,
address: DataTypes.STRING,
weigth: DataTypes.INTEGER
}, {});
place.associate = function(models) {
// associations can be defined here
place.hasMany(models.event)
};
return place;
};

View file

@ -0,0 +1,14 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const setting = sequelize.define('setting', {
key: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
index: true,
},
value: DataTypes.JSON
}, {});
return setting;
};

View file

@ -1,9 +0,0 @@
const db = require('../db')
const Sequelize = require('sequelize')
const Settings = db.define('settings', {
key: { type: Sequelize.STRING, primaryKey: true, index: true },
value: Sequelize.JSON
})
module.exports = Settings

19
server/api/models/tag.js Normal file
View file

@ -0,0 +1,19 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const tag = sequelize.define('tag', {
tag: {
type: DataTypes.STRING,
index: true,
primaryKey: true
},
weigth: DataTypes.INTEGER,
color: DataTypes.STRING
}, {});
tag.associate = function(models) {
tag.belongsToMany(models.event, { through: 'event_tags' })
// associations can be defined here
};
return tag;
};

View file

@ -1,34 +1,39 @@
const Sequelize = require('sequelize') 'use strict';
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt')
const db = require('../db')
const User = db.define('user', { module.exports = (sequelize, DataTypes) => {
const user = sequelize.define('user', {
email: { email: {
type: Sequelize.STRING, type: DataTypes.STRING,
unique: { msg: 'err.register_error' }, unique: { msg: 'err.register_error' },
index: true, index: true,
allowNull: false allowNull: false
}, },
description: Sequelize.TEXT, description: DataTypes.TEXT,
password: Sequelize.STRING, password: DataTypes.STRING,
recover_code: Sequelize.STRING, recover_code: DataTypes.STRING,
is_admin: Sequelize.BOOLEAN, is_admin: DataTypes.BOOLEAN,
is_active: Sequelize.BOOLEAN, is_active: DataTypes.BOOLEAN
mastodon_auth: Sequelize.JSON }, {});
})
User.prototype.comparePassword = async function (pwd) { user.associate = function(models) {
// associations can be defined here
user.hasMany(models.event)
};
user.prototype.comparePassword = async function (pwd) {
if (!this.password) return false if (!this.password) return false
const ret = await bcrypt.compare(pwd, this.password) const ret = await bcrypt.compare(pwd, this.password)
return ret return ret
} }
User.beforeSave(async (user, options) => { user.beforeSave(async (user, options) => {
if (user.changed('password')) { if (user.changed('password')) {
const salt = await bcrypt.genSalt(10) const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(user.password, salt) const hash = await bcrypt.hash(user.password, salt)
user.password = hash user.password = hash
} }
}) })
module.exports = User return user;
};

View file

@ -0,0 +1,23 @@
doctype xml
rss(version='2.0')
channel
title #{config.title}
link #{config.baseurl}
description #{config.description}
language #{config.locale}
//- if events.length
lastBuildDate= new Date(posts[0].publishedAt).toUTCString()
each event in events
item
title= event.title
link #{config.baseurl}/event/#{event.id}
description
| <![CDATA[
| <h4>#{event.title}</h4>
| <strong>#{event.place.name} - #{event.place.address}</strong>
| #{moment(event.start_datetime).format("dddd, D MMMM HH:mm")}<br/>
| <img src="#{config.apiurl}/../uploads/#{event.image_path}"/>
| <pre>!{event.description}</pre>
| ]]>
pubDate= new Date(event.createdAt).toUTCString()
guid(isPermaLink='false') #{config.baseurl}/event/#{event.id}

View file

@ -3,8 +3,6 @@
*/ */
const env = process.env.NODE_ENV || 'development' const env = process.env.NODE_ENV || 'development'
console.log(__dirname + '/db.sqlite')
/** /**
* Database configuration * Database configuration
* `development` configuration is enabled running `yarn dev` * `development` configuration is enabled running `yarn dev`
@ -15,44 +13,47 @@ const DB_CONF = {
development: { development: {
storage: __dirname + '/db.sqlite', storage: __dirname + '/db.sqlite',
dialect: 'sqlite', dialect: 'sqlite',
logging: false
}, },
production: { production: {
storage: __dirname + '/db.sqlite', username: '',
dialect: 'sqlite', password: '',
database: 'gancio',
host: 'localhost',
dialect: 'postgres',
logging: false logging: false
// username: 'docker', },
// password: 'docker',
// database: 'gancio',
// host: 'db',
// dialect: 'postgres',
// logging: false
}
} }
const SECRET_CONF = {
// where events/users confirmation email are sent
admin: 'gancio@example.com',
db: DB_CONF[env],
// jwt salt secret (generate it randomly)
secret: '',
// smtp account to send email
smtp: {
host: process.env.SMTP_HOST || 'mail.example.com',
secure: true,
auth: {
user: process.env.SMTP_USER || 'gancio@example.com',
pass: process.env.SMTP_PASS || ''
}
},
}
/** /**
* Main Gancio configuration * Main Gancio configuration
*/ */
const GANCIO_CONF = { const SHARED_CONF = {
locale: 'it', locale: 'it',
title: 'GANCIO', title: 'GANCIO',
description: 'A calendar for radical communities', description: 'A calendar for radical communities',
baseurl: '',
db: DB_CONF[env],
admin_email: process.env.ADMIN_EMAIL, baseurl: env === 'development' ? 'http://localhost:3000': 'https://gancio.example.com',
env
smtp: {
host: process.env.SMTP_HOST,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
},
secret: 'notsosecret'
} }
module.exports = GANCIO_CONF module.exports = { SHARED_CONF, SECRET_CONF }

41
server/firstrun.js Normal file
View file

@ -0,0 +1,41 @@
// check config.js existance
const fs = require('fs')
const path = require('path')
const config_path = path.join(__dirname, 'config.js')
if (!fs.existsSync(config_path)) {
console.error(`Configuration file not found at '${config_path}. Please copy 'config.example.js' and modify it.`)
process.exit(1)
}
const { SECRET_CONF, SHARED_CONF } = require(config_path)
if (!SECRET_CONF.secret) {
console.error(`Please specify a random 'secret' in '${config_path}'!`)
process.exit(1);
}
const Sequelize = require('sequelize')
let db
try {
db = new Sequelize(SECRET_CONF.db)
} catch(e) {
console.error(`DB Error: check '${SHARED_CONF.env}' configuration.\n (sequelize error -> ${e})`)
process.exit(1)
}
// return db existence
module.exports = db.authenticate()
.then ( () => {
require('./api/models')
if (SHARED_CONF.env === 'development') {
console.error('DB Force sync')
return db.sync({force: true})
}
})
.catch(e => {
console.error(e)
console.error(`DB Error: check '${SHARED_CONF.env}' configuration\n (sequelize error -> ${e})`)
process.exit(1)
})

View file

@ -1,23 +1,12 @@
const firstRun = require('./firstrun')
const express = require('express') const express = require('express')
const consola = require('consola') const consola = require('consola')
const morgan = require('morgan') const morgan = require('morgan')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const path = require('path') const path = require('path')
const { Nuxt, Builder } = require('nuxt')
const app = express() const app = express()
const cors = require('cors') const { Nuxt, Builder } = require('nuxt')
const notifier = require('./notifier')
const corsConfig = {
allowedHeaders: ['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')
async function start() { async function start() {
// Init Nuxt.js // Init Nuxt.js
@ -34,12 +23,8 @@ async function start() {
} }
// Give nuxt middleware to express // Give nuxt middleware to express
app.use(cors(corsConfig))
app.use(morgan('dev')) app.use(morgan('dev'))
app.use('/media/', express.static(path.join(__dirname, '..', 'uploads'))) app.use('/media/', express.static(path.join(__dirname, '..', 'uploads')))
app.use(cookieParser())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(nuxt.render) app.use(nuxt.render)
// Listen the server // Listen the server
@ -49,5 +34,5 @@ async function start() {
badge: true badge: true
}) })
} }
start()
notifier.startLoop(20) firstRun.then(start)

View file

@ -0,0 +1,38 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('comments', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
eventId: {
type: Sequelize.INTEGER,
references: {
model: 'event',
key: 'id'
}
},
activitypub_id: {
type: Sequelize.BIGINT,
index: true,
},
data: {
type: Sequelize.JSON
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('comments');
}
};

View file

@ -0,0 +1,45 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
email: {
type: Sequelize.STRING,
unique: { msg: 'err.register_error' },
index: true,
allowNull: false
},
description: {
type: Sequelize.TEXT
},
password: {
type: Sequelize.STRING
},
recover_code: {
type: Sequelize.STRING
},
is_admin: {
type: Sequelize.BOOLEAN
},
is_active: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('users');
}
};

View file

@ -0,0 +1,66 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('events', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
title: {
type: Sequelize.STRING
},
slug: {
type: Sequelize.STRING,
index: true,
},
description: {
type: Sequelize.TEXT
},
multidate: {
type: Sequelize.BOOLEAN
},
start_datetime: {
type: Sequelize.DATE
},
end_datetime: {
type: Sequelize.DATE
},
image_path: {
type: Sequelize.STRING
},
is_visible: {
type: Sequelize.BOOLEAN
},
activitypub_id: {
type: Sequelize.BIGINT
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
}
},
placeId: {
type: Sequelize.INTEGER,
references: {
model: 'places',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('events');
}
};

View file

@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('places', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
address: {
type: Sequelize.STRING
},
weigth: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('places');
}
};

View file

@ -0,0 +1,37 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('notifications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
filters: {
type: Sequelize.JSON
},
email: {
type: Sequelize.STRING
},
remove_code: {
type: Sequelize.STRING
},
type: {
type: Sequelize.ENUM,
values: ['mail', 'admin_email', 'mastodon']
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('notifications');
}
};

View file

@ -0,0 +1,29 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('tags', {
tag: {
type: Sequelize.STRING,
allowNull: false,
primaryKey: true
},
weigth: {
type: Sequelize.INTEGER
},
color: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('tags');
}
};

View file

@ -0,0 +1,38 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('event_notification', {
eventId: {
type: Sequelize.INTEGER,
references: {
model: 'events',
key: 'id'
}
},
notificationId: {
type: Sequelize.INTEGER,
references: {
model: 'notifications',
key: 'id'
}
},
status: {
type: Sequelize.ENUM,
values: ['new', 'sent', 'error'],
defaultValue: 'new',
index: true
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('event_notification');
}
};

View file

@ -0,0 +1,27 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('settings', {
key: {
type: Sequelize.STRING,
primaryKey: true,
allowNull: false,
index: true,
},
value: {
type: Sequelize.JSON
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('settings');
}
};

View file

@ -0,0 +1,32 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('event_tags', {
eventId: {
type: Sequelize.INTEGER,
references: {
model: 'events',
key: 'id'
}
},
tagTag: {
type: Sequelize.STRING,
references: {
model: 'tags',
key: 'tag'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('event_tags');
}
};

View file

@ -1,10 +1,10 @@
// const mail = require('./mail') // const mail = require('./mail')
const bot = require('./api/controller/bot') const bot = require('./api/controller/bot')
const settingsController = require('./api/controller/settings') const settingsController = require('./api/controller/settings')
// const config = require('./api/config.js') const config = require('./config.js')
const { Event, Notification, EventNotification, const { Event, Notification, EventNotification,
User, Place, Tag } = require('./api/model') User, Place, Tag } = require('./api/models')
let settings let settings
async function sendNotification (notification, event, eventNotification) { async function sendNotification (notification, event, eventNotification) {

View file

@ -2,29 +2,26 @@ import moment from 'dayjs'
import intersection from 'lodash/intersection' import intersection from 'lodash/intersection'
import map from 'lodash/map' import map from 'lodash/map'
export const state = () => ({ export const state = () => ({
config: {},
events: [], events: [],
user: {},
locale: 'it',
logged: false,
token: '',
tags: [], tags: [],
places: [], places: [],
settings: {},
filters: { filters: {
tags: [], tags: [],
places: [] places: [],
},
show_past_events: false, show_past_events: false,
show_recurrent_events: false,
show_pinned_event: false
},
}) })
export const getters = { export const getters = {
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
// TOFIX: use lodash // TOFIX: use lodash
@ -95,6 +92,12 @@ export const mutations = {
}, },
showPastEvents (state, show) { showPastEvents (state, show) {
state.show_past_events = show state.show_past_events = show
},
setSettings (state, settings) {
state.settings = settings
},
setConfig (state, config) {
state.config = config
} }
} }
@ -130,5 +133,11 @@ export const actions = {
}, },
showPastEvents({ commit }, show) { showPastEvents({ commit }, show) {
commit('showPastEvents', show) commit('showPastEvents', show)
},
setSettings({ commit }, settings) {
commit('setSettings', settings)
},
setConfig({ commit }, config) {
commit('setConfig', config)
} }
} }