303 lines
7.2 KiB
JavaScript
303 lines
7.2 KiB
JavaScript
const bcrypt = require("bcryptjs");
|
|
|
|
const utils = require("../utils");
|
|
const redis = require("../redis");
|
|
const knex = require("../knex");
|
|
const env = require("../env");
|
|
|
|
const CustomError = utils.CustomError;
|
|
|
|
const selectable = [
|
|
"links.id",
|
|
"links.address",
|
|
"links.banned",
|
|
"links.created_at",
|
|
"links.domain_id",
|
|
"links.updated_at",
|
|
"links.password",
|
|
"links.description",
|
|
"links.expire_in",
|
|
"links.target",
|
|
"links.visit_count",
|
|
"links.user_id",
|
|
"links.uuid",
|
|
"domains.address as domain"
|
|
];
|
|
|
|
const selectable_admin = [
|
|
...selectable,
|
|
"users.email as email"
|
|
];
|
|
|
|
function normalizeMatch(match) {
|
|
const newMatch = { ...match };
|
|
|
|
if (newMatch.address) {
|
|
newMatch["links.address"] = newMatch.address;
|
|
delete newMatch.address;
|
|
}
|
|
|
|
if (newMatch.user_id) {
|
|
newMatch["links.user_id"] = newMatch.user_id;
|
|
delete newMatch.user_id;
|
|
}
|
|
|
|
if (newMatch.uuid) {
|
|
newMatch["links.uuid"] = newMatch.uuid;
|
|
delete newMatch.uuid;
|
|
}
|
|
|
|
if (newMatch.banned !== undefined) {
|
|
newMatch["links.banned"] = newMatch.banned;
|
|
delete newMatch.banned;
|
|
}
|
|
|
|
return newMatch;
|
|
}
|
|
|
|
async function total(match, params) {
|
|
const normalizedMatch = normalizeMatch(match);
|
|
const query = knex("links");
|
|
|
|
Object.entries(normalizedMatch).forEach(([key, value]) => {
|
|
query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
|
|
});
|
|
|
|
if (params?.search) {
|
|
query[knex.compatibleILIKE](
|
|
knex.raw("concat_ws(' ', description, links.address, target, domains.address)"),
|
|
"%" + params.search + "%"
|
|
);
|
|
}
|
|
query.leftJoin("domains", "links.domain_id", "domains.id");
|
|
query.count("* as count");
|
|
|
|
const [{ count }] = await query;
|
|
|
|
return typeof count === "number" ? count : parseInt(count);
|
|
}
|
|
|
|
async function totalAdmin(match, params) {
|
|
const query = knex("links");
|
|
|
|
Object.entries(normalizeMatch(match)).forEach(([key, value]) => {
|
|
query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
|
|
});
|
|
|
|
if (params?.user) {
|
|
const id = parseInt(params?.user);
|
|
if (Number.isNaN(id)) {
|
|
query[knex.compatibleILIKE]("users.email", "%" + params.user + "%");
|
|
} else {
|
|
query.andWhere("links.user_id", params.user);
|
|
}
|
|
}
|
|
|
|
if (params?.search) {
|
|
query[knex.compatibleILIKE](
|
|
knex.raw("concat_ws(' ', description, links.address, target)"),
|
|
"%" + params.search + "%"
|
|
);
|
|
}
|
|
|
|
if (params?.domain) {
|
|
query[knex.compatibleILIKE]("domains.address", "%" + params.domain + "%");
|
|
}
|
|
|
|
query.leftJoin("domains", "links.domain_id", "domains.id");
|
|
query.leftJoin("users", "links.user_id", "users.id");
|
|
query.count("* as count");
|
|
|
|
const [{ count }] = await query;
|
|
|
|
return typeof count === "number" ? count : parseInt(count);
|
|
}
|
|
|
|
async function get(match, params) {
|
|
const query = knex("links")
|
|
.select(...selectable)
|
|
.where(normalizeMatch(match))
|
|
.offset(params.skip)
|
|
.limit(params.limit)
|
|
.orderBy("links.id", "desc");
|
|
|
|
if (params?.search) {
|
|
query[knex.compatibleILIKE](
|
|
knex.raw("concat_ws(' ', description, links.address, target, domains.address)"),
|
|
"%" + params.search + "%"
|
|
);
|
|
}
|
|
|
|
query.leftJoin("domains", "links.domain_id", "domains.id");
|
|
|
|
return query;
|
|
}
|
|
|
|
async function getAdmin(match, params) {
|
|
const query = knex("links").select(...selectable_admin);
|
|
|
|
Object.entries(normalizeMatch(match)).forEach(([key, value]) => {
|
|
query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
|
|
});
|
|
|
|
query
|
|
.orderBy("links.id", "desc")
|
|
.offset(params.skip)
|
|
.limit(params.limit)
|
|
|
|
if (params?.user) {
|
|
const id = parseInt(params?.user);
|
|
if (Number.isNaN(id)) {
|
|
query[knex.compatibleILIKE]("users.email", "%" + params.user + "%");
|
|
} else {
|
|
query.andWhere("links.user_id", params.user);
|
|
}
|
|
}
|
|
|
|
if (params?.search) {
|
|
query[knex.compatibleILIKE](
|
|
knex.raw("concat_ws(' ', description, links.address, target)"),
|
|
"%" + params.search + "%"
|
|
);
|
|
}
|
|
|
|
if (params?.domain) {
|
|
query[knex.compatibleILIKE]("domains.address", "%" + params.domain + "%");
|
|
}
|
|
|
|
query.leftJoin("domains", "links.domain_id", "domains.id");
|
|
query.leftJoin("users", "links.user_id", "users.id");
|
|
|
|
return query;
|
|
}
|
|
|
|
async function find(match) {
|
|
if (match.address && match.domain_id !== undefined && env.REDIS_ENABLED) {
|
|
const key = redis.key.link(match.address, match.domain_id);
|
|
const cachedLink = await redis.client.get(key);
|
|
if (cachedLink) return JSON.parse(cachedLink);
|
|
}
|
|
|
|
const link = await knex("links")
|
|
.select(...selectable)
|
|
.where(normalizeMatch(match))
|
|
.leftJoin("domains", "links.domain_id", "domains.id")
|
|
.first();
|
|
|
|
if (link && env.REDIS_ENABLED) {
|
|
const key = redis.key.link(link.address, link.domain_id);
|
|
redis.client.set(key, JSON.stringify(link), "EX", 60 * 15);
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
async function create(params) {
|
|
let encryptedPassword = null;
|
|
|
|
if (params.password) {
|
|
const salt = await bcrypt.genSalt(12);
|
|
encryptedPassword = await bcrypt.hash(params.password, salt);
|
|
}
|
|
|
|
let [link] = await knex(
|
|
"links"
|
|
).insert(
|
|
{
|
|
password: encryptedPassword,
|
|
domain_id: params.domain_id || null,
|
|
user_id: params.user_id || null,
|
|
address: params.address,
|
|
description: params.description || null,
|
|
expire_in: params.expire_in || null,
|
|
target: params.target
|
|
},
|
|
"*"
|
|
);
|
|
|
|
// mysql doesn't return the whole link, but rather the id number only
|
|
// so we need to fetch the link ourselves
|
|
if (typeof link === "number") {
|
|
link = await knex("links").where("id", link).first();
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
async function remove(match) {
|
|
const link = await knex("links").where(match).first();
|
|
|
|
if (!link) {
|
|
return { isRemoved: false, error: "Could not find the link.", link: null }
|
|
}
|
|
|
|
const deletedLink = await knex("links").where("id", link.id).delete();
|
|
|
|
if (env.REDIS_ENABLED) {
|
|
redis.remove.link(link);
|
|
}
|
|
|
|
return { isRemoved: !!deletedLink, link };
|
|
}
|
|
|
|
async function batchRemove(match) {
|
|
const query = knex("links");
|
|
|
|
Object.entries(match).forEach(([key, value]) => {
|
|
query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
|
|
});
|
|
|
|
const links = await query.clone();
|
|
|
|
await query.delete();
|
|
|
|
if (env.REDIS_ENABLED) {
|
|
links.forEach(redis.remove.link);
|
|
}
|
|
}
|
|
|
|
async function update(match, update) {
|
|
if (update.password) {
|
|
const salt = await bcrypt.genSalt(12);
|
|
update.password = await bcrypt.hash(update.password, salt);
|
|
}
|
|
|
|
// if the links' adddress or domain is changed,
|
|
// make sure to delete the original links from cache
|
|
let links = []
|
|
if (env.REDIS_ENABLED && (update.address || update.domain_id)) {
|
|
links = await knex("links").select('*').where(match);
|
|
}
|
|
|
|
await knex("links")
|
|
.where(match)
|
|
.update({ ...update, updated_at: utils.dateToUTC(new Date()) });
|
|
|
|
const updated_links = await knex("links").select('*').where(match);
|
|
|
|
if (env.REDIS_ENABLED) {
|
|
links.forEach(redis.remove.link);
|
|
updated_links.forEach(redis.remove.link);
|
|
}
|
|
|
|
return updated_links;
|
|
}
|
|
|
|
function incrementVisit(match) {
|
|
return knex("links").where(match).increment("visit_count", 1);
|
|
}
|
|
|
|
module.exports = {
|
|
normalizeMatch,
|
|
batchRemove,
|
|
create,
|
|
find,
|
|
get,
|
|
getAdmin,
|
|
incrementVisit,
|
|
remove,
|
|
total,
|
|
totalAdmin,
|
|
update,
|
|
}
|