114 lines
4.0 KiB
JavaScript
114 lines
4.0 KiB
JavaScript
/**
|
|
* BooksController
|
|
*
|
|
* @description :: Server-side actions for handling incoming requests.
|
|
* @help :: See https://sailsjs.com/docs/concepts/actions
|
|
*/
|
|
|
|
const HttpError = require('../errors/HttpError')
|
|
const { hmacSign } = require('../util')
|
|
const request = require('request')
|
|
const uriRegex = /^(.+:\/\/)?(.+\.)*(.+\.).{1,}(:\d+)?(.+)?/i
|
|
|
|
module.exports = {
|
|
publish: async function (req, res) {
|
|
try {
|
|
const body = req.body
|
|
const host = req.hostname
|
|
let result
|
|
|
|
if (!host) throw new HttpError(400, 'Missing hostname')
|
|
if (!body) throw new HttpError(400, 'Missing body')
|
|
if (!body.metadata) throw new HttpError(400, 'Missing OPDS metadata')
|
|
if (!body.metadata['@type'] || body.metadata['@type'] !== 'http://schema.org/Book') throw new HttpError(400, 'Invalid \'@type\': expected \'http://schema.org/Book\'')
|
|
|
|
let tags = (body.metadata.tags || '').split(/,\s*/).filter(x => x.length)
|
|
if (!tags.length && body.metadata.title) tags = body.metadata.title.replace(/[^\w\s]/g, '').split(/\s+/).filter(x => x.length >= 3)
|
|
const query = {
|
|
hostname: host,
|
|
title: body.metadata.title,
|
|
author: body.metadata.author,
|
|
publisher: body.metadata.publisher,
|
|
identifier: body.metadata.identifier,
|
|
tags: JSON.stringify(tags || []),
|
|
version: body.metadata.modified.replace(/\D/g, '')
|
|
}
|
|
|
|
const bookExists = await Book.findOne(query)
|
|
|
|
if (bookExists) {
|
|
throw new HttpError(400, 'Ebook already exists')
|
|
} else {
|
|
const { publisher, title, author, identifier } = body.metadata
|
|
// require at least 3 fields to be filled out
|
|
if ([title, identifier, author, publisher].reduce((a, x) => a + (x ? 1 : 0), 0) >= 3) {
|
|
result = await Book.create({
|
|
...query,
|
|
opds: body
|
|
}).fetch()
|
|
} else {
|
|
throw new HttpError(400, 'Please fill out at least 3 opds metadata fields (title, author, publisher, identifier)')
|
|
}
|
|
}
|
|
|
|
sendUpdatesAsync(result)
|
|
return res.json({
|
|
...result,
|
|
tags: JSON.parse(result.tags || '[]')
|
|
})
|
|
} catch (e) {
|
|
if (e instanceof HttpError) return e.send(res)
|
|
return res.status(500).json({
|
|
error: e.message
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
async function sendUpdatesAsync (book) {
|
|
const id = book.id
|
|
const targets = await TargetUrl.find()
|
|
if (!book) return
|
|
for (const i in targets) {
|
|
try {
|
|
const item = targets[i]
|
|
const user = await User.findOne({ id: item.user })
|
|
const { author: fAuthor, publisher: fPublisher, title: fTitle, identifier: fIsbn, tags: fTags, url } = item
|
|
const { author: bAuthor, publisher: bPublisher, title: bTitle, identifier: bIsbn, tags: bTags, opds } = book
|
|
|
|
if (uriRegex.test(url)) {
|
|
if (fAuthor && !((bAuthor || '').includes(fAuthor))) continue
|
|
if (fPublisher && !((bPublisher || '').includes(fPublisher))) continue
|
|
if (fTitle && !((bTitle || '').includes(fTitle))) continue
|
|
if (fIsbn && !((bIsbn || '').includes(fIsbn))) continue
|
|
|
|
const filterTags = JSON.parse(fTags || '[]')
|
|
if (filterTags.length && filterTags[0].length) {
|
|
const otherSet = new Set(filterTags)
|
|
if (!([...new Set(JSON.parse(bTags || '[]'))].filter(x => otherSet.has(x)).length)) continue
|
|
}
|
|
sails.log('sending ' + book.id + ' info to ' + url)
|
|
|
|
let content = opds
|
|
const timestamp = Date.now()
|
|
request.post({
|
|
url: url,
|
|
headers: {
|
|
'User-Agent': 'RoE-aggregator',
|
|
'X-RoE-Signature': hmacSign(user.signing_secret, timestamp, JSON.stringify(content)),
|
|
'X-RoE-Request-Timestamp': timestamp
|
|
},
|
|
body: content,
|
|
json: true
|
|
}, function (err, httpResp, body) {
|
|
if (err) {
|
|
sails.log(`error: failed to send book ${id} to ${url}`)
|
|
}
|
|
})
|
|
}
|
|
} catch (e) {
|
|
sails.log(`error: ${e.message}\n${e.stack}`)
|
|
}
|
|
}
|
|
}
|