models initialization refactored, better dev experience as backend hmr is working

This commit is contained in:
lesion 2022-12-23 01:08:14 +01:00
parent 380eaa87ca
commit cbed0288fe
No known key found for this signature in database
GPG key ID: 352918250B012177
43 changed files with 624 additions and 707 deletions

View file

@ -1,5 +1,9 @@
All notable changes to this project will be documented in this file. 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 ### 1.6.1 - 15 dec '22
- allow edit tags in admin panel, fix #170 - allow edit tags in admin panel, fix #170
- fix header / fallback image upload, fix #222 - fix header / fallback image upload, fix #222

View file

@ -75,7 +75,7 @@
"passport-oauth2-client-password": "^0.1.2", "passport-oauth2-client-password": "^0.1.2",
"passport-oauth2-client-public": "^0.0.1", "passport-oauth2-client-public": "^0.0.1",
"pg": "^8.8.0", "pg": "^8.8.0",
"sequelize": "^6.27.0", "sequelize": "^6.28.0",
"sequelize-slugify": "^1.6.2", "sequelize-slugify": "^1.6.2",
"sharp": "^0.27.2", "sharp": "^0.27.2",
"sqlite3": "^5.1.4", "sqlite3": "^5.1.4",

View file

@ -1,4 +1,5 @@
const Announcement = require('../models/announcement') const { Announcement } = require('../models/models')
const log = require('../../log') const log = require('../../log')
const announceController = { const announceController = {

View file

@ -1,4 +1,4 @@
const APUser = require('../models/ap_user') const { APUser } = require('../models/models')
const apUserController = { const apUserController = {
async toggleBlock (req, res) { async toggleBlock (req, res) {

View file

@ -1,8 +1,5 @@
const Collection = require('../models/collection') const { Collection, Filter, Event, Tag, Place } = require('../models/models')
const Filter = require('../models/filter')
const Event = require('../models/event')
const Tag = require('../models/tag')
const Place = require('../models/place')
const log = require('../../log') const log = require('../../log')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const { col: Col } = require('../../helpers') const { col: Col } = require('../../helpers')
@ -114,7 +111,7 @@ const collectionController = {
res.json(collection) res.json(collection)
} catch (e) { } catch (e) {
log.error(`Create collection failed ${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) { async addFilter (req, res) {
const collectionId = req.body.collectionId const { collectionId, tags, places } = req.body
const tags = req.body.tags
const places = req.body.places
try { try {
const filter = await Filter.create({ collectionId, tags, places }) filter = await Filter.create({ collectionId, tags, places })
return res.json(filter) return res.json(filter)
} catch (e) { } catch (e) {
log.error(String(e)) log.error(String(e))
return res.status(500) return res.sendStatus(400)
} }
}, },
@ -170,6 +166,4 @@ const collectionController = {
} }
module.exports = collectionController module.exports = collectionController

View file

@ -9,12 +9,10 @@ const Sequelize = require('sequelize')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const helpers = require('../../helpers') const helpers = require('../../helpers')
const Col = helpers.col const Col = helpers.col
const Event = require('../models/event') const notifier = require('../../notifier')
const Resource = require('../models/resource')
const Tag = require('../models/tag') const { Event, Resource, Tag, Place, Notification, APUser } = require('../models/models')
const Place = require('../models/place')
const Notification = require('../models/notification')
const APUser = require('../models/ap_user')
const exportController = require('./export') const exportController = require('./export')
const tagController = require('./tag') 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) { async _get(slug) {
// retrocompatibility, old events URL does not use slug, use id as fallback // retrocompatibility, old events URL does not use slug, use id as fallback
const id = Number(slug) || -1 const id = Number(slug) || -1
@ -317,7 +287,6 @@ const eventController = {
res.sendStatus(200) res.sendStatus(200)
// send notification // send notification
const notifier = require('../../notifier')
notifier.notifyEvent('Create', event.id) notifier.notifyEvent('Create', event.id)
} catch (e) { } catch (e) {
log.error('[EVENT]', e) log.error('[EVENT]', e)

View file

@ -1,6 +1,4 @@
const Event = require('../models/event') const { Event, Place, Tag } = require('../models/models')
const Place = require('../models/place')
const Tag = require('../models/tag')
const { htmlToText } = require('html-to-text') const { htmlToText } = require('html-to-text')
const { Op, literal } = require('sequelize') const { Op, literal } = require('sequelize')

View file

@ -1,6 +1,5 @@
const APUser = require('../models/ap_user') const { APUser, Instance, Resource } = require('../models/models')
const Instance = require('../models/instance')
const Resource = require('../models/resource')
const Sequelize = require('sequelize') const Sequelize = require('sequelize')
const instancesController = { const instancesController = {

View file

@ -1,4 +1,4 @@
const User = require('../models/user') const User = require('../models/modles')
const metrics = { const metrics = {

View file

@ -2,12 +2,9 @@ const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser') const cookieParser = require('cookie-parser')
const session = require('cookie-session') const session = require('cookie-session')
const OAuthClient = require('../models/oauth_client') const { OAuthClient, OAuthToken, OAuthCode, User } = require('../models/models')
const OAuthToken = require('../models/oauth_token')
const OAuthCode = require('../models/oauth_code')
const helpers = require('../../helpers.js') const helpers = require('../../helpers.js')
const User = require('../models/user')
const passport = require('passport') const passport = require('passport')
const get = require('lodash/get') const get = require('lodash/get')

View file

@ -1,5 +1,5 @@
const Place = require('../models/place') const { Place, Event } = require('../models/models')
const Event = require('../models/event')
const eventController = require('./event') const eventController = require('./event')
const exportController = require('./export') const exportController = require('./export')

View file

@ -2,11 +2,12 @@ const path = require('path')
const fs = require('fs') const fs = require('fs')
const log = require('../../log') const log = require('../../log')
const config = require('../../config') const config = require('../../config')
const settingsController = require('./settings')
const notifier = require('../../notifier')
const pluginController = { const pluginController = {
plugins: [], plugins: [],
getAll(_req, res) { getAll(_req, res) {
const settingsController = require('./settings')
// return plugins and inner settings // return plugins and inner settings
const plugins = pluginController.plugins.map( ({ configuration }) => { const plugins = pluginController.plugins.map( ({ configuration }) => {
if (settingsController.settings['plugin_' + configuration.name]) { if (settingsController.settings['plugin_' + configuration.name]) {
@ -18,7 +19,6 @@ const pluginController = {
}, },
togglePlugin(req, res) { togglePlugin(req, res) {
const settingsController = require('./settings')
const pluginName = req.params.plugin const pluginName = req.params.plugin
const pluginSettings = settingsController.settings['plugin_' + pluginName] const pluginSettings = settingsController.settings['plugin_' + pluginName]
if (!pluginSettings) { return res.sendStatus(404) } if (!pluginSettings) { return res.sendStatus(404) }
@ -33,7 +33,6 @@ const pluginController = {
}, },
unloadPlugin(pluginName) { unloadPlugin(pluginName) {
const settingsController = require('./settings')
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName) const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
const settings = settingsController.settings['plugin_' + pluginName] const settings = settingsController.settings['plugin_' + pluginName]
if (!plugin) { if (!plugin) {
@ -59,14 +58,12 @@ const pluginController = {
}, },
loadPlugin(pluginName) { loadPlugin(pluginName) {
const settingsController = require('./settings')
const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName) const plugin = pluginController.plugins.find(p => p.configuration.name === pluginName)
const settings = settingsController.settings['plugin_' + pluginName] const settings = settingsController.settings['plugin_' + pluginName]
if (!plugin) { if (!plugin) {
log.warn(`Plugin ${pluginName} not found`) log.warn(`Plugin ${pluginName} not found`)
return return
} }
const notifier = require('../../notifier')
log.info('Load plugin ' + pluginName) log.info('Load plugin ' + pluginName)
if (typeof plugin.onEventCreate === 'function') { if (typeof plugin.onEventCreate === 'function') {
notifier.emitter.on('Create', plugin.onEventCreate) notifier.emitter.on('Create', plugin.onEventCreate)
@ -88,7 +85,6 @@ const pluginController = {
}, },
_load() { _load() {
const settingsController = require('./settings')
// load custom plugins // load custom plugins
const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins') const plugins_path = config.plugins_path || path.resolve(process.env.cwd || '', 'plugins')
log.info(`Loading plugin ${plugins_path}`) log.info(`Loading plugin ${plugins_path}`)

View file

@ -1,6 +1,4 @@
const Resource = require('../models/resource') const { Resource, APUser, Event } = require('../models/models')
const APUser = require('../models/ap_user')
const Event = require('../models/event')
const get = require('lodash/get') const get = require('lodash/get')
const resourceController = { const resourceController = {

View file

@ -1,6 +1,5 @@
const path = require('path') const path = require('path')
const URL = require('url') const URL = require('url')
const fs = require('fs')
const crypto = require('crypto') const crypto = require('crypto')
const { promisify } = require('util') const { promisify } = require('util')
const sharp = require('sharp') const sharp = require('sharp')
@ -9,7 +8,7 @@ const generateKeyPair = promisify(crypto.generateKeyPair)
const log = require('../../log') const log = require('../../log')
// const locales = require('../../../locales/index') // const locales = require('../../../locales/index')
const escape = require('lodash/escape') const escape = require('lodash/escape')
const pluginController = require('./plugins') const DB = require('../models/models')
let defaultHostname let defaultHostname
try { try {
@ -30,7 +29,7 @@ const defaultSettings = {
allow_multidate_event: true, allow_multidate_event: true,
allow_recurrent_event: false, allow_recurrent_event: false,
recurrent_event_visible: false, recurrent_event_visible: false,
allow_geolocation: true, allow_geolocation: false,
geocoding_provider_type: 'Nominatim', geocoding_provider_type: 'Nominatim',
geocoding_provider: 'https://nominatim.openstreetmap.org/search', geocoding_provider: 'https://nominatim.openstreetmap.org/search',
geocoding_countrycodes: [], geocoding_countrycodes: [],
@ -74,8 +73,7 @@ const settingsController = {
// initialize instance settings from db // initialize instance settings from db
// note that this is done only once when the server starts // note that this is done only once when the server starts
// and not for each request // and not for each request
const Setting = require('../models/setting') const settings = await DB.Setting.findAll()
const settings = await Setting.findAll()
settingsController.settings = defaultSettings settingsController.settings = defaultSettings
settings.forEach(s => { settings.forEach(s => {
if (s.is_secret) { if (s.is_secret) {
@ -117,15 +115,14 @@ const settingsController = {
// } // }
// }) // })
// } // }
const pluginController = require('./plugins')
pluginController._load() pluginController._load()
}, },
async set (key, value, is_secret = false) { async set (key, value, is_secret = false) {
const Setting = require('../models/setting')
log.info(`SET ${key} ${is_secret ? '*****' : value}`) log.info(`SET ${key} ${is_secret ? '*****' : value}`)
try { try {
const [setting, created] = await Setting.findOrCreate({ const [setting, created] = await DB.Setting.findOrCreate({
where: { key }, where: { key },
defaults: { value, is_secret } defaults: { value, is_secret }
}) })

View file

@ -7,6 +7,8 @@ const settingsController = require('./settings')
const path = require('path') const path = require('path')
const escape = require('lodash/escape') const escape = require('lodash/escape')
const DB = require('../models/models')
const setupController = { const setupController = {
async _setupDb (dbConf) { async _setupDb (dbConf) {
@ -23,7 +25,10 @@ const setupController = {
// try to connect // try to connect
dbConf.logging = false dbConf.logging = false
await db.connect(dbConf) db.connect(dbConf)
db.loadModels()
db.associates()
await db.sequelize.authenticate()
// is empty ? // is empty ?
const isEmpty = await db.isEmpty() const isEmpty = await db.isEmpty()
@ -69,8 +74,7 @@ const setupController = {
// create admin // create admin
const password = helpers.randomString() const password = helpers.randomString()
const email = `admin` const email = `admin`
const User = require('../models/user') await DB.User.create({
await User.create({
email, email,
password, password,
is_admin: true, is_admin: true,

View file

@ -1,5 +1,4 @@
const Tag = require('../models/tag') const { Tag, Event } = require('../models/models')
const Event = require('../models/event')
const uniq = require('lodash/uniq') const uniq = require('lodash/uniq')
const log = require('../../log') const log = require('../../log')
@ -82,29 +81,35 @@ module.exports = {
return res.json(tags.map(t => t.tag)) return res.json(tags.map(t => t.tag))
}, },
async updateTag (req, res) { // async updateTag (req, res) {
const tag = await Tag.findByPk(req.body.tag) // const tag = await Tag.findByPk(req.body.tag)
await tag.update(req.body) // await tag.update(req.body)
res.json(place) // res.json(place)
}, // },
async updateTag (req, res) { async updateTag (req, res) {
const oldtag = await Tag.findByPk(req.body.tag) try {
const newtag = await Tag.findByPk(req.body.newTag) 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 the new tag does not exists, just rename the old one
if (!newtag) { if (!newtag) {
oldtag.tag = req.body.newTag log.info(`Rename tag ${oldtag.tag} to ${req.body.newTag}`)
await oldtag.update({ tag: req.body.newTag }) await Tag.update({ tag: req.body.newTag }, { where: { tag: req.body.tag }, raw: true })
} else {
// in case it exists: } else {
// - search for events with old tag // in case it exists:
const events = await oldtag.getEvents() // - search for events with old tag
// - substitute it with the new one const events = await oldtag.getEvents()
await oldtag.removeEvents(events) // - substitute it with the new one
await newtag.addEvents(events) 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) { async remove (req, res) {

View file

@ -2,7 +2,7 @@ const crypto = require('crypto')
const { Op } = require('sequelize') const { Op } = require('sequelize')
const config = require('../../config') const config = require('../../config')
const mail = require('../mail') const mail = require('../mail')
const User = require('../models/user') const { User } = require('../models/models')
const settingsController = require('./settings') const settingsController = require('./settings')
const log = require('../../log') const log = require('../../log')
const linkify = require('linkifyjs') const linkify = require('linkifyjs')

View file

@ -5,219 +5,223 @@ const cors = require('cors')()
const config = require('../config') const config = require('../config')
const log = require('../log') const log = require('../log')
const api = express.Router() const collectionController = require('./controller/collection')
api.use(express.urlencoded({ extended: false })) const setupController = require('./controller/setup')
api.use(express.json()) 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 api = express.Router()
const settingsController = require('./controller/settings') api.use(express.urlencoded({ extended: false }))
api.post('/settings', settingsController.setRequest) api.use(express.json())
api.post('/setup/db', setupController.setupDb)
api.post('/setup/restart', setupController.restart)
api.post('/settings/smtp', settingsController.testSMTP) if (config.status !== 'READY') {
} else { api.post('/settings', settingsController.setRequest)
api.post('/setup/db', setupController.setupDb)
const { isAuth, isAdmin } = require('./auth') api.post('/setup/restart', setupController.restart)
const eventController = require('./controller/event') api.post('/settings/smtp', settingsController.testSMTP)
const placeController = require('./controller/place')
const tagController = require('./controller/tag') } else {
const settingsController = require('./controller/settings')
const exportController = require('./controller/export') const { isAuth, isAdmin } = require('./auth')
const userController = require('./controller/user') const upload = multer({ storage })
const instanceController = require('./controller/instance')
const apUserController = require('./controller/ap_user') /**
const resourceController = require('./controller/resource') * Get current authenticated user
const oauthController = require('./controller/oauth') * @category User
const announceController = require('./controller/announce') * @name /api/user
const collectionController = require('./controller/collection') * @type GET
const pluginController = require('./controller/plugins') * @example **Response**
const helpers = require('../helpers') * ```json
const storage = require('./storage') {
const upload = multer({ storage }) "description" : null,
"recover_code" : "",
/** "id" : 1,
* Get current authenticated user "createdAt" : "2020-01-29T18:10:16.630Z",
* @category User "updatedAt" : "2020-01-30T22:42:14.789Z",
* @name /api/user "is_active" : true,
* @type GET "settings" : "{}",
* @example **Response** "email" : "eventi@cisti.org",
* ```json "is_admin" : true
{ }
"description" : null, ```
"recover_code" : "", */
"id" : 1, api.get('/ping', (_req, res) => res.sendStatus(200))
"createdAt" : "2020-01-29T18:10:16.630Z", api.get('/user', isAuth, (req, res) => res.json(req.user))
"updatedAt" : "2020-01-30T22:42:14.789Z",
"is_active" : true,
"settings" : "{}", api.post('/user/recover', userController.forgotPassword)
"email" : "eventi@cisti.org", api.post('/user/check_recover_code', userController.checkRecoverCode)
"is_admin" : true 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.use((_req, res) => res.sendStatus(404))
api.get('/ping', (_req, res) => res.sendStatus(200))
api.get('/user', isAuth, (req, res) => res.json(req.user)) // Handle 500
api.use((error, _req, res, _next) => {
log.error('[API ERROR]', error)
res.status(500).send('500: Internal Server Error')
})
return api
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.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

View file

@ -1,12 +1,6 @@
const sequelize = require('./index').sequelize module.exports = (sequelize, DataTypes) =>
const { Model, DataTypes } = require('sequelize') sequelize.define('announcement', {
title: DataTypes.STRING,
class Announcement extends Model {} announcement: DataTypes.STRING,
visible: DataTypes.BOOLEAN
Announcement.init({ })
title: DataTypes.STRING,
announcement: DataTypes.STRING,
visible: DataTypes.BOOLEAN
}, { sequelize, modelName: 'announcement' })
module.exports = Announcement

View file

@ -1,9 +1,6 @@
const sequelize = require('./index').sequelize
const { Model, DataTypes } = require('sequelize')
class APUser extends Model {} module.exports = (sequelize, DataTypes) =>
sequelize.define('ap_user', {
APUser.init({
ap_id: { ap_id: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true primaryKey: true
@ -11,6 +8,4 @@ APUser.init({
follower: DataTypes.BOOLEAN, follower: DataTypes.BOOLEAN,
blocked: DataTypes.BOOLEAN, blocked: DataTypes.BOOLEAN,
object: DataTypes.JSON object: DataTypes.JSON
}, { sequelize, modelName: 'ap_user' }) })
module.exports = APUser

View file

@ -1,28 +1,20 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('collection', {
id: {
class Collection extends Model {} type: DataTypes.INTEGER,
autoIncrement: true,
// TODO: slugify! primaryKey: true,
Collection.init({ },
id: { name: {
type: DataTypes.INTEGER, type: DataTypes.STRING,
autoIncrement: true, unique: true,
primaryKey: true, index: true,
}, allowNull: false
name: { },
type: DataTypes.STRING, isActor: {
unique: true, type: DataTypes.BOOLEAN
index: true, },
allowNull: false isTop: {
}, type: DataTypes.BOOLEAN
isActor: { }
type: DataTypes.BOOLEAN }, { timestamps: false })
},
isTop: {
type: DataTypes.BOOLEAN
}
}, { sequelize, modelName: 'collection', timestamps: false })
module.exports = Collection

View file

@ -1,18 +1,5 @@
const config = require('../../config') const config = require('../../config')
const { htmlToText } = require('html-to-text') 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 dayjs = require('dayjs')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
@ -20,108 +7,88 @@ const utc = require('dayjs/plugin/utc')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
class Event extends Model {} // class Event extends Model {}
module.exports = (sequelize, DataTypes) => {
Event.init({ const Event = sequelize.define('event', {
id: { id: {
allowNull: false, allowNull: false,
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
primaryKey: true, primaryKey: true,
autoIncrement: true autoIncrement: true
}, },
title: DataTypes.STRING, title: DataTypes.STRING,
slug: { slug: {
type: DataTypes.STRING, type: DataTypes.STRING,
index: true, index: true,
unique: true unique: true
}, },
description: DataTypes.TEXT, description: DataTypes.TEXT,
multidate: DataTypes.BOOLEAN, multidate: DataTypes.BOOLEAN,
start_datetime: { start_datetime: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
index: true index: true
}, },
end_datetime: { end_datetime: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
index: true index: true
}, },
image_path: DataTypes.STRING, image_path: DataTypes.STRING,
media: DataTypes.JSON, media: DataTypes.JSON,
is_visible: DataTypes.BOOLEAN, is_visible: DataTypes.BOOLEAN,
recurrent: DataTypes.JSON, recurrent: DataTypes.JSON,
likes: { type: DataTypes.JSON, defaultValue: [] }, likes: { type: DataTypes.JSON, defaultValue: [] },
boost: { type: DataTypes.JSON, defaultValue: [] } boost: { type: DataTypes.JSON, defaultValue: [] }
}, { sequelize, modelName: 'event' }) })
Event.belongsTo(Place) Event.prototype.toAP = function (username, locale, to = []) {
Place.hasMany(Event) const tags = this.tags && this.tags.map(t => t.tag.replace(/[ #]/g, '_'))
const plainDescription = htmlToText(this.description && this.description.replace('\n', '').slice(0, 1000))
Event.belongsTo(User) const content = `
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 = `
📍 ${this.place && this.place.name} 📍 ${this.place && this.place.name}
📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')} 📅 ${dayjs.unix(this.start_datetime).tz().locale(locale).format('dddd, D MMMM (HH:mm)')}
${plainDescription} ${plainDescription}
` `
const attachment = [] const attachment = []
if (this.media && this.media.length) { if (this.media && this.media.length) {
attachment.push({ attachment.push({
type: 'Document', type: 'Document',
mediaType: 'image/jpeg', mediaType: 'image/jpeg',
url: `${config.baseurl}/media/${this.media[0].url}`, url: `${config.baseurl}/media/${this.media[0].url}`,
name: this.media[0].name || this.title || '', name: this.media[0].name || this.title || '',
blurHash: null, blurHash: null,
focalPoint: this.media[0].focalPoint || [0, 0] 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 Event
}
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

View file

@ -1,15 +1,9 @@
const sequelize = require('./index').sequelize module.exports = (sequelize, DataTypes) =>
const { Model, DataTypes } = require('sequelize') sequelize.define('event_notification', {
class EventNotification extends Model {}
EventNotification.init({
status: { status: {
type: DataTypes.ENUM, type: DataTypes.ENUM,
values: ['new', 'sent', 'error', 'sending'], values: ['new', 'sent', 'error', 'sending'],
defaultValue: 'new', defaultValue: 'new',
index: true index: true
} }
}, { sequelize, modelName: 'event_notification' }) })
module.exports = EventNotification

View file

@ -1,24 +1,20 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const Collection = require('./collection') sequelize.define('filter',
const sequelize = require('./index').sequelize {
id: {
class Filter extends Model {} type: DataTypes.INTEGER,
primaryKey: true,
Filter.init({ autoIncrement: true,
id: { },
type: DataTypes.INTEGER, tags: {
primaryKey: true, type: DataTypes.JSON,
autoIncrement: true, },
}, places: {
tags: { type: DataTypes.JSON,
type: DataTypes.JSON, }
}, }, {
places: { indexes: [
type: DataTypes.JSON, { fields: ['collectionId', 'tags', 'places'], unique: true }
} ],
}, { sequelize, modelName: 'filter', timestamps: false }) timestamps: false
})
Filter.belongsTo(Collection)
Collection.hasMany(Filter)
module.exports = Filter

View file

@ -4,10 +4,78 @@ const Umzug = require('umzug')
const path = require('path') const path = require('path')
const config = require('../../config') const config = require('../../config')
const log = require('../../log') 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 = { const db = {
sequelize: null, 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() { close() {
if (db.sequelize) { if (db.sequelize) {
return db.sequelize.close() return db.sequelize.close()
@ -28,7 +96,6 @@ const db = {
} }
} }
db.sequelize = new Sequelize(dbConf) db.sequelize = new Sequelize(dbConf)
return db.sequelize.authenticate()
}, },
async isEmpty() { async isEmpty() {
try { try {
@ -57,13 +124,12 @@ const db = {
}) })
return umzug.up() return umzug.up()
}, },
async initialize() { initialize() {
if (config.status === 'CONFIGURED') { if (config.status === 'CONFIGURED') {
try { try {
await db.connect() db.connect()
log.debug('Running migrations') db.loadModels()
await db.runMigrations() db.associates()
return settingsController.load()
} catch (e) { } catch (e) {
log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`) log.warn(` ⚠️ Cannot connect to db, check your configuration => ${e}`)
process.exit(1) process.exit(1)

View file

@ -1,11 +1,5 @@
module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('instance', {
const { Model, DataTypes } = require('sequelize')
const APUser = require('./ap_user')
class Instance extends Model {}
Instance.init({
domain: { domain: {
primaryKey: true, primaryKey: true,
allowNull: false, allowNull: false,
@ -14,9 +8,4 @@ Instance.init({
name: DataTypes.STRING, name: DataTypes.STRING,
blocked: DataTypes.BOOLEAN, blocked: DataTypes.BOOLEAN,
data: DataTypes.JSON data: DataTypes.JSON
}, { sequelize, modelName: 'instance' }) })
Instance.hasMany(APUser)
APUser.belongsTo(Instance)
module.exports = Instance

View file

@ -0,0 +1,2 @@
// export default models
module.exports = {}

View file

@ -1,10 +1,5 @@
module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('notification', {
const { Model, DataTypes } = require('sequelize')
class Notification extends Model {}
Notification.init({
filters: DataTypes.JSON, filters: DataTypes.JSON,
email: DataTypes.STRING, email: DataTypes.STRING,
remove_code: DataTypes.STRING, remove_code: DataTypes.STRING,
@ -24,6 +19,4 @@ Notification.init({
unique: true, unique: true,
fields: ['action', 'type'] fields: ['action', 'type']
}] }]
}) })
module.exports = Notification

View file

@ -1,10 +1,5 @@
module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('oauth_client', {
const { Model, DataTypes } = require('sequelize')
class OAuthClient extends Model {}
OAuthClient.init({
id: { id: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true, primaryKey: true,
@ -15,6 +10,4 @@ OAuthClient.init({
scopes: DataTypes.STRING, scopes: DataTypes.STRING,
redirectUris: DataTypes.STRING, redirectUris: DataTypes.STRING,
website: DataTypes.STRING website: DataTypes.STRING
}, { sequelize, modelName: 'oauth_client' }) })
module.exports = OAuthClient

View file

@ -1,13 +1,5 @@
module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('oauth_code', {
const { Model, DataTypes } = require('sequelize')
const User = require('./user')
const OAuthClient = require('./oauth_client')
class OAuthCode extends Model {}
OAuthCode.init({
authorizationCode: { authorizationCode: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true primaryKey: true
@ -15,9 +7,4 @@ OAuthCode.init({
expiresAt: DataTypes.DATE, expiresAt: DataTypes.DATE,
scope: DataTypes.STRING, scope: DataTypes.STRING,
redirect_uri: DataTypes.STRING redirect_uri: DataTypes.STRING
}, { sequelize, modelName: 'oauth_code' }) })
OAuthCode.belongsTo(User)
OAuthCode.belongsTo(OAuthClient, { as: 'client' })
module.exports = OAuthCode

View file

@ -1,13 +1,5 @@
module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('oauth_token', {
const { Model, DataTypes } = require('sequelize')
const User = require('./user')
const OAuthClient = require('./oauth_client')
class OAuthToken extends Model {}
OAuthToken.init({
accessToken: { accessToken: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
@ -27,9 +19,4 @@ OAuthToken.init({
} }
}, },
scope: DataTypes.STRING scope: DataTypes.STRING
}, { sequelize, modelName: 'oauth_token' }) })
OAuthToken.belongsTo(User)
OAuthToken.belongsTo(OAuthClient, { as: 'client' })
module.exports = OAuthToken

View file

@ -1,9 +1,5 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('place', {
class Place extends Model {}
Place.init({
name: { name: {
type: DataTypes.STRING, type: DataTypes.STRING,
unique: true, unique: true,
@ -13,6 +9,4 @@ Place.init({
address: DataTypes.STRING, address: DataTypes.STRING,
latitude: DataTypes.FLOAT, latitude: DataTypes.FLOAT,
longitude: DataTypes.FLOAT, longitude: DataTypes.FLOAT,
}, { sequelize, modelName: 'place' }) })
module.exports = Place

View file

@ -1,11 +1,5 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('resource', {
const APUser = require('./ap_user')
class Resource extends Model {}
Resource.init({
activitypub_id: { activitypub_id: {
type: DataTypes.STRING, type: DataTypes.STRING,
index: true, index: true,
@ -13,9 +7,4 @@ Resource.init({
}, },
hidden: DataTypes.BOOLEAN, hidden: DataTypes.BOOLEAN,
data: DataTypes.JSON data: DataTypes.JSON
}, { sequelize, modelName: 'resource' }) })
APUser.hasMany(Resource)
Resource.belongsTo(APUser)
module.exports = Resource

View file

@ -1,9 +1,5 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('setting', {
class Setting extends Model {}
Setting.init({
key: { key: {
type: DataTypes.STRING, type: DataTypes.STRING,
primaryKey: true, primaryKey: true,
@ -12,6 +8,4 @@ Setting.init({
}, },
value: DataTypes.JSON, value: DataTypes.JSON,
is_secret: DataTypes.BOOLEAN is_secret: DataTypes.BOOLEAN
}, { sequelize, modelName: 'setting' }) })
module.exports = Setting

View file

@ -1,15 +1,9 @@
const { Model, DataTypes } = require('sequelize') module.exports = (sequelize, DataTypes) =>
const sequelize = require('./index').sequelize sequelize.define('tag', {
class Tag extends Model {}
Tag.init({
tag: { tag: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
index: true, index: true,
primaryKey: true primaryKey: true
} }
}, { sequelize, modelName: 'tag' }) })
module.exports = Tag

View file

@ -1,53 +1,49 @@
const bcrypt = require('bcryptjs') const bcrypt = require('bcryptjs')
const { Model, DataTypes } = require('sequelize')
const sequelize = require('./index').sequelize
class User extends Model {} module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('user', {
User.init({ settings: {
settings: { type: DataTypes.JSON,
type: DataTypes.JSON, defaultValue: []
defaultValue: []
},
email: {
type: DataTypes.STRING,
unique: { msg: 'error.email_taken' },
validate: {
notEmpty: true
}, },
index: true, email: {
allowNull: false type: DataTypes.STRING,
}, unique: { msg: 'error.email_taken' },
description: DataTypes.TEXT, validate: {
password: DataTypes.STRING, notEmpty: true
recover_code: DataTypes.STRING, },
is_admin: DataTypes.BOOLEAN, index: true,
is_active: DataTypes.BOOLEAN allowNull: false
}, {
sequelize,
modelName: 'user',
scopes: {
withoutPassword: {
attributes: { exclude: ['password', 'recover_code'] }
}, },
withRecover: { description: DataTypes.TEXT,
attributes: { exclude: ['password'] } 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) { return User
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
}
})
module.exports = User

View file

@ -9,7 +9,7 @@ function _initializeDB () {
async function modify (args) { async function modify (args) {
await _initializeDB() await _initializeDB()
const helpers = require('../helpers') 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 } }) const user = await User.findOne({ where: { email: args.account } })
console.log() console.log()
if (!user) { if (!user) {

View file

@ -1,10 +1,10 @@
const axios = require('axios') const axios = require('axios')
// const request = require('request')
const crypto = require('crypto') const crypto = require('crypto')
const config = require('../config') const config = require('../config')
const httpSignature = require('http-signature') 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 url = require('url')
const settingsController = require('../api/controller/settings') const settingsController = require('../api/controller/settings')
const log = require('../log') const log = require('../log')

View file

@ -270,9 +270,9 @@ module.exports = {
}, },
async APRedirect(req, res, next) { async APRedirect(req, res, next) {
const eventController = require('../server/api/controller/event')
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json' const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
if (acceptJson) { if (acceptJson) {
const eventController = require('../server/api/controller/event')
const event = await eventController._get(req.params.slug) const event = await eventController._get(req.params.slug)
if (event) { if (event) {
return res.redirect(`/federation/m/${event.id}`) return res.redirect(`/federation/m/${event.id}`)

View file

@ -1,4 +1,11 @@
const config = require('../server/config') 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 = { const initialize = {
// close connections/port/unix socket // close connections/port/unix socket
@ -19,14 +26,14 @@ const initialize = {
}, },
async start () { async start () {
const log = require('../server/log')
const settingsController = require('./api/controller/settings')
const db = require('./api/models/index')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
dayjs.extend(timezone) dayjs.extend(timezone)
if (config.status == 'CONFIGURED') { if (config.status == 'CONFIGURED') {
await db.initialize() await db.sequelize.authenticate()
log.debug('Running migrations')
await db.runMigrations()
await settingsController.load()
config.status = 'READY' config.status = 'READY'
} else { } else {
if (process.env.GANCIO_DB_DIALECT) { if (process.env.GANCIO_DB_DIALECT) {

View file

@ -4,14 +4,10 @@ const mail = require('./api/mail')
const log = require('./log') const log = require('./log')
const fediverseHelpers = require('./federation/helpers') 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 settingsController = require('./api/controller/settings')
const notifier = { const notifier = {
@ -37,7 +33,36 @@ const notifier = {
return Promise.all(promises) 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) { async notifyEvent (action, eventId) {
const event = await Event.findByPk(eventId, { const event = await Event.findByPk(eventId, {
include: [Tag, Place, Notification, User] include: [Tag, Place, Notification, User]
}) })
@ -46,7 +71,7 @@ const notifier = {
log.debug(action, event.title) log.debug(action, event.title)
// insert notifications // insert notifications
const notifications = await eventController.getNotifications(event, action) const notifications = await notifier.getNotifications(event, action)
await event.addNotifications(notifications) await event.addNotifications(notifications)
const event_notifications = await event.getNotifications({ through: { where: { status: 'new' } } }) const event_notifications = await event.getNotifications({ through: { where: { status: 'new' } } })

View file

@ -4,23 +4,22 @@ const initialize = require('./initialize.server')
const config = require('./config') const config = require('./config')
const helpers = require('./helpers') const helpers = require('./helpers')
const api = require('./api')
app.use([
helpers.initSettings,
helpers.logRequest,
helpers.serveStatic()
])
async function main () { async function main () {
await initialize.start() await initialize.start()
app.use([
helpers.initSettings,
helpers.logRequest,
helpers.serveStatic()
])
// const metricsController = require('./metrics') // const metricsController = require('./metrics')
// const promBundle = require('express-prom-bundle') // const promBundle = require('express-prom-bundle')
// const metricsMiddleware = promBundle({ includeMethod: true }) // const metricsMiddleware = promBundle({ includeMethod: true })
const log = require('./log') const log = require('./log')
const api = require('./api')
app.enable('trust proxy') app.enable('trust proxy')
@ -60,7 +59,7 @@ async function main () {
} }
// api! // api!
app.use('/api', api) app.use('/api', api())
// // Handle 500 // // Handle 500
app.use((error, _req, res, _next) => { app.use((error, _req, res, _next) => {
@ -87,8 +86,6 @@ if (process.env.NODE_ENV !== 'test') {
main() main()
} }
// app.listen(13120)
module.exports = { module.exports = {
main, main,
handler: app, handler: app,

View file

@ -10634,10 +10634,10 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
retry-as-promised@^6.1.0: retry-as-promised@^7.0.3:
version "6.1.0" version "7.0.3"
resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-6.1.0.tgz#11eca9a0f97804d552ec8e74bc4eb839bd226dc4" resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-7.0.3.tgz#ca3c13b15525a7bfbf0f56d2996f0e75649d068b"
integrity sha512-Hj/jY+wFC+SB9SDlIIFWiGOHnNG0swYbGYsOj2BJ8u2HKUaobNKab0OIC0zOLYzDy0mb7A4xA5BMo4LMz5YtEA== integrity sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q==
retry@^0.12.0: retry@^0.12.0:
version "0.12.0" version "0.12.0"
@ -10926,10 +10926,10 @@ sequelize-slugify@^1.6.2:
dependencies: dependencies:
sluglife "^0.9.8" sluglife "^0.9.8"
sequelize@^6.27.0: sequelize@^6.28.0:
version "6.27.0" version "6.28.0"
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.27.0.tgz#b267e76997df57842cc1e2c1c1d7e02405bcdb9c" resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.28.0.tgz#d6bc4e36647e8501635467c0777c45a33f5d5ba8"
integrity sha512-Rm7BM8HQekeABup0KdtSHriu8ppJuHj2TJWCxvZtzU6j8V1LVnBk2rs38P8r4gMWgdLKs5NYoLC4il95KLsv0w== integrity sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==
dependencies: dependencies:
"@types/debug" "^4.1.7" "@types/debug" "^4.1.7"
"@types/validator" "^13.7.1" "@types/validator" "^13.7.1"
@ -10940,7 +10940,7 @@ sequelize@^6.27.0:
moment "^2.29.1" moment "^2.29.1"
moment-timezone "^0.5.34" moment-timezone "^0.5.34"
pg-connection-string "^2.5.0" pg-connection-string "^2.5.0"
retry-as-promised "^6.1.0" retry-as-promised "^7.0.3"
semver "^7.3.5" semver "^7.3.5"
sequelize-pool "^7.1.0" sequelize-pool "^7.1.0"
toposort-class "^1.0.1" toposort-class "^1.0.1"