fix: Proper processing of date fields (Domain Expiry) with cleanup of unnecessary Date comparison functions (#6638)

Co-authored-by: Frank Elsinga <frank@elsinga.de>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Shaan
2026-01-09 09:18:17 +06:00
committed by GitHub
parent 0f61d7ee1b
commit a117add908
4 changed files with 20 additions and 65 deletions

View File

@@ -2,10 +2,11 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
const { R } = require("redbean-node"); const { R } = require("redbean-node");
const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util"); const { log, TYPES_WITH_DOMAIN_EXPIRY_SUPPORT_VIA_FIELD } = require("../../src/util");
const { parse: parseTld } = require("tldts"); const { parse: parseTld } = require("tldts");
const { getDaysRemaining, getDaysBetween, setting, setSetting } = require("../util-server"); const { setting, setSetting } = require("../util-server");
const { Notification } = require("../notification"); const { Notification } = require("../notification");
const { default: NodeFetchCache, MemoryCache } = require("node-fetch-cache"); const { default: NodeFetchCache, MemoryCache } = require("node-fetch-cache");
const TranslatableError = require("../translatable-error"); const TranslatableError = require("../translatable-error");
const dayjs = require("dayjs");
const cachedFetch = process.env.NODE_ENV const cachedFetch = process.env.NODE_ENV
? NodeFetchCache.create({ ? NodeFetchCache.create({
@@ -199,18 +200,18 @@ class DomainExpiry extends BeanModel {
* @returns {Promise<DomainExpiry>} Domain expiry bean * @returns {Promise<DomainExpiry>} Domain expiry bean
*/ */
static async findByDomainNameOrCreate(domainName) { static async findByDomainNameOrCreate(domainName) {
const existing = await DomainExpiry.findByName(domainName); let domain = await DomainExpiry.findByName(domainName);
if (existing) { if (!domain && domainName) {
return existing; domain = await DomainExpiry.createByName(domainName);
} }
return DomainExpiry.createByName(domainName); return domain;
} }
/** /**
* @returns {number} number of days remaining before expiry * @returns {number} number of days remaining before expiry
*/ */
get daysRemaining() { get daysRemaining() {
return getDaysRemaining(new Date(), new Date(this.expiry)); return dayjs.utc(this.expiry).diff(dayjs.utc(), "day");
} }
/** /**
@@ -227,20 +228,20 @@ class DomainExpiry extends BeanModel {
*/ */
static async checkExpiry(domainName) { static async checkExpiry(domainName) {
let bean = await DomainExpiry.findByDomainNameOrCreate(domainName); let bean = await DomainExpiry.findByDomainNameOrCreate(domainName);
let expiryDate; let expiryDate;
if (bean?.lastCheck && getDaysBetween(new Date(bean.lastCheck), new Date()) < 1) {
if (bean?.lastCheck && dayjs.utc(bean.lastCheck).diff(dayjs.utc(), "day") < 1) {
log.debug("domain_expiry", `Domain expiry already checked recently for ${bean.domain}, won't re-check.`); log.debug("domain_expiry", `Domain expiry already checked recently for ${bean.domain}, won't re-check.`);
return bean.expiry; return bean.expiry;
} else if (bean) { } else if (bean) {
expiryDate = await bean.getExpiryDate(); expiryDate = await bean.getExpiryDate();
if (new Date(expiryDate) > new Date(bean.expiry)) { if (dayjs.utc(expiryDate).isAfter(dayjs.utc(bean.expiry))) {
bean.lastExpiryNotificationSent = null; bean.lastExpiryNotificationSent = null;
} }
bean.expiry = expiryDate; bean.expiry = R.isoDateTimeMillis(expiryDate);
bean.lastCheck = new Date(); bean.lastCheck = R.isoDateTimeMillis(dayjs.utc());
await R.store(bean); await R.store(bean);
} }
@@ -272,7 +273,7 @@ class DomainExpiry extends BeanModel {
return; return;
} }
const daysRemaining = getDaysRemaining(new Date(), domain.expiry); const daysRemaining = domain.daysRemaining;
const lastSent = domain.lastExpiryNotificationSent; const lastSent = domain.lastExpiryNotificationSent;
log.debug("domain_expiry", `${domainName} expires in ${daysRemaining} days`); log.debug("domain_expiry", `${domainName} expires in ${daysRemaining} days`);

View File

@@ -26,6 +26,7 @@ const {
}, },
} = require("node-radius-utils"); } = require("node-radius-utils");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
dayjs.extend(require("dayjs/plugin/utc"));
// SASLOptions used in JSDoc // SASLOptions used in JSDoc
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@@ -393,33 +394,6 @@ exports.setSettings = async function (type, data) {
await Settings.setSettings(type, data); await Settings.setSettings(type, data);
}; };
// ssl-checker by @dyaa
//https://github.com/dyaa/ssl-checker/blob/master/src/index.ts
/**
* Get number of days between two dates
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number} Number of days
*/
const getDaysBetween = (validFrom, validTo) => Math.round(Math.abs(+validFrom - +validTo) / 8.64e7);
exports.getDaysBetween = getDaysBetween;
/**
* Get days remaining from a time range
* @param {Date} validFrom Start date
* @param {Date} validTo End date
* @returns {number} Number of days remaining
*/
const getDaysRemaining = (validFrom, validTo) => {
const daysRemaining = getDaysBetween(validFrom, validTo);
if (new Date(validTo).getTime() < new Date(validFrom).getTime()) {
return -daysRemaining;
}
return daysRemaining;
};
exports.getDaysRemaining = getDaysRemaining;
/** /**
* Fix certificate info for display * Fix certificate info for display
* @param {object} info The chain obtained from getPeerCertificate() * @param {object} info The chain obtained from getPeerCertificate()
@@ -440,7 +414,7 @@ const parseCertificateInfo = function (info) {
} }
link.validTo = new Date(link.valid_to); link.validTo = new Date(link.valid_to);
link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", "); link.validFor = link.subjectaltname?.replace(/DNS:|IP Address:/g, "").split(", ");
link.daysRemaining = getDaysRemaining(new Date(), link.validTo); link.daysRemaining = dayjs.utc(link.validTo).diff(dayjs.utc(), "day");
existingList[link.fingerprint] = true; existingList[link.fingerprint] = true;

View File

@@ -9,6 +9,8 @@ const { R } = require("redbean-node");
const { Notification } = require("../../server/notification"); const { Notification } = require("../../server/notification");
const { Settings } = require("../../server/settings"); const { Settings } = require("../../server/settings");
const { setSetting } = require("../../server/util-server"); const { setSetting } = require("../../server/util-server");
const dayjs = require("dayjs");
dayjs.extend(require("dayjs/plugin/utc"));
const testDb = new TestDB(); const testDb = new TestDB();
@@ -231,7 +233,7 @@ describe("Domain Expiry", () => {
test("checkExpiry() caches expiration date in database", async () => { test("checkExpiry() caches expiration date in database", async () => {
await DomainExpiry.checkExpiry("google.com"); // RDAP -> Cache await DomainExpiry.checkExpiry("google.com"); // RDAP -> Cache
const domain = await DomainExpiry.findByName("google.com"); const domain = await DomainExpiry.findByName("google.com");
assert(Date.now() - domain.lastCheck < 5 * 1000); assert(dayjs.utc().diff(dayjs.utc(domain.lastCheck), "second") < 5);
}); });
test("sendNotifications() triggers notification for expiring domain", async () => { test("sendNotifications() triggers notification for expiring domain", async () => {
@@ -240,7 +242,8 @@ describe("Domain Expiry", () => {
port: 3010, port: 3010,
url: "capture", url: "capture",
}; };
await setSetting("domainExpiryNotifyDays", [1, 2, 1500], "general"); const manyDays = 3650;
await setSetting("domainExpiryNotifyDays", [manyDays], "general");
const notif = R.convertToBean("notification", { const notif = R.convertToBean("notification", {
config: JSON.stringify({ config: JSON.stringify({
type: "webhook", type: "webhook",
@@ -252,8 +255,6 @@ describe("Domain Expiry", () => {
user_id: 1, user_id: 1,
name: "Testhook", name: "Testhook",
}); });
const manyDays = 3650;
setSetting("domainExpiryNotifyDays", [manyDays], "general");
const [, data] = await Promise.all([ const [, data] = await Promise.all([
DomainExpiry.sendNotifications("google.com", [notif]), DomainExpiry.sendNotifications("google.com", [notif]),
mockWebhook(hook.port, hook.url), mockWebhook(hook.port, hook.url),

View File

@@ -2,33 +2,12 @@ const { describe, test } = require("node:test");
const assert = require("node:assert"); const assert = require("node:assert");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const { getDaysRemaining, getDaysBetween } = require("../../server/util-server");
const { SQL_DATETIME_FORMAT } = require("../../src/util"); const { SQL_DATETIME_FORMAT } = require("../../src/util");
dayjs.extend(require("dayjs/plugin/utc")); dayjs.extend(require("dayjs/plugin/utc"));
dayjs.extend(require("dayjs/plugin/customParseFormat")); dayjs.extend(require("dayjs/plugin/customParseFormat"));
describe("Server Utilities", () => { describe("Server Utilities", () => {
test("getDaysBetween() calculates days between dates within same month", () => {
const days = getDaysBetween(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
});
test("getDaysBetween() calculates days between dates across years", () => {
const days = getDaysBetween(new Date(2024, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 368);
});
test("getDaysRemaining() returns positive value when target date is in future", () => {
const days = getDaysRemaining(new Date(2025, 9, 7), new Date(2025, 9, 10));
assert.strictEqual(days, 3);
});
test("getDaysRemaining() returns negative value when target date is in past", () => {
const days = getDaysRemaining(new Date(2025, 9, 10), new Date(2025, 9, 7));
assert.strictEqual(days, -3);
});
test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => { test("SQL_DATETIME_FORMAT constant matches MariaDB/MySQL format", () => {
assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss"); assert.strictEqual(SQL_DATETIME_FORMAT, "YYYY-MM-DD HH:mm:ss");
}); });