You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: stats architecture, API reference, and contributor guidelines
Documents the counter ledger, canonical lock order, sum-on-read totals,
tombstone fold, retention, and egress accounting in server/ARCHITECTURE.md;
the dashboard component contracts in webapp/ARCHITECTURE.md; every new
endpoint, field, sort value, and visibility rule in the API reference; and
the requirement that code comments be self-contained and production-ready
in the contributor guidelines.
Copy file name to clipboardExpand all lines: .agents/workflows/review-changes.md
+8-1Lines changed: 8 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,6 +54,9 @@ For every modified file, evaluate against ALL of the following checklist:
54
54
-[ ] Does the change follow existing patterns in the codebase?
55
55
-[ ] Are naming conventions consistent (variables, functions, types)?
56
56
-[ ] Does it match the code style of surrounding files?
57
+
-[ ] Are non-obvious policy helpers commented? Check public API validators, persistent-state mutations, complex SQL/GORM queries, stats/business policy, security/privacy behavior, and concurrency assumptions.
58
+
-[ ] Do comments explain intent, accepted inputs, defaults, return meaning, or invariants where those are not obvious from nearby code?
59
+
-[ ] Are comments self-contained and production-ready? Flag any comment or test name that references development-process artifacts (task/ticket numbers, decision IDs, plan/backlog/review docs, audit findings, agent briefs, uncommitted files) or narrates how a decision was reached instead of stating the technical rationale. Doc pointers are fine only when they cite a committed file by its actual heading.
57
60
58
61
#### Completeness
59
62
-[ ] Are all necessary files modified (tests, docs, config)?
@@ -75,6 +78,8 @@ For every modified file, evaluate against ALL of the following checklist:
75
78
-[ ] Are new behaviors covered by tests?
76
79
-[ ] Do existing tests still pass with these changes?
77
80
-[ ] Are test assertions meaningful (not just "no error")?
81
+
-[ ] For large backend changes, compare package coverage before/after and identify new uncovered functions.
82
+
-[ ] Are complex SQL/GORM queries, migrations/backfills, counter mutations, concurrency paths, and public API validation covered by focused backend tests? E2E coverage is useful but not a substitute for these.
78
83
79
84
#### Documentation (`docs/`, README, `--help`)
80
85
-[ ] Do user-facing docs reflect the new/changed behavior?
@@ -89,6 +94,7 @@ For every modified file, evaluate against ALL of the following checklist:
89
94
-[ ] Are new/changed request/response fields documented with examples?
90
95
-[ ] Are removed or renamed fields noted for backward compatibility?
91
96
-[ ] Are new query parameters listed in the relevant filter table?
97
+
-[ ] Treat HTTP API documentation drift as a critical review finding.
92
98
93
99
#### CSS / Theming (if webapp styles were changed)
94
100
-[ ] Do changes use design tokens (`surface-*`, `accent-*`) instead of hardcoded colors?
@@ -114,6 +120,7 @@ For every modified file, evaluate against ALL of the following checklist:
114
120
- Verify the change aligns with documented patterns
115
121
- Check if AGENTS.md needs updating
116
122
-**If any HTTP handler, route, or API response struct was modified**: check `docs/reference/api.md` and flag any drift as a 🔴 Critical issue
123
+
-**If stats, counters, migrations, or SQL/GORM queries changed**: verify direct backend tests exist for the new logic and flag E2E-only coverage as a review issue
117
124
118
125
### 4. Lint and build
119
126
@@ -207,4 +214,4 @@ Do NOT auto-fix without asking. Present the findings first, let the user decide.
207
214
- Don't just rubber-stamp changes with "LGTM" unless they're truly clean
208
215
- Pay special attention to subtle issues: off-by-one, missing error handling, stdout/stderr confusion, goroutine leaks
209
216
- If a change seems incomplete (e.g., missing tests for new behavior), flag it
210
-
- Compare against ARCHITECTURE.md and project conventions
217
+
- Compare against ARCHITECTURE.md and project conventions
├── server/ ← Go server (see server/ARCHITECTURE.md)
48
48
│ ├── main.go ← entry point
49
49
│ ├── plikd.cfg ← default config
@@ -139,8 +139,25 @@ make vuln # govulncheck (report only)
139
139
-**Helm persistence**: the chart has two independent PVCs — `persistence` for uploaded files (`/home/plik/server/files`) and `dbPersistence` for the SQLite database (`/home/plik/server/db`). Both default to `emptyDir` when disabled. The default `MetadataBackendConfig.ConnectionString` is `/home/plik/server/db/plik.db`.
140
140
-**Run tests before committing**: `make lint && make test`
141
141
-**Keep ARCHITECTURE.md files in sync**: Each root folder has its own — update the one closest to your change
142
+
-**Comment non-obvious policy and invariants**: Add concise comments for helpers that validate public API input, mutate persistent state, build complex SQL/GORM queries, encode security/privacy behavior, rely on concurrency assumptions, or implement statistics/business policy. Comments should explain accepted input domains, default behavior, return meaning, and why the rule exists when those details are not obvious from the name and surrounding call site. Prefer comments that explain why the rule exists over comments that restate the code.
143
+
-**Comments must be self-contained and production-ready**: A comment (or test name) may only reference things that live in the repository. Never reference development-process artifacts — task or ticket numbers, decision IDs, plan/backlog/review documents, audit findings, agent briefs, or any uncommitted file — and never narrate how a decision was reached ("as decided during review", "per the maintainer"). State the technical rationale itself; when the long-form policy lives in a committed doc (e.g. an ARCHITECTURE.md section), point there by its actual heading.
144
+
-**Test at the right layer**: E2E tests complement, but do not replace, focused backend tests for SQL/GORM queries, migrations/backfills, counter mutations, concurrency, and public API validation.
142
145
-**Release process**: Before creating a GitHub release, update the version in `README.md` and move `charts/plik/CHANGELOG.md` entries from `[Unreleased]` to the new version heading
143
146
147
+
## Agent Workflows
148
+
149
+
Project workflows live in `.agents/workflows/`. Use them when the matching task comes up:
150
+
151
+
| Workflow | Purpose |
152
+
|----------|---------|
153
+
|`plan.md`| Create an implementation plan before non-trivial features or refactors |
154
+
|`review-changes.md`| Critically review local diffs before committing or preparing a PR |
155
+
|`commit.md`| Commit or push changes with the mandatory user approval gate |
156
+
|`prepare-pr.md`| Run review/pre-flight checks, commit, push, and draft a PR |
157
+
|`cut-release.md`| Walk through the release checklist |
158
+
|`add-language.md`| Add a new webapp locale end to end |
159
+
|`review-language.md`| Review locale quality, placeholders, plurals, and semantic distinctions |
160
+
144
161
## Documentation
145
162
146
163
The documentation lives in two places:
@@ -171,4 +188,3 @@ make docs # Build docs (builds client, injects help+plikrc, b
| GET |`/me/uploads`|`GetUserUploads`| List uploads (paginated, sortable by `sort` (`date`/`size`) and `order` (`desc`/`asc`), filterable by `token` and badge settings: `oneShot`, `removable`, `stream`, `extendTTL`, `password`, `e2ee`) |
256
+
| GET |`/me/uploads`|`GetUserUploads`| List uploads (paginated, sortable by `sort` (`date`/`size`/`downloads`) and `order` (`desc`/`asc`), filterable by `token` and badge settings: `oneShot`, `removable`, `stream`, `extendTTL`, `password`, `e2ee`) |
257
257
| DELETE |`/me/uploads`|`RemoveUserUploads`| Remove all uploads |
258
-
| GET |`/me/stats`|`GetUserStatistics`| User stats |
258
+
| GET |`/me/stats`|`GetUserStatistics`| Counter-backed user stats: legacy flat `uploads`/`files`/`totalSize` plus a nested `usage` object (current + lifetime counters, stats-tracking start timestamp, download windows); `token` scopes `usage` to the current API token (no download windows) |
259
+
| GET |`/me/stats/downloads/daily`|`GetUserDownloadsDaily`| Current user's dense daily download series (`days` 1–31, default 30, strict 400); bare oldest-first array of `{day, downloads, bytes}`|
259
260
260
261
### User Management Endpoints (authenticated — session cookie required)
261
262
@@ -270,10 +271,13 @@ When processing a request, limits are resolved via the custom `Context`:
270
271
| Method | Path | Handler | Description |
271
272
|--------|------|---------|-------------|
272
273
| POST |`/user`|`CreateUser`| Create user |
273
-
| GET |`/stats`|`GetServerStatistics`| Server stats |
274
-
| GET |`/users`|`GetUsers`| List users (paginated, filterable by `provider` and `admin` status) |
274
+
| GET |`/stats`|`GetServerStatistics`| Counter-backed server stats: legacy flat totals (`users`, `uploads`, `anonymousUploads`, `files`, `totalSize`, `anonymousTotalSize`), top-level `lifetimeUsers`, and nested `usage`/`anonymousUsage` objects (current + lifetime counters, downloads, feature usage; download windows on server `usage` only) |
275
+
| GET |`/stats/downloads/daily`|`GetServerDownloadsDaily`| Server-wide dense daily download series (`days` 1–31, default 30, strict 400); bare oldest-first array of `{day, downloads, bytes}`|
276
+
| GET |`/stats/trending/uploads`|`GetTrendingUploads`| Trending uploads (`window=all\|1d\|7d\|30d`, `limit`) |
277
+
| GET |`/stats/trending/files`|`GetTrendingFiles`| Trending files (`window=all\|1d\|7d\|30d`, `limit`) |
278
+
| GET |`/users`|`GetUsers`| List users (paginated, sortable by `sort` (`date`/`size`/`lifetimeSize`), filterable by `provider` and `admin` status) |
275
279
| GET |`/users/search`|`SearchUsers`| Search users by login/name/email (LIKE query, `q`, `limit`, `provider`, `admin`) |
276
-
| GET |`/uploads`|`GetUploads`| List all uploads (paginated, filterable by `user`, `token`, `sort`, badge settings: `oneShot`, `removable`, `stream`, `extendTTL`, `password`, `e2ee`) |
280
+
| GET |`/uploads`|`GetUploads`| List all uploads (paginated, sortable by `sort` (`date`/`size`/`downloads`), filterable by `user`, `token`, and badge settings: `oneShot`, `removable`, `stream`, `extendTTL`, `password`, `e2ee`) |
277
281
278
282
---
279
283
@@ -421,8 +425,21 @@ GORM-based with auto-migrations via gormigrate.
421
425
|`tokens`|`Token`| Upload tokens, FK to users |
422
426
|`settings`|`Setting`| Server settings (e.g., auth signing key) |
|`usage_stats`|`UsageStats`| Flat scoped current/lifetime counters keyed by `(user_id, token)`: `("", "")` for server, `("__anonymous__", "")` for anonymous usage, `(userID, "")` for users, and `(ownerUserID, token)` for API tokens |
429
+
|`download_stats_daily`|`DownloadStatsDaily`| Daily download rollups keyed by day/entity type/entity ID for trending windows; cleanup keeps today plus 30 previous UTC-day buckets |
424
430
|`migrations`| (gormigrate) | Schema migration history |
425
431
432
+
### Stats Model
433
+
434
+
Plik's stats system is database-backed so several server instances can safely share one metadata database. Counters are updated in the same metadata transactions that create uploads, complete files, remove retained data, and record counted downloads. The detailed implementation contract lives in [server/ARCHITECTURE.md](server/ARCHITECTURE.md#stats-architecture); the system-level invariants are:
435
+
436
+
-**Current stats** describe retained state now: active uploads, uploaded files, retained size, current feature usage, current TTL distribution, and current file-size distribution.
-**StartedAt** is the "Stats since ..." timestamp. Migration-created rows start at migration/backfill time because already purged metadata cannot be reconstructed.
439
+
-**Anonymous usage** is tracked through the synthetic `("__anonymous__", "")` usage row instead of a real user.
440
+
-**Downloads** are logical intent events, not bytes. Full-file `GET` requests count, start-range `GET` requests count, non-start ranges and malformed ranges do not. Direct file download stats are recorded after auth/status/backend-open/range-policy checks and before streaming completes; archive download stats are recorded after a successful archive close.
441
+
-**Trending and server download windows** use upload/file download counts and daily rollups for `1d`, `7d`, and `30d` UTC-day windows. Trending is admin-only. Cleanup keeps today plus 30 previous UTC-day rollup buckets, one extra bucket beyond the 30-day display window.
0 commit comments