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 @@
+
+