From a3e75a18306ddd6ae3c80b32cba55d15226f426f Mon Sep 17 00:00:00 2001 From: lesion Date: Thu, 15 Dec 2022 09:57:42 +0100 Subject: [PATCH 01/33] v1.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38712722..4e8ceceb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gancio", - "version": "1.6.0", + "version": "1.6.1", "description": "A shared agenda for local communities", "author": "lesion", "scripts": { From 32c9d27499d6eb2faa8e0e4133767d7bd17022d7 Mon Sep 17 00:00:00 2001 From: Renne Rocha Date: Thu, 15 Dec 2022 14:16:27 +0000 Subject: [PATCH 02/33] Translated using Weblate (Portuguese) Currently translated at 94.4% (290 of 307 strings) Translation: Gancio/Web Translate-URL: https://hosted.weblate.org/projects/gancio/web/pt/ --- locales/pt.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index a74aa379..c0c91e77 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -189,7 +189,10 @@ "known_users": "Usuários conhecidos", "created_at": "Criado em", "hide_calendar": "Ocultar calendário", - "blocked": "Bloqueado" + "blocked": "Bloqueado", + "admin_email": "E-mail do admin", + "tilelayer_provider_attribution": "Atribuição", + "geolocation": "Geolocalização" }, "event": { "follow_me_description": "Uma das maneiras de se manter atualizado com os eventos publicados aqui em {title},\né seguir a conta {account} no Fediverso, por exemplo via Mastodon, e possivelmente adicionar recursos para um evento a partir de lá.

\nSe você nunca ouviu falar sobre Mastodon ou do Fediverso nós recomendamos ler este artigo.

Entre com sua instância abaixo (e.g. mastodon.social)", From 40a9b8fd62c3e50d6cc3975e247afb030c380549 Mon Sep 17 00:00:00 2001 From: sedum Date: Tue, 20 Dec 2022 06:25:50 +0100 Subject: [PATCH 03/33] fix typo in nominatim docs --- docs/install/nominatim.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install/nominatim.md b/docs/install/nominatim.md index 034864ee..5202861a 100644 --- a/docs/install/nominatim.md +++ b/docs/install/nominatim.md @@ -106,7 +106,7 @@ docker-compose pull See [Requirements](#requirements) about downloading the `.osm.pbf` files ```bash cd docs/docker/nominatim/ -wget https://download.geofabrik.de/europe/italy/nord-ovest-updates/nord-ovest-latest.osm.pbf \ +wget https://download.geofabrik.de/europe/italy/nord-ovest-latest.osm.pbf \ ./nominatim/data/default.osm.pbf ``` From e8f744960b95f4c87f8047b79314939093f86e8f Mon Sep 17 00:00:00 2001 From: sedum Date: Tue, 20 Dec 2022 06:26:37 +0100 Subject: [PATCH 04/33] add missing photon osm_property locality --- components/WhereInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/WhereInput.vue b/components/WhereInput.vue index 0caa47c4..1b41a0ee 100644 --- a/components/WhereInput.vue +++ b/components/WhereInput.vue @@ -256,7 +256,7 @@ export default { this.addressList = [] } } else if (this.geocoding_provider_type == "Photon") { - let photon_properties = ['housenumber', 'street', 'district', 'city', 'county', 'state', 'postcode', 'country'] + let photon_properties = ['housenumber', 'street', 'locality', 'district', 'city', 'county', 'state', 'postcode', 'country'] if (ret) { this.addressList = ret.features.map(v => { From cbed0288febff621029d6a71b0c9087c7dad439c Mon Sep 17 00:00:00 2001 From: lesion Date: Fri, 23 Dec 2022 01:08:14 +0100 Subject: [PATCH 05/33] models initialization refactored, better dev experience as backend hmr is working --- CHANGELOG | 4 + package.json | 2 +- server/api/controller/announce.js | 3 +- server/api/controller/ap_user.js | 2 +- server/api/controller/collection.js | 20 +- server/api/controller/event.js | 39 +-- server/api/controller/export.js | 4 +- server/api/controller/instance.js | 5 +- server/api/controller/metrics.js | 2 +- server/api/controller/oauth.js | 5 +- server/api/controller/place.js | 4 +- server/api/controller/plugins.js | 8 +- server/api/controller/resource.js | 4 +- server/api/controller/settings.js | 13 +- server/api/controller/setup.js | 10 +- server/api/controller/tag.js | 47 +-- server/api/controller/user.js | 2 +- server/api/index.js | 424 +++++++++++++------------ server/api/models/announcement.js | 18 +- server/api/models/ap_user.js | 11 +- server/api/models/collection.js | 48 ++- server/api/models/event.js | 195 +++++------- server/api/models/eventnotification.js | 12 +- server/api/models/filter.js | 44 ++- server/api/models/index.js | 80 ++++- server/api/models/instance.js | 17 +- server/api/models/models.js | 2 + server/api/models/notification.js | 13 +- server/api/models/oauth_client.js | 13 +- server/api/models/oauth_code.js | 19 +- server/api/models/oauth_token.js | 19 +- server/api/models/place.js | 12 +- server/api/models/resource.js | 17 +- server/api/models/setting.js | 12 +- server/api/models/tag.js | 12 +- server/api/models/user.js | 84 +++-- server/cli/accounts.js | 2 +- server/federation/helpers.js | 6 +- server/helpers.js | 2 +- server/initialize.server.js | 15 +- server/notifier.js | 41 ++- server/routes.js | 21 +- yarn.lock | 18 +- 43 files changed, 624 insertions(+), 707 deletions(-) create mode 100644 server/api/models/models.js diff --git a/CHANGELOG b/CHANGELOG index cbeb5a5c..5b72fad8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ All notable changes to this project will be documented in this file. +### + + - models initialization refactored, better dev xperience as backend hmr is working + ### 1.6.1 - 15 dec '22 - allow edit tags in admin panel, fix #170 - fix header / fallback image upload, fix #222 diff --git a/package.json b/package.json index 4e8ceceb..461ab87a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "passport-oauth2-client-password": "^0.1.2", "passport-oauth2-client-public": "^0.0.1", "pg": "^8.8.0", - "sequelize": "^6.27.0", + "sequelize": "^6.28.0", "sequelize-slugify": "^1.6.2", "sharp": "^0.27.2", "sqlite3": "^5.1.4", diff --git a/server/api/controller/announce.js b/server/api/controller/announce.js index 24a56827..b9e9c826 100644 --- a/server/api/controller/announce.js +++ b/server/api/controller/announce.js @@ -1,4 +1,5 @@ -const Announcement = require('../models/announcement') +const { Announcement } = require('../models/models') + const log = require('../../log') const announceController = { diff --git a/server/api/controller/ap_user.js b/server/api/controller/ap_user.js index 4a2e9aa2..7673194c 100644 --- a/server/api/controller/ap_user.js +++ b/server/api/controller/ap_user.js @@ -1,4 +1,4 @@ -const APUser = require('../models/ap_user') +const { APUser } = require('../models/models') const apUserController = { async toggleBlock (req, res) { diff --git a/server/api/controller/collection.js b/server/api/controller/collection.js index 065c85ed..b00493a0 100644 --- a/server/api/controller/collection.js +++ b/server/api/controller/collection.js @@ -1,8 +1,5 @@ -const Collection = require('../models/collection') -const Filter = require('../models/filter') -const Event = require('../models/event') -const Tag = require('../models/tag') -const Place = require('../models/place') +const { Collection, Filter, Event, Tag, Place } = require('../models/models') + const log = require('../../log') const dayjs = require('dayjs') const { col: Col } = require('../../helpers') @@ -114,7 +111,7 @@ const collectionController = { res.json(collection) } catch (e) { log.error(`Create collection failed ${e}`) - res.sendStatus(400) + res.status(400).send(e) } }, @@ -138,15 +135,14 @@ const collectionController = { }, async addFilter (req, res) { - const collectionId = req.body.collectionId - const tags = req.body.tags - const places = req.body.places + const { collectionId, tags, places } = req.body + try { - const filter = await Filter.create({ collectionId, tags, places }) + filter = await Filter.create({ collectionId, tags, places }) return res.json(filter) } catch (e) { log.error(String(e)) - return res.status(500) + return res.sendStatus(400) } }, @@ -170,6 +166,4 @@ const collectionController = { } - - module.exports = collectionController \ No newline at end of file diff --git a/server/api/controller/event.js b/server/api/controller/event.js index 2c801af9..9a16aacf 100644 --- a/server/api/controller/event.js +++ b/server/api/controller/event.js @@ -9,12 +9,10 @@ const Sequelize = require('sequelize') const dayjs = require('dayjs') const helpers = require('../../helpers') const Col = helpers.col -const Event = require('../models/event') -const Resource = require('../models/resource') -const Tag = require('../models/tag') -const Place = require('../models/place') -const Notification = require('../models/notification') -const APUser = require('../models/ap_user') +const notifier = require('../../notifier') + +const { Event, Resource, Tag, Place, Notification, APUser } = require('../models/models') + const exportController = require('./export') const tagController = require('./tag') @@ -155,34 +153,6 @@ const eventController = { }, - async getNotifications(event, action) { - log.debug(`getNotifications ${event.title} ${action}`) - function match(event, filters) { - // matches if no filter specified - if (!filters) { return true } - - // check for visibility - if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false } - - if (!filters.tags && !filters.places) { return true } - if (!filters.tags.length && !filters.places.length) { return true } - if (filters.tags.length) { - const m = intersection(event.tags.map(t => t.tag), filters.tags) - if (m.length > 0) { return true } - } - if (filters.places.length) { - if (filters.places.find(p => p === event.place.name)) { - return true - } - } - } - - const notifications = await Notification.findAll({ where: { action }, include: [Event] }) - - // get notification that matches with selected event - return notifications.filter(notification => match(event, notification.filters)) - }, - async _get(slug) { // retrocompatibility, old events URL does not use slug, use id as fallback const id = Number(slug) || -1 @@ -317,7 +287,6 @@ const eventController = { res.sendStatus(200) // send notification - const notifier = require('../../notifier') notifier.notifyEvent('Create', event.id) } catch (e) { log.error('[EVENT]', e) diff --git a/server/api/controller/export.js b/server/api/controller/export.js index 13ac9b8b..2aa410b1 100644 --- a/server/api/controller/export.js +++ b/server/api/controller/export.js @@ -1,6 +1,4 @@ -const Event = require('../models/event') -const Place = require('../models/place') -const Tag = require('../models/tag') +const { Event, Place, Tag } = require('../models/models') const { htmlToText } = require('html-to-text') const { Op, literal } = require('sequelize') diff --git a/server/api/controller/instance.js b/server/api/controller/instance.js index 2cea4141..47baa2ee 100644 --- a/server/api/controller/instance.js +++ b/server/api/controller/instance.js @@ -1,6 +1,5 @@ -const APUser = require('../models/ap_user') -const Instance = require('../models/instance') -const Resource = require('../models/resource') +const { APUser, Instance, Resource } = require('../models/models') + const Sequelize = require('sequelize') const instancesController = { diff --git a/server/api/controller/metrics.js b/server/api/controller/metrics.js index f4b5ec08..0d4bb287 100644 --- a/server/api/controller/metrics.js +++ b/server/api/controller/metrics.js @@ -1,4 +1,4 @@ -const User = require('../models/user') +const User = require('../models/modles') const metrics = { diff --git a/server/api/controller/oauth.js b/server/api/controller/oauth.js index e7e76ed3..a1a1ef9a 100644 --- a/server/api/controller/oauth.js +++ b/server/api/controller/oauth.js @@ -2,12 +2,9 @@ const bodyParser = require('body-parser') const cookieParser = require('cookie-parser') const session = require('cookie-session') -const OAuthClient = require('../models/oauth_client') -const OAuthToken = require('../models/oauth_token') -const OAuthCode = require('../models/oauth_code') +const { OAuthClient, OAuthToken, OAuthCode, User } = require('../models/models') const helpers = require('../../helpers.js') -const User = require('../models/user') const passport = require('passport') const get = require('lodash/get') diff --git a/server/api/controller/place.js b/server/api/controller/place.js index f84b0d76..8fd1aa3b 100644 --- a/server/api/controller/place.js +++ b/server/api/controller/place.js @@ -1,5 +1,5 @@ -const Place = require('../models/place') -const Event = require('../models/event') +const { Place, Event } = require('../models/models') + const eventController = require('./event') const exportController = require('./export') diff --git a/server/api/controller/plugins.js b/server/api/controller/plugins.js index defcca40..49833366 100644 --- a/server/api/controller/plugins.js +++ b/server/api/controller/plugins.js @@ -2,11 +2,12 @@ const path = require('path') const fs = require('fs') const log = require('../../log') const config = require('../../config') +const settingsController = require('./settings') +const notifier = require('../../notifier') const pluginController = { plugins: [], getAll(_req, res) { - const settingsController = require('./settings') // return plugins and inner settings const plugins = pluginController.plugins.map( ({ configuration }) => { if (settingsController.settings['plugin_' + configuration.name]) { @@ -18,7 +19,6 @@ const pluginController = { }, togglePlugin(req, res) { - const settingsController = require('./settings') const pluginName = req.params.plugin const pluginSettings = settingsController.settings['plugin_' + pluginName] if (!pluginSettings) { return res.sendStatus(404) } @@ -33,7 +33,6 @@ const pluginController = { }, unloadPlugin(pluginName) { - const settingsController = require('./settings') const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName) const settings = settingsController.settings['plugin_' + pluginName] if (!plugin) { @@ -59,14 +58,12 @@ const pluginController = { }, loadPlugin(pluginName) { - const settingsController = require('./settings') const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName) const settings = settingsController.settings['plugin_' + pluginName] if (!plugin) { log.warn(`Plugin ${pluginName} not found`) return } - const notifier = require('../../notifier') log.info('Load plugin ' + pluginName) if (typeof plugin.onEventCreate === 'function') { notifier.emitter.on('Create', plugin.onEventCreate) @@ -88,7 +85,6 @@ const pluginController = { }, _load() { - const settingsController = require('./settings') // load custom plugins const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins') log.info(`Loading plugin ${plugins_path}`) diff --git a/server/api/controller/resource.js b/server/api/controller/resource.js index 2d854de2..9d151094 100644 --- a/server/api/controller/resource.js +++ b/server/api/controller/resource.js @@ -1,6 +1,4 @@ -const Resource = require('../models/resource') -const APUser = require('../models/ap_user') -const Event = require('../models/event') +const { Resource, APUser, Event } = require('../models/models') const get = require('lodash/get') const resourceController = { diff --git a/server/api/controller/settings.js b/server/api/controller/settings.js index b3d1dfad..0645bb26 100644 --- a/server/api/controller/settings.js +++ b/server/api/controller/settings.js @@ -1,6 +1,5 @@ const path = require('path') const URL = require('url') -const fs = require('fs') const crypto = require('crypto') const { promisify } = require('util') const sharp = require('sharp') @@ -9,7 +8,7 @@ const generateKeyPair = promisify(crypto.generateKeyPair) const log = require('../../log') // const locales = require('../../../locales/index') const escape = require('lodash/escape') -const pluginController = require('./plugins') +const DB = require('../models/models') let defaultHostname try { @@ -30,7 +29,7 @@ const defaultSettings = { allow_multidate_event: true, allow_recurrent_event: false, recurrent_event_visible: false, - allow_geolocation: true, + allow_geolocation: false, geocoding_provider_type: 'Nominatim', geocoding_provider: 'https://nominatim.openstreetmap.org/search', geocoding_countrycodes: [], @@ -74,8 +73,7 @@ const settingsController = { // initialize instance settings from db // note that this is done only once when the server starts // and not for each request - const Setting = require('../models/setting') - const settings = await Setting.findAll() + const settings = await DB.Setting.findAll() settingsController.settings = defaultSettings settings.forEach(s => { if (s.is_secret) { @@ -117,15 +115,14 @@ const settingsController = { // } // }) // } - + const pluginController = require('./plugins') pluginController._load() }, async set (key, value, is_secret = false) { - const Setting = require('../models/setting') log.info(`SET ${key} ${is_secret ? '*****' : value}`) try { - const [setting, created] = await Setting.findOrCreate({ + const [setting, created] = await DB.Setting.findOrCreate({ where: { key }, defaults: { value, is_secret } }) diff --git a/server/api/controller/setup.js b/server/api/controller/setup.js index 541c5a27..90be7be3 100644 --- a/server/api/controller/setup.js +++ b/server/api/controller/setup.js @@ -7,6 +7,8 @@ const settingsController = require('./settings') const path = require('path') const escape = require('lodash/escape') +const DB = require('../models/models') + const setupController = { async _setupDb (dbConf) { @@ -23,7 +25,10 @@ const setupController = { // try to connect dbConf.logging = false - await db.connect(dbConf) + db.connect(dbConf) + db.loadModels() + db.associates() + await db.sequelize.authenticate() // is empty ? const isEmpty = await db.isEmpty() @@ -69,8 +74,7 @@ const setupController = { // create admin const password = helpers.randomString() const email = `admin` - const User = require('../models/user') - await User.create({ + await DB.User.create({ email, password, is_admin: true, diff --git a/server/api/controller/tag.js b/server/api/controller/tag.js index d581179b..66d30320 100644 --- a/server/api/controller/tag.js +++ b/server/api/controller/tag.js @@ -1,5 +1,4 @@ -const Tag = require('../models/tag') -const Event = require('../models/event') +const { Tag, Event } = require('../models/models') const uniq = require('lodash/uniq') const log = require('../../log') @@ -82,29 +81,35 @@ module.exports = { return res.json(tags.map(t => t.tag)) }, - async updateTag (req, res) { - const tag = await Tag.findByPk(req.body.tag) - await tag.update(req.body) - res.json(place) - }, + // async updateTag (req, res) { + // const tag = await Tag.findByPk(req.body.tag) + // await tag.update(req.body) + // res.json(place) + // }, async updateTag (req, res) { - const oldtag = await Tag.findByPk(req.body.tag) - const newtag = await Tag.findByPk(req.body.newTag) + try { + const oldtag = await Tag.findByPk(req.body.tag) + const newtag = await Tag.findByPk(req.body.newTag) - // if the new tag does not exists, just rename the old one - if (!newtag) { - oldtag.tag = req.body.newTag - await oldtag.update({ tag: req.body.newTag }) - } else { - // in case it exists: - // - search for events with old tag - const events = await oldtag.getEvents() - // - substitute it with the new one - await oldtag.removeEvents(events) - await newtag.addEvents(events) + // if the new tag does not exists, just rename the old one + if (!newtag) { + log.info(`Rename tag ${oldtag.tag} to ${req.body.newTag}`) + await Tag.update({ tag: req.body.newTag }, { where: { tag: req.body.tag }, raw: true }) + + } else { + // in case it exists: + // - search for events with old tag + const events = await oldtag.getEvents() + // - substitute it with the new one + await oldtag.removeEvents(events) + await newtag.addEvents(events) + } + res.sendStatus(200) + } catch (e) { + console.error(e) + res.sendStatus(400) } - res.sendStatus(200) }, async remove (req, res) { diff --git a/server/api/controller/user.js b/server/api/controller/user.js index c1e482ee..4fa8c900 100644 --- a/server/api/controller/user.js +++ b/server/api/controller/user.js @@ -2,7 +2,7 @@ const crypto = require('crypto') const { Op } = require('sequelize') const config = require('../../config') const mail = require('../mail') -const User = require('../models/user') +const { User } = require('../models/models') const settingsController = require('./settings') const log = require('../../log') const linkify = require('linkifyjs') diff --git a/server/api/index.js b/server/api/index.js index 4fbed0a9..8d1ccbc9 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -5,219 +5,223 @@ const cors = require('cors')() const config = require('../config') const log = require('../log') -const api = express.Router() -api.use(express.urlencoded({ extended: false })) -api.use(express.json()) +const collectionController = require('./controller/collection') +const setupController = require('./controller/setup') +const settingsController = require('./controller/settings') +const eventController = require('./controller/event') +const placeController = require('./controller/place') +const tagController = require('./controller/tag') +const exportController = require('./controller/export') +const userController = require('./controller/user') +const instanceController = require('./controller/instance') +const apUserController = require('./controller/ap_user') +const resourceController = require('./controller/resource') +const oauthController = require('./controller/oauth') +const announceController = require('./controller/announce') +const pluginController = require('./controller/plugins') +const helpers = require('../helpers') +const storage = require('./storage') -if (config.status !== 'READY') { +module.exports = () => { - const setupController = require('./controller/setup') - const settingsController = require('./controller/settings') - api.post('/settings', settingsController.setRequest) - api.post('/setup/db', setupController.setupDb) - api.post('/setup/restart', setupController.restart) - api.post('/settings/smtp', settingsController.testSMTP) - -} else { - - const { isAuth, isAdmin } = require('./auth') - const eventController = require('./controller/event') - const placeController = require('./controller/place') - const tagController = require('./controller/tag') - const settingsController = require('./controller/settings') - const exportController = require('./controller/export') - const userController = require('./controller/user') - const instanceController = require('./controller/instance') - const apUserController = require('./controller/ap_user') - const resourceController = require('./controller/resource') - const oauthController = require('./controller/oauth') - const announceController = require('./controller/announce') - const collectionController = require('./controller/collection') - const pluginController = require('./controller/plugins') - const helpers = require('../helpers') - const storage = require('./storage') - const upload = multer({ storage }) - - /** - * Get current authenticated user - * @category User - * @name /api/user - * @type GET - * @example **Response** - * ```json - { - "description" : null, - "recover_code" : "", - "id" : 1, - "createdAt" : "2020-01-29T18:10:16.630Z", - "updatedAt" : "2020-01-30T22:42:14.789Z", - "is_active" : true, - "settings" : "{}", - "email" : "eventi@cisti.org", - "is_admin" : true + const api = express.Router() + api.use(express.urlencoded({ extended: false })) + api.use(express.json()) + + + if (config.status !== 'READY') { + + api.post('/settings', settingsController.setRequest) + api.post('/setup/db', setupController.setupDb) + api.post('/setup/restart', setupController.restart) + api.post('/settings/smtp', settingsController.testSMTP) + + } else { + + const { isAuth, isAdmin } = require('./auth') + const upload = multer({ storage }) + + /** + * Get current authenticated user + * @category User + * @name /api/user + * @type GET + * @example **Response** + * ```json + { + "description" : null, + "recover_code" : "", + "id" : 1, + "createdAt" : "2020-01-29T18:10:16.630Z", + "updatedAt" : "2020-01-30T22:42:14.789Z", + "is_active" : true, + "settings" : "{}", + "email" : "eventi@cisti.org", + "is_admin" : true + } + ``` + */ + api.get('/ping', (_req, res) => res.sendStatus(200)) + api.get('/user', isAuth, (req, res) => res.json(req.user)) + + + api.post('/user/recover', userController.forgotPassword) + api.post('/user/check_recover_code', userController.checkRecoverCode) + api.post('/user/recover_password', userController.updatePasswordWithRecoverCode) + + // register and add users + api.post('/user/register', userController.register) + api.post('/user', isAdmin, userController.create) + + // update user + api.put('/user', isAuth, userController.update) + + // delete user + api.delete('/user/:id', isAdmin, userController.remove) + api.delete('/user', isAuth, userController.remove) + + // get all users + api.get('/users', isAdmin, userController.getAll) + + /** + * Get events + * @category Event + * @name /api/events + * @type GET + * @param {integer} [start] - start timestamp (default: now) + * @param {integer} [end] - end timestamp (optional) + * @param {array} [tags] - List of tags + * @param {array} [places] - List of places id + * @param {integer} [max] - Limit events + * @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings) + * @param {integer} [page] - Pagination + * @param {boolean} [older] - select <= start instead of >= + * @example ***Example*** + * [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events) + * [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42) + */ + + api.get('/events', cors, eventController.select) + + /** + * Add a new event + * @category Event + * @name /api/event + * @type POST + * @info `Content-Type` has to be `multipart/form-data` to support image upload + * @param {string} title - event's title + * @param {string} description - event's description (html accepted and sanitized) + * @param {string} place_name - the name of the place + * @param {string} [place_address] - the address of the place + * @param {float} [place_latitude] - the latitude of the place + * @param {float} [place_longitude] - the longitude of the place + * @param {integer} start_datetime - start timestamp + * @param {integer} multidate - is a multidate event? + * @param {array} tags - List of tags + * @param {object} [recurrent] - Recurrent event details + * @param {string} [recurrent.frequency] - could be `1w` or `2w` + * @param {array} [recurrent.days] - array of days + * @param {image} [image] - Image + */ + + // allow anyone to add an event (anon event has to be confirmed, TODO: flood protection) + api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add) + + api.get('/event/search', eventController.search) + + api.put('/event', isAuth, upload.single('image'), eventController.update) + api.get('/event/import', isAuth, helpers.importURL) + + // remove event + api.delete('/event/:id', isAuth, eventController.remove) + + // get tags/places + api.get('/event/meta', eventController.searchMeta) + + // add event notification TODO + api.post('/event/notification', eventController.addNotification) + api.delete('/event/notification/:code', eventController.delNotification) + + api.post('/settings', isAdmin, settingsController.setRequest) + api.get('/settings', isAdmin, settingsController.getAll) + api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo) + api.post('/settings/fallbackImage', isAdmin, multer({ dest: config.upload_path }).single('fallbackImage'), settingsController.setFallbackImage) + api.post('/settings/headerImage', isAdmin, multer({ dest: config.upload_path }).single('headerImage'), settingsController.setHeaderImage) + api.post('/settings/smtp', isAdmin, settingsController.testSMTP) + api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings) + + // get unconfirmed events + api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed) + + // [un]confirm event + api.put('/event/confirm/:event_id', isAuth, eventController.confirm) + api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm) + + // get event + api.get('/event/:event_slug.:format?', cors, eventController.get) + + // export events (rss/ics) + api.get('/export/:type', cors, exportController.export) + + + // - PLACES + api.get('/places', isAdmin, placeController.getAll) + api.get('/place/:placeName', cors, placeController.getEvents) + api.get('/place', cors, placeController.search) + api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim) + api.get('/placeOSM/Photon/:place_details', cors, placeController._photon) + api.put('/place', isAdmin, placeController.updatePlace) + + // - TAGS + api.get('/tags', isAdmin, tagController.getAll) + api.get('/tag', cors, tagController.search) + api.get('/tag/:tag', cors, tagController.getEvents) + api.delete('/tag/:tag', isAdmin, tagController.remove) + api.put('/tag', isAdmin, tagController.updateTag) + + + // - FEDIVERSE INSTANCES, MODERATION, RESOURCES + api.get('/instances', isAdmin, instanceController.getAll) + api.get('/instances/:instance_domain', isAdmin, instanceController.get) + api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock) + api.post('/instances/toggle_user_block', isAdmin, apUserController.toggleBlock) + api.put('/resources/:resource_id', isAdmin, resourceController.hide) + api.delete('/resources/:resource_id', isAdmin, resourceController.remove) + api.get('/resources', isAdmin, resourceController.getAll) + + // - ADMIN ANNOUNCEMENTS + api.get('/announcements', isAdmin, announceController.getAll) + api.post('/announcements', isAdmin, announceController.add) + api.put('/announcements/:announce_id', isAdmin, announceController.update) + api.delete('/announcements/:announce_id', isAdmin, announceController.remove) + + // - COLLECTIONS + api.get('/collections/:name', cors, collectionController.getEvents) + api.get('/collections', collectionController.getAll) + api.post('/collections', isAdmin, collectionController.add) + api.delete('/collection/:id', isAdmin, collectionController.remove) + api.get('/filter/:collection_id', isAdmin, collectionController.getFilters) + api.post('/filter', isAdmin, collectionController.addFilter) + api.delete('/filter/:id', isAdmin, collectionController.removeFilter) + + // - PLUGINS + api.get('/plugins', isAdmin, pluginController.getAll) + api.put('/plugin/:plugin', isAdmin, pluginController.togglePlugin) + + // OAUTH + api.get('/clients', isAuth, oauthController.getClients) + api.get('/client/:client_id', isAuth, oauthController.getClient) + api.post('/client', oauthController.createClient) } - ``` - */ - api.get('/ping', (_req, res) => res.sendStatus(200)) - api.get('/user', isAuth, (req, res) => res.json(req.user)) + + api.use((_req, res) => res.sendStatus(404)) + + // Handle 500 + api.use((error, _req, res, _next) => { + log.error('[API ERROR]', error) + res.status(500).send('500: Internal Server Error') + }) - - api.post('/user/recover', userController.forgotPassword) - api.post('/user/check_recover_code', userController.checkRecoverCode) - api.post('/user/recover_password', userController.updatePasswordWithRecoverCode) - - // register and add users - api.post('/user/register', userController.register) - api.post('/user', isAdmin, userController.create) - - // update user - api.put('/user', isAuth, userController.update) - - // delete user - api.delete('/user/:id', isAdmin, userController.remove) - api.delete('/user', isAuth, userController.remove) - - // get all users - api.get('/users', isAdmin, userController.getAll) - - /** - * Get events - * @category Event - * @name /api/events - * @type GET - * @param {integer} [start] - start timestamp (default: now) - * @param {integer} [end] - end timestamp (optional) - * @param {array} [tags] - List of tags - * @param {array} [places] - List of places id - * @param {integer} [max] - Limit events - * @param {boolean} [show_recurrent] - Show also recurrent events (default: as choosen in admin settings) - * @param {integer} [page] - Pagination - * @param {boolean} [older] - select <= start instead of >= - * @example ***Example*** - * [https://demo.gancio.org/api/events](https://demo.gancio.org/api/events) - * [usage example](https://framagit.org/les/gancio/-/blob/master/webcomponents/src/GancioEvents.svelte#L18-42) - */ - - api.get('/events', cors, eventController.select) - - /** - * Add a new event - * @category Event - * @name /api/event - * @type POST - * @info `Content-Type` has to be `multipart/form-data` to support image upload - * @param {string} title - event's title - * @param {string} description - event's description (html accepted and sanitized) - * @param {string} place_name - the name of the place - * @param {string} [place_address] - the address of the place - * @param {float} [place_latitude] - the latitude of the place - * @param {float} [place_longitude] - the longitude of the place - * @param {integer} start_datetime - start timestamp - * @param {integer} multidate - is a multidate event? - * @param {array} tags - List of tags - * @param {object} [recurrent] - Recurrent event details - * @param {string} [recurrent.frequency] - could be `1w` or `2w` - * @param {array} [recurrent.days] - array of days - * @param {image} [image] - Image - */ - - // allow anyone to add an event (anon event has to be confirmed, TODO: flood protection) - api.post('/event', eventController.isAnonEventAllowed, upload.single('image'), eventController.add) - - api.get('/event/search', eventController.search) - - api.put('/event', isAuth, upload.single('image'), eventController.update) - api.get('/event/import', isAuth, helpers.importURL) - - // remove event - api.delete('/event/:id', isAuth, eventController.remove) - - // get tags/places - api.get('/event/meta', eventController.searchMeta) - - // add event notification TODO - api.post('/event/notification', eventController.addNotification) - api.delete('/event/notification/:code', eventController.delNotification) - - api.post('/settings', isAdmin, settingsController.setRequest) - api.get('/settings', isAdmin, settingsController.getAll) - api.post('/settings/logo', isAdmin, multer({ dest: config.upload_path }).single('logo'), settingsController.setLogo) - api.post('/settings/fallbackImage', isAdmin, multer({ dest: config.upload_path }).single('fallbackImage'), settingsController.setFallbackImage) - api.post('/settings/headerImage', isAdmin, multer({ dest: config.upload_path }).single('headerImage'), settingsController.setHeaderImage) - api.post('/settings/smtp', isAdmin, settingsController.testSMTP) - api.get('/settings/smtp', isAdmin, settingsController.getSMTPSettings) - - // get unconfirmed events - api.get('/event/unconfirmed', isAdmin, eventController.getUnconfirmed) - - // [un]confirm event - api.put('/event/confirm/:event_id', isAuth, eventController.confirm) - api.put('/event/unconfirm/:event_id', isAuth, eventController.unconfirm) - - // get event - api.get('/event/:event_slug.:format?', cors, eventController.get) - - // export events (rss/ics) - api.get('/export/:type', cors, exportController.export) - - - // - PLACES - api.get('/places', isAdmin, placeController.getAll) - api.get('/place/:placeName', cors, placeController.getEvents) - api.get('/place', cors, placeController.search) - api.get('/placeOSM/Nominatim/:place_details', cors, placeController._nominatim) - api.get('/placeOSM/Photon/:place_details', cors, placeController._photon) - api.put('/place', isAdmin, placeController.updatePlace) - - // - TAGS - api.get('/tags', isAdmin, tagController.getAll) - api.get('/tag', cors, tagController.search) - api.get('/tag/:tag', cors, tagController.getEvents) - api.delete('/tag/:tag', isAdmin, tagController.remove) - api.put('/tag', isAdmin, tagController.updateTag) - - - // - FEDIVERSE INSTANCES, MODERATION, RESOURCES - api.get('/instances', isAdmin, instanceController.getAll) - api.get('/instances/:instance_domain', isAdmin, instanceController.get) - api.post('/instances/toggle_block', isAdmin, instanceController.toggleBlock) - api.post('/instances/toggle_user_block', isAdmin, apUserController.toggleBlock) - api.put('/resources/:resource_id', isAdmin, resourceController.hide) - api.delete('/resources/:resource_id', isAdmin, resourceController.remove) - api.get('/resources', isAdmin, resourceController.getAll) - - // - ADMIN ANNOUNCEMENTS - api.get('/announcements', isAdmin, announceController.getAll) - api.post('/announcements', isAdmin, announceController.add) - api.put('/announcements/:announce_id', isAdmin, announceController.update) - api.delete('/announcements/:announce_id', isAdmin, announceController.remove) - - // - COLLECTIONS - api.get('/collections/:name', cors, collectionController.getEvents) - api.get('/collections', collectionController.getAll) - api.post('/collections', isAdmin, collectionController.add) - api.delete('/collection/:id', isAdmin, collectionController.remove) - api.get('/filter/:collection_id', isAdmin, collectionController.getFilters) - api.post('/filter', isAdmin, collectionController.addFilter) - api.delete('/filter/:id', isAdmin, collectionController.removeFilter) - - // - PLUGINS - api.get('/plugins', isAdmin, pluginController.getAll) - api.put('/plugin/:plugin', isAdmin, pluginController.togglePlugin) - - // OAUTH - api.get('/clients', isAuth, oauthController.getClients) - api.get('/client/:client_id', isAuth, oauthController.getClient) - api.post('/client', oauthController.createClient) + return api } - -api.use((_req, res) => res.sendStatus(404)) - -// Handle 500 -api.use((error, _req, res, _next) => { - log.error('[API ERROR]', error) - res.status(500).send('500: Internal Server Error') -}) - -module.exports = api diff --git a/server/api/models/announcement.js b/server/api/models/announcement.js index 0b0926c5..6b410793 100644 --- a/server/api/models/announcement.js +++ b/server/api/models/announcement.js @@ -1,12 +1,6 @@ -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -class Announcement extends Model {} - -Announcement.init({ - title: DataTypes.STRING, - announcement: DataTypes.STRING, - visible: DataTypes.BOOLEAN -}, { sequelize, modelName: 'announcement' }) - -module.exports = Announcement +module.exports = (sequelize, DataTypes) => + sequelize.define('announcement', { + title: DataTypes.STRING, + announcement: DataTypes.STRING, + visible: DataTypes.BOOLEAN + }) diff --git a/server/api/models/ap_user.js b/server/api/models/ap_user.js index 20e80a55..c2c1d8f7 100644 --- a/server/api/models/ap_user.js +++ b/server/api/models/ap_user.js @@ -1,9 +1,6 @@ -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') -class APUser extends Model {} - -APUser.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('ap_user', { ap_id: { type: DataTypes.STRING, primaryKey: true @@ -11,6 +8,4 @@ APUser.init({ follower: DataTypes.BOOLEAN, blocked: DataTypes.BOOLEAN, object: DataTypes.JSON -}, { sequelize, modelName: 'ap_user' }) - -module.exports = APUser +}) diff --git a/server/api/models/collection.js b/server/api/models/collection.js index 4df48141..d07cee69 100644 --- a/server/api/models/collection.js +++ b/server/api/models/collection.js @@ -1,28 +1,20 @@ -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize - -class Collection extends Model {} - -// TODO: slugify! -Collection.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - name: { - type: DataTypes.STRING, - unique: true, - index: true, - allowNull: false - }, - isActor: { - type: DataTypes.BOOLEAN - }, - isTop: { - type: DataTypes.BOOLEAN - } -}, { sequelize, modelName: 'collection', timestamps: false }) - - -module.exports = Collection +module.exports = (sequelize, DataTypes) => + sequelize.define('collection', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + unique: true, + index: true, + allowNull: false + }, + isActor: { + type: DataTypes.BOOLEAN + }, + isTop: { + type: DataTypes.BOOLEAN + } + }, { timestamps: false }) diff --git a/server/api/models/event.js b/server/api/models/event.js index 75d2067f..c0e6c7d5 100644 --- a/server/api/models/event.js +++ b/server/api/models/event.js @@ -1,18 +1,5 @@ const config = require('../../config') const { htmlToText } = require('html-to-text') - -const { Model, DataTypes } = require('sequelize') -const SequelizeSlugify = require('sequelize-slugify') - -const sequelize = require('./index').sequelize - -const Resource = require('./resource') -const Notification = require('./notification') -const EventNotification = require('./eventnotification') -const Place = require('./place') -const User = require('./user') -const Tag = require('./tag') - const dayjs = require('dayjs') const timezone = require('dayjs/plugin/timezone') const utc = require('dayjs/plugin/utc') @@ -20,108 +7,88 @@ const utc = require('dayjs/plugin/utc') dayjs.extend(utc) dayjs.extend(timezone) -class Event extends Model {} - -Event.init({ - id: { - allowNull: false, - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: DataTypes.STRING, - slug: { - type: DataTypes.STRING, - index: true, - unique: true - }, - description: DataTypes.TEXT, - multidate: DataTypes.BOOLEAN, - start_datetime: { - type: DataTypes.INTEGER, - index: true - }, - end_datetime: { - type: DataTypes.INTEGER, - index: true - }, - image_path: DataTypes.STRING, - media: DataTypes.JSON, - is_visible: DataTypes.BOOLEAN, - recurrent: DataTypes.JSON, - likes: { type: DataTypes.JSON, defaultValue: [] }, - boost: { type: DataTypes.JSON, defaultValue: [] } -}, { sequelize, modelName: 'event' }) - -Event.belongsTo(Place) -Place.hasMany(Event) - -Event.belongsTo(User) -User.hasMany(Event) - -Event.belongsToMany(Tag, { through: 'event_tags' }) -Tag.belongsToMany(Event, { through: 'event_tags' }) - -Event.belongsToMany(Notification, { through: EventNotification }) -Notification.belongsToMany(Event, { through: EventNotification }) - -Event.hasMany(Resource) -Resource.belongsTo(Event) - -Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' }) -Event.belongsTo(Event, { as: 'parent' }) - -SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false }) - -Event.prototype.toAP = function (username, locale, to = []) { - const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_')) - const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000)) - const content = ` +// class Event extends Model {} +module.exports = (sequelize, DataTypes) => { + const Event = sequelize.define('event', { + id: { + allowNull: false, + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + title: DataTypes.STRING, + slug: { + type: DataTypes.STRING, + index: true, + unique: true + }, + description: DataTypes.TEXT, + multidate: DataTypes.BOOLEAN, + start_datetime: { + type: DataTypes.INTEGER, + index: true + }, + end_datetime: { + type: DataTypes.INTEGER, + index: true + }, + image_path: DataTypes.STRING, + media: DataTypes.JSON, + is_visible: DataTypes.BOOLEAN, + recurrent: DataTypes.JSON, + likes: { type: DataTypes.JSON, defaultValue: [] }, + boost: { type: DataTypes.JSON, defaultValue: [] } + }) + + Event.prototype.toAP = function (username, locale, to = []) { + const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_')) + const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000)) + const content = ` 📍 ${this.place && this.place.name} 📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')} - + ${plainDescription} - ` - - const attachment = [] - if (this.media && this.media.length) { - attachment.push({ - type: 'Document', - mediaType: 'image/jpeg', - url: `${config.baseurl}/media/${this.media[0].url}`, - name: this.media[0].name || this.title || '', - blurHash: null, - focalPoint: this.media[0].focalPoint || [0, 0] - }) + ` + + const attachment = [] + if (this.media && this.media.length) { + attachment.push({ + type: 'Document', + mediaType: 'image/jpeg', + url: `${config.baseurl}/media/${this.media[0].url}`, + name: this.media[0].name || this.title || '', + blurHash: null, + focalPoint: this.media[0].focalPoint || [0, 0] + }) + } + + + return { + id: `${config.baseurl}/federation/m/${this.id}`, + name: this.title, + url: `${config.baseurl}/event/${this.slug || this.id}`, + type: 'Event', + startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(), + endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null, + location: { + name: this.place.name, + address: this.place.address, + latitude: this.place.latitude, + longitude: this.place.longitude + }, + attachment, + tag: tags && tags.map(tag => ({ + type: 'Hashtag', + name: '#' + tag, + href: `${config.baseurl}/tag/${tag}` + })), + published: dayjs(this.createdAt).utc().format(), + attributedTo: `${config.baseurl}/federation/u/${username}`, + to: ['https://www.w3.org/ns/activitystreams#Public'], + cc: [`${config.baseurl}/federation/u/${username}/followers`], + content, + summary: content + } } - - - return { - id: `${config.baseurl}/federation/m/${this.id}`, - name: this.title, - url: `${config.baseurl}/event/${this.slug || this.id}`, - type: 'Event', - startTime: dayjs.unix(this.start_datetime).tz().locale(locale).format(), - endTime: this.end_datetime ? dayjs.unix(this.end_datetime).tz().locale(locale).format() : null, - location: { - name: this.place.name, - address: this.place.address, - latitude: this.place.latitude, - longitude: this.place.longitude - }, - attachment, - tag: tags && tags.map(tag => ({ - type: 'Hashtag', - name: '#' + tag, - href: `${config.baseurl}/tag/${tag}` - })), - published: dayjs(this.createdAt).utc().format(), - attributedTo: `${config.baseurl}/federation/u/${username}`, - to: ['https://www.w3.org/ns/activitystreams#Public'], - cc: [`${config.baseurl}/federation/u/${username}/followers`], - content, - summary: content - } -} - -module.exports = Event + return Event +} \ No newline at end of file diff --git a/server/api/models/eventnotification.js b/server/api/models/eventnotification.js index 662f05e9..ad8dfac0 100644 --- a/server/api/models/eventnotification.js +++ b/server/api/models/eventnotification.js @@ -1,15 +1,9 @@ -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -class EventNotification extends Model {} - -EventNotification.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('event_notification', { status: { type: DataTypes.ENUM, values: ['new', 'sent', 'error', 'sending'], defaultValue: 'new', index: true } -}, { sequelize, modelName: 'event_notification' }) - -module.exports = EventNotification +}) \ No newline at end of file diff --git a/server/api/models/filter.js b/server/api/models/filter.js index 1a6cbe8a..a1077035 100644 --- a/server/api/models/filter.js +++ b/server/api/models/filter.js @@ -1,24 +1,20 @@ -const { Model, DataTypes } = require('sequelize') -const Collection = require('./collection') -const sequelize = require('./index').sequelize - -class Filter extends Model {} - -Filter.init({ - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - tags: { - type: DataTypes.JSON, - }, - places: { - type: DataTypes.JSON, - } -}, { sequelize, modelName: 'filter', timestamps: false }) - -Filter.belongsTo(Collection) -Collection.hasMany(Filter) - -module.exports = Filter +module.exports = (sequelize, DataTypes) => + sequelize.define('filter', + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + tags: { + type: DataTypes.JSON, + }, + places: { + type: DataTypes.JSON, + } + }, { + indexes: [ + { fields: ['collectionId', 'tags', 'places'], unique: true } + ], + timestamps: false + }) \ No newline at end of file diff --git a/server/api/models/index.js b/server/api/models/index.js index 2174f1dc..be5308bb 100644 --- a/server/api/models/index.js +++ b/server/api/models/index.js @@ -4,10 +4,78 @@ const Umzug = require('umzug') const path = require('path') const config = require('../../config') const log = require('../../log') -const settingsController = require('../controller/settings') +const SequelizeSlugify = require('sequelize-slugify') +const DB = require('./models') + +const models = { + Announcement: require('./announcement'), + APUser: require('./ap_user'), + Collection: require('./collection'), + Event: require('./event'), + EventNotification: require('./eventnotification'), + Filter: require('./filter'), + Instance: require('./instance'), + Notification: require('./notification'), + OAuthClient: require('./oauth_client'), + OAuthCode: require('./oauth_code'), + OAuthToken: require('./oauth_token'), + Place: require('./place'), + Resource: require('./resource'), + Setting: require('./setting'), + Tag: require('./tag'), + User: require('./user'), +} const db = { sequelize: null, + loadModels () { + + for (const modelName in models) { + const m = models[modelName](db.sequelize, Sequelize.DataTypes) + DB[modelName] = m + } + + }, + associates () { + const { Filter, Collection, APUser, Instance, User, Event, EventNotification, Tag, + OAuthCode, OAuthClient, OAuthToken, Resource, Place, Notification } = DB + + Filter.belongsTo(Collection) + Collection.hasMany(Filter) + + Instance.hasMany(APUser) + APUser.belongsTo(Instance) + + OAuthCode.belongsTo(User) + OAuthCode.belongsTo(OAuthClient, { as: 'client' }) + + OAuthToken.belongsTo(User) + OAuthToken.belongsTo(OAuthClient, { as: 'client' }) + + APUser.hasMany(Resource) + Resource.belongsTo(APUser) + + Event.belongsTo(Place) + Place.hasMany(Event) + + Event.belongsTo(User) + User.hasMany(Event) + + Event.belongsToMany(Tag, { through: 'event_tags' }) + Tag.belongsToMany(Event, { through: 'event_tags' }) + + Event.belongsToMany(Notification, { through: EventNotification }) + Notification.belongsToMany(Event, { through: EventNotification }) + + Event.hasMany(Resource) + Resource.belongsTo(Event) + + Event.hasMany(Event, { as: 'child', foreignKey: 'parentId' }) + Event.belongsTo(Event, { as: 'parent' }) + + SequelizeSlugify.slugifyModel(Event, { source: ['title'], overwrite: false }) + + }, close() { if (db.sequelize) { return db.sequelize.close() @@ -28,7 +96,6 @@ const db = { } } db.sequelize = new Sequelize(dbConf) - return db.sequelize.authenticate() }, async isEmpty() { try { @@ -57,13 +124,12 @@ const db = { }) return umzug.up() }, - async initialize() { + initialize() { if (config.status === 'CONFIGURED') { try { - await db.connect() - log.debug('Running migrations') - await db.runMigrations() - return settingsController.load() + db.connect() + db.loadModels() + db.associates() } catch (e) { log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`) process.exit(1) diff --git a/server/api/models/instance.js b/server/api/models/instance.js index a9f8e96b..3d15e82f 100644 --- a/server/api/models/instance.js +++ b/server/api/models/instance.js @@ -1,11 +1,5 @@ - -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') -const APUser = require('./ap_user') - -class Instance extends Model {} - -Instance.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('instance', { domain: { primaryKey: true, allowNull: false, @@ -14,9 +8,4 @@ Instance.init({ name: DataTypes.STRING, blocked: DataTypes.BOOLEAN, data: DataTypes.JSON -}, { sequelize, modelName: 'instance' }) - -Instance.hasMany(APUser) -APUser.belongsTo(Instance) - -module.exports = Instance +}) \ No newline at end of file diff --git a/server/api/models/models.js b/server/api/models/models.js new file mode 100644 index 00000000..0b46a05d --- /dev/null +++ b/server/api/models/models.js @@ -0,0 +1,2 @@ +// export default models +module.exports = {} \ No newline at end of file diff --git a/server/api/models/notification.js b/server/api/models/notification.js index 19879a77..8d36e89e 100644 --- a/server/api/models/notification.js +++ b/server/api/models/notification.js @@ -1,10 +1,5 @@ - -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -class Notification extends Model {} - -Notification.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('notification', { filters: DataTypes.JSON, email: DataTypes.STRING, remove_code: DataTypes.STRING, @@ -24,6 +19,4 @@ Notification.init({ unique: true, fields: ['action', 'type'] }] -}) - -module.exports = Notification +}) \ No newline at end of file diff --git a/server/api/models/oauth_client.js b/server/api/models/oauth_client.js index 103aaf59..e30e05ab 100644 --- a/server/api/models/oauth_client.js +++ b/server/api/models/oauth_client.js @@ -1,10 +1,5 @@ - -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -class OAuthClient extends Model {} - -OAuthClient.init({ +module.exports = (sequelize, DataTypes) => +sequelize.define('oauth_client', { id: { type: DataTypes.STRING, primaryKey: true, @@ -15,6 +10,4 @@ OAuthClient.init({ scopes: DataTypes.STRING, redirectUris: DataTypes.STRING, website: DataTypes.STRING -}, { sequelize, modelName: 'oauth_client' }) - -module.exports = OAuthClient +}) \ No newline at end of file diff --git a/server/api/models/oauth_code.js b/server/api/models/oauth_code.js index 45b98fe5..ba82de74 100644 --- a/server/api/models/oauth_code.js +++ b/server/api/models/oauth_code.js @@ -1,13 +1,5 @@ - -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -const User = require('./user') -const OAuthClient = require('./oauth_client') - -class OAuthCode extends Model {} - -OAuthCode.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('oauth_code', { authorizationCode: { type: DataTypes.STRING, primaryKey: true @@ -15,9 +7,4 @@ OAuthCode.init({ expiresAt: DataTypes.DATE, scope: DataTypes.STRING, redirect_uri: DataTypes.STRING -}, { sequelize, modelName: 'oauth_code' }) - -OAuthCode.belongsTo(User) -OAuthCode.belongsTo(OAuthClient, { as: 'client' }) - -module.exports = OAuthCode +}) \ No newline at end of file diff --git a/server/api/models/oauth_token.js b/server/api/models/oauth_token.js index 2530bf1c..984ece06 100644 --- a/server/api/models/oauth_token.js +++ b/server/api/models/oauth_token.js @@ -1,13 +1,5 @@ - -const sequelize = require('./index').sequelize -const { Model, DataTypes } = require('sequelize') - -const User = require('./user') -const OAuthClient = require('./oauth_client') - -class OAuthToken extends Model {} - -OAuthToken.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('oauth_token', { accessToken: { type: DataTypes.STRING, allowNull: false, @@ -27,9 +19,4 @@ OAuthToken.init({ } }, scope: DataTypes.STRING -}, { sequelize, modelName: 'oauth_token' }) - -OAuthToken.belongsTo(User) -OAuthToken.belongsTo(OAuthClient, { as: 'client' }) - -module.exports = OAuthToken +}) \ No newline at end of file diff --git a/server/api/models/place.js b/server/api/models/place.js index 0294599b..734824d5 100644 --- a/server/api/models/place.js +++ b/server/api/models/place.js @@ -1,9 +1,5 @@ -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize - -class Place extends Model {} - -Place.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('place', { name: { type: DataTypes.STRING, unique: true, @@ -13,6 +9,4 @@ Place.init({ address: DataTypes.STRING, latitude: DataTypes.FLOAT, longitude: DataTypes.FLOAT, -}, { sequelize, modelName: 'place' }) - -module.exports = Place +}) \ No newline at end of file diff --git a/server/api/models/resource.js b/server/api/models/resource.js index 60d65e09..1774287c 100644 --- a/server/api/models/resource.js +++ b/server/api/models/resource.js @@ -1,11 +1,5 @@ -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize - -const APUser = require('./ap_user') - -class Resource extends Model {} - -Resource.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('resource', { activitypub_id: { type: DataTypes.STRING, index: true, @@ -13,9 +7,4 @@ Resource.init({ }, hidden: DataTypes.BOOLEAN, data: DataTypes.JSON -}, { sequelize, modelName: 'resource' }) - -APUser.hasMany(Resource) -Resource.belongsTo(APUser) - -module.exports = Resource +}) \ No newline at end of file diff --git a/server/api/models/setting.js b/server/api/models/setting.js index 5d20fa56..cd73feba 100644 --- a/server/api/models/setting.js +++ b/server/api/models/setting.js @@ -1,9 +1,5 @@ -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize - -class Setting extends Model {} - -Setting.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('setting', { key: { type: DataTypes.STRING, primaryKey: true, @@ -12,6 +8,4 @@ Setting.init({ }, value: DataTypes.JSON, is_secret: DataTypes.BOOLEAN -}, { sequelize, modelName: 'setting' }) - -module.exports = Setting +}) diff --git a/server/api/models/tag.js b/server/api/models/tag.js index 5dd9e300..dfab832b 100644 --- a/server/api/models/tag.js +++ b/server/api/models/tag.js @@ -1,15 +1,9 @@ -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize - -class Tag extends Model {} - -Tag.init({ +module.exports = (sequelize, DataTypes) => + sequelize.define('tag', { tag: { type: DataTypes.STRING, allowNull: false, index: true, primaryKey: true } -}, { sequelize, modelName: 'tag' }) - -module.exports = Tag +}) \ No newline at end of file diff --git a/server/api/models/user.js b/server/api/models/user.js index 7b0b0f6f..68fd5bd0 100644 --- a/server/api/models/user.js +++ b/server/api/models/user.js @@ -1,53 +1,49 @@ const bcrypt = require('bcryptjs') -const { Model, DataTypes } = require('sequelize') -const sequelize = require('./index').sequelize -class User extends Model {} - -User.init({ - settings: { - type: DataTypes.JSON, - defaultValue: [] - }, - email: { - type: DataTypes.STRING, - unique: { msg: 'error.email_taken' }, - validate: { - notEmpty: true +module.exports = (sequelize, DataTypes) => { + const User = sequelize.define('user', { + settings: { + type: DataTypes.JSON, + defaultValue: [] }, - index: true, - allowNull: false - }, - description: DataTypes.TEXT, - password: DataTypes.STRING, - recover_code: DataTypes.STRING, - is_admin: DataTypes.BOOLEAN, - is_active: DataTypes.BOOLEAN -}, { - sequelize, - modelName: 'user', - scopes: { - withoutPassword: { - attributes: { exclude: ['password', 'recover_code'] } + email: { + type: DataTypes.STRING, + unique: { msg: 'error.email_taken' }, + validate: { + notEmpty: true + }, + index: true, + allowNull: false }, - withRecover: { - attributes: { exclude: ['password'] } + description: DataTypes.TEXT, + password: DataTypes.STRING, + recover_code: DataTypes.STRING, + is_admin: DataTypes.BOOLEAN, + is_active: DataTypes.BOOLEAN + }, { + scopes: { + withoutPassword: { + attributes: { exclude: ['password', 'recover_code'] } + }, + withRecover: { + attributes: { exclude: ['password'] } + } } + }) + + User.prototype.comparePassword = async function (pwd) { + if (!this.password) { return false } + return bcrypt.compare(pwd, this.password) } -}) + + User.beforeSave(async (user, _options) => { + if (user.changed('password')) { + const salt = await bcrypt.genSalt(10) + const hash = await bcrypt.hash(user.password, salt) + user.password = hash + } + }) -User.prototype.comparePassword = async function (pwd) { - if (!this.password) { return false } - return bcrypt.compare(pwd, this.password) + return User } - -User.beforeSave(async (user, _options) => { - if (user.changed('password')) { - const salt = await bcrypt.genSalt(10) - const hash = await bcrypt.hash(user.password, salt) - user.password = hash - } -}) - -module.exports = User diff --git a/server/cli/accounts.js b/server/cli/accounts.js index e41611c6..c1c9bca6 100644 --- a/server/cli/accounts.js +++ b/server/cli/accounts.js @@ -9,7 +9,7 @@ function _initializeDB () { async function modify (args) { await _initializeDB() const helpers = require('../helpers') - const User = require('../api/models/user') + const { User } = require('../api/models/models') const user = await User.findOne({ where: { email: args.account } }) console.log() if (!user) { diff --git a/server/federation/helpers.js b/server/federation/helpers.js index a84b0c70..5454b35d 100644 --- a/server/federation/helpers.js +++ b/server/federation/helpers.js @@ -1,10 +1,10 @@ const axios = require('axios') -// const request = require('request') const crypto = require('crypto') const config = require('../config') const httpSignature = require('http-signature') -const APUser = require('../api/models/ap_user') -const Instance = require('../api/models/instance') + +const { APUser, Instance } = require('../api/models/models') + const url = require('url') const settingsController = require('../api/controller/settings') const log = require('../log') diff --git a/server/helpers.js b/server/helpers.js index 5fa385f5..17f6a289 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -270,9 +270,9 @@ module.exports = { }, async APRedirect(req, res, next) { + const eventController = require('../server/api/controller/event') const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json' if (acceptJson) { - const eventController = require('../server/api/controller/event') const event = await eventController._get(req.params.slug) if (event) { return res.redirect(`/federation/m/${event.id}`) diff --git a/server/initialize.server.js b/server/initialize.server.js index 51b6d9d4..121555de 100644 --- a/server/initialize.server.js +++ b/server/initialize.server.js @@ -1,4 +1,11 @@ const config = require('../server/config') +const db = require('./api/models/index') +const log = require('../server/log') + +db.initialize() + +const settingsController = require('./api/controller/settings') + const initialize = { // close connections/port/unix socket @@ -19,14 +26,14 @@ const initialize = { }, async start () { - const log = require('../server/log') - const settingsController = require('./api/controller/settings') - const db = require('./api/models/index') const dayjs = require('dayjs') const timezone = require('dayjs/plugin/timezone') dayjs.extend(timezone) if (config.status == 'CONFIGURED') { - await db.initialize() + await db.sequelize.authenticate() + log.debug('Running migrations') + await db.runMigrations() + await settingsController.load() config.status = 'READY' } else { if (process.env.GANCIO_DB_DIALECT) { diff --git a/server/notifier.js b/server/notifier.js index be5e9f56..07d08458 100644 --- a/server/notifier.js +++ b/server/notifier.js @@ -4,14 +4,10 @@ const mail = require('./api/mail') const log = require('./log') const fediverseHelpers = require('./federation/helpers') -const Event = require('./api/models/event') -const Notification = require('./api/models/notification') -const EventNotification = require('./api/models/eventnotification') -const User = require('./api/models/user') -const Place = require('./api/models/place') -const Tag = require('./api/models/tag') -const eventController = require('./api/controller/event') +const { Event, Notification, EventNotification, User, Place, Tag } = require('./api/models/models') + + const settingsController = require('./api/controller/settings') const notifier = { @@ -37,7 +33,36 @@ const notifier = { return Promise.all(promises) }, + async getNotifications(event, action) { + log.debug(`getNotifications ${event.title} ${action}`) + function match(event, filters) { + // matches if no filter specified + if (!filters) { return true } + + // check for visibility + if (typeof filters.is_visible !== 'undefined' && filters.is_visible !== event.is_visible) { return false } + + if (!filters.tags && !filters.places) { return true } + if (!filters.tags.length && !filters.places.length) { return true } + if (filters.tags.length) { + const m = intersection(event.tags.map(t => t.tag), filters.tags) + if (m.length > 0) { return true } + } + if (filters.places.length) { + if (filters.places.find(p => p === event.place.name)) { + return true + } + } + } + + const notifications = await Notification.findAll({ where: { action }, include: [Event] }) + + // get notification that matches with selected event + return notifications.filter(notification => match(event, notification.filters)) + }, + async notifyEvent (action, eventId) { + const event = await Event.findByPk(eventId, { include: [Tag, Place, Notification, User] }) @@ -46,7 +71,7 @@ const notifier = { log.debug(action, event.title) // insert notifications - const notifications = await eventController.getNotifications(event, action) + const notifications = await notifier.getNotifications(event, action) await event.addNotifications(notifications) const event_notifications = await event.getNotifications({ through: { where: { status: 'new' } } }) diff --git a/server/routes.js b/server/routes.js index c61e6d27..5863cc0a 100644 --- a/server/routes.js +++ b/server/routes.js @@ -4,23 +4,22 @@ const initialize = require('./initialize.server') const config = require('./config') const helpers = require('./helpers') - -app.use([ - helpers.initSettings, - helpers.logRequest, - helpers.serveStatic() -]) +const api = require('./api') async function main () { - + await initialize.start() - + + app.use([ + helpers.initSettings, + helpers.logRequest, + helpers.serveStatic() + ]) // const metricsController = require('./metrics') // const promBundle = require('express-prom-bundle') // const metricsMiddleware = promBundle({ includeMethod: true }) const log = require('./log') - const api = require('./api') app.enable('trust proxy') @@ -60,7 +59,7 @@ async function main () { } // api! - app.use('/api', api) + app.use('/api', api()) // // Handle 500 app.use((error, _req, res, _next) => { @@ -87,8 +86,6 @@ if (process.env.NODE_ENV !== 'test') { main() } -// app.listen(13120) - module.exports = { main, handler: app, diff --git a/yarn.lock b/yarn.lock index 195be9a9..bc6cebae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10634,10 +10634,10 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-as-promised@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-6.1.0.tgz#11eca9a0f97804d552ec8e74bc4eb839bd226dc4" - integrity sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA== +retry-as-promised@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-7.0.3.tgz#ca3c13b15525a7bfbf0f56d2996f0e75649d068b" + integrity sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q== retry@^0.12.0: version "0.12.0" @@ -10926,10 +10926,10 @@ sequelize-slugify@^1.6.2: dependencies: sluglife "^0.9.8" -sequelize@^6.27.0: - version "6.27.0" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.27.0.tgz#b267e76997df57842cc1e2c1c1d7e02405bcdb9c" - integrity sha512-Rm7BM8HQekeABup0KdtSHriu8ppJuHj2TJWCxvZtzU6j8V1LVnBk2rs38P8r4gMWgdLKs5NYoLC4il95KLsv0w== +sequelize@^6.28.0: + version "6.28.0" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.28.0.tgz#d6bc4e36647e8501635467c0777c45a33f5d5ba8" + integrity sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw== dependencies: "@types/debug" "^4.1.7" "@types/validator" "^13.7.1" @@ -10940,7 +10940,7 @@ sequelize@^6.27.0: moment "^2.29.1" moment-timezone "^0.5.34" pg-connection-string "^2.5.0" - retry-as-promised "^6.1.0" + retry-as-promised "^7.0.3" semver "^7.3.5" sequelize-pool "^7.1.0" toposort-class "^1.0.1" From 34ca8fc124ed1b4e316496b292dca7019c175d1c Mon Sep 17 00:00:00 2001 From: lesion Date: Fri, 23 Dec 2022 01:09:18 +0100 Subject: [PATCH 06/33] allow html in confirm dialog text --- components/Confirm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Confirm.vue b/components/Confirm.vue index 0c8f2919..05ddc28f 100644 --- a/components/Confirm.vue +++ b/components/Confirm.vue @@ -9,7 +9,7 @@ v-dialog(v-model='show' @keydown.esc='cancel') v-card v-card-title {{ title }} - v-card-text(v-show='!!message') {{ message }} + v-card-text(v-show='!!message' v-html='message') v-card-actions v-spacer v-btn(outlined color='error' @click='cancel') {{$t('common.cancel')}} From fb72f6d693cf29d0c496eee53c980a42014c0f01 Mon Sep 17 00:00:00 2001 From: lesion Date: Fri, 23 Dec 2022 01:11:57 +0100 Subject: [PATCH 07/33] check for duplicates filter on collections + loading fix #218 --- components/admin/Collections.vue | 22 ++++++++++++++++++---- components/admin/Tags.vue | 3 +-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/components/admin/Collections.vue b/components/admin/Collections.vue index 5dd87531..2f9da6a5 100644 --- a/components/admin/Collections.vue +++ b/components/admin/Collections.vue @@ -69,7 +69,7 @@ v-container //- v-list-item-subtitle(v-text='item.address') v-col(cols=2) - v-btn(color='primary' text @click='addFilter' :disabled='!collection.id || !filterPlaces.length && !filterTags.length') add + v-btn(color='primary' :loading='loading' text @click='addFilter' :disabled='loading || !collection.id || !filterPlaces.length && !filterTags.length') add v-data-table( :headers='filterHeaders' @@ -110,6 +110,9 @@ v-container \ No newline at end of file + From 901c11e6ccd6a0eea362729ae50c049555df5315 Mon Sep 17 00:00:00 2001 From: lesion Date: Mon, 9 Jan 2023 16:56:01 +0100 Subject: [PATCH 30/33] calendar loading.. --- components/Calendar.vue | 9 +++++---- components/DateInput.vue | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/Calendar.vue b/components/Calendar.vue index 1bdca9f8..397fb635 100644 --- a/components/Calendar.vue +++ b/components/Calendar.vue @@ -15,13 +15,14 @@ aria-label='Calendar' is-expanded is-inline) - template(v-slot="{ inputValue, inputEvents }") + //- template(v-slot="{ inputValue, inputEvents }") v-btn#calendarButton(v-on='inputEvents' text tile :color='selectedDate ? "primary" : "" ') {{inputValue || $t('common.calendar')}} v-icon(v-if='selectedDate' v-text='mdiClose' right small icon @click.prevent.stop='selectedDate = null') v-icon(v-else v-text='mdiChevronDown' right small icon) - template(v-slot:placeholder) - v-btn#calendarButton(text tile) {{$t('common.calendar')}} - v-icon(v-text='mdiChevronDown' right small icon) + .calh.d-flex.justify-center.align-center(slot='placeholder') + v-progress-circular(indeterminate) + //- v-btn#calendarButton(text tile) {{$t('common.calendar')}} + //- v-icon(v-text='mdiChevronDown' right small icon) diff --git a/components/DateInput.vue b/components/DateInput.vue index 9166e2c7..2b45b374 100644 --- a/components/DateInput.vue +++ b/components/DateInput.vue @@ -24,8 +24,9 @@ v-col(cols=12) is-inline is-expanded :min-date='type !== "recurrent" && new Date()') - template(#placeholder) - span.calc Loading + //- template(#placeholder) + .d-flex.calh.justify-center(slot='placeholder') + v-progress-circular(indeterminate) div.text-center.mb-2(v-if='type === "recurrent"') span(v-if='value.recurrent.frequency !== "1m" && value.recurrent.frequency !== "2m"') {{ whenPatterns }} @@ -94,7 +95,7 @@ v-col(cols=12) \ No newline at end of file + diff --git a/components/WhereInput.vue b/components/WhereInput.vue index 1b41a0ee..75eb3615 100644 --- a/components/WhereInput.vue +++ b/components/WhereInput.vue @@ -125,9 +125,14 @@ export default { return matches } }, + mounted () { + this.$nextTick( () => { + this.search() + }) + }, methods: { search: debounce(async function(ev) { - const search = ev.target.value.trim().toLowerCase() + const search = ev ? ev.target.value.trim().toLowerCase() : '' this.places = await this.$axios.$get(`place?search=${search}`) if (!search && this.places.length) { return this.places } const matches = this.places.find(p => search === p.name.toLocaleLowerCase()) diff --git a/locales/en.json b/locales/en.json index cc9756c5..ae87414a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -147,6 +147,7 @@ "recurrent": "Recurring", "edit_recurrent": "Edit recurring event:", "show_recurrent": "recurring events", + "show_multidate": "multidate events", "show_past": "also prior events", "only_future": "only upcoming events", "recurrent_description": "Choose frequency and select days", diff --git a/pages/add/_edit.vue b/pages/add/_edit.vue index 5c58d976..8132de1a 100644 --- a/pages/add/_edit.vue +++ b/pages/add/_edit.vue @@ -179,7 +179,6 @@ export default { filteredTags() { if (!this.tagName) { return this.tags.slice(0, 10).map(t => t.tag) } const tagName = this.tagName.trim().toLowerCase() - console.log(tagName) return this.tags.filter(t => t.tag.toLowerCase().includes(tagName)).map(t => t.tag) } }, @@ -245,6 +244,8 @@ export default { if (this.date.dueHour) { [hour, minute] = this.date.dueHour.split(':') formData.append('end_datetime', dayjs(this.date.due).hour(Number(hour)).minute(Number(minute)).second(0).unix()) + } else if (!!this.date.multidate) { + formData.append('end_datetime', dayjs(this.date.due).hour(24).minute(0).second(0).unix()) } if (this.edit) { diff --git a/pages/index.vue b/pages/index.vue index b127c77a..33e1dac6 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,6 +1,5 @@