Source: routes/v1/verify-books.js

/** @module routes/v1 */

// Register router

const { authorize } = require('../../utils.js');
const fs = require('fs');

const express = require('express');
const router = express.Router();

const SpineImageController = require('../../controllers/SpineImageController.js');
const TokenController = require('../../controllers/TokenController.js');

const { SpineImageFactory } = require('../../classes/books/SpineImage.js');
const { TokenFactory } = require('../../classes/users/Token.js');

/** Route for getting spine images to verify */
router.get('/contribute/verify/get', async (req, res) => {
    if (!await authorize(['contribute.verify.get'], req, res)) {
        return;
    }

    const imageInfo = [];
    
    const spineImagesUnconfirmed = await new SpineImageController().byCrowdApproved(5, false);

    for (const spineImageDb of spineImagesUnconfirmed) {
        if (imageInfo.length >= 3) {
            break;
        }

        const spineImage = await new SpineImageFactory().load(spineImageDb).create();

        const edition = await spineImage.getEdition();
        const book = await edition.getBook();

        const spineFullPath = process.env.STORAGE_PATH + '/spine-images/' + spineImage.filepath;
        if (!fs.existsSync(spineFullPath)) {
            await spineImage.delete();
            continue;
        }

        imageInfo.push({
            id: spineImage.id,
            name: book.title,
            image: base64_encode(spineFullPath),
        });
    }

    res.send({ "image_info": imageInfo });
});

/** Route for submitting verification */
router.post('/contribute/verify/submit', async (req, res) => {
    if (!await authorize(['contribute.verify.submit'], req, res)) {
        return;
    }

    if (req.body.confirmed !== 'true' && req.body.confirmed !== 'false') {
        res.status(400).send({ message: 'Invalid confirmed' });
        return;
    }

    const imageId = req.body.image_id;
    const confirmed = req.body.confirmed === 'true';
    const adminForce = req.body.admin_force === 'true';

    if (!imageId) {
        res.status(400).send({ message: 'Missing image_id' });
        return;
    } else if (isNaN(imageId)) {
        res.status(400).send({ message: 'Invalid image_id' });
        return;
    }

    const spineImageDbRecord = await new SpineImageController().byId(parseInt(imageId));
    if (!spineImageDbRecord) {
        res.status(400).send({ message: 'Image not found' });
        return;
    }

    const spineImage = await new SpineImageFactory().load(spineImageDbRecord).create();

    const user = await (await new TokenFactory().load(await new TokenController().byTokenString(req.headers['authorization'])).create()).getUser();
    user.addContribution('VERIFY_SPINE_IMAGE', spineImage.id);

    if (adminForce) {
        const isAdmin = await user.getMeta('flag.admin') === 'true';
        if (!isAdmin) {
            res.status(403).send({ message: 'Insufficient permissions' });
            return;
        }

        if (confirmed) {
            await spineImage.setMeta('crowdApproved', true);
        } else {
            await spineImage.delete();
        }

        res.send({ message: 'Verification submitted' });
        return;
    }

    let crowdConfirmations = await spineImage.getMeta('crowdConfirmations');
    let crowdRejections = await spineImage.getMeta('crowdRejections');

    if (confirmed) {
        crowdConfirmations++;
        await spineImage.setMeta('crowdConfirmations', crowdConfirmations);
    } else {
        crowdRejections++;
        await spineImage.setMeta('crowdRejections', crowdRejections);
    }

    if (crowdConfirmations / (crowdConfirmations + crowdRejections) >= 0.6 && crowdConfirmations >= 8) {
        await spineImage.setMeta('crowdApproved', true);
    }

    if (crowdRejections / (crowdConfirmations + crowdRejections) >= 0.6 && crowdRejections >= 8) {
        await spineImage.delete();
    }

    res.send({ message: 'Verification submitted' });
});
    

module.exports = router;

// Functions

/**
 * Encodes a file to base64
 * 
 * @param {string} filePath
 * @returns {string}
 */
function base64_encode(filePath) {
    const bitmap = fs.readFileSync(filePath);
    return bitmap.toString('base64');
}