@rackbops/plugin-tracker 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -11
- package/dist/plugin.js +1696 -297
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,8 +4,11 @@ The task tracker for Rackbops Clerk: the host of [`Rackbops/docket`](https://git
|
|
|
4
4
|
on a rackbops-discord-bot instance. Plan of record: Rackbops/Tooling
|
|
5
5
|
`research/city-hall-task-tracker.md` (sections 0, 5.1-5.3, 5.5, 5.8, 5.10). This version is the core
|
|
6
6
|
(rackbops-bot-plugins#78), the Discord surface of the first slice (#79) -- reminders by slash
|
|
7
|
-
command, delivered by DM, answered by button, with history --
|
|
8
|
-
(#80): sign-in by one-time link, my tasks, a task's history, and settings
|
|
7
|
+
command, delivered by DM, answered by button, with history -- the first slice of the web area
|
|
8
|
+
(#80): sign-in by one-time link, my tasks, a task's history, and settings -- renewals and
|
|
9
|
+
the price tracker (#81, plan E6): two more types on the notify side, no model, nothing sent to
|
|
10
|
+
city-hall -- and the web area's task editor (#80, slice 2): make, edit, pause, resume and delete
|
|
11
|
+
your own tasks from the web, under the commands' own rules.
|
|
9
12
|
|
|
10
13
|
## Commands
|
|
11
14
|
|
|
@@ -17,11 +20,14 @@ as in a server (plan 5.5, item 39).
|
|
|
17
20
|
| `/allow user` | a tracker admin | Admits a person: they can now `/register`. |
|
|
18
21
|
| `/register [hour] [zone]` | an admitted person | Signs up, or changes the preferred hour (0-23, default 9) and time zone (default `America/New_York`). The reply says an admin can see every task. |
|
|
19
22
|
| `/remind text [when] [repeat]` | a registered person | A reminder by DM. `when` is docket's `parseWhen` grammar (`in 20 minutes`, `tomorrow 9am`, `fri at 17:30`); `repeat` is once (the default), daily, weekly or monthly. A repeating one with no `when` starts today at the preferred hour. |
|
|
23
|
+
| `/renewal name amount currency renews [unit] [every] [lead] [note]` | registered | A subscription, domain, warranty or membership (docket's `renewal`, a `period` schedule). `renews` is the next renewal or expiry date, `YYYY-MM-DD`, today or later; `unit` yearly (the default), monthly, weekly or daily, `every` how many of those; the ask comes `lead` days before (default 7) at the preferred hour, with Keep, Cancel, Renewed and Snooze. If that ask is already past but the date is not, the first ask comes within a minute (a zone or hour change before it fires drops it for the next period's). Keep and Renewed record what was paid; Cancel ends it. |
|
|
24
|
+
| `/price url [name] [hours] [drop] [baseline] [near]` | registered | A price (docket's `price`, a `poll` schedule every `hours`, default 12, at most 168). The page is read once at once, and nothing is created unless a price is found in it; then the first check, within a minute, DMs the starting price, and a drop of `drop` percent or more (default 10) from the `baseline` -- the last (default), first or highest price seen -- DMs the owner once per crossing. `near` is the words just before the price, for a page with no structured price. At most 20 per person. |
|
|
20
25
|
| `/tasks` | registered | Your active tasks and the ones you receive, each with its next run; then your paused ones. |
|
|
21
26
|
| `/task done task` / `/task snooze task [until]` | the task's owner | Answers the task's latest reminder (snooze: an hour, or until `until`). |
|
|
22
|
-
| `/task
|
|
27
|
+
| `/task decide task choice [amount]` | the renewal's owner | Answers the renewal's latest ask -- keep, cancel or renewed -- with the amount actually paid when it changed (a button cannot carry one). Use it instead of the button, not after it: a run is answered once. |
|
|
28
|
+
| `/task history task` | the owner, an accepted recipient, or an admin | Every run -- due, status, how it was answered, text replies -- and the task's changes; for a renewal, what each period cost and the total; for a price, the last check and the readings with the low and the high. |
|
|
23
29
|
| `/task share task user` | the task's owner | Invites an admitted, registered member who can be DMed: they get one consent DM with accept and decline. |
|
|
24
|
-
| `/task resume task` | the task's owner | Resumes a task paused because a recipient could not be DMed
|
|
30
|
+
| `/task resume task` | the task's owner | Resumes a paused task: one paused because a recipient could not be DMed goes on without that recipient; one its owner paused on the web simply goes on. |
|
|
25
31
|
| `/settings hour hour` | registered | The preferred hour; recurring reminders with no time of their own move to it. |
|
|
26
32
|
| `/web` | registered | A one-time link to sign in to the web area (below), good for 10 minutes and one use. Says so when `TRACKER_WEB_URL` is unset. |
|
|
27
33
|
|
|
@@ -53,8 +59,11 @@ served at a hashed path, cached for a year).
|
|
|
53
59
|
|
|
54
60
|
| Path | What |
|
|
55
61
|
|---|---|
|
|
56
|
-
| `/` | My tasks: the same list as `/tasks` (active tasks owned and received, next run in your zone; paused ones and why), each linking to its history. |
|
|
57
|
-
| `/tasks/<id>` | A task's history: the same as `/task history`, for the owner, an accepted recipient or an admin. Anyone else gets the same 404 as an unknown id. |
|
|
62
|
+
| `/` | My tasks: the same list as `/tasks` (active tasks owned and received, next run in your zone; paused ones and why), each linking to its history, and links to make a new one. |
|
|
63
|
+
| `/tasks/<id>` | A task's history: the same as `/task history`, for the owner, an accepted recipient or an admin. Anyone else gets the same 404 as an unknown id. The owner also sees Edit, Pause or Resume, and Delete. |
|
|
64
|
+
| `/new/reminder`, `/new/renewal`, `/new/price` | The editor's new-task forms (GET), and making one (POST). |
|
|
65
|
+
| `/tasks/<id>/edit` | The owner's edit form (GET) and saving it (POST). |
|
|
66
|
+
| `/tasks/<id>/pause`, `/resume`, `/delete` | POST only. Delete answers a confirmation first; only a second post carrying `confirm=yes` deletes. |
|
|
58
67
|
| `/settings` | Preferred hour and time zone, checked as `/register` checks them. |
|
|
59
68
|
| `/signin` | Where a request that is not signed in is sent: says to run `/web`. |
|
|
60
69
|
| `/login?t=...` | The link `/web` gives. |
|
|
@@ -78,6 +87,33 @@ than `TRACKER_WEB_URL`'s is refused. The sign-in post has no session yet, so it
|
|
|
78
87
|
double-submit token from a `SameSite=Strict` cookie the link's page sets: another site cannot sign
|
|
79
88
|
you in as someone else. Sign out is a POST too.
|
|
80
89
|
|
|
90
|
+
**The task editor** (slice 2). The forms take the options of `/remind`, `/renewal` and `/price`,
|
|
91
|
+
and the same functions check and make them: the same defaults, limits and messages, the page read
|
|
92
|
+
once before a price tracker is made, at most 20 price trackers per person wherever they were made,
|
|
93
|
+
and `near` turned into the same bounded pattern; a person has one new price tracker's page read in
|
|
94
|
+
flight at a time. Every person may have at most 200 active or paused tasks of all types together,
|
|
95
|
+
made anywhere. The length limits are one set (`src/limits.ts`) for the slash options, the forms
|
|
96
|
+
and the server's checks. An edit changes a reminder's text, time and repeat
|
|
97
|
+
(an empty time keeps the one it has, unless the repeat changes); a renewal's name, amount,
|
|
98
|
+
currency, date, unit, every, lead and note (a new amount is the one the next ask quotes; a new date
|
|
99
|
+
gets `/renewal`'s first-ask rule, but a date a run already asked about is not asked again); a price's name, interval,
|
|
100
|
+
drop and baseline, never its page. A schedule change cancels and replaces what is queued and keeps
|
|
101
|
+
snoozes, as a zone move does, and each change is in the task's history. An empty field keeps what
|
|
102
|
+
the task has (an empty note clears it; an empty price name is the page's address). A renewal date
|
|
103
|
+
that is one of the schedule's own period dates keeps the schedule and its anchor as they are, so the
|
|
104
|
+
31st stays the 31st; only a different date re-anchors. A finished task cannot be
|
|
105
|
+
edited. Pause holds everything the task would send until Resume, and a person's delivery resuming
|
|
106
|
+
does not undo it; a run missed while paused fires once, late, on resume, and a zone or hour change
|
|
107
|
+
re-times the run a paused task holds -- same period date or local day, new zone or hour, still one
|
|
108
|
+
run -- so it is neither lost nor sent at the old time. Resume on a task paused
|
|
109
|
+
for failed DMs is `/task resume`. Delete archives the task: it leaves every list, its queued runs
|
|
110
|
+
are dropped and nothing more is sent, but its history is kept (an admin can see every task) and
|
|
111
|
+
its page stays for the owner. Every editor post passes the session, CSRF and `Origin` checks; the
|
|
112
|
+
task is loaded by the id in the path and must be the viewer's own, checked again in the write
|
|
113
|
+
queue, and anyone else's -- even one an admin can see -- answers the same 404 as an unknown id. A
|
|
114
|
+
refused form comes back with what was typed and why. Form bodies are capped at 32 KiB, read no
|
|
115
|
+
further than that.
|
|
116
|
+
|
|
81
117
|
**Headers.** `Content-Security-Policy: default-src 'none'; style-src 'self'; img-src 'none';
|
|
82
118
|
form-action 'self'; frame-ancestors 'none'; base-uri 'none'`, `X-Content-Type-Options: nosniff`,
|
|
83
119
|
`X-Frame-Options: DENY`. Every value on a page is HTML-escaped. An unknown path is 404; a method
|
|
@@ -107,13 +143,16 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
|
|
|
107
143
|
| Piece | File | Notes |
|
|
108
144
|
|---|---|---|
|
|
109
145
|
| Store | `src/store.ts`, `src/schema.ts` | docket's `Store` port on `bun:sqlite`, in `<dataDir>/tracker/tracker.sqlite` (WAL). A Discord id belongs to at most one user. Schema versioned by `PRAGMA user_version`; a shipped migration is never edited. `store.test.ts` runs docket's `STORE_CONTRACT` against it. |
|
|
110
|
-
| Notify lane | `src/notify-lane.ts` | docket's `Lanes.tickNotify` on
|
|
146
|
+
| Notify lane | `src/notify-lane.ts` | docket's `Lanes.tickNotify` on two host ticks, every 60 s, through a view of the store that skips a paused task's due runs: `notify` runs every type but the page readers, `poll` runs only them (`price`) with the Fetch port, so a slow page never holds up a reminder. Registers `reminder`, `renewal` and `price`. The execute lane is not ticked. |
|
|
147
|
+
| Page reads | `src/fetch.ts` | docket's `Fetch` port for `price`: http or https on the default port, no credentials, every resolved address public (no loopback, private, link-local, CGNAT, multicast or reserved range, IPv4 or IPv6), redirects followed by hand and re-checked (at most 5), 15 s including the name lookup, at most 3 MB kept. The body is then rebuilt in linear time (`src/page.ts`) to just what extraction reads -- JSON-LD, meta tags, and the page with every `<` blanked, so `near` still reads text, attributes and script data -- because docket's extraction patterns take quadratic time on a page of unclosed tags. A read the tick's abort cuts short requeues its run instead of counting a miss. A DNS answer that changes between the check and the read is not caught here. |
|
|
148
|
+
| Renewals, prices | `src/tracked.ts`, `src/price.ts`, `src/series.ts`, `src/page.ts` | `/renewal`, `/price` and `/task decide`; the series lines of `/task history`. The series (docket's `series` table, schema 1) holds a renewal's paid amounts and a price's readings. |
|
|
111
149
|
| Delivery | `src/notifier.ts`, `src/claims.ts`, `src/buttons.ts` | docket's `Notifier` over `host.dm`, with the buttons. Each (occurrence, person) is claimed in `delivery_claims` before the DM is sent and settled after; a claim never settled is not resent -- it is logged once, as unconfirmed. |
|
|
112
150
|
| Pause | `src/delivery-health.ts` | The per-person failure count, the pause and the resume. |
|
|
113
151
|
| People | `src/people.ts`, `src/admissions.ts` | Discord id, time zone, preferred hour, admin flag -- in the tracker's store, never usr; who admitted each person and when they registered. `TRACKER_ADMIN_DISCORD_IDS` only ever grants admin. |
|
|
114
152
|
| Commands | `src/discord.ts`, `src/interactions.ts`, `src/discord-common.ts` (discord.js); `src/actions.ts`, `src/press.ts`, `src/history.ts`, `src/access.ts` | The discord.js files read options and render; the rest is Discord-free over the injected store, clock and notifier. Store writes are handled one at a time; Discord lookups and DMs run outside that queue. |
|
|
115
153
|
| Health | `src/health.ts` | `GET /tracker/healthz` (through the bot's HTTP router): `200` `ok`/`starting`, `503` `inactive`/`stale`/`blocked`. |
|
|
116
|
-
| Web area | `src/web/` | `app.ts`
|
|
154
|
+
| Web area | `src/web/` | `app.ts` authenticates and dispatches (pure over the injected store and clock), `routes.ts` names the paths; `pages.ts`, `editor-pages.ts` and `html.ts` render; `editor.ts` reads the editor's forms and calls the shared rules; `signin-link.ts` and `sessions.ts` hold the sign-in; `theme.ts` the stylesheet; `command.ts` is `/web`. |
|
|
155
|
+
| Task rules | `src/reminders.ts`, `src/tracked.ts`, `src/price.ts`, `src/edit.ts`, `src/manage.ts`, `src/limits.ts` | What makes, edits, pauses, resumes and deletes a task, and the limits on it, Discord-free, for the commands and the web editor alike. |
|
|
117
156
|
|
|
118
157
|
## Configuration
|
|
119
158
|
|
|
@@ -127,10 +166,11 @@ The `__Host-` prefix, which would stop that, requires `Path=/` and so cannot be
|
|
|
127
166
|
|
|
128
167
|
## Not yet
|
|
129
168
|
|
|
130
|
-
- The rest of the web area (E5):
|
|
131
|
-
and the JSON task API. Until forget-me and the admin view exist, nobody can be taken off the
|
|
169
|
+
- The rest of the web area (E5): the admin view, lifting a decline block, forget-me (which would
|
|
170
|
+
erase a deleted task), sharing a task from the web, and the JSON task API. Until forget-me and the admin view exist, nobody can be taken off the
|
|
132
171
|
tracker, so a web session ends only by sign-out, expiry, or the membership re-check. Discord OAuth2 as a second sign-in method, if chosen (plan item 41).
|
|
133
|
-
- The
|
|
172
|
+
- The city-hall Executor adapter (the execute lane).
|
|
173
|
+
- Editing in Discord (the editor is on the web only), and changing a price tracker's page or `near` after it is made (make a new one); a free-form pattern for `price` (`near` is the safe subset: a user's regular expression run on a large page could hang the bot).
|
|
134
174
|
- An optional Discord-role gate (plan 5.5), and showing unconfirmed deliveries to admins anywhere but the log.
|
|
135
175
|
|
|
136
176
|
docket-core, docket-types and `@rackbops/styles` are `devDependencies`: `bun build` bundles them
|