Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import io.github.devcavin.backend.security.JwtTokenProvider
import io.github.devcavin.backend.web.dto.auth.AuthResponse
import io.github.devcavin.backend.web.dto.auth.AuthenticatedUser
import io.github.devcavin.backend.web.dto.auth.LoginRequest
import jakarta.transaction.Transactional
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.OffsetDateTime
import java.util.UUID

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.github.devcavin.backend.service

import io.github.devcavin.backend.domain.model.User
import io.github.devcavin.backend.domain.repository.VisitStatusRepository
import io.github.devcavin.backend.domain.repository.VisitorRepository
import io.github.devcavin.backend.web.dto.dashboard.DashboardFeed
import io.github.devcavin.backend.web.dto.dashboard.DashboardSummary
import io.github.devcavin.backend.web.dto.visitor.toResponse
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.OffsetDateTime
import java.time.ZoneOffset

@Service
class DashboardService(
private val visitorRepository: VisitorRepository,
private val visitorStatusRepository: VisitStatusRepository,

@Value("\${gatelog.scheduler.overdue-threshold-hours:2}")
private val overdueThresholdHours: Long,
) {
@Transactional(readOnly = true)
fun getFeed(requestedBy: User): DashboardFeed {
val siteId = requestedBy.site.id!!
val now = OffsetDateTime.now(ZoneOffset.UTC)
val startOfDay = now.toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC)
val endOfDay = startOfDay.plusDays(1)
val overdueThreshold = now.minusHours(overdueThresholdHours)

val checkedInStatus = visitorStatusRepository.findByName("CHECKED_IN")!!
val checkedOutStatus = visitorStatusRepository.findByName("CHECKED_OUT")!!
val overdueStatus = visitorStatusRepository.findByName("OVERDUE")!!

// summary counts bar
val currentlyOnPremises = visitorRepository.countBySiteIdAndVisitStatus(
siteId = siteId,
visitStatus = checkedInStatus
)

val checkedInToday = visitorRepository.findAllCheckedInToday(
siteId = siteId,
startOfDay = startOfDay,
endOfDay = endOfDay,
pageable = PageRequest.of(0, 1)
).totalElements

val checkedOutToday = visitorRepository.findAllBySiteIdAndVisitStatus(
siteId = siteId,
visitStatus = checkedOutStatus,
pageable = PageRequest.of(0, 1)
).totalElements

val overdueCount = visitorRepository.findAllBySiteIdAndVisitStatus(
siteId = siteId,
visitStatus = overdueStatus,
pageable = PageRequest.of(0, 1)
).totalElements

val activeVisitors = visitorRepository.findAllBySiteIdAndVisitStatus(
siteId = siteId,
visitStatus = checkedInStatus,
pageable = PageRequest.of(0, 10,
Sort.by(Sort.Direction.DESC, "checkoutTime"))
).content.map { it.toResponse() }

val overdueVisitors = visitorRepository.findAllOverdue(
siteId = siteId,
threshold = overdueThreshold
).map { it.toResponse() }

val recentlyCheckedOut = visitorRepository.findAllBySiteIdAndVisitStatus(
siteId = siteId,
visitStatus = checkedOutStatus,
pageable = PageRequest.of(0, 10,
Sort.by(Sort.Direction.DESC, "checkoutTime"))
).content.map { it.toResponse() }

return DashboardFeed(
summary = DashboardSummary(
currentlyOnPremises = currentlyOnPremises,
checkedInToday = checkedInToday,
checkedOutToday = checkedOutToday,
overdueCount = overdueCount
),
activeVisitors = activeVisitors,
overdueVisitors = overdueVisitors,
recentlyCheckedOut = recentlyCheckedOut
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.devcavin.backend.service

import io.github.devcavin.backend.common.exception.ResourceNotFoundException
import io.github.devcavin.backend.domain.repository.SiteRepository
import io.github.devcavin.backend.domain.repository.VisitStatusRepository
import io.github.devcavin.backend.domain.repository.VisitorRepository
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.OffsetDateTime
import java.time.ZoneOffset

@Service
class OverdueVisitorJob(
private val visitorRepository: VisitorRepository,
private val visitorStatusRepository: VisitStatusRepository,
private val siteRepository: SiteRepository,

@Value("\${gatelog.scheduler.overdue-threshold-hours:2}")
private val overdueThresholdHours: Long
) {
private val log = LoggerFactory.getLogger(OverdueVisitorJob::class.java)

// runs scheduler every 15 mins
@Scheduled(fixedRateString = "\${gatelog.scheduler.overdue-job-rate-ms:900000}")
@Transactional
fun flagOverdueVisitors() {
val overdueStatus = visitorStatusRepository.findByName("OVERDUE")?: throw ResourceNotFoundException("Visit Status", "OVERDUE")

val threshold = OffsetDateTime.now(ZoneOffset.UTC).minusHours(overdueThresholdHours)

val sites = siteRepository.findAll()

var totalFlagged = 0

sites.forEach { site ->
val flagged = visitorRepository.markOverdue(
siteId = site.id!!,
threshold = threshold,
overdueStatus = overdueStatus
)

if (flagged > 0) {
log.info("Flagged $flagged overdue visitor(s) at site $site")
}

totalFlagged += flagged
}

if (totalFlagged > 0) {
log.info("Overdue job complete - $totalFlagged visitor(s) flagged")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import io.github.devcavin.backend.domain.repository.UserRepository
import io.github.devcavin.backend.web.dto.user.CreateUserRequest
import io.github.devcavin.backend.web.dto.user.UserResponse
import io.github.devcavin.backend.web.dto.user.toResponse
import jakarta.transaction.Transactional
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.UUID

@Service
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.github.devcavin.backend.web.controller

import io.github.devcavin.backend.domain.model.User
import io.github.devcavin.backend.service.DashboardService
import io.github.devcavin.backend.web.dto.dashboard.DashboardFeed
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/dashboard")
class DashboardController(
private val dashboardService: DashboardService
) {
@GetMapping
fun getFeed(
@AuthenticationPrincipal requestedBy: User,
): ResponseEntity<DashboardFeed> {
val feed = dashboardService.getFeed(requestedBy)

return ResponseEntity.status(HttpStatus.OK)
.body(feed)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.github.devcavin.backend.web.dto.dashboard

import io.github.devcavin.backend.web.dto.visitor.VisitorResponse
import java.time.OffsetDateTime

data class DashboardSummary(
val currentlyOnPremises: Long,
val checkedInToday: Long,
val checkedOutToday: Long,
val overdueCount: Long,
val asOf: OffsetDateTime = OffsetDateTime.now()
)

data class DashboardFeed(
val summary: DashboardSummary,
val activeVisitors: List<VisitorResponse>,
val overdueVisitors: List<VisitorResponse>,
val recentlyCheckedOut: List<VisitorResponse>
)
9 changes: 8 additions & 1 deletion backend/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ spring:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.PostgreSQLDialect
jdbc:
batch_size: 50
open-in-view: false

flyway:
enabled: true
Expand All @@ -39,4 +42,8 @@ gatelog:
refresh-token-expiry-days: ${JWT_REFRESH_TOKEN_EXPIRY_DAYS:7}

cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS}
allowed-origins: ${CORS_ALLOWED_ORIGINS}

scheduler:
overdue-threshold-hours: 2
overdue-job-rate-ms: 900000
Loading