From cbed0288febff621029d6a71b0c9087c7dad439c Mon Sep 17 00:00:00 2001 From: lesion Date: Fri, 23 Dec 2022 01:08:14 +0100 Subject: [PATCH] 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"