change: Add timetable id to BahnTimetable

This commit is contained in:
2025-08-25 15:45:45 +02:00
parent 076411258d
commit b11b180611
11 changed files with 42 additions and 18 deletions
@@ -4,6 +4,7 @@ import de.twomartens.timetable.bahnApi.model.db.BahnStationStop
import de.twomartens.timetable.bahnApi.model.db.BahnStopEvent import de.twomartens.timetable.bahnApi.model.db.BahnStopEvent
import de.twomartens.timetable.bahnApi.model.db.BahnTimetable import de.twomartens.timetable.bahnApi.model.db.BahnTimetable
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import de.twomartens.timetable.types.NonEmptyString import de.twomartens.timetable.types.NonEmptyString
@@ -21,12 +22,13 @@ interface BahnTimetableMapper {
@Mapping(target = "created", ignore = true) @Mapping(target = "created", ignore = true)
@Mapping(target = "lastModified", ignore = true) @Mapping(target = "lastModified", ignore = true)
fun mapToDB(dto: de.twomartens.timetable.bahnApi.model.dto.BahnTimetable, fun mapToDB(dto: de.twomartens.timetable.bahnApi.model.dto.BahnTimetable,
userId: UserId, routeId: RouteId, userId: UserId, routeId: RouteId, tswTimetableId: TimetableId,
hourAtDay: HourAtDay, hourAtDay: HourAtDay,
clock: Clock): BahnTimetable { clock: Clock): BahnTimetable {
return BahnTimetable( return BahnTimetable(
userId, userId,
routeId, routeId,
tswTimetableId,
dto.eva, dto.eva,
ZonedDateTime.of(hourAtDay.dateTime, clock.zone), ZonedDateTime.of(hourAtDay.dateTime, clock.zone),
NonEmptyString(dto.station), NonEmptyString(dto.station),
@@ -2,6 +2,7 @@ package de.twomartens.timetable.bahnApi.model
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@@ -20,11 +21,12 @@ class TaskFactory {
countTasks(existingTasks) countTasks(existingTasks)
} }
fun createTaskAndUpdateCounter(userId: UserId, routeId: RouteId, eva: Eva, fun createTaskAndUpdateCounter(userId: UserId, routeId: RouteId,
tswTimetableId: TimetableId, eva: Eva,
hourAtDay: HourAtDay): ScheduledFetchTask { hourAtDay: HourAtDay): ScheduledFetchTask {
val fetchDateTime = calculateNextAvailableFetchDateTime(hourAtDay) val fetchDateTime = calculateNextAvailableFetchDateTime(hourAtDay)
incrementTaskCountForHourAtDay(hourAtDay) incrementTaskCountForHourAtDay(hourAtDay)
return ScheduledFetchTask(userId, routeId, eva, hourAtDay, fetchDateTime) return ScheduledFetchTask(userId, routeId, tswTimetableId, eva, hourAtDay, fetchDateTime)
} }
private fun calculateNextAvailableFetchDateTime(hourAtDay: HourAtDay): LocalDateTime { private fun calculateNextAvailableFetchDateTime(hourAtDay: HourAtDay): LocalDateTime {
@@ -2,6 +2,7 @@ package de.twomartens.timetable.bahnApi.model.db
import de.twomartens.timetable.bahnApi.model.Eva import de.twomartens.timetable.bahnApi.model.Eva
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.NonEmptyString import de.twomartens.timetable.types.NonEmptyString
import org.bson.types.ObjectId import org.bson.types.ObjectId
@@ -14,10 +15,12 @@ import java.time.Instant
import java.time.ZonedDateTime import java.time.ZonedDateTime
@Document @Document
@CompoundIndex(def = "{'userId': 1, 'routeId': 1, 'eva': 1, 'hourAtDay': 1}", unique = true) @CompoundIndex(def = "{'userId': 1, 'routeId': 1, 'tswTimetableId': 1, 'eva': 1, 'hourAtDay': 1}",
unique = true)
data class BahnTimetable( data class BahnTimetable(
var userId: UserId, var userId: UserId,
var routeId: RouteId, var routeId: RouteId,
var tswTimetableId: TimetableId,
var eva: Eva, var eva: Eva,
var hourAtDay: ZonedDateTime, var hourAtDay: ZonedDateTime,
var station: NonEmptyString, var station: NonEmptyString,
@@ -2,6 +2,7 @@ package de.twomartens.timetable.bahnApi.model.db
import de.twomartens.timetable.bahnApi.model.Eva import de.twomartens.timetable.bahnApi.model.Eva
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import org.bson.types.ObjectId import org.bson.types.ObjectId
@@ -14,10 +15,11 @@ import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
@Document @Document
@CompoundIndex(def = "{'userId': 1, 'routeId': 1, 'eva': 1, 'fetchedDateTime': 1}") @CompoundIndex(def = "{'userId': 1, 'routeId': 1, 'tswTimetableId': 1, 'eva': 1, 'fetchedDateTime': 1}")
data class ScheduledFetchTask( data class ScheduledFetchTask(
var userId: UserId, var userId: UserId,
var routeId: RouteId, var routeId: RouteId,
var tswTimetableId: TimetableId,
var eva: Eva, var eva: Eva,
var fetchedDateTime: HourAtDay, var fetchedDateTime: HourAtDay,
var scheduledExecutionDateTime: LocalDateTime var scheduledExecutionDateTime: LocalDateTime
@@ -2,19 +2,19 @@ package de.twomartens.timetable.bahnApi.repository
import de.twomartens.timetable.bahnApi.model.Eva import de.twomartens.timetable.bahnApi.model.Eva
import de.twomartens.timetable.bahnApi.model.db.BahnTimetable import de.twomartens.timetable.bahnApi.model.db.BahnTimetable
import de.twomartens.timetable.types.HourAtDay
import org.bson.types.ObjectId import org.bson.types.ObjectId
import org.springframework.data.domain.ExampleMatcher import org.springframework.data.domain.ExampleMatcher
import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.data.mongodb.repository.MongoRepository
import java.time.ZonedDateTime
// TODO: figure out what ID type should be
interface BahnTimetableRepository : MongoRepository<BahnTimetable, ObjectId> { interface BahnTimetableRepository : MongoRepository<BahnTimetable, ObjectId> {
fun findByEvaAndHourAtDay(eva: Eva, hourAtDay: HourAtDay): BahnTimetable? fun findByEvaAndHourAtDay(eva: Eva, hourAtDay: ZonedDateTime): BahnTimetable?
fun getExampleMatcher(): ExampleMatcher { fun getExampleMatcher(): ExampleMatcher {
return ExampleMatcher.matching() return ExampleMatcher.matching()
.withMatcher("userId", ExampleMatcher.GenericPropertyMatchers.exact()) .withMatcher("userId", ExampleMatcher.GenericPropertyMatchers.exact())
.withMatcher("routeId", ExampleMatcher.GenericPropertyMatchers.exact()) .withMatcher("routeId", ExampleMatcher.GenericPropertyMatchers.exact())
.withMatcher("tswTimetableId", ExampleMatcher.GenericPropertyMatchers.exact())
.withMatcher("eva", ExampleMatcher.GenericPropertyMatchers.exact()) .withMatcher("eva", ExampleMatcher.GenericPropertyMatchers.exact())
.withMatcher("hourAtDay", ExampleMatcher.GenericPropertyMatchers.exact()) .withMatcher("hourAtDay", ExampleMatcher.GenericPropertyMatchers.exact())
} }
@@ -7,6 +7,7 @@ import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
import de.twomartens.timetable.bahnApi.repository.BahnStationRepository import de.twomartens.timetable.bahnApi.repository.BahnStationRepository
import de.twomartens.timetable.bahnApi.repository.BahnTimetableRepository import de.twomartens.timetable.bahnApi.repository.BahnTimetableRepository
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.model.db.Station import de.twomartens.timetable.model.db.Station
import de.twomartens.timetable.model.repository.StationRepository import de.twomartens.timetable.model.repository.StationRepository
@@ -93,9 +94,11 @@ open class BahnDatabaseService(
} }
fun storeTimetable(timetable: BahnTimetable, userId: UserId, routeId: RouteId, fun storeTimetable(timetable: BahnTimetable, userId: UserId, routeId: RouteId,
tswTimetableId: TimetableId,
hourAtDay: HourAtDay) { hourAtDay: HourAtDay) {
val matcher = bahnTimetableRepository.getExampleMatcher() val matcher = bahnTimetableRepository.getExampleMatcher()
val dbTimetable = bahnTimetableMapper.mapToDB(timetable, userId, routeId, hourAtDay, clock) val dbTimetable = bahnTimetableMapper.mapToDB(timetable, userId, routeId,
tswTimetableId, hourAtDay, clock)
val existingTimetable = bahnTimetableRepository.findOne(Example.of(dbTimetable, matcher)) val existingTimetable = bahnTimetableRepository.findOne(Example.of(dbTimetable, matcher))
existingTimetable existingTimetable
.map { .map {
@@ -7,6 +7,7 @@ import de.twomartens.timetable.bahnApi.tasks.DeleteScheduledTask
import de.twomartens.timetable.bahnApi.tasks.FetchTimetableTask import de.twomartens.timetable.bahnApi.tasks.FetchTimetableTask
import de.twomartens.timetable.bahnApi.tasks.StoreTimetableTask import de.twomartens.timetable.bahnApi.tasks.StoreTimetableTask
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
@@ -27,8 +28,10 @@ class FetchTaskScheduler(
private val scheduledFetchTaskRepository: ScheduledFetchTaskRepository private val scheduledFetchTaskRepository: ScheduledFetchTaskRepository
) { ) {
fun scheduleStoreTask(timetable: BahnTimetable, userId: UserId, routeId: RouteId, fun scheduleStoreTask(timetable: BahnTimetable, userId: UserId, routeId: RouteId,
tswTimetableId: TimetableId,
hourAtDay: HourAtDay) { hourAtDay: HourAtDay) {
val storeTask = StoreTimetableTask(timetable, userId, routeId, hourAtDay, bahnDatabaseService) val storeTask = StoreTimetableTask(timetable, userId, routeId, tswTimetableId,
hourAtDay, bahnDatabaseService)
threadPoolTaskExecutor.execute(storeTask) threadPoolTaskExecutor.execute(storeTask)
} }
@@ -82,6 +85,7 @@ class FetchTaskScheduler(
zonedExecutionTime: ZonedDateTime zonedExecutionTime: ZonedDateTime
) { ) {
val timetableTask = FetchTimetableTask(scheduledFetchTask.userId, scheduledFetchTask.routeId, val timetableTask = FetchTimetableTask(scheduledFetchTask.userId, scheduledFetchTask.routeId,
scheduledFetchTask.tswTimetableId,
scheduledFetchTask.eva, scheduledFetchTask.fetchedDateTime, scheduledFetchTask.eva, scheduledFetchTask.fetchedDateTime,
bahnApiService, this) bahnApiService, this)
threadPoolTaskScheduler.schedule(timetableTask, zonedExecutionTime.toInstant()) threadPoolTaskScheduler.schedule(timetableTask, zonedExecutionTime.toInstant())
@@ -6,6 +6,7 @@ import de.twomartens.timetable.bahnApi.model.FetchDates
import de.twomartens.timetable.bahnApi.model.TaskFactory import de.twomartens.timetable.bahnApi.model.TaskFactory
import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask import de.twomartens.timetable.bahnApi.model.db.ScheduledFetchTask
import de.twomartens.timetable.bahnApi.repository.ScheduledFetchTaskRepository import de.twomartens.timetable.bahnApi.repository.ScheduledFetchTaskRepository
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.db.TswRoute import de.twomartens.timetable.model.db.TswRoute
import de.twomartens.timetable.types.Hour import de.twomartens.timetable.types.Hour
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
@@ -52,12 +53,13 @@ class ScheduledTaskService(
lastUpdate = updateTime lastUpdate = updateTime
} }
fun triggerTimetableFetch(tswRoute: TswRoute, fetchedDate: LocalDate) { fun triggerTimetableFetch(tswRoute: TswRoute, tswTimetableId: TimetableId,
fetchedDate: LocalDate) {
log.info { log.info {
"Trigger timetable fetch: [route ${tswRoute.name}]" "Trigger timetable fetch: [route ${tswRoute.name}]"
} }
val fetchDates = calculateDatesToFetch(fetchedDate) val fetchDates = calculateDatesToFetch(fetchedDate)
val newTasks = buildScheduledTasks(tswRoute, fetchDates) val newTasks = buildScheduledTasks(tswRoute, tswTimetableId, fetchDates)
storeTasksInDatabaseAndStoreCreationTime(newTasks) storeTasksInDatabaseAndStoreCreationTime(newTasks)
val event = FetchTasksCreatedEvent(this) val event = FetchTasksCreatedEvent(this)
@@ -72,6 +74,7 @@ class ScheduledTaskService(
private fun buildScheduledTasks( private fun buildScheduledTasks(
tswRoute: TswRoute, tswRoute: TswRoute,
tswTimetableId: TimetableId,
fetchDates: FetchDates fetchDates: FetchDates
): List<ScheduledFetchTask> { ): List<ScheduledFetchTask> {
val newTasks = mutableListOf<ScheduledFetchTask>() val newTasks = mutableListOf<ScheduledFetchTask>()
@@ -80,18 +83,18 @@ class ScheduledTaskService(
val eva = Eva.of(stationId) val eva = Eva.of(stationId)
var hourAtDay = HourAtDay.of(Hour.of(23), fetchDates.previousDay) var hourAtDay = HourAtDay.of(Hour.of(23), fetchDates.previousDay)
var newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId, var newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId,
tswRoute.routeId, eva, hourAtDay) tswRoute.routeId, tswTimetableId, eva, hourAtDay)
newTasks.add(newTask) newTasks.add(newTask)
for (hour in 0..23) { for (hour in 0..23) {
hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.fetchDate) hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.fetchDate)
newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId, newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId,
tswRoute.routeId, eva, hourAtDay) tswRoute.routeId, tswTimetableId, eva, hourAtDay)
newTasks.add(newTask) newTasks.add(newTask)
} }
for (hour in 0..3) { for (hour in 0..3) {
hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.nextDate) hourAtDay = HourAtDay.of(Hour.of(hour), fetchDates.nextDate)
newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId, newTask = taskFactory.createTaskAndUpdateCounter(tswRoute.userId,
tswRoute.routeId, eva, hourAtDay) tswRoute.routeId, tswTimetableId, eva, hourAtDay)
newTasks.add(newTask) newTasks.add(newTask)
} }
} }
@@ -4,6 +4,7 @@ import de.twomartens.timetable.bahnApi.model.Eva
import de.twomartens.timetable.bahnApi.service.BahnApiService import de.twomartens.timetable.bahnApi.service.BahnApiService
import de.twomartens.timetable.bahnApi.service.FetchTaskScheduler import de.twomartens.timetable.bahnApi.service.FetchTaskScheduler
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import mu.KotlinLogging import mu.KotlinLogging
@@ -13,6 +14,7 @@ import org.springframework.scheduling.annotation.Async
open class FetchTimetableTask( open class FetchTimetableTask(
private val userId: UserId, private val userId: UserId,
private val routeId: RouteId, private val routeId: RouteId,
private val tswTimetableId: TimetableId,
private val eva: Eva, private val eva: Eva,
private val hourAtDay: HourAtDay, private val hourAtDay: HourAtDay,
private val bahnApiService: BahnApiService, private val bahnApiService: BahnApiService,
@@ -22,7 +24,7 @@ open class FetchTimetableTask(
"Fetch timetable: [eva: $eva], [date: ${hourAtDay.date}], [hour: ${hourAtDay.hour}]" "Fetch timetable: [eva: $eva], [date: ${hourAtDay.date}], [hour: ${hourAtDay.hour}]"
} }
val timetable = bahnApiService.fetchTimetable(eva, hourAtDay) val timetable = bahnApiService.fetchTimetable(eva, hourAtDay)
scheduler.scheduleStoreTask(timetable, userId, routeId, hourAtDay) scheduler.scheduleStoreTask(timetable, userId, routeId, tswTimetableId, hourAtDay)
} }
companion object { companion object {
@@ -3,6 +3,7 @@ package de.twomartens.timetable.bahnApi.tasks
import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable import de.twomartens.timetable.bahnApi.model.dto.BahnTimetable
import de.twomartens.timetable.bahnApi.service.BahnDatabaseService import de.twomartens.timetable.bahnApi.service.BahnDatabaseService
import de.twomartens.timetable.model.common.RouteId import de.twomartens.timetable.model.common.RouteId
import de.twomartens.timetable.model.common.TimetableId
import de.twomartens.timetable.model.common.UserId import de.twomartens.timetable.model.common.UserId
import de.twomartens.timetable.types.HourAtDay import de.twomartens.timetable.types.HourAtDay
import mu.KotlinLogging import mu.KotlinLogging
@@ -13,12 +14,13 @@ open class StoreTimetableTask(
private val timetable: BahnTimetable, private val timetable: BahnTimetable,
private val userId: UserId, private val userId: UserId,
private val routeId: RouteId, private val routeId: RouteId,
private val tswTimetableId: TimetableId,
private val hourAtDay: HourAtDay, private val hourAtDay: HourAtDay,
private val bahnDatabaseService: BahnDatabaseService private val bahnDatabaseService: BahnDatabaseService
) : Runnable { ) : Runnable {
override fun run() { override fun run() {
log.info { "Store timetable: [eva: ${timetable.eva}], [station: ${timetable.station}], [hour: ${hourAtDay}]" } log.info { "Store timetable: [eva: ${timetable.eva}], [station: ${timetable.station}], [hour: ${hourAtDay}]" }
bahnDatabaseService.storeTimetable(timetable, userId, routeId, hourAtDay) bahnDatabaseService.storeTimetable(timetable, userId, routeId, tswTimetableId, hourAtDay)
} }
companion object { companion object {
@@ -181,7 +181,8 @@ class TimetableController(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
"Route name must belong to existing route" "Route name must belong to existing route"
) )
scheduledTaskService.triggerTimetableFetch(route, timetable.fetchDate) scheduledTaskService.triggerTimetableFetch(route, timetable.timetableId,
timetable.fetchDate)
} else { } else {
timetable.name = NonEmptyString(body.name) timetable.name = NonEmptyString(body.name)
} }