diff --git a/Database.md b/Database.md new file mode 100644 index 0000000..101cac6 --- /dev/null +++ b/Database.md @@ -0,0 +1,132 @@ +# Database + +GM-Relay uses PostgreSQL. Schema changes are managed by DbUp migrations embedded in `GmRelay.Bot`. + +## Migrations + +Current migrations: + +- `V001__initial_schema.sql` — base tables for players, groups, sessions and participants. +- `V002__add_session_batch.sql` — `sessions.batch_id` for batch scheduling. +- `V003__add_thread_id.sql` — `sessions.thread_id` for Telegram forum topics. +- `V004__add_reschedule_proposals.sql` — proposal/vote tables for rescheduling. +- `V005__add_batch_message_id.sql` — `sessions.batch_message_id` for editing the original batch message. + +## Core tables + +### players + +Known Telegram users. + +Important fields: + +- `telegram_id` — unique Telegram user ID. +- `display_name` — current display name captured from Telegram. +- `telegram_username` — optional username. + +### game_groups + +Telegram groups where games are organized. + +Important fields: + +- `telegram_chat_id` — unique Telegram chat ID. +- `name` — current Telegram chat title. +- `gm_telegram_id` — Telegram ID of the GM for this group. + +### game_group_members + +Membership table between groups and players. The current feature set mostly works through session participation and `gm_telegram_id`; this table is part of the base schema for group membership. + +### sessions + +Individual game sessions. + +Important fields: + +- `batch_id` — groups multiple sessions created by one `/newsession` command. +- `group_id` — owning Telegram group. +- `title` — game title. +- `join_link` — connection link sent before the game. +- `scheduled_at` — UTC timestamp. +- `status` — `Planned`, `ConfirmationSent`, `Confirmed`, or `Cancelled`. +- `confirmation_message_id` — Telegram message ID for RSVP prompt. +- `link_message_id` — Telegram message ID for pre-game link reminder. +- `thread_id` — Telegram forum topic ID, if created. +- `batch_message_id` — original Telegram message with batch schedule. + +There is a partial index `ix_sessions_pending` over `scheduled_at` for active statuses. + +### session_participants + +Per-session player participation and RSVP state. + +Important fields: + +- `session_id` and `player_id` — unique pair. +- `is_gm` — whether participant is the GM. +- `rsvp_status` — `Pending`, `Confirmed`, or `Declined`. +- `responded_at` — RSVP timestamp. + +## Reschedule tables + +### reschedule_proposals + +Tracks one active reschedule flow per session. + +Important fields: + +- `session_id` — target session. +- `proposed_at` — proposed new UTC time; null while waiting for GM input. +- `proposed_by` — GM Telegram ID. +- `status` — `AwaitingTime`, `Voting`, `Approved`, `Rejected`. +- `vote_message_id` and `vote_chat_id` — Telegram voting message location. + +A partial unique index allows only one active proposal per session while status is `AwaitingTime` or `Voting`. + +### reschedule_votes + +Stores participant votes for a proposal. + +Important fields: + +- `proposal_id`. +- `player_id`. +- `vote` — `yes` or `no`. +- `voted_at`. + +Each participant has one vote per proposal; repeated voting updates the stored vote. + +## Status model + +Session statuses: + +```text +Planned -> ConfirmationSent -> Confirmed +Planned -> Cancelled +ConfirmationSent -> Cancelled +Confirmed -> ConfirmationSent, if a confirmed participant later declines +``` + +Reschedule statuses: + +```text +AwaitingTime -> Voting -> Approved +AwaitingTime -> Voting -> Rejected +AwaitingTime -> Approved, if no participants exist +``` + +RSVP statuses: + +```text +Pending -> Confirmed +Pending -> Declined +Confirmed -> Declined +Declined -> Confirmed +``` + +## Time handling + +User-facing input and output are Moscow time (`UTC+3`). `MoscowTime.TryParseMoscow` converts accepted local formats into UTC before saving to `scheduled_at`. + +Web edit form shows Moscow time, then converts it back to UTC before updating the database. \ No newline at end of file