// minio.js // Copyright (C) 2021 Digital Telepresence, LLC // License: Apache-2.0 'use strict'; const path = require('path'); const mongoose = require('mongoose'); const pug = require('pug'); const Link = mongoose.model('Link'); const striptags = require('striptags'); const isUrlValid = require('url-validation'); const { SiteService, SiteError } = require('../../lib/site-lib'); class LinkService extends SiteService { constructor (dtp) { super(dtp, module.exports); this.populateLink = [ { path: 'user', select: '_id username username_lc displayName picture', }, ]; } async start ( ) { this.templates = { }; let script = path.join(this.dtp.config.root, 'app', 'views', 'link', 'components', 'list-item-standalone.pug'); this.templates.link = pug.compileFile(script); } renderTemplate (templateId, viewModel) { return this.templates[templateId](viewModel); } async create (user, linkDefinition) { if (!user.permissions.canCreateLinks) { throw new SiteError(403, 'You are prohibited from creating links'); } this.validateUrl(linkDefinition.href); const NOW = new Date(); const link = new Link(); link.created = NOW; link.user = user._id; link.label = striptags(linkDefinition.label.trim()); link.href = striptags(linkDefinition.href.trim()); await link.save(); return link.toObject(); } async update (link, linkDefinition) { this.validateUrl(linkDefinition.href); const updateOp = { $set: { } }; if (linkDefinition.label) { link.label = updateOp.$set.label = striptags(linkDefinition.label.trim()); } if (linkDefinition.href) { link.href = updateOp.$set.href = striptags(linkDefinition.href.trim()); } await Link.updateOne({ _id: link._id }, updateOp); } async getById (linkId) { const link = await Link .findById(linkId) .populate(this.populateLink) .lean(); return link; } async getForUser (user, pagination) { let links; if (pagination) { links = await Link .find({ user: user._id }) .sort({ order: 1 }) .skip(pagination.skip) .limit(pagination.cpp) .lean(); } else { links = await Link .find({ user: user._id }) .sort({ order: 1 }) .lean(); } return links; } async getRecent (maxCount = 3) { const links = await Link .find() .sort({ created: -1 }) .limit(maxCount) .populate(this.populateLink) .lean(); return links; } async getPopular (maxCount) { const links = await Link .find() .sort({ 'stats.uniqueVisitCount': -1 }) .limit(maxCount) .populate(this.populateLink) .lean(); return links; } async getAdmin (pagination) { const links = await Link .find() .sort({ created: -1 }) .skip(pagination.skip) .limit(pagination.cpp) .populate([ { path: 'user', select: '_id username username_lc displayName picture', }, ]) .lean(); return links; } async getTotalCount ( ) { return await Link.estimatedDocumentCount(); } async remove (link) { const { resource: resourceService } = this.dtp.services; await resourceService.remove('Link', link._id); } async setItemOrder (links) { for await (const link of links) { this.log.debug('set link order', { link }); await Link.updateOne({ _id: link._id }, { $set: { order: link.order } }); } } validateUrl (href) { if (!isUrlValid(href)) { throw new SiteError(406, 'Invalid link URL'); } const urlTest = href.toLowerCase().trim(); this.log.debug('testing URL for validity', { urlTest }); if (!urlTest.startsWith('https://') && !urlTest.startsWith('http://')) { throw new SiteError(406, 'Invalid link URL'); } if (urlTest.startsWith('https://libertylinks') || urlTest.startsWith('http://libertylinks') || urlTest.startsWith('https://www.libertylinks') || urlTest.startsWith('http://libertylinks')) { throw new SiteError(406, 'Invalid link URL'); } } } module.exports = { slug: 'link', name: 'link', create: (dtp) => { return new LinkService(dtp); }, };