Commit Graph

10 Commits

Author SHA1 Message Date
Kamil 'Kamaś' Bruchal e17aefb9e3 fix: assign duty weeks on their fetch-time tab across year rollover
Around New Year the empty-rota guard keeps the old year's cached rows
while resolveRange has already moved to the new year's tab, so an
auto-assignment could write an old-tab row index onto the new tab.
Stamp each DutyWeek with the tab it was fetched from and prefer it when
building the assignment range (clock fallback for pre-upgrade caches).

Also: widen the default fetch range to A1:G200 so a full year of rows
plus roster can't be silently truncated, add -race/golangci-lint/
govulncheck gates to CI (container pinned to 1.26.4-alpine), and add
.env.example documenting every configuration variable.
2026-08-19 16:49:11 +02:00
Kamil 'Kamaś' Bruchal 6573144b14 feat: auto-assign empty duty weeks via service-account sheet writes
Migrate Sheets auth from an API key to a service-account credential
(GOOGLE_CREDENTIALS_FILE) with the read+write spreadsheets scope, add a
WriteSheet primitive, and use it to actively manage empty duty weeks in
the weekly reminder.

Each fire now handles a fixed this-week/next-week model (replacing the
DUTY_AHEAD_DAYS announce):
- this week empty -> pick the two people with the fewest duties (random
  tie-break), write their names into cols B/C, and announce
- this week filled -> announce who is on duty
- next week empty -> ask people to volunteer before it is auto-assigned

Col G ("Liczba dyżurów") is a live formula, so names-only writes keep the
fewest-duties selection fair across weeks. Selection, row tracking, range
building, and every fire() branch are unit-tested; live tests for sheet
read/write are env-gated.
2026-06-19 03:48:59 +02:00
Kamil 'Kamaś' Bruchal 4f9cc6c359 feat: harden rota sync and throttle manual /sync
Resolve the current-year sheet tab per fetch instead of freezing it at
startup, so a long-running process follows the New Year rollover without a
restart. Guard against the regression this introduces: a successful fetch
that returns no duty weeks (e.g. a freshly created, empty next-year tab) now
keeps the last-good cache instead of clobbering it with empty data.

Surface a silently-stale cache — which is not a crash, so process-level
supervision can't catch it — by escalating to a loud error log once no sync
has succeeded for 6h (new Store.LastSynced accessor backs the check).

Throttle manual /sync to once per minute across the chat to avoid spamming
the Google Sheets API; the check-and-set is mutex-guarded since Telegram
dispatches each update in its own goroutine.

Update README accordingly.
2026-06-19 02:05:04 +02:00
Kamil 'Kamaś' Bruchal 15b2fc9093 feat: ping on-duty members by Telegram handle in reminders
Read each member's Telegram handle from the new sheet column F (duty
count moves to G) and mention on-duty people as @handle in the weekly
reminder, so they get a real notification. Names without a handle, or
not found in the people list, fall back to plain text.

- sheet.go: add colHandle, Person.Handle; parse handle column
- scheduler.go: resolve names to @mentions via handlesByName/mention
- service.go: widen default SHEET_RANGE to column G
2026-06-19 01:40:20 +02:00
Kamil 'Kamaś' Bruchal 2e75d15f9d refactor: address review findings (slog, fetch timeout, Docker)
- add per-request timeout to the Sheets fetch so a hung call can't block /sync
- stop leaking raw sync errors to chat; log detail, reply generically
- migrate logging from log to structured log/slog
- drop duplicate godotenv.Load from ReadSheet (LoadConfig loads .env at startup)
- add multi-stage distroless Dockerfile + .dockerignore; embed tzdata via import
- document single-instance long-polling constraint in README
- translate sheet.go comments to English
- move package doc to doc.go; remove superseded root main.go and live_test.go
2026-06-18 21:38:37 +02:00
Kamil 'Kamaś' Bruchal 881bd5be03 feat: add weekly duty reminder scheduler
Add Reminder that posts a weekly cleaning-duty reminder to a Telegram
group on a configurable schedule (REMINDER_WEEKDAY/REMINDER_HOUR), in
Europe/Warsaw time, announcing the duty DUTY_AHEAD_DAYS out. Reminders
are disabled unless GROUP_CHAT_ID is set. Wire ReminderConfig parsing
into LoadConfig with validation, document the commands and reminder
config in README, and cover config loading with tests.
2026-06-18 02:36:06 +02:00
Kamil 'Kamaś' Bruchal a7bbbdc084 refactor: files consolidation 2026-06-17 21:29:35 +02:00
Kamil 'Kamaś' Bruchal cb0cc679c8 fix: read GOOGLE_SHEETS_ID and default range to current-year tab
LoadConfig read SPREADSHEET_ID, but the .env defines GOOGLE_SHEETS_ID,
so config loading failed. The default SHEET_RANGE was also a hardcoded
'Dyżury!A1:F100', but the spreadsheet tabs are named per year (e.g.
'2026'), producing a 400 'Unable to parse range'. Read GOOGLE_SHEETS_ID
and derive the default range from the current year.

Add a RUN_LIVE-gated integration test that drives the full
LoadConfig -> Service -> Syncer.Sync -> Store path against the live
Sheet, and gitignore the generated rota.json cache.
2026-06-17 21:16:25 +02:00
Kamil 'Kamaś' Bruchal 6c177c9514 fix: lint, interval validation, corrupt-cache tolerance, background init sync, concurrent test 2026-06-17 20:45:29 +02:00
Kamil 'Kamaś' Bruchal 7d799c74f1 feat: add Config + Service wiring for cache and background sync 2026-06-17 20:35:05 +02:00