feat: Replace SQLite with per-user JSON storage (fixes #2) #3

Merged
shoko merged 3 commits from fix/issue-2-json-storage into main 2026-04-02 17:44:08 +02:00
Showing only changes of commit 2e7b20ed81 - Show all commits

View File

@@ -37,22 +37,56 @@ The bot works and 53/53 tests pass. But `db.py` is ~300 lines with subtle connec
## Proposal ## Proposal
**Replace SQLite with a per-user JSON file storage system.** **Replace SQLite with a JSON file storage system — one directory per group or DM user.**
### Storage Design ### Storage Design
``` ```
data/ data/
── users/ ── {group_id}/
── {telegram_user_id}.json # one file per user ── group.json # group bounties (all bounties in this group)
│ └── {user_id}.json # user tracking within this group (which bounty IDs they track)
└── {user_id}/
└── user.json # user's personal bounties (DM — only this user)
``` ```
**File structure (`users/{id}.json`):** **Bot context lookup:**
| Context | Entry point |
|---|---|
| In group (`chat_id = -100123`) | `data/-100123/group.json` |
| In DM (`chat_id = 123`) | `data/123/user.json` |
**File: `data/{group_id}/group.json`** — group bounties:
```json
{
"group_id": -100123,
"bounties": [
{
"id": 1,
"created_by_user_id": 456,
"text": "Fix login bug",
"link": "https://github.com/example/repo/issues/1",
"due_date_ts": 1735689600,
"created_at": 1735603200
}
]
}
```
**File: `data/{group_id}/{user_id}.json`** — user tracking in a group:
```json
{
"user_id": 456,
"tracked": [1, 5, 9]
}
```
**File: `data/{user_id}/user.json`** — user's personal bounties (DM):
```json ```json
{ {
"user_id": 123, "user_id": 123,
"username": "alice", "bounties": [
"personal_bounties": [
{ {
"id": 1, "id": 1,
"text": "Fix login bug", "text": "Fix login bug",
@@ -60,25 +94,23 @@ data/
"due_date_ts": 1735689600, "due_date_ts": 1735689600,
"created_at": 1735603200 "created_at": 1735603200
} }
],
"tracked_bounties": [
{"bounty_id": 5, "group_id": -1001, "created_at": 1735600000},
{"bounty_id": 3, "group_id": null, "created_at": 1735590000}
] ]
} }
``` ```
### Key design decisions ### Key design decisions
1. **Single file per user** — No group-level files. Personal bounties live in the creator's file. Group bounties live in the creator's file with `group_id` set. 1. **Group/DM as directory**`chat_id` is the gateway. Group → `data/{group_id}/group.json`. DM → `data/{user_id}/user.json`. No scanning needed.
2. **Bounty IDs are sequential integers per file** — Not global. Each user's file has its own `next_id` counter. This avoids coordination between users at the cost of non-global IDs (acceptable for personal use). 2. **Tracking is per-group-per-user**`data/{group_id}/{user_id}.json` stores the list of bounty IDs this user tracks in this group. Simple, isolated.
3. **Cross-group tracking** — When Alice (in Group A) tracks a bounty created by Bob in Group B, Alice's file stores `{bounty_id: X, group_id: -100B}`. To display it, the bot loads Bob's file and finds bounty `X`. 3. **No cross-group access** — Group bounties live only in that group's file. A member of Group A cannot see or track Group B's bounties.
4. **No reminders in v1** — Drop the cron/reminder system entirely. The `reminder_log` table and `cron.py` are removed. Reminders can be added back as a v2 feature with a simpler design (e.g., just a "due soon" filter on `/my`). 4. **Bounty IDs are sequential integers per group** — Not global. Each `group.json` has its own `next_id` counter.
5. **No admin model in v1** — Drop `group_admins` table. Group bounties are open to anyone in the group to add/edit/delete. The creator can be the only one who can modify (enforced by `created_by_user_id` check). 5. **No reminders in v1** — Drop the cron/reminder system entirely. The `reminder_log` table and `cron.py` are removed.
6. **No admin model in v1** — Anyone in the group can add bounties. Only the bounty creator can edit/delete (enforced by `created_by_user_id` check).
### Deleted components ### Deleted components