Merge branch 'master' of framagit.org:/les/gancio into feat/allowgeoloc

This commit is contained in:
sedum
2022-09-08 21:17:14 +02:00
26 changed files with 2580 additions and 3957 deletions

View File

@@ -23,18 +23,18 @@ const log = require('../../log')
const eventController = {
async searchMeta (req, res) {
async searchMeta(req, res) {
const search = req.query.search
const places = await Place.findAll({
order: [[Sequelize.col('w'), 'DESC']],
where: {
[Op.or]: [
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%' ),
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('address')), 'LIKE', '%' + search + '%')
]
},
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')),'INTEGER'), 'w']],
attributes: [['name', 'label'], 'address', 'id', [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('events.placeId')), 'INTEGER'), 'w']],
include: [{ model: Event, where: { is_visible: true }, required: true, attributes: [] }],
group: ['place.id'],
raw: true
@@ -45,7 +45,7 @@ const eventController = {
where: {
tag: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('tag')), 'LIKE', '%' + search + '%'),
},
attributes: [['tag','label'], [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'INTEGER'), 'w']],
attributes: [['tag', 'label'], [Sequelize.cast(Sequelize.fn('COUNT', Sequelize.col('tag.tag')), 'INTEGER'), 'w']],
include: [{ model: Event, where: { is_visible: true }, attributes: [], through: { attributes: [] }, required: true }],
group: ['tag.tag'],
raw: true
@@ -57,13 +57,13 @@ const eventController = {
}).concat(tags.map(t => {
t.type = 'tag'
return t
})).sort( (a, b) => b.w - a.w).slice(0, 10)
})).sort((a, b) => b.w - a.w).slice(0, 10)
return res.json(ret)
},
async search (req, res) {
async search(req, res) {
const search = req.query.search.trim().toLocaleLowerCase()
const show_recurrent = req.query.show_recurrent || false
const end = req.query.end
@@ -89,11 +89,11 @@ const eventController = {
if (search) {
replacements.push(search)
where[Op.or] =
[
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
]
[
{ title: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('title')), 'LIKE', '%' + search + '%') },
Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), 'LIKE', '%' + search + '%'),
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) = ?`))
]
}
@@ -110,7 +110,7 @@ const eventController = {
attributes: ['tag'],
through: { attributes: [] }
},
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'details'] }
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
],
replacements,
limit: 30,
@@ -129,9 +129,9 @@ const eventController = {
},
async getNotifications (event, action) {
async getNotifications(event, action) {
log.debug(`getNotifications ${event.title} ${action}`)
function match (event, filters) {
function match(event, filters) {
// matches if no filter specified
if (!filters) { return true }
@@ -170,7 +170,7 @@ const eventController = {
})
},
async get (req, res) {
async get(req, res) {
const format = req.params.format || 'json'
const is_admin = res.locals.user && res.locals.user.is_admin
const slug = req.params.event_slug
@@ -192,7 +192,7 @@ const eventController = {
},
include: [
{ model: Tag, required: false, attributes: ['tag'], through: { attributes: [] } },
{ model: Place, attributes: ['name', 'address', 'details', 'id'] },
{ model: Place, attributes: ['name', 'address', 'id'] },
{
model: Resource,
where: !is_admin && { hidden: false },
@@ -270,7 +270,7 @@ const eventController = {
/** confirm an anonymous event
* and send related notifications
*/
async confirm (req, res) {
async confirm(req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id, { include: [Place, Tag] })
if (!event) {
@@ -299,7 +299,7 @@ const eventController = {
}
},
async unconfirm (req, res) {
async unconfirm(req, res) {
const id = Number(req.params.event_id)
const event = await Event.findByPk(id)
if (!event) { return req.sendStatus(404) }
@@ -318,7 +318,7 @@ const eventController = {
},
/** get all unconfirmed events */
async getUnconfirmed (_req, res) {
async getUnconfirmed(_req, res) {
try {
const events = await Event.findAll({
where: {
@@ -336,7 +336,7 @@ const eventController = {
}
},
async addNotification (req, res) {
async addNotification(req, res) {
try {
const notification = {
filters: { is_visible: true },
@@ -351,7 +351,7 @@ const eventController = {
}
},
async delNotification (req, res) {
async delNotification(req, res) {
const remove_code = req.params.code
try {
const notification = await Notification.findOne({ where: { remove_code } })
@@ -362,14 +362,14 @@ const eventController = {
res.sendStatus(200)
},
async isAnonEventAllowed (_req, res, next) {
async isAnonEventAllowed(_req, res, next) {
if (!res.locals.settings.allow_anon_event && !res.locals.user) {
return res.sendStatus(403)
}
next()
},
async add (req, res) {
async add(req, res) {
// req.err comes from multer streaming error
if (req.err) {
log.warn(req.err)
@@ -380,7 +380,7 @@ const eventController = {
const body = req.body
const recurrent = body.recurrent ? JSON.parse(body.recurrent) : null
const required_fields = [ 'title', 'start_datetime']
const required_fields = ['title', 'start_datetime']
let missing_field = required_fields.find(required_field => !body[required_field])
if (missing_field) {
log.warn(`${missing_field} required`)
@@ -398,7 +398,7 @@ const eventController = {
if (!body.place_name) {
return res.status(400).send(`Place not found`)
}
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase()) })
if (!place) {
if (!body.place_address || !body.place_name) {
return res.status(400).send(`place_id or place_name and place_address required`)
@@ -480,7 +480,7 @@ const eventController = {
}
},
async update (req, res) {
async update(req, res) {
if (res.err) {
log.warn(req.err)
return res.status(400).json(req.err.toString())
@@ -538,7 +538,7 @@ const eventController = {
} else if (body.image_focalpoint && event.media.length) {
let focalpoint = body.image_focalpoint ? body.image_focalpoint.split(',') : ['0', '0']
focalpoint = [parseFloat(parseFloat(focalpoint[0]).toFixed(2)), parseFloat(parseFloat(focalpoint[1]).toFixed(2))]
eventDetails.media = [ { ...event.media[0], focalpoint } ] // [0].focalpoint = focalpoint
eventDetails.media = [{ ...event.media[0], focalpoint }] // [0].focalpoint = focalpoint
}
await event.update(eventDetails)
@@ -553,7 +553,7 @@ const eventController = {
if (!body.place_name) {
return res.status(400).send(`Place not found`)
}
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase() )})
place = await Place.findOne({ where: Sequelize.where(Sequelize.fn('LOWER', Sequelize.col('name')), Op.eq, body.place_name.trim().toLocaleLowerCase()) })
if (!place) {
if (!body.place_address || !body.place_name) {
return res.status(400).send(`place_id or place_name and place_address required`)
@@ -595,7 +595,7 @@ const eventController = {
}
},
async remove (req, res) {
async remove(req, res) {
const event = await Event.findByPk(req.params.id)
// check if event is mine (or user is admin)
if (event && (res.locals.user.is_admin || res.locals.user.id === event.userId)) {
@@ -626,9 +626,9 @@ const eventController = {
/**
* Method to search for events with pagination and filtering
* @returns
* @returns
*/
async _select ({
async _select({
start = dayjs().unix(),
end,
tags,
@@ -668,7 +668,7 @@ const eventController = {
const replacements = []
if (tags && places) {
where[Op.and] = [
{ placeId: places ? places.split(',') : []},
{ placeId: places ? places.split(',') : [] },
Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=${Col('event.id')} AND LOWER(${Col('tagTag')}) in (?)`))
]
replacements.push(tags)
@@ -692,7 +692,7 @@ const eventController = {
attributes: {
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'description', 'resources', 'recurrent', 'placeId', 'image_path']
},
order: [['start_datetime', older ? 'DESC' : 'ASC' ]],
order: [['start_datetime', older ? 'DESC' : 'ASC']],
include: [
{
model: Tag,
@@ -700,7 +700,7 @@ const eventController = {
attributes: ['tag'],
through: { attributes: [] }
},
{ model: Place, required: true, attributes: ['id', 'name', 'address', 'details'] }
{ model: Place, required: true, attributes: ['id', 'name', 'address'] }
],
...pagination,
replacements
@@ -719,14 +719,14 @@ const eventController = {
/**
* Select events based on params
*/
async select (req, res) {
async select(req, res) {
const settings = res.locals.settings
const start = req.query.start || dayjs().unix()
const end = req.query.end
const tags = req.query.tags
const places = req.query.places
const limit = req.query.max
const page = req.query.page = 0
const limit = Number(req.query.max) || 0
const page = Number(req.query.page) || 0
const older = req.query.older || false
const show_recurrent = settings.allow_recurrent_event &&
@@ -740,7 +740,7 @@ const eventController = {
/**
* Ensure we have the next instance of a recurrent event
*/
async _createRecurrentOccurrence (e, startAt) {
async _createRecurrentOccurrence(e, startAt) {
log.debug(`Create recurrent event [${e.id}] ${e.title}"`)
const event = {
parentId: e.id,
@@ -800,12 +800,12 @@ const eventController = {
/**
* Create instances of recurrent events
*/
async _createRecurrent (start_datetime = dayjs().unix()) {
async _createRecurrent(start_datetime = dayjs().unix()) {
// select recurrent events and its childs
const events = await Event.findAll({
where: { is_visible: true, recurrent: { [Op.ne]: null } },
include: [{ model: Tag, required: false },
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } }}],
{ model: Event, as: 'child', required: false, where: { start_datetime: { [Op.gte]: start_datetime } } }],
order: [['child', 'start_datetime', 'DESC']]
})
@@ -813,7 +813,7 @@ const eventController = {
const creations = events.map(e => {
if (e.child.length) {
if (e.child.find(c => c.is_visible)) return
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime+1))
return eventController._createRecurrentOccurrence(e, dayjs.unix(e.child[0].start_datetime + 1))
}
return eventController._createRecurrentOccurrence(e, dayjs())
})

View File

@@ -48,14 +48,14 @@ domPurify.addHook('beforeSanitizeElements', node => {
module.exports = {
randomString (length = 12) {
randomString(length = 12) {
const wishlist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
return Array.from(crypto.randomFillSync(new Uint32Array(length)))
.map(x => wishlist[x % wishlist.length])
.join('')
},
sanitizeHTML (html) {
sanitizeHTML(html) {
return domPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'br', 'i', 'span',
'h6', 'b', 'a', 'li', 'ul', 'ol', 'code', 'blockquote', 'u', 's', 'strong'],
@@ -63,7 +63,7 @@ module.exports = {
})
},
async setUserLocale (req, res, next) {
async setUserLocale(req, res, next) {
// select locale based on cookie? and accept-language header
acceptLanguage.languages(Object.keys(locales))
res.locals.acceptedLocale = acceptLanguage.get(req.headers['accept-language'])
@@ -71,7 +71,7 @@ module.exports = {
next()
},
async initSettings (_req, res, next) {
async initSettings(_req, res, next) {
// initialize settings
res.locals.settings = cloneDeep(settingsController.settings)
delete res.locals.settings.smtp
@@ -87,12 +87,15 @@ module.exports = {
next()
},
serveStatic () {
serveStatic() {
const router = express.Router()
// serve images/thumb
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' } ), (_req, res) => res.sendStatus(404))
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' }), (_req, res) => res.sendStatus(404))
router.use('/download/:filename', (req, res, next) => {
return res.download(req.params.filename, undefined, { root: config.upload_path }, err => res.status(404).send('Not found (but nice try 😊)'))
})
router.use('/noimg.svg', express.static('./static/noimg.svg'))
router.use('/logo.png', (req, res, next) => {
const logoPath = res.locals.settings.logo || './static/gancio'
return express.static(logoPath + '.png')(req, res, next)
@@ -106,12 +109,12 @@ module.exports = {
return router
},
logRequest (req, _res, next) {
logRequest(req, _res, next) {
log.debug(`${req.method} ${req.path}`)
next()
},
col (field) {
col(field) {
if (config.db.dialect === 'postgres') {
return '"' + field.split('.').join('"."') + '"'
} else if (config.db.dialect === 'mariadb') {
@@ -121,14 +124,14 @@ module.exports = {
}
},
async getImageFromURL (url) {
async getImageFromURL(url) {
log.debug(`getImageFromURL ${url}`)
const filename = crypto.randomBytes(16).toString('hex')
const sharpStream = sharp({ failOnError: true })
const promises = [
sharpStream.clone().resize(500, null, { withoutEnlargement: true }).jpeg({ effort: 6, mozjpeg: true }).toFile(path.resolve(config.upload_path, 'thumb', filename + '.jpg')),
sharpStream.clone().resize(1200, null, { withoutEnlargement: true } ).jpeg({ quality: 95, effort: 6, mozjpeg: true}).toFile(path.resolve(config.upload_path, filename + '.jpg')),
sharpStream.clone().resize(1200, null, { withoutEnlargement: true }).jpeg({ quality: 95, effort: 6, mozjpeg: true }).toFile(path.resolve(config.upload_path, filename + '.jpg')),
]
const response = await axios({ method: 'GET', url: encodeURI(url), responseType: 'stream' })
@@ -157,7 +160,7 @@ module.exports = {
* Import events from url
* It does supports ICS and H-EVENT
*/
async importURL (req, res) {
async importURL(req, res) {
const URL = req.query.URL
try {
const response = await axios.get(URL)
@@ -210,7 +213,7 @@ module.exports = {
}
},
getWeekdayN (date, n, weekday) {
getWeekdayN(date, n, weekday) {
let cursor
if (n === -1) {
cursor = date.endOf('month')
@@ -227,8 +230,8 @@ module.exports = {
log.debug(cursor)
return cursor
},
async APRedirect (req, res, next) {
async APRedirect(req, res, next) {
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
if (acceptJson) {
const eventController = require('../server/api/controller/event')
@@ -240,7 +243,7 @@ module.exports = {
next()
},
async feedRedirect (req, res, next) {
async feedRedirect(req, res, next) {
const accepted = req.accepts('html', 'application/rss+xml', 'text/calendar')
if (['application/rss+xml', 'text/calendar'].includes(accepted) && /^\/(tag|place|collection)\/.*/.test(req.path)) {
return res.redirect((accepted === 'application/rss+xml' ? '/feed/rss' : '/feed/ics') + req.path)