TFTgogo

Java / Spring Boot / MySQL / JWT / SSE

Commits may appear as beancan0325, my previous GitHub username.

1. Project Overview and Key Features — a TFT community for party recruitment and chat

Team project | 2026.05 ~ 2026.06

TFTgogo is a community service where Teamfight Tactics players can check game information, recruit people to play with, and chat with participants. The user action looks simple, but the service has to check login state, party capacity, existing participation, closed recruitment posts, and chat permissions at the same time. My work focused on keeping party participation and realtime chat state consistent between the screen and the server.

My Contribution

Party participation guards, chat permission boundaries, auth-state UI rules, and SSE recovery evidence.

Decision

Backend service rules should stop invalid joins and writes even if the UI is stale or bypassed.

Verification

Regression tests and code evidence cover stale auth state, duplicate participation, and chat write boundaries.

Limitation

Full multithread load testing for party capacity was not completed, so this is documented as remaining work.

Case 1. Three cache invalidation paths that can be missed

Failure state
A deck aggregation commits to the database while stale meta-deck rankings remain visible from cache.
Root cause
Pre-commit eviction, exception paths that skip @CacheEvict, and an async lambda that bypasses the Spring proxy each create a different invalidation gap.
Decision
Use TransactionAwareCacheManagerProxy for post-commit eviction, explicit cleanup in exception and async finally blocks, and AtomicBoolean.compareAndSet to reject overlapping aggregations.
Code evidence
global/config/CacheConfig.java, domain/deck/service/impl/MetaDeckServiceImpl.java, domain/deck/service/impl/AdminDeckServiceImpl.java
Verification / limit
MetaDeckServiceImplTest.java covers overlap and flag recovery. Caffeine remains a single-instance cache; multi-instance deployment would need distributed invalidation.

Case 2. Two users racing for the final party seat

Failure state
Two requests read the same remaining capacity and both pass validation, exceeding capacity or duplicating participation.
Root cause
A plain read-check-update sequence leaves a race window between validation and persistence.
Decision
Lock member → party in a fixed order with PESSIMISTIC_WRITE inside one transaction, with a three-second lock timeout.
Code evidence
domain/community/service/impl/CommunityPartyServiceImpl.java, domain/community/repository/PartyPostRepository.java, domain/member/repository/MemberRepository.java
Verification / limit
CommunityPartyServiceImplTest.java covers full parties, duplicate joins, and lock order. A real multithread load test has not yet been run.

Case 3. Same email, different social account ownership

Failure state
Email-only matching can merge accounts owned through different identity providers.
Decision
Use provider + socialId as the ownership key, enforce a composite database constraint, and re-query after a concurrent first-signup conflict.
Code evidence
domain/member/service/impl/MemberServiceImpl.java, domain/member/repository/MemberRepository.java, db/migration/V1__init_schema.sql
Verification
MemberServiceImplTest.java and SocialMemberCreationServiceTest.java cover provider identity and convergence after unique-constraint conflicts.
Outcome
The model prioritizes account ownership over convenient but unsafe email-based merging.

Stale auth state

The page checks current authentication before cached participation state can pollute the UI.

InputisJoined=true
from stale cache
Auth checkisAuthenticated
checked first
Guardunauthenticated
button disabled
Outputlogin required
no stale action
ProblemA stale participation value can show joined controls to a logged-out user.
DecisionCheck the current auth state before participation state.
ResultLogged-out users are pinned to the login-required state, even with stale joined data.

Duplicate participation guard

Duplicate joins are handled as a server-side domain consistency issue before persistence.

Requestjoin/create
party action
Lockmember row
for mutation
Checkowned post or
accepted party
Stopthrow before
repository.save
ProblemConcurrent or external requests cannot be stopped by disabled buttons alone.
DecisionLock the member row and check active posts/participations before saving.
ResultThe duplicate path exits before save is called.

Chat permission boundary

Community visibility stays open, while message-writing responsibility stays tied to an authenticated user.

GETmessages/stream
public
POSTsend message
principal required
401clear token
unauthorized
403client error
keep token
ProblemLocking reads hurts visibility, but open writes remove user accountability.
DecisionKeep GET/SSE reads public and require authentication for POST writes.
ResultUsers can read openly, while writes remain traceable to an authenticated identity.

SSE lifecycle

Realtime failure is modeled as retry, terminal failure, or auth expiry instead of one generic error.

OpenSSE stream
connected
Mergesnapshot/message
query cache
Retry1s / 2.5s / 5s
max 3
Stopexpired auth or
failed recovery
ProblemDisconnects, retries, auth expiry, and recovery failure can all look like one error.
DecisionSeparate retry eligibility and delay into a reconnect policy.
ResultThe UI can distinguish recoverable failures from terminal failures.

2. Design

Auth first, state second

The UI checks the current auth state before any cached participation state, so logged-out users never see stale joined controls.

Service-layer duplicate guard

The server does not rely on disabled buttons; member row locking and active participation checks stop duplicate joins before persistence.

Read/write permission split

GET/SSE chat reads remain public, while POST message writes require an authenticated user identity.

Realtime recovery rules

SSE snapshots and messages merge into React Query cache, while retry limits and auth expiry stay explicit.

3. Project Problem-Solution

Auth State

Preventing stale participation state after logout

Problem

After logout, stale isJoined data could remain and show joined-state actions to unauthenticated users.

Cause

Frontend participation cache and current authentication state could update at different times.

Fix

Check isAuthenticated before joined state and lock the unauthenticated combination with a regression test.

Result

Even with stale joined data, unauthenticated users stay in the login-required state and cannot see invalid actions.

Party Domain Rule

Blocking duplicate participation from concurrent or bypassed requests

Problem

Disabled buttons cannot stop another tab or direct request from trying to join multiple parties.

Cause

UI-only guards cannot guarantee domain consistency right before server persistence.

Fix

Check active posts and existing participation before save, and stop duplicate paths before repository persistence.

Result

The duplicate path can be verified with a test that repository save is never called.

Realtime Chat

Separating public chat reads from authenticated message writes

Problem

Locking all chat access hurts visibility, while open writes remove user accountability.

Cause

GET/SSE reads and POST writes have different product and security requirements.

Fix

Keep message reads and SSE streams public, but require AuthenticationPrincipal for message writes.

Result

Users can read chat openly, while write actions stay tied to an authenticated identity.

4. Key Features and Code Evidence

TFTgogo auth before joined guard code screenshot
Auth-first button guard

isAuthenticated is checked before isJoined so logged-out users do not see joined controls.

TFTgogo duplicate join guard code screenshot
Duplicate join guard

Another active participation stops the flow before a new record is saved.

TFTgogo duplicate join save-not-called regression test
Save-not-called test

The duplicate participation path is verified by asserting that persistence is not called.

TFTgogo public chat read SecurityConfig code screenshot
Public chat reads

Chat message reads and SSE stream GET requests are left public.

TFTgogo authenticated message write controller code screenshot
Authenticated writes

Message writes use AuthenticationPrincipal so user accountability remains explicit.

TFTgogo SSE reconnect policy code screenshot
SSE reconnect policy

Retry eligibility and delay are separated into a testable policy function.

5. Collaboration Process

Feature branches

Changes were kept scoped by feature branch and reviewed through pull requests.

Pull request review

Auth, DTO responses, service validation, and tests were checked at PR scope.

Issue tracking

Community, party, chat, and SSE work was split into trackable feature tasks.

Labels and releases

bug, feature, frontend, backend, and priority labels plus release notes are kept as collaboration evidence.

6. GitHub Collaboration Evidence