feat(web): add completed-game portfolio to GM showcase (issue #108) #118

Merged
Toutsu merged 31 commits from codex/feature-issue-108-portfolio into main 2026-06-02 18:28:49 +03:00
2 changed files with 1218 additions and 8 deletions
Showing only changes of commit ac417731d6 - Show all commits
File diff suppressed because it is too large Load Diff
@@ -15,7 +15,7 @@ Add a public portfolio of completed tabletop adventures. A club owner or co-GM c
- A portfolio item is an independent adventure entity, not a flag on one session.
- One adventure can reference multiple completed sessions from the same club.
- Reviews are submitted by authenticated players, not entered manually by a GM.
- A player can review an adventure after being registered for at least one linked completed session.
- A player can review an adventure after being actively registered as a non-GM participant for at least one linked completed session. Waitlisted players are not eligible.
- Each player can submit one review per adventure.
- A review is public only after the player explicitly consents to publication and a club owner or co-GM approves it.
- Public reviews show a display-name snapshot captured at submission time. They never expose platform IDs or account links.
@@ -30,9 +30,9 @@ Add a public portfolio of completed tabletop adventures. A club owner or co-GM c
## Architecture
Add a portfolio vertical slice to `GmRelay.Web` and a schema migration in `GmRelay.Bot`. The portfolio tables reference the existing `game_groups`, `players`, and `sessions` tables but do not change the recruitment catalog query or its future-session filters.
Add a bounded portfolio vertical slice to `GmRelay.Web` and a schema migration in `GmRelay.Bot`. The portfolio tables reference the existing `game_groups`, `players`, and `sessions` tables but do not change the recruitment catalog query or its future-session filters.
The protected management flow is exposed through `AuthorizedSessionService`, which reuses the existing owner/co-GM group authorization model. Public reads and authenticated review submission are exposed through `ISessionStore` and `SessionService`.
Keep portfolio persistence separate from the already large scheduling store. `IPortfolioStore` and `PortfolioService` own portfolio reads, writes, and review submission. `AuthorizedPortfolioService` wraps protected management operations and reuses `ISessionStore.IsGroupManagerAsync` plus the existing current-user identity model for owner/co-GM authorization. Public Razor pages inject `IPortfolioStore` directly for sanitized reads.
Cover storage is isolated behind `IPortfolioCoverStorage`. Pages and services work with generated storage keys and public paths rather than physical file locations. The local implementation stores files in a persistent mounted directory and serves them through a dedicated request path. A future S3 implementation can generate equivalent public paths or signed delivery URLs while preserving the same service contract and database fields.
@@ -173,7 +173,7 @@ The storage key remains provider-neutral. A future S3-compatible implementation
## Service Contracts
Add sanitized DTOs to `ISessionStore`. Public DTOs must not expose player IDs, group IDs, session IDs, platform identifiers, moderator IDs, physical storage paths, or join links.
Add sanitized DTOs to `IPortfolioStore`. Public DTOs must not expose player IDs, group IDs, session IDs, platform identifiers, moderator IDs, physical storage paths, or join links.
Representative contracts:
@@ -210,7 +210,7 @@ Protected DTOs may carry IDs needed for editing and moderation.
### Protected Management
Through `AuthorizedSessionService`:
Through `AuthorizedPortfolioService`:
- Load draft and published adventure cards for a managed club.
- Load eligible completed sessions for a managed club.
@@ -229,7 +229,7 @@ An authenticated user can submit a review from `/portfolio/{slug}` only when:
- The adventure is public.
- The user explicitly checks publication consent.
- The user is registered in `session_participants` for at least one linked session.
- The user is registered in `session_participants` as a non-GM participant with `registration_status = 'Active'` for at least one linked session.
- The linked session is in the past.
- The user has not submitted a review for this adventure before.
@@ -245,11 +245,11 @@ Extend `GroupDetails.razor` with a completed-adventures section:
- List draft and published portfolio cards.
- Show title, publication state, linked-session count, displayed-GM count, and review moderation count.
- Provide a create action and edit links.
- Provide a create action, edit links, and a link to the club's completed-session list.
### Completed Session Quick Action
Extend session history with an "Добавить в портфолио" action for a completed session that is not already linked. The action opens the adventure editor with that session preselected.
Add a protected `/group/{groupId}/completed` page that lists past sessions for a managed club. Extend that page and session history with an "Добавить в портфолио" action for a completed session that is not already linked. The action opens the adventure editor with that session preselected.
### Adventure Editor
@@ -320,6 +320,8 @@ volumes:
Development configuration uses a local directory under the application content root or an explicitly configured path.
The Web Docker image creates `/app/portfolio-covers` and assigns it to `$APP_UID` before switching to the non-root runtime user.
---
## Documentation