Track chat session lifecycles: added session start/end times, database schema, and persistence logic.

This commit is contained in:
2026-08-01 17:40:48 +02:00
parent b1267068a5
commit 3462d82716
4 changed files with 95 additions and 11 deletions
@@ -1,5 +1,10 @@
package com.alttd.altitudeweb.services.chat.event_publisher;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
import com.alttd.altitudeweb.database.web_db.chat_session.ChatSession;
import com.alttd.altitudeweb.database.web_db.chat_session.ChatSessionMapper;
import com.alttd.altitudeweb.setup.Connection;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
@@ -7,6 +12,8 @@ import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -27,19 +34,10 @@ public class EventPublisher {
emitterMap.computeIfAbsent(eventUser.uuid(), k -> new CopyOnWriteArrayList<>()).add(emitter);
eventUserMap.put(eventUser.uuid(), eventUser);
Instant sessionStart = Instant.now();
log.info("User {} subscribed, active emitters: {}", eventUser.uuid(), countActive());
Runnable cleanup = () -> {
List<SseEmitter> userEmitters = emitterMap.get(eventUser.uuid());
if (userEmitters != null) {
userEmitters.remove(emitter);
if (userEmitters.isEmpty()) {
emitterMap.remove(eventUser.uuid());
eventUserMap.remove(eventUser.uuid());
}
}
log.info("Cleaned up emitter for {}, active emitters: {}", eventUser.uuid(), countActive());
};
Runnable cleanup = () -> handleCleanup(eventUser, emitter, sessionStart);
// covers: clean disconnect (tab closed normally), server-side timeout, write failure
emitter.onCompletion(cleanup);
@@ -100,4 +98,39 @@ public class EventPublisher {
sendToUser(emitter.uuid(), eventName, json);
});
}
private void handleCleanup(EventUser eventUser, SseEmitter emitter, Instant sessionStart) {
List<SseEmitter> userEmitters = emitterMap.get(eventUser.uuid());
if (userEmitters != null) {
userEmitters.remove(emitter);
if (userEmitters.isEmpty()) {
emitterMap.remove(eventUser.uuid());
eventUserMap.remove(eventUser.uuid());
}
}
Instant sessionEnd = Instant.now();
Duration between = Duration.between(sessionStart, sessionEnd);
log.info("Cleaned up emitter for {}, active emitters: {}, session duration: {}",
eventUser.uuid(), countActive(), between
);
ChatSession chatSession = ChatSession.builder()
.uuid(eventUser.uuid())
.session_start(sessionStart)
.session_end(sessionEnd)
.build();
saveSession(chatSession);
}
private void saveSession(ChatSession chatSession) {
Connection.getConnection(Databases.DEFAULT)
.runQuery(sqlSession -> {
log.debug("Saving chat session");
try {
sqlSession.getMapper(ChatSessionMapper.class).storeSession(chatSession);
log.debug("Saved chat session");
} catch (Exception e) {
log.error("Failed to save chat session", e);
}
});
}
}