Public Access
fix(sync): stop the nightly sync when the Steam ID is gone
After an unlink in Auth, or when a verified link took the ID away, the nightly job kept syncing the old Steam ID for up to 14 days. A games-count request without a steamId claim now clears user_seen_at. steam_id stays, so a later link with another ID still soft-deletes the old games.
This commit is contained in:
1 parent
393cdddf06
commit
a389bdd0c7
5 files changed
+83
-1
No files matched your search
@@ -129,7 +129,8 @@ Inside a module: `api` (controllers, DTOs), `service` (business logic), `domain`
|
||||
1. `GET /api/library/games`, `/games-count` and `/picker` call `LibrarySyncService.syncIfStale`. It starts a sync
|
||||
when the last one is older than 24 hours, older than 15 minutes after a failure, or was for another Steam ID.
|
||||
`POST /api/library/sync` starts one at once, but at most every 60 seconds (else `429` with `Retry-After`).
|
||||
2. A nightly job (23:55, Europe/Berlin) syncs every account used in the last 14 days, one after the other.
|
||||
2. A nightly job (23:55, Europe/Berlin) syncs every account used in the last 14 days, one after the other. A
|
||||
request without a `steamId` claim (the user unlinked the Steam account in Auth) takes the user out of it.
|
||||
3. `LibrarySyncWorker` runs in the background. `user_steam_sync` lets only one run per user start.
|
||||
4. The worker loads the wishlist and the owned games from Steam. It updates `steam_game` and `user_game`, adds
|
||||
the playtime growth of the day to `user_game_playtime_day`, and soft-deletes Steam games the user no longer
|
||||
|
||||
@@ -70,6 +70,8 @@ public class LibraryController {
|
||||
Long userId = principal.getIdAsLong();
|
||||
if (principal.steamId() != null) {
|
||||
syncService.syncIfStale(userId, String.valueOf(principal.steamId()));
|
||||
} else {
|
||||
syncService.stopNightlySync(userId);
|
||||
}
|
||||
long count = queryService.countOwnedGames(userId);
|
||||
return ResponseEntity.ok(Map.of("count", count, "hasSteamId", principal.steamId() != null));
|
||||
|
||||
@@ -52,6 +52,15 @@ public interface UserSteamSyncRepository extends JpaRepository<UserSteamSync, Lo
|
||||
@Query(value = "UPDATE user_steam_sync SET user_seen_at = :now WHERE user_id = :userId", nativeQuery = true)
|
||||
void touchUserSeen(@Param("userId") Long userId, @Param("now") Instant now);
|
||||
|
||||
/**
|
||||
* Takes the user out of the nightly sync. steam_id stays, so a later sync with another ID still sees the change.
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query(value = "UPDATE user_steam_sync SET user_seen_at = NULL WHERE user_id = :userId AND user_seen_at IS NOT NULL",
|
||||
nativeQuery = true)
|
||||
void clearUserSeen(@Param("userId") Long userId);
|
||||
|
||||
List<UserSteamSync> findByUserSeenAtAfter(Instant since);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@ public class LibrarySyncService {
|
||||
accounts.forEach(sync -> worker.runSyncNow(sync.getUserId(), sync.getSteamId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* For a user whose token has no Steam ID any more (unlinked, or taken by a verified link in Auth): the nightly
|
||||
* job would otherwise keep syncing the old ID until the user is 14 days inactive.
|
||||
*/
|
||||
public void stopNightlySync(Long userId) {
|
||||
userSteamSyncRepository.clearUserSeen(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* User-triggered sync. Throws SyncCooldownException if recently synced or running.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package core_service.modules.library.api;
|
||||
|
||||
import core_service.modules.library.repo.UserSteamSyncRepository;
|
||||
import core_service.shared.test.AbstractIntegrationTest;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* A user who unlinked the Steam ID, or lost it to a verified link, must drop out of the nightly sync.
|
||||
*/
|
||||
@AutoConfigureMockMvc
|
||||
class LibraryControllerSteamIdGoneTest extends AbstractIntegrationTest {
|
||||
|
||||
private static final long USER_ID = 77L;
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
@Autowired
|
||||
private JdbcTemplate jdbc;
|
||||
@Autowired
|
||||
private UserSteamSyncRepository userSteamSyncRepository;
|
||||
|
||||
@BeforeEach
|
||||
void seedSyncedUser() {
|
||||
jdbc.update("DELETE FROM user_steam_sync WHERE user_id = ?", USER_ID);
|
||||
jdbc.update("INSERT INTO user_steam_sync (user_id, steam_id, last_synced_at, last_sync_status, user_seen_at) "
|
||||
+ "VALUES (?, '76561198000000001', now(), 'OK', now())", USER_ID);
|
||||
}
|
||||
|
||||
private static Cookie loginWithoutSteamId() {
|
||||
return new Cookie("tbp_access", Jwts.builder()
|
||||
.subject("unlinked@example.com")
|
||||
.claim("userId", String.valueOf(USER_ID))
|
||||
.expiration(new Date(System.currentTimeMillis() + 60_000))
|
||||
.signWith(TEST_KEYS.getPrivate(), Jwts.SIG.EdDSA)
|
||||
.compact());
|
||||
}
|
||||
|
||||
@Test
|
||||
void gamesCount_WithoutSteamIdClaim_StopsTheNightlySyncOfTheOldId() throws Exception {
|
||||
mockMvc.perform(get("/api/library/games-count").cookie(loginWithoutSteamId())).andExpect(status().isOk());
|
||||
|
||||
assertTrue(userSteamSyncRepository.findByUserSeenAtAfter(Instant.now().minus(Duration.ofDays(14))).stream()
|
||||
.noneMatch(sync -> sync.getUserId() == USER_ID));
|
||||
// The old ID stays: a later link with another ID must still soft-delete the old games.
|
||||
assertTrue(userSteamSyncRepository.findById(USER_ID).orElseThrow().getSteamId().equals("76561198000000001"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user