sidequest_common.models.epgp#
EPGP loot system: WoW characters, raid sessions/encounters, loot awards, and the EP/GP ledger.
Source of truth is the site (local://addon-contract.md); the sidequest-sync addon exports raid facts and
ledger entries as an SQX1: string, an officer pastes it (or uploads SavedVariables) into
POST /api/epgp/imports, and the import service upserts by each record’s addon-assigned external_id so
re-importing the same string is a no-op. GP/EP decay (15% per weekly reset) is never stored: it’s applied on read
from EpgpLedgerEntry.at (see sidequest_api.domain.epgp.services). A voided row (voided_at set) is kept
forever and excluded from standings; it can never be un-voided.
Classes#
One boss pull (or kill) within a raid session, for the loot log and the progression board. |
|
A browser sign-in for SideQuest Companion, device-flow style ( |
|
One |
|
One EP or GP change: an addon-exported award, a loot GP charge, or a site-made officer adjustment. |
|
A long-lived bearer token for the |
|
One item awarded during a raid session, with the GP charge recorded as a twin |
|
One raid night, as recorded by the addon's session recorder and exported by |
|
One character's attendance window within one raid session, standby (bench) or not. |
|
One in-game character, keyed |
Module Contents#
- class sidequest_common.models.epgp.Encounter#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne boss pull (or kill) within a raid session, for the loot log and the progression board.
characters/standbyare the addon-reported character keys present for the pull; kept as JSON rather than a join table since nothing queries them per-character (only the session’sSessionCharacterPresencerows drive attendance and standings).- characters: sqlalchemy.orm.Mapped[list[str]]#
- difficulty_id: sqlalchemy.orm.Mapped[int]#
- encounter_id: sqlalchemy.orm.Mapped[int]#
- ended_at: sqlalchemy.orm.Mapped[datetime.datetime]#
- external_id: sqlalchemy.orm.Mapped[str]#
- name: sqlalchemy.orm.Mapped[str]#
- session_id: sqlalchemy.orm.Mapped[uuid.UUID]#
- size: sqlalchemy.orm.Mapped[int]#
- standby: sqlalchemy.orm.Mapped[list[str]]#
- started_at: sqlalchemy.orm.Mapped[datetime.datetime]#
- success: sqlalchemy.orm.Mapped[bool]#
- class sidequest_common.models.epgp.EpgpDeviceAuthorization#
Bases:
advanced_alchemy.base.UUIDAuditBaseA browser sign-in for SideQuest Companion, device-flow style (
gh auth login).The companion gets a
device_code/user_codepair fromPOST /api/epgp/device-authorizations, opensverification_uri_completefor the officer to approve, and pollsPOST .../tokenwithdevice_code.device_code_hashis the sha256 hex digest of the device code; the plaintext is handed to the device once and never stored.user_codeis kept normalized (8 chars, uppercase, no dash). The approving officer is re-checked for officer access on the first poll after approval, when the sync token is minted;consumed_atthen marks the row so a later poll can’t replay the token.- approved_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- approved_by_member_id: sqlalchemy.orm.Mapped[int | None]#
- consumed_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- denied_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- device_code_hash: sqlalchemy.orm.Mapped[str]#
- device_name: sqlalchemy.orm.Mapped[str]#
- expires_at: sqlalchemy.orm.Mapped[datetime.datetime]#
- last_polled_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- sync_token_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- user_code: sqlalchemy.orm.Mapped[str]#
- class sidequest_common.models.epgp.EpgpImportAudit#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne
POST /api/epgp/importscall: who ran it, which addon recorder it came from, and what changed.- counts: sqlalchemy.orm.Mapped[dict]#
- member_id: sqlalchemy.orm.Mapped[int | None]#
- recorder: sqlalchemy.orm.Mapped[str]#
- class sidequest_common.models.epgp.EpgpLedgerEntry#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne EP or GP change: an addon-exported award, a loot GP charge, or a site-made officer adjustment.
external_idisnull(and so excluded from the unique constraint’s enforcement across site rows) for adjustments made directly on the site (POST /api/epgp/adjustments); addon-exported rows always carry it, and re-importing the same row is a no-op.character_id/member_idare both nullable: an addon row ties to the character that earned it (member_idresolved through the character’s link at read time for the ledger/standings), while a site adjustment ties directly to the target member with no character.- amount: sqlalchemy.orm.Mapped[int]#
- at: sqlalchemy.orm.Mapped[datetime.datetime]#
- character_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- external_id: sqlalchemy.orm.Mapped[str | None]#
- kind: sqlalchemy.orm.Mapped[str]#
- loot_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- member_id: sqlalchemy.orm.Mapped[int | None]#
- note: sqlalchemy.orm.Mapped[str | None]#
- officer: sqlalchemy.orm.Mapped[str]#
- reason: sqlalchemy.orm.Mapped[str]#
- session_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- source: sqlalchemy.orm.Mapped[str]#
- voided_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- class sidequest_common.models.epgp.EpgpSyncToken#
Bases:
advanced_alchemy.base.UUIDAuditBaseA long-lived bearer token for the
sidequest-syncuploader or SideQuest Companion, scoped to one member.token_hashis the sha256 hex digest of the token (formatsqs_+ 40 url-safe chars); the plaintext is shown to the officer once, at creation, and never stored.POST /api/epgp/importsandGET /api/epgp/game-importboth accept the token only while its member still holds an officer role, checked at request time (not cached on the row).- last_used_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- member_id: sqlalchemy.orm.Mapped[int]#
- name: sqlalchemy.orm.Mapped[str]#
- revoked_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- token_hash: sqlalchemy.orm.Mapped[str]#
- class sidequest_common.models.epgp.LootAward#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne item awarded during a raid session, with the GP charge recorded as a twin
EpgpLedgerEntry.officeris the awarding officer’s in-game character name (addon data, not a site member link).- at: sqlalchemy.orm.Mapped[datetime.datetime]#
- character_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- encounter_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- external_id: sqlalchemy.orm.Mapped[str]#
- gp: sqlalchemy.orm.Mapped[int]#
- inv_type: sqlalchemy.orm.Mapped[str]#
- item_id: sqlalchemy.orm.Mapped[int]#
- item_name: sqlalchemy.orm.Mapped[str]#
- officer: sqlalchemy.orm.Mapped[str]#
- quality: sqlalchemy.orm.Mapped[int]#
- response: sqlalchemy.orm.Mapped[str]#
- session_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- voided_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- class sidequest_common.models.epgp.RaidSession#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne raid night, as recorded by the addon’s session recorder and exported by
id.event_idlinks to the site’sGuildEventwhen the officer started it from a scheduled raid (drives the attendance hand-off);nullfor an ad-hoc session with no matching event.recorderis the officer character that exported the session, used for the one-recorder-per-raid-night conflict rule.- difficulty_id: sqlalchemy.orm.Mapped[int]#
- ended_at: sqlalchemy.orm.Mapped[datetime.datetime]#
- event_id: sqlalchemy.orm.Mapped[uuid.UUID | None]#
- external_id: sqlalchemy.orm.Mapped[str]#
- instance_id: sqlalchemy.orm.Mapped[int]#
- instance_name: sqlalchemy.orm.Mapped[str]#
- recorder: sqlalchemy.orm.Mapped[str]#
- size: sqlalchemy.orm.Mapped[int]#
- started_at: sqlalchemy.orm.Mapped[datetime.datetime]#
- class sidequest_common.models.epgp.SessionCharacterPresence#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne character’s attendance window within one raid session, standby (bench) or not.
Drives the attendance hand-off: every member linked to a session character (standby included, as bench) gets marked attended on the linked event’s sign-up.
- character_id: sqlalchemy.orm.Mapped[uuid.UUID]#
- first_seen: sqlalchemy.orm.Mapped[datetime.datetime]#
- last_seen: sqlalchemy.orm.Mapped[datetime.datetime]#
- session_id: sqlalchemy.orm.Mapped[uuid.UUID]#
- standby: sqlalchemy.orm.Mapped[bool]#
- class sidequest_common.models.epgp.WowCharacter#
Bases:
advanced_alchemy.base.UUIDAuditBaseOne in-game character, keyed
"name-realm"lowercase (case-insensitive match per the contract).member_idlinks the character to a site member, sharing that member’s EP/GP balance;sourcerecords how the link happened (claim: the member self-claimed it on the account page;officer: an officer set it on the characters admin screen;signup: auto-linked because the name matched exactly one member’s event sign-up character;import: seen in an addon import with no link yet).is_mainflags the character shown as a member’s primary on standings.- is_main: sqlalchemy.orm.Mapped[bool]#
- key: sqlalchemy.orm.Mapped[str]#
- last_seen_at: sqlalchemy.orm.Mapped[datetime.datetime | None]#
- member_id: sqlalchemy.orm.Mapped[int | None]#
- name: sqlalchemy.orm.Mapped[str]#
- realm: sqlalchemy.orm.Mapped[str]#
- source: sqlalchemy.orm.Mapped[str]#
- wow_class: sqlalchemy.orm.Mapped[str | None]#