kutt/server/queries/link.js
2024-08-11 18:41:03 +03:30

192 lines
4.3 KiB
JavaScript

const bcrypt = require("bcryptjs");
// FIXME: circular dependency
const CustomError = require("../utils").CustomError;
const redis = require("../redis");
const knex = require("../knex");
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"
];
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;
}
return newMatch;
};
async function total(match, params) {
const query = knex("links");
Object.entries(match).forEach(([key, value]) => {
query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
});
if (params?.search) {
query.andWhereRaw(
"links.description || ' ' || links.address || ' ' || target ILIKE '%' || ? || '%'",
[params.search]
);
}
const [{ count }] = await query.count("id");
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("created_at", "desc");
if (params?.search) {
query.andWhereRaw(
"concat_ws(' ', description, links.address, target, domains.address) ILIKE '%' || ? || '%'",
[params.search]
);
}
query.leftJoin("domains", "links.domain_id", "domains.id");
const links = await query;
return links;
}
async function find(match) {
if (match.address && match.domain_id) {
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) {
const key = redis.key.link(link.address, link.domain_id);
redis.client.set(key, JSON.stringify(link), "EX", 60 * 60 * 2);
}
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);
}
const [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
},
"*"
);
return link;
}
async function remove(match) {
const link = await knex("links").where(match).first();
if (!link) {
throw new CustomError("Link was not found.");
}
const deletedLink = await knex("links").where("id", link.id).delete();
redis.remove.link(link);
return !!deletedLink;
}
async function batchRemove(match) {
const deleteQuery = knex("links");
const findQuery = knex("links");
Object.entries(match).forEach(([key, value]) => {
findQuery.andWhere(key, ...(Array.isArray(value) ? value : [value]));
deleteQuery.andWhere(key, ...(Array.isArray(value) ? value : [value]));
});
const links = await findQuery;
links.forEach(redis.remove.link);
await deleteQuery.delete();
}
async function update(match, update) {
if (update.password) {
const salt = await bcrypt.genSalt(12);
update.password = await bcrypt.hash(update.password, salt);
}
const links = await knex("links")
.where(match)
.update({ ...update, updated_at: new Date().toISOString() }, "*");
links.forEach(redis.remove.link);
return links;
}
function incrementVisit(match) {
return knex("links").where(match).increment("visit_count", 1);
}
module.exports = {
normalizeMatch,
batchRemove,
create,
find,
get,
incrementVisit,
remove,
total,
update,
}