Skip to content

Commit 9b27774

Browse files
Fix potential desync issue when opening setup shortly after joining (#894)
1 parent c77c56b commit 9b27774

5 files changed

Lines changed: 41 additions & 3 deletions

File tree

frontend/src/App.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async function loadSession() {
8080
// If token exists, try to load session data
8181
if (storedToken) {
8282
try {
83-
const [userSessionData, tokensData] = await Promise.all([
83+
let [userSessionData, tokensData] = await Promise.all([
8484
api.getSession(),
8585
api.getUserTokens()
8686
]);
@@ -94,12 +94,28 @@ async function loadSession() {
9494
// 3. First guild with admin role
9595
// 4. First guild in list
9696
let guildId: string | null = urlParams.get('guild') || (route.query.guild as string | null);
97+
const requestedGuildId = guildId;
9798
9899
if (!guildId) {
99100
// Fallback to localStorage if no query param
100101
guildId = localStorage.getItem('reputation_bot_guild_id');
101102
}
102103
104+
// If a specific guild was requested but not in the session (e.g. bot just joined),
105+
// refresh the session from Discord to pick up newly added guilds
106+
if (requestedGuildId && !userSessionData.guilds[requestedGuildId] && !isBotOwner) {
107+
try {
108+
const refreshed = await api.refreshSession();
109+
setUserSession(refreshed);
110+
userSessionData = refreshed;
111+
if (userSessionData.guilds[requestedGuildId]) {
112+
guildId = requestedGuildId;
113+
}
114+
} catch (e) {
115+
console.error('Failed to refresh session for new guild:', e);
116+
}
117+
}
118+
103119
if (!guildId || (!userSessionData.guilds[guildId] && !isBotOwner)) {
104120
// Find first admin guild
105121
const adminGuild = Object.entries(userSessionData.guilds)

frontend/src/views/SetupView.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const router = useRouter()
3131
const route = useRoute()
3232
const {session, loadSettings, settingsLoading, settingsError, refreshGuilds} = useSession()
3333
34-
const hasAttemptedRefresh = ref(false)
34+
const SETUP_REFRESH_KEY = 'reputation-bot-setup-refresh-attempted'
35+
const hasAttemptedRefresh = ref(!!sessionStorage.getItem(SETUP_REFRESH_KEY))
3536
const isAutoRefreshing = ref(false)
3637
3738
// Watch session and load settings as soon as session becomes available
@@ -122,6 +123,7 @@ const currentStepData = computed(() => steps.find(s => s.id === currentStep.valu
122123
watch([currentStepData, session], async ([stepData, sess]) => {
123124
if (stepData?.requiresSettings && !sess && !hasAttemptedRefresh.value) {
124125
hasAttemptedRefresh.value = true
126+
sessionStorage.setItem(SETUP_REFRESH_KEY, 'true')
125127
isAutoRefreshing.value = true
126128
try {
127129
const success = await refreshGuilds()
@@ -132,6 +134,10 @@ watch([currentStepData, session], async ([stepData, sess]) => {
132134
isAutoRefreshing.value = false
133135
}
134136
}
137+
// Clear the refresh flag once session is available (refresh succeeded)
138+
if (sess && hasAttemptedRefresh.value) {
139+
sessionStorage.removeItem(SETUP_REFRESH_KEY)
140+
}
135141
}, {immediate: true})
136142
137143
const canProceed = ref(true)

src/main/java/de/chojo/repbot/web/routes/v1/session/SessionRoute.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ private static void getGuildSettings(@NotNull Context ctx) {
125125
})
126126
private void refreshUserSession(@NotNull Context ctx) {
127127
UserSession session = ctx.sessionAttribute(SessionAttribute.USER_SESSION);
128-
sessionService.invalidateUserSession(session.token());
128+
sessionService.invalidateUserSessionFull(session.token(), session.userId());
129129
UserSession refreshed = sessionService
130130
.getUserSession(ctx)
131131
.orElseThrow(() -> new io.javalin.http.UnauthorizedResponse("Session expired"));

src/main/java/de/chojo/repbot/web/services/DiscordOAuthService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ public synchronized List<DiscordGuild> getUserGuilds(String accessToken) throws
129129
* Revokes a Discord OAuth token for the current application.
130130
* See https://discord.com/developers/docs/topics/oauth2#revoking-tokens
131131
*/
132+
public void invalidateGuildsCache(String accessToken) {
133+
userGuildsCache.invalidate(accessToken);
134+
}
135+
132136
public void revokeToken(String token) throws IOException, InterruptedException {
133137
userCache.invalidate(token);
134138
userGuildsCache.invalidate(token);

src/main/java/de/chojo/repbot/web/services/SessionService.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ public void invalidateUserSession(String token) {
153153
userSessions.invalidate(token);
154154
}
155155

156+
/**
157+
* Fully refreshes a user session by invalidating both the session cache
158+
* and the Discord guild list cache, ensuring fresh data from Discord API.
159+
*/
160+
public void invalidateUserSessionFull(String token, long userId) {
161+
userSessions.invalidate(token);
162+
userRepository
163+
.byId(userId)
164+
.token()
165+
.ifPresent(userToken -> discordOAuthService.invalidateGuildsCache(userToken.accessToken()));
166+
}
167+
156168
public void invalidateAllUserSessions() {
157169
userSessions.invalidateAll();
158170
}

0 commit comments

Comments
 (0)