diff --git a/server/handlers/domains.handler.js b/server/handlers/domains.handler.js index 75a9d0197..6b377322b 100644 --- a/server/handlers/domains.handler.js +++ b/server/handlers/domains.handler.js @@ -86,7 +86,7 @@ async function remove(req, res) { async function removeAdmin(req, res) { const id = req.params.id; - const links = req.query.links + const links = req.query.links; const domain = await query.domain.find({ id }); @@ -116,6 +116,8 @@ async function getAdmin(req, res) { const { limit, skip } = req.context; const search = req.query.search; const user = req.query.user; + const sortBy = req.query.sortBy; + const sortOrder = req.query.sortOrder; const banned = utils.parseBooleanQuery(req.query.banned); const owner = utils.parseBooleanQuery(req.query.owner); const links = utils.parseBooleanQuery(req.query.links); @@ -126,7 +128,7 @@ async function getAdmin(req, res) { }; const [data, total] = await Promise.all([ - query.domain.getAdmin(match, { limit, search, user, links, skip }), + query.domain.getAdmin(match, { limit, search, user, links, skip, sortBy, sortOrder }), query.domain.totalAdmin(match, { search, user, links }) ]); @@ -138,6 +140,15 @@ async function getAdmin(req, res) { total_formatted: total.toLocaleString("en-US"), limit, skip, + query: { + sortBy: sortBy || "created_at", + sortOrder: sortOrder || "desc", + search, + user, + banned: req.query.banned, + owner: req.query.owner, + links: req.query.links + }, table_domains: domains, }) return; @@ -210,4 +221,4 @@ module.exports = { getAdmin, remove, removeAdmin, -} \ No newline at end of file +} diff --git a/server/handlers/links.handler.js b/server/handlers/links.handler.js index 93a0a6489..fbc3db77a 100644 --- a/server/handlers/links.handler.js +++ b/server/handlers/links.handler.js @@ -19,6 +19,8 @@ const dnsLookup = promisify(dns.lookup); async function get(req, res) { const { limit, skip } = req.context; const search = req.query.search; + const sortBy = req.query.sortBy; + const sortOrder = req.query.sortOrder; const userId = req.user.id; const match = { @@ -26,7 +28,7 @@ async function get(req, res) { }; const [data, total] = await Promise.all([ - query.link.get(match, { limit, search, skip }), + query.link.get(match, { limit, search, skip, sortBy, sortOrder }), query.link.total(match, { search }) ]); @@ -35,6 +37,11 @@ async function get(req, res) { total, limit, skip, + query: { + sortBy: sortBy || "created_at", + sortOrder: sortOrder || "desc", + search + }, links: data.map(utils.sanitize.link_html), }) return; @@ -53,6 +60,8 @@ async function getAdmin(req, res) { const search = req.query.search; const user = req.query.user; let domain = req.query.domain; + const sortBy = req.query.sortBy; + const sortOrder = req.query.sortOrder; const banned = utils.parseBooleanQuery(req.query.banned); const anonymous = utils.parseBooleanQuery(req.query.anonymous); const has_domain = utils.parseBooleanQuery(req.query.has_domain); @@ -63,15 +72,15 @@ async function getAdmin(req, res) { ...(has_domain !== undefined && { domain_id: [has_domain ? "is not" : "is", null] }), }; - // if domain is equal to the defualt domain, - // it means admins is looking for links with the defualt domain (no custom user domain) + // if domain is equal to the default domain, + // it means admin is looking for links with the default domain (no custom user domain) if (domain === env.DEFAULT_DOMAIN) { domain = undefined; match.domain_id = null; } const [data, total] = await Promise.all([ - query.link.getAdmin(match, { limit, search, user, domain, skip }), + query.link.getAdmin(match, { limit, search, user, domain, skip, sortBy, sortOrder }), query.link.totalAdmin(match, { search, user, domain }) ]); @@ -83,6 +92,16 @@ async function getAdmin(req, res) { total_formatted: total.toLocaleString("en-US"), limit, skip, + query: { + sortBy: sortBy || "created_at", + sortOrder: sortOrder || "desc", + search, + user, + domain, + banned: req.query.banned, + anonymous: req.query.anonymous, + has_domain: req.query.has_domain + }, links, }) return; @@ -659,4 +678,4 @@ module.exports = { redirect, redirectProtected, redirectCustomDomainHomepage, -} \ No newline at end of file +} diff --git a/server/handlers/users.handler.js b/server/handlers/users.handler.js index 99023d8e1..bb1641738 100644 --- a/server/handlers/users.handler.js +++ b/server/handlers/users.handler.js @@ -5,6 +5,8 @@ const utils = require("../utils"); const mail = require("../mail"); const env = require("../env"); +const CustomError = utils.CustomError; + async function get(req, res) { const domains = await query.domain.get({ user_id: req.user.id }); @@ -63,7 +65,7 @@ async function removeByAdmin(req, res) { async function getAdmin(req, res) { const { limit, skip, all } = req.context; - const { role, search } = req.query; + const { role, search, sortBy, sortOrder } = req.query; const userId = req.user.id; const verified = utils.parseBooleanQuery(req.query.verified); const banned = utils.parseBooleanQuery(req.query.banned); @@ -77,7 +79,7 @@ async function getAdmin(req, res) { }; const [data, total] = await Promise.all([ - query.user.getAdmin(match, { limit, search, domains, links, skip }), + query.user.getAdmin(match, { limit, search, domains, links, skip, sortBy, sortOrder }), query.user.totalAdmin(match, { search, domains, links }) ]); @@ -89,6 +91,16 @@ async function getAdmin(req, res) { total_formatted: total.toLocaleString("en-US"), limit, skip, + query: { + sortBy: sortBy || "created_at", + sortOrder: sortOrder || "desc", + search, + role, + verified: req.query.verified, + banned: req.query.banned, + domains: req.query.domains, + links: req.query.links + }, users, }) return; @@ -138,7 +150,8 @@ async function ban(req, res) { // 5. wait for all tasks to finish await Promise.all(tasks).catch((err) => { - throw new CustomError("Couldn't ban entries."); + console.error("User ban operation failed:", err); + throw new CustomError(`Couldn't ban entries: ${err.message}`); }); // 6. send response @@ -182,4 +195,4 @@ module.exports = { getAdmin, remove, removeByAdmin, -} \ No newline at end of file +} diff --git a/server/queries/domain.queries.js b/server/queries/domain.queries.js index d4d15674f..05261bd0d 100644 --- a/server/queries/domain.queries.js +++ b/server/queries/domain.queries.js @@ -61,7 +61,7 @@ async function add(params) { } async function update(match, update) { - // if the domains' adddress is changed, + // if the domains' address is changed, // make sure to delete the original domains from cache let domains = [] if (env.REDIS_ENABLED && update.address) { @@ -134,11 +134,23 @@ async function getAdmin(match, params) { .offset(params.skip) .limit(params.limit) .fromRaw("domains") - .orderBy("domains.id", "desc") .groupBy(1) .groupBy("l.links_count") .groupBy("users.email"); + // Handle sorting + const sortBy = params.sortBy || "created_at"; + const sortOrder = params.sortOrder || "desc"; + + if (sortBy === "created_at") { + query.orderBy("domains.created_at", sortOrder); + } else if (sortBy === "links_count") { + query.orderBy("l.links_count", sortOrder); + } else { + // Default fallback + query.orderBy("domains.id", "desc"); + } + if (params?.user) { const id = parseInt(params?.user); if (Number.isNaN(id)) { @@ -228,4 +240,4 @@ module.exports = { remove, totalAdmin, update, -} \ No newline at end of file +} diff --git a/server/queries/link.queries.js b/server/queries/link.queries.js index 12f5480cc..26735e69e 100644 --- a/server/queries/link.queries.js +++ b/server/queries/link.queries.js @@ -123,8 +123,20 @@ async function get(match, params) { .select(...selectable) .where(normalizeMatch(match)) .offset(params.skip) - .limit(params.limit) - .orderBy("links.id", "desc"); + .limit(params.limit); + + // handle sorting + const sortBy = params.sortBy || "created_at"; + const sortOrder = params.sortOrder || "desc"; + + if (sortBy === "created_at") { + query.orderBy("links.created_at", sortOrder); + } else if (sortBy === "visit_count") { + query.orderBy("links.visit_count", sortOrder); + } else { + // default fallback to id desc for any invalid sortBy + query.orderBy("links.id", "desc"); + } if (params?.search) { query[knex.compatibleILIKE]( @@ -146,10 +158,22 @@ async function getAdmin(match, params) { }); query - .orderBy("links.id", "desc") .offset(params.skip) .limit(params.limit) + // handle sorting + const sortBy = params.sortBy || "created_at"; + const sortOrder = params.sortOrder || "desc"; + + if (sortBy === "created_at") { + query.orderBy("links.created_at", sortOrder); + } else if (sortBy === "visit_count") { + query.orderBy("links.visit_count", sortOrder); + } else { + // default fallback to id desc for any invalid sortBy + query.orderBy("links.id", "desc"); + } + if (params?.user) { const id = parseInt(params?.user); if (Number.isNaN(id)) { @@ -267,7 +291,7 @@ async function update(match, update) { update.password = await bcrypt.hash(update.password, salt); } - // if the links' adddress or domain is changed, + // if the links' address or domain is changed, // make sure to delete the original links from cache let links = [] if (env.REDIS_ENABLED && (update.address || update.domain_id)) { diff --git a/server/queries/user.queries.js b/server/queries/user.queries.js index 857f3e3b2..acd4836fb 100644 --- a/server/queries/user.queries.js +++ b/server/queries/user.queries.js @@ -136,10 +136,22 @@ async function getAdmin(match, params) { .where(normalizeMatch(match)) .offset(params.skip) .limit(params.limit) - .orderBy("users.id", "desc") .groupBy(1) .groupBy("l.links_count") .groupBy("d.domains"); + + // handle sorting + const sortBy = params.sortBy || "created_at"; + const sortOrder = params.sortOrder || "desc"; + + if (sortBy === "created_at") { + query.orderBy("users.created_at", sortOrder); + } else if (sortBy === "links_count") { + query.orderBy("l.links_count", sortOrder); + } else { + // Default fallback + query.orderBy("users.id", "desc"); + } if (params?.search) { const id = parseInt(params?.search); diff --git a/server/queues/visit.js b/server/queues/visit.js index a05acd19b..f146ceb01 100644 --- a/server/queues/visit.js +++ b/server/queues/visit.js @@ -10,13 +10,13 @@ const osList = ["Windows", "Mac OS", "Linux", "Android", "iOS"]; function filterInBrowser(agent) { return function(item) { - return agent.family.toLowerCase().includes(item.toLocaleLowerCase()); + return agent.family.toLowerCase().includes(item.toLowerCase()); } } function filterInOs(agent) { return function(item) { - return agent.os.family.toLowerCase().includes(item.toLocaleLowerCase()); + return agent.os.family.toLowerCase().includes(item.toLowerCase()); } } @@ -48,4 +48,4 @@ module.exports = function({ data }) { ); return Promise.all(tasks); -} \ No newline at end of file +} diff --git a/server/views/partials/admin/dialog/mesasge.hbs b/server/views/partials/admin/dialog/message.hbs similarity index 100% rename from server/views/partials/admin/dialog/mesasge.hbs rename to server/views/partials/admin/dialog/message.hbs diff --git a/server/views/partials/admin/domains/thead.hbs b/server/views/partials/admin/domains/thead.hbs index 1e36cad58..514dba241 100644 --- a/server/views/partials/admin/domains/thead.hbs +++ b/server/views/partials/admin/domains/thead.hbs @@ -63,6 +63,8 @@ + +