river-of-ebooks/api/controllers/BooksController.js

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}`)
}
}
}