Implement built-in cron worker

This commit is contained in:
2025-06-04 16:49:00 +02:00
parent 9bc036113f
commit ec3c1e541c
8 changed files with 55 additions and 58 deletions

4
.gitignore vendored
View File

@@ -145,4 +145,6 @@ dist
# End of https://www.toptal.com/developers/gitignore/api/node # End of https://www.toptal.com/developers/gitignore/api/node
src/generated src/generated
*.yaml
*.json

View File

@@ -28,13 +28,6 @@ in
default = "root"; default = "root";
}; };
scanTimer = mkOption {
type = types.str;
description = "The systemd's timer interval when the app will be performing the scan for new tasks";
example = "*-*-* *:00";
default = "*-*-* *:10";
};
config = mkOption { config = mkOption {
type = types.attrs; type = types.attrs;
description = "The obsidian-tasks-reminder config which will be eventually converted to yaml"; description = "The obsidian-tasks-reminder config which will be eventually converted to yaml";
@@ -56,46 +49,17 @@ in
config = mkIf cfg.enable { config = mkIf cfg.enable {
environment.systemPackages = [app]; environment.systemPackages = [app];
systemd.timers.obsidian-tasks-reminder-scanner = {
description = "Scan for new Obsidian tasks";
wantedBy = ["timers.target"];
timerConfig = {
OnCalendar = cfg.scanTimer;
Unit = "obsidian-tasks-reminder-scanner.service";
};
};
systemd.timers.obsidian-tasks-reminder = {
description = "Notify about Obsidian tasks";
wantedBy = ["timers.target"];
timerConfig = {
OnCalendar = "*-*-* *:*:00";
Unit = "obsidian-tasks-reminder.service";
};
};
systemd.services.obsidian-tasks-reminder-scanner = {
description = "Scan for new Obsidian tasks";
serviceConfig = {
Type = "oneshot";
User = cfg.user;
};
script = "${app}/bin/obsidian-tasks-reminder -s";
};
systemd.services.obsidian-tasks-reminder = { systemd.services.obsidian-tasks-reminder = {
description = "Notify about Obsidian tasks"; enable = true;
description = "Obsidian Tasks Notifier";
wantedBy = ["network-online.target"];
after = ["network-online.target"];
serviceConfig = { serviceConfig = {
Type = "oneshot"; ExecStart = "${app}/bin/obsidian-tasks-reminder";
User = cfg.user; User = cfg.user;
}; };
script = "${app}/bin/obsidian-tasks-reminder -n";
}; };
}; };
} }

29
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"commander": "^13.0.0", "commander": "^13.0.0",
"cron": "^4.3.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"peggy": "^4.2.0", "peggy": "^4.2.0",
"yaml": "^2.7.0" "yaml": "^2.7.0"
@@ -482,6 +483,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@types/luxon": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
"integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.10.6", "version": "22.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz",
@@ -576,6 +583,19 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/cron": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.1.tgz",
"integrity": "sha512-7x7DoEOxV11t3OPWWMjj1xrL1PGkTV5RV+/54IJTZD7gStiaMploY43EkeBSkDZTLRbUwk+OISbQ0TR133oXyA==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.6.0",
"luxon": "~3.6.0"
},
"engines": {
"node": ">=18.x"
}
},
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.11.13", "version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@@ -793,6 +813,15 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/luxon": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

View File

@@ -21,6 +21,7 @@
}, },
"dependencies": { "dependencies": {
"commander": "^13.0.0", "commander": "^13.0.0",
"cron": "^4.3.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"peggy": "^4.2.0", "peggy": "^4.2.0",
"yaml": "^2.7.0" "yaml": "^2.7.0"

View File

@@ -7,5 +7,5 @@ buildNpmPackage {
pname = "obsidian-tasks-reminder"; pname = "obsidian-tasks-reminder";
version = "0.0.1"; version = "0.0.1";
src = ./.; src = ./.;
npmDepsHash = "sha256-ofPAFnHbW+M0uQN/OXDLK+PQ3ls6AtD58/AOmSxn754="; npmDepsHash = "sha256-K06A/j2GfM0nCiFv4Pho907VWLa2pPHjtV9BgKxwdnE=";
} }

View File

@@ -3,6 +3,7 @@ import { CLIOptions } from "../types/cli";
import { loadConfig } from "../config"; import { loadConfig } from "../config";
import { notify, scan, test } from "../runner"; import { notify, scan, test } from "../runner";
import { CronJob } from "cron";
const getOptions = () => program const getOptions = () => program
.name("obsidian-tasks-reminder") .name("obsidian-tasks-reminder")
@@ -10,9 +11,7 @@ const getOptions = () => program
.requiredOption("-c, --config <file>", "sets the path to the YAML file with configuration") .requiredOption("-c, --config <file>", "sets the path to the YAML file with configuration")
.option("-x, --set <arg>", "overrides the config option for this specific run (arg: <key>=<name>, i.e. backend.ntfy.enable=false", (v: string, prev: string[]) => prev.concat([v]), []) .option("-x, --set <arg>", "overrides the config option for this specific run (arg: <key>=<name>, i.e. backend.ntfy.enable=false", (v: string, prev: string[]) => prev.concat([v]), [])
.option("-t, --test", "evaluates the query, applies the mapper and prints to stdout the notifications about to be trigger, without actual triggering them") .option("-t, --test", "evaluates the query, applies the mapper and prints to stdout the notifications about to be trigger, without actual triggering them")
.option("-s, --scan", "scans new tasks for future notifications and generates the database") .option("-p, --profile <name>", "(applicable only with '--test' option) limits the current operation only to specified profile. If missing, all profiles will be affected")
.option("-n, --notify", "reads the generated database and triggers notifications if any")
.option("-p, --profile <name>", "limits the current operation only to specified profile. If missing, all profiles will be affected")
.parse() .parse()
.opts<CLIOptions>(); .opts<CLIOptions>();
@@ -43,14 +42,16 @@ const getOptions = () => program
await test(config, options.profile); await test(config, options.profile);
return; return;
} }
if (options.scan) {
await scan(config, options.profile);
return;
}
if (options.notify) { const scanJob = CronJob.from({
await notify(config, options.profile); cronTime: config.scanCron ?? '0 0 * * * *',
return; onTick () { scan(config) },
} start: true,
});
const notifyJob = CronJob.from({
cronTime: config.notifyCron ?? '0 * * * * *',
onTick() { notify(config) },
start: true,
});
} }

View File

@@ -2,7 +2,5 @@ export type CLIOptions = {
config: string; config: string;
profile?: string; profile?: string;
test: boolean; test: boolean;
scan: boolean;
notify: boolean;
set: string[]; set: string[];
}; };

View File

@@ -1,5 +1,7 @@
export type Config = { export type Config = {
profiles: Record<string, ProfileConfig>; profiles: Record<string, ProfileConfig>;
notifyCron?: string;
scanCron?: string;
}; };
export type ProfileConfig = { export type ProfileConfig = {