2023-11-21 22:14:33 +01:00
|
|
|
const { Collection, Filter, Event, Tag, Place, APUser } = require('../models/models')
|
2022-12-23 01:08:14 +01:00
|
|
|
|
2022-05-20 13:04:07 +02:00
|
|
|
const log = require('../../log')
|
2023-03-28 19:02:08 +02:00
|
|
|
const { DateTime } = require('luxon')
|
2023-06-18 22:17:11 +02:00
|
|
|
const { col: Col, queryParamToBool } = require('../../helpers')
|
2022-05-25 10:55:39 +02:00
|
|
|
const { Op, Sequelize } = require('sequelize')
|
2023-11-21 22:14:33 +01:00
|
|
|
const { getActor, followActor, unfollowActor } = require('../../federation/helpers')
|
2022-05-20 13:04:07 +02:00
|
|
|
|
2022-06-18 01:10:27 +02:00
|
|
|
const collectionController = {
|
2022-05-20 13:04:07 +02:00
|
|
|
|
2023-11-09 22:32:45 +01:00
|
|
|
// get all collections
|
2022-05-20 13:04:07 +02:00
|
|
|
async getAll (req, res) {
|
2023-06-18 22:17:11 +02:00
|
|
|
const withFilters = queryParamToBool(req.query.withFilters)
|
2023-11-09 22:32:45 +01:00
|
|
|
const pin = req.query.pin
|
2022-06-18 01:10:27 +02:00
|
|
|
let collections
|
2022-05-25 10:55:39 +02:00
|
|
|
if (withFilters) {
|
2022-07-27 11:23:57 +02:00
|
|
|
collections = await Collection.findAll({ include: [ Filter ] })
|
2022-05-25 10:55:39 +02:00
|
|
|
} else {
|
2023-11-09 22:32:45 +01:00
|
|
|
if (pin) {
|
|
|
|
collections = await Collection.findAll({ where: { isTop: true }})
|
|
|
|
} else {
|
|
|
|
collections = await Collection.findAll()
|
|
|
|
}
|
2022-05-25 10:55:39 +02:00
|
|
|
}
|
|
|
|
|
2022-06-18 01:10:27 +02:00
|
|
|
return res.json(collections)
|
2022-05-20 13:04:07 +02:00
|
|
|
},
|
|
|
|
|
2023-11-09 22:32:45 +01:00
|
|
|
|
|
|
|
async togglePin (req, res) {
|
|
|
|
const id = req.params.id
|
|
|
|
try {
|
|
|
|
const collection = await Collection.findByPk(id)
|
|
|
|
if (!collection) { return req.sendStatus(404) }
|
|
|
|
await collection.update({ isTop: !collection.isTop })
|
|
|
|
res.json(!collection.isTop)
|
|
|
|
} catch (e) {
|
|
|
|
log.error(e)
|
|
|
|
res.sendStatus(404)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2022-05-20 13:04:07 +02:00
|
|
|
async getEvents (req, res) {
|
|
|
|
const name = req.params.name
|
2023-12-28 00:47:31 +01:00
|
|
|
const limit = req.query.max || 10
|
|
|
|
const start = req.query.start_at || DateTime.local().toUnixInteger()
|
2023-10-30 09:36:18 +01:00
|
|
|
|
|
|
|
try {
|
2023-12-28 00:47:31 +01:00
|
|
|
const events = await collectionController._getEvents({ name, start, limit })
|
2023-12-15 16:39:56 +01:00
|
|
|
log.debug(`[COLLECTION] (${name}) events: ${events?.length}`)
|
|
|
|
return res.json(events)
|
2023-10-30 09:36:18 +01:00
|
|
|
} catch (e) {
|
2023-12-28 00:47:31 +01:00
|
|
|
log.error('[COLLECTION] Error in getEvents: %s', String(e))
|
2023-10-30 09:36:18 +01:00
|
|
|
return res.sendStatus(404)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// return events from collection
|
2023-12-28 00:47:31 +01:00
|
|
|
async _getEvents ({ name, start, end, limit }) {
|
2022-05-20 13:04:07 +02:00
|
|
|
|
2022-06-18 01:10:27 +02:00
|
|
|
const collection = await Collection.findOne({ where: { name } })
|
|
|
|
if (!collection) {
|
2023-10-30 09:36:18 +01:00
|
|
|
throw new Error(`Collection ${name} not found`)
|
2022-05-25 10:55:39 +02:00
|
|
|
}
|
2022-05-20 13:04:07 +02:00
|
|
|
|
2023-10-30 09:36:18 +01:00
|
|
|
const filters = await Filter.findAll({ where: { collectionId: collection.id } })
|
2023-12-28 00:47:31 +01:00
|
|
|
|
|
|
|
// collection is empty if there are no filters
|
2022-06-18 01:10:27 +02:00
|
|
|
if (!filters.length) {
|
2023-10-30 09:36:18 +01:00
|
|
|
return []
|
2022-06-18 01:10:27 +02:00
|
|
|
}
|
2023-12-15 16:39:56 +01:00
|
|
|
|
2023-12-28 00:47:31 +01:00
|
|
|
// init stardard filter
|
2022-05-20 13:04:07 +02:00
|
|
|
const where = {
|
|
|
|
// do not include parent recurrent event
|
|
|
|
recurrent: null,
|
|
|
|
|
|
|
|
// confirmed event only
|
|
|
|
is_visible: true,
|
2023-12-28 00:47:31 +01:00
|
|
|
[Op.or]: {
|
|
|
|
start_datetime: { [Op.gte]: 1*start },
|
|
|
|
end_datetime: { [Op.gte]: 1*start }
|
|
|
|
}
|
|
|
|
}
|
2022-05-20 13:04:07 +02:00
|
|
|
|
2023-12-28 00:47:31 +01:00
|
|
|
if (end) {
|
|
|
|
where.start_datetime = { [Op.lte]: end }
|
2022-05-20 13:04:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const replacements = []
|
|
|
|
const ors = []
|
2023-11-21 22:14:33 +01:00
|
|
|
|
|
|
|
// collections are a set of filters to match
|
2022-05-20 13:04:07 +02:00
|
|
|
filters.forEach(f => {
|
2023-12-28 00:47:31 +01:00
|
|
|
|
|
|
|
let conditions = []
|
|
|
|
|
2022-05-20 13:04:07 +02:00
|
|
|
if (f.tags && f.tags.length) {
|
2022-06-18 01:10:27 +02:00
|
|
|
const tags = Sequelize.fn('EXISTS', Sequelize.literal(`SELECT 1 FROM event_tags WHERE ${Col('event_tags.eventId')}=event.id AND ${Col('tagTag')} in (?)`))
|
2022-05-20 13:04:07 +02:00
|
|
|
replacements.push(f.tags)
|
2023-12-28 00:47:31 +01:00
|
|
|
conditions.push(tags)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f.places && f.places.length) {
|
|
|
|
conditions.push({ placeId: f.places.map(p => p.id) })
|
2022-05-20 13:04:07 +02:00
|
|
|
}
|
2023-12-28 00:47:31 +01:00
|
|
|
|
|
|
|
if (f.actors && f.actors.length) {
|
|
|
|
conditions.push({ apUserApId: f.actors.map(a => a.ap_id)})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!conditions.length) return
|
|
|
|
ors.push(conditions.length === 1 ? conditions[0] : { [Op.and]: conditions })
|
2022-05-20 13:04:07 +02:00
|
|
|
})
|
|
|
|
|
2022-06-18 01:10:27 +02:00
|
|
|
where[Op.and] = { [Op.or]: ors }
|
2022-05-20 13:04:07 +02:00
|
|
|
|
|
|
|
const events = await Event.findAll({
|
|
|
|
where,
|
|
|
|
attributes: {
|
2023-12-28 00:47:31 +01:00
|
|
|
exclude: ['likes', 'boost', 'userId', 'is_visible', 'createdAt', 'description', 'resources', 'ap_id']
|
2022-05-20 13:04:07 +02:00
|
|
|
},
|
|
|
|
order: ['start_datetime'],
|
|
|
|
include: [
|
|
|
|
{
|
|
|
|
model: Tag,
|
2022-06-18 01:10:27 +02:00
|
|
|
// order: [Sequelize.literal('(SELECT COUNT("tagTag") FROM event_tags WHERE tagTag = tag) DESC')],
|
2022-05-20 13:04:07 +02:00
|
|
|
attributes: ['tag'],
|
|
|
|
through: { attributes: [] }
|
|
|
|
},
|
2023-11-21 22:14:33 +01:00
|
|
|
{ model: Place, required: true, attributes: ['id', 'name', 'address'] },
|
2022-05-20 13:04:07 +02:00
|
|
|
],
|
2023-12-28 00:47:31 +01:00
|
|
|
...( limit && { limit }),
|
2022-05-20 13:04:07 +02:00
|
|
|
replacements
|
|
|
|
}).catch(e => {
|
|
|
|
log.error('[EVENT]', e)
|
|
|
|
return []
|
|
|
|
})
|
|
|
|
|
2023-10-30 09:36:18 +01:00
|
|
|
return events.map(e => {
|
2022-05-20 13:04:07 +02:00
|
|
|
e = e.get()
|
|
|
|
e.tags = e.tags ? e.tags.map(t => t && t.tag) : []
|
|
|
|
return e
|
|
|
|
})
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
async add (req, res) {
|
2022-06-18 01:10:27 +02:00
|
|
|
const collectionDetail = {
|
2022-05-20 13:04:07 +02:00
|
|
|
name: req.body.name,
|
|
|
|
isActor: true,
|
|
|
|
isTop: true
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: validation
|
2022-07-27 11:23:57 +02:00
|
|
|
log.info(`Create collection: ${req.body.name}`)
|
|
|
|
try {
|
|
|
|
const collection = await Collection.create(collectionDetail)
|
|
|
|
res.json(collection)
|
|
|
|
} catch (e) {
|
|
|
|
log.error(`Create collection failed ${e}`)
|
2022-12-23 01:08:14 +01:00
|
|
|
res.status(400).send(e)
|
2022-07-27 11:23:57 +02:00
|
|
|
}
|
2022-05-20 13:04:07 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
async remove (req, res) {
|
2022-06-18 01:10:27 +02:00
|
|
|
const collection_id = req.params.id
|
|
|
|
log.info('Remove collection', collection_id)
|
2022-05-20 13:04:07 +02:00
|
|
|
try {
|
2022-06-18 01:10:27 +02:00
|
|
|
const collection = await Collection.findByPk(collection_id)
|
|
|
|
await collection.destroy()
|
2022-05-20 13:04:07 +02:00
|
|
|
res.sendStatus(200)
|
|
|
|
} catch (e) {
|
2022-07-27 11:23:57 +02:00
|
|
|
log.error('Remove collection failed:' + String(e))
|
2022-05-20 13:04:07 +02:00
|
|
|
res.sendStatus(404)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async getFilters (req, res) {
|
2022-06-18 01:10:27 +02:00
|
|
|
const collectionId = req.params.collection_id
|
|
|
|
const filters = await Filter.findAll({ where: { collectionId } })
|
2022-05-20 13:04:07 +02:00
|
|
|
return res.json(filters)
|
2023-06-17 09:30:44 +02:00
|
|
|
},
|
2022-05-20 13:04:07 +02:00
|
|
|
|
|
|
|
async addFilter (req, res) {
|
2023-11-21 22:14:33 +01:00
|
|
|
const { collectionId, tags, places, actors } = req.body
|
2023-06-17 09:30:44 +02:00
|
|
|
|
2022-05-20 13:04:07 +02:00
|
|
|
try {
|
2023-11-21 22:14:33 +01:00
|
|
|
if (actors?.length) {
|
|
|
|
const actors_to_follow = await APUser.findAll({ where: { ap_id: { [Op.in]: actors.map(a => a.ap_id) }} })
|
|
|
|
await Promise.all(actors_to_follow.map(followActor))
|
|
|
|
}
|
|
|
|
const filter = await Filter.create({ collectionId, tags, places, actors })
|
2022-05-20 13:04:07 +02:00
|
|
|
return res.json(filter)
|
|
|
|
} catch (e) {
|
|
|
|
log.error(String(e))
|
2022-12-23 01:08:14 +01:00
|
|
|
return res.sendStatus(400)
|
2022-05-20 13:04:07 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
async removeFilter (req, res) {
|
|
|
|
const filter_id = req.params.id
|
2022-07-27 11:23:57 +02:00
|
|
|
log.info(`Remove filter ${filter_id}`)
|
2022-05-20 13:04:07 +02:00
|
|
|
try {
|
|
|
|
const filter = await Filter.findByPk(filter_id)
|
2022-07-27 11:23:57 +02:00
|
|
|
if (!filter) {
|
|
|
|
return res.sendStatus(404)
|
|
|
|
}
|
2022-05-20 13:04:07 +02:00
|
|
|
await filter.destroy()
|
|
|
|
res.sendStatus(200)
|
|
|
|
} catch (e) {
|
|
|
|
log.error('Remove filter failed:', e)
|
|
|
|
res.sendStatus(404)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-06-17 09:30:44 +02:00
|
|
|
module.exports = collectionController
|