mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-04-27 20:59:48 -05:00
Working on adding event support in the new impl
Just need to finish the remaining CRUD methods (create, update, delete)
This commit is contained in:
@@ -3,7 +3,9 @@ package org.dreamexposure.discal.core.business
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.`object`.new.Calendar
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata.*
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata.Host
|
||||
import org.dreamexposure.discal.core.`object`.new.Event
|
||||
import java.time.Instant
|
||||
|
||||
interface CalendarProvider {
|
||||
val host: Host
|
||||
@@ -16,5 +18,14 @@ interface CalendarProvider {
|
||||
|
||||
suspend fun deleteCalendar(guildId: Snowflake, metadata: CalendarMetadata)
|
||||
|
||||
|
||||
|
||||
suspend fun getEvent(guildId: Snowflake, calendar: Calendar, id: String): Event?
|
||||
|
||||
suspend fun getUpcomingEvents(guildId: Snowflake, calendar: Calendar, amount: Int): List<Event>
|
||||
|
||||
suspend fun getOngoingEvents(guildId: Snowflake, calendar: Calendar): List<Event>
|
||||
|
||||
suspend fun getEventsInTimeRange(guildId: Snowflake, calendar: Calendar, start: Instant, end: Instant): List<Event>
|
||||
// TODO: Implement the rest of required CRUD functions
|
||||
}
|
||||
|
||||
@@ -6,24 +6,29 @@ import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import kotlinx.coroutines.reactor.mono
|
||||
import org.dreamexposure.discal.CalendarCache
|
||||
import org.dreamexposure.discal.CalendarMetadataCache
|
||||
import org.dreamexposure.discal.EventCache
|
||||
import org.dreamexposure.discal.core.crypto.AESEncryption
|
||||
import org.dreamexposure.discal.core.database.CalendarMetadataData
|
||||
import org.dreamexposure.discal.core.database.CalendarMetadataRepository
|
||||
import org.dreamexposure.discal.core.exceptions.NotFoundException
|
||||
import org.dreamexposure.discal.core.`object`.new.Calendar
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata
|
||||
import org.dreamexposure.discal.core.`object`.new.Event
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
|
||||
@Component
|
||||
class CalendarService(
|
||||
private val calendarMetadataRepository: CalendarMetadataRepository,
|
||||
private val calendarMetadataCache: CalendarMetadataCache,
|
||||
private val eventCache: EventCache,
|
||||
private val calendarProviders: List<CalendarProvider>,
|
||||
private val calendarCache: CalendarCache,
|
||||
private val settingsService: GuildSettingsService,
|
||||
private val staticMessageService: StaticMessageService,
|
||||
private val rsvpService: RsvpService,
|
||||
private val announcementService: AnnouncementService,
|
||||
private val eventMetadataService: EventMetadataService,
|
||||
) {
|
||||
/////////
|
||||
/// Calendar count
|
||||
@@ -161,20 +166,70 @@ class CalendarService(
|
||||
calendarMetadataCache.evict(key = guildId)
|
||||
|
||||
/*
|
||||
// TODO: Need to call a modern version of DatabaseManager.deleteCalendarAndRelatedData method
|
||||
This is a set of calls to replicate the behavior of the old monolith db call
|
||||
that would go through all tables to handle deleting (as cascade delete constraints have not yet been added,
|
||||
and to update the calendar number references down-stream. This is again for user-convenience, or so I tell myself
|
||||
as a cope for how badly designed this project originally was and I just, can't let go of it,
|
||||
so I keep trying to fix it bit by bit <3
|
||||
*/
|
||||
// TODO: Delete related events
|
||||
eventMetadataService.deleteEventMetadataForCalendarDeletion(guildId, number)
|
||||
rsvpService.deleteRsvpForCalendarDeletion(guildId, number)
|
||||
announcementService.deleteAnnouncementsForCalendarDeletion(guildId, number)
|
||||
staticMessageService.deleteStaticMessagesForCalendarDeletion(guildId, number)
|
||||
|
||||
}
|
||||
|
||||
/////////
|
||||
/// Event
|
||||
/// TODO: Need to figure out if I can fetch event sets from cache one day (eg, ongoing events)
|
||||
/////////
|
||||
suspend fun getEvent(guildId: Snowflake, calendarNumber: Int, id: String): Event? {
|
||||
var event = eventCache.get(guildId, id)
|
||||
if (event != null) return event
|
||||
|
||||
val calendar = getCalendar(guildId, calendarNumber) ?: return null
|
||||
|
||||
event = calendarProviders
|
||||
.first { it.host == calendar.metadata.host }
|
||||
.getEvent(guildId, calendar, id)
|
||||
if (event != null) eventCache.put(guildId, id, event)
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
suspend fun getUpcomingEvents(guildId: Snowflake, calendarNumber: Int, amount: Int): List<Event> {
|
||||
val calendar = getCalendar(guildId, calendarNumber) ?: return emptyList()
|
||||
|
||||
val events = calendarProviders
|
||||
.first { it.host == calendar.metadata.host }
|
||||
.getUpcomingEvents(guildId, calendar, amount)
|
||||
events.forEach { event -> eventCache.put(guildId, event.id, event) }
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
suspend fun getOngoingEvents(guildId: Snowflake, calendarNumber: Int): List<Event> {
|
||||
val calendar = getCalendar(guildId, calendarNumber) ?: return emptyList()
|
||||
|
||||
val events = calendarProviders
|
||||
.first { it.host == calendar.metadata.host }
|
||||
.getOngoingEvents(guildId, calendar)
|
||||
events.forEach { event -> eventCache.put(guildId, event.id, event) }
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
suspend fun getEventsInTimeRange(guildId: Snowflake, calendarNumber: Int, start: Instant, end: Instant): List<Event> {
|
||||
val calendar = getCalendar(guildId, calendarNumber) ?: return emptyList()
|
||||
|
||||
val events = calendarProviders
|
||||
.first { it.host == calendar.metadata.host }
|
||||
.getEventsInTimeRange(guildId, calendar, start, end)
|
||||
events.forEach { event -> eventCache.put(guildId, event.id, event) }
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// TODO: Add remaining CRUD methods (create/update/delete)
|
||||
|
||||
/////////
|
||||
/// Extra functions
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.dreamexposure.discal.core.business
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import kotlinx.coroutines.reactor.awaitSingle
|
||||
import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.dreamexposure.discal.EventCache
|
||||
import org.dreamexposure.discal.core.database.EventMetadataData
|
||||
import org.dreamexposure.discal.core.database.EventMetadataRepository
|
||||
import org.dreamexposure.discal.core.`object`.new.EventMetadata
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class EventMetadataService(
|
||||
private val eventMetadataRepository: EventMetadataRepository,
|
||||
private val eventCache: EventCache,
|
||||
) {
|
||||
|
||||
/////////
|
||||
/// Event metadata - Prefer using full Event implementation in CalendarService
|
||||
/////////
|
||||
suspend fun getEventMetadata(guildId: Snowflake, eventId: String): EventMetadata? {
|
||||
val computedId = eventId.split("_")[0]
|
||||
|
||||
return eventMetadataRepository.findByGuildIdAndEventId(guildId.asLong(), computedId)
|
||||
.map(::EventMetadata)
|
||||
.awaitSingleOrNull()
|
||||
}
|
||||
|
||||
suspend fun getMultipleEventsMetadata(guildId: Snowflake, eventIds: List<String>): List<EventMetadata> {
|
||||
val computedIds = eventIds.map { eventId -> eventId.split("_")[0] }
|
||||
|
||||
return eventMetadataRepository.findAllByGuildIdAndEventIdIn(guildId.asLong(), computedIds)
|
||||
.map(::EventMetadata)
|
||||
.collectList()
|
||||
.awaitSingle()
|
||||
}
|
||||
|
||||
suspend fun createEventMetadata(event: EventMetadata): EventMetadata {
|
||||
val computedId = event.id.split("_")[0]
|
||||
|
||||
return eventMetadataRepository.save(EventMetadataData(
|
||||
guildId = event.guildId.asLong(),
|
||||
eventId = computedId,
|
||||
calendarNumber = event.calendarNumber,
|
||||
eventEnd = event.eventEnd.toEpochMilli(),
|
||||
imageLink = event.imageLink,
|
||||
)).map(::EventMetadata).awaitSingle()
|
||||
}
|
||||
|
||||
suspend fun updateEventMetadata(event: EventMetadata) {
|
||||
val computedId = event.id.split("_")[0]
|
||||
|
||||
eventMetadataRepository.updateByGuildIdAndEventId(
|
||||
guildId = event.guildId.asLong(),
|
||||
eventId = computedId,
|
||||
calendarNumber = event.calendarNumber,
|
||||
eventEnd = event.eventEnd.toEpochMilli(),
|
||||
imageLink = event.imageLink
|
||||
).awaitSingleOrNull()
|
||||
}
|
||||
|
||||
suspend fun deleteEventMetadataForCalendarDeletion(guildId: Snowflake, calendarNumber: Int) {
|
||||
eventMetadataRepository.deleteAllByGuildIdAndCalendarNumber(guildId.asLong(), calendarNumber).awaitSingleOrNull()
|
||||
eventMetadataRepository.decrementCalendarsByGuildIdAndCalendarNumber(guildId.asLong(), calendarNumber).awaitSingleOrNull()
|
||||
eventCache.evictAll(guildId)
|
||||
}
|
||||
}
|
||||
+119
-4
@@ -3,24 +3,34 @@ package org.dreamexposure.discal.core.business.google
|
||||
import com.google.api.services.calendar.model.AclRule
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.business.CalendarProvider
|
||||
import org.dreamexposure.discal.core.business.EventMetadataService
|
||||
import org.dreamexposure.discal.core.config.Config
|
||||
import org.dreamexposure.discal.core.crypto.KeyGenerator
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.exceptions.ApiException
|
||||
import org.dreamexposure.discal.core.extensions.google.asInstant
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.`object`.new.Calendar
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata
|
||||
import org.dreamexposure.discal.core.`object`.new.Event
|
||||
import org.dreamexposure.discal.core.`object`.new.EventMetadata
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.random.Random
|
||||
import com.google.api.services.calendar.model.Event as GoogleEvent
|
||||
|
||||
@Component
|
||||
class GoogleCalendarProviderService(
|
||||
val googleCalendarApiWrapper: GoogleCalendarApiWrapper,
|
||||
val eventMetadataService: EventMetadataService,
|
||||
) : CalendarProvider {
|
||||
override val host = CalendarMetadata.Host.GOOGLE
|
||||
|
||||
private fun randomCredentialId() = Random.nextInt(Config.SECRET_GOOGLE_CREDENTIAL_COUNT.getInt())
|
||||
|
||||
/////////
|
||||
/// Calendar
|
||||
/////////
|
||||
override suspend fun getCalendar(metadata: CalendarMetadata): Calendar? {
|
||||
val response = googleCalendarApiWrapper.getCalendar(metadata)
|
||||
if (response.entity == null) return null
|
||||
@@ -67,7 +77,10 @@ class GoogleCalendarProviderService(
|
||||
AclRule().setScope(AclRule.Scope().setType("default")).setRole("reader"),
|
||||
metadata
|
||||
)
|
||||
if (aclRuleResponse.error != null) throw ApiException(aclRuleResponse.error.error, aclRuleResponse.error.exception)
|
||||
if (aclRuleResponse.error != null) throw ApiException(
|
||||
aclRuleResponse.error.error,
|
||||
aclRuleResponse.error.exception
|
||||
)
|
||||
|
||||
return Calendar(
|
||||
metadata = metadata,
|
||||
@@ -93,7 +106,10 @@ class GoogleCalendarProviderService(
|
||||
AclRule().setScope(AclRule.Scope().setType("default")).setRole("reader"),
|
||||
metadata
|
||||
)
|
||||
if (aclRuleResponse.error != null) throw ApiException(aclRuleResponse.error.error, aclRuleResponse.error.exception)
|
||||
if (aclRuleResponse.error != null) throw ApiException(
|
||||
aclRuleResponse.error.error,
|
||||
aclRuleResponse.error.exception
|
||||
)
|
||||
|
||||
return Calendar(
|
||||
metadata = metadata,
|
||||
@@ -108,4 +124,103 @@ class GoogleCalendarProviderService(
|
||||
val response = googleCalendarApiWrapper.deleteCalendar(metadata)
|
||||
if (response.error != null) throw ApiException(response.error.error, response.error.exception)
|
||||
}
|
||||
|
||||
/////////
|
||||
/// Events
|
||||
/////////
|
||||
override suspend fun getEvent(guildId: Snowflake, calendar: Calendar, id: String): Event? {
|
||||
val response = googleCalendarApiWrapper.getEvent(calendar.metadata, id)
|
||||
if (response.entity == null) return null
|
||||
val baseEvent = response.entity
|
||||
|
||||
val metadata = eventMetadataService.getEventMetadata(guildId, id) ?: EventMetadata(id, guildId, calendar.metadata.number)
|
||||
|
||||
return mapGoogleEventToDisCalEvent(calendar, baseEvent, metadata)
|
||||
}
|
||||
|
||||
override suspend fun getUpcomingEvents(guildId: Snowflake, calendar: Calendar, amount: Int): List<Event> {
|
||||
val response = googleCalendarApiWrapper.getEvents(calendar.metadata, amount, Instant.now())
|
||||
if (response.entity == null) return emptyList()
|
||||
|
||||
return loadEvents(guildId, calendar, response.entity)
|
||||
}
|
||||
|
||||
override suspend fun getOngoingEvents(guildId: Snowflake, calendar: Calendar): List<Event> {
|
||||
val now = Instant.now()
|
||||
val start = now.minus(14, ChronoUnit.DAYS) // 2 weeks ago
|
||||
val end = now.plus(1, ChronoUnit.DAYS) // One day from now
|
||||
|
||||
|
||||
val response = googleCalendarApiWrapper.getEvents(calendar.metadata, start, end)
|
||||
if (response.entity == null) return emptyList()
|
||||
|
||||
// Filter for only the ongoing events
|
||||
val filtered = response.entity
|
||||
.filter { it.start.asInstant(calendar.timezone).isBefore(now) }
|
||||
.filter { it.end.asInstant(calendar.timezone).isAfter(now) }
|
||||
|
||||
return loadEvents(guildId, calendar, filtered)
|
||||
}
|
||||
|
||||
override suspend fun getEventsInTimeRange(guildId: Snowflake, calendar: Calendar, start: Instant, end: Instant): List<Event> {
|
||||
val response = googleCalendarApiWrapper.getEvents(calendar.metadata, start, end)
|
||||
if (response.entity == null) return emptyList()
|
||||
|
||||
return loadEvents(guildId, calendar, response.entity)
|
||||
}
|
||||
|
||||
/////////
|
||||
/// Private util functions
|
||||
/////////
|
||||
private fun randomCredentialId() = Random.nextInt(Config.SECRET_GOOGLE_CREDENTIAL_COUNT.getInt())
|
||||
|
||||
private suspend fun loadEvents(guildId: Snowflake, calendar: Calendar, events: List<GoogleEvent>): List<Event> {
|
||||
val metadataList = eventMetadataService.getMultipleEventsMetadata(guildId, events.map { it.id})
|
||||
|
||||
return events.map { googleEvent ->
|
||||
val computedId = googleEvent.id.split("_")[0]
|
||||
val metadata = metadataList.firstOrNull { it.id == computedId } ?: EventMetadata(googleEvent.id, guildId, calendar.metadata.number)
|
||||
|
||||
mapGoogleEventToDisCalEvent(calendar, googleEvent, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapGoogleEventToDisCalEvent(calendar: Calendar, baseEvent: GoogleEvent, metadata: EventMetadata): Event {
|
||||
return Event(
|
||||
id = baseEvent.id,
|
||||
guildId = calendar.metadata.guildId,
|
||||
calendarNumber = calendar.metadata.number,
|
||||
name = baseEvent.summary.orEmpty(),
|
||||
description = baseEvent.description.orEmpty(),
|
||||
location = baseEvent.location.orEmpty(),
|
||||
link = baseEvent.htmlLink.orEmpty(),
|
||||
color = if (baseEvent.colorId.isNullOrBlank()) {
|
||||
EventColor.fromNameOrHexOrId(baseEvent.colorId)
|
||||
} else EventColor.NONE,
|
||||
start = if (baseEvent.start.dateTime != null) {
|
||||
Instant.ofEpochMilli(baseEvent.start.dateTime.value)
|
||||
} else Instant.ofEpochMilli(baseEvent.start.date.value)
|
||||
.plus(1, ChronoUnit.DAYS)
|
||||
.atZone(calendar.timezone)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
.atZone(calendar.timezone)
|
||||
.toInstant(),
|
||||
end = if (baseEvent.end.dateTime != null) {
|
||||
Instant.ofEpochMilli(baseEvent.end.dateTime.value)
|
||||
} else Instant.ofEpochMilli(baseEvent.end.date.value)
|
||||
.plus(1, ChronoUnit.DAYS)
|
||||
.atZone(calendar.timezone)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
.atZone(calendar.timezone)
|
||||
.toInstant(),
|
||||
recur = !baseEvent.recurrence.isNullOrEmpty(),
|
||||
recurrence = if (baseEvent.recurrence.isNullOrEmpty()) Recurrence() else Recurrence.fromRRule(baseEvent.recurrence[0]),
|
||||
image = metadata.imageLink,
|
||||
timezone = calendar.timezone,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class CacheConfig {
|
||||
private val announcementTll = Config.CACHE_TTL_ANNOUNCEMENT_MINUTES.getLong().asMinutes()
|
||||
private val wizardTtl = Config.TIMING_WIZARD_TIMEOUT_MINUTES.getLong().asMinutes()
|
||||
private val calendarTokenTtl = Config.CACHE_TTL_CALENDAR_TOKEN_MINUTES.getLong().asMinutes()
|
||||
private val eventTtl = Config.CACHE_TTL_EVENTS_MINUTES.getLong().asMinutes()
|
||||
|
||||
|
||||
// Redis caching
|
||||
@@ -79,6 +80,12 @@ class CacheConfig {
|
||||
fun announcementWizardRedisCache(objectMapper: ObjectMapper, redisTemplate: ReactiveStringRedisTemplate): AnnouncementWizardStateCache =
|
||||
RedisStringCacheRepository(objectMapper, redisTemplate, "AnnouncementWizards", wizardTtl)
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnProperty("bot.cache.redis", havingValue = "true")
|
||||
fun eventRedisCache(objectMapper: ObjectMapper, redisTemplate: ReactiveStringRedisTemplate): EventCache =
|
||||
RedisStringCacheRepository(objectMapper, redisTemplate, "Events", eventTtl)
|
||||
|
||||
|
||||
// In-memory fallback caching
|
||||
@Bean
|
||||
@@ -110,4 +117,7 @@ class CacheConfig {
|
||||
|
||||
@Bean
|
||||
fun calendarTokenFallbackCache(): CalendarTokenCache = JdkCacheRepository(calendarTokenTtl)
|
||||
|
||||
@Bean
|
||||
fun eventFallbackCache(): EventCache = JdkCacheRepository(eventTtl)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ enum class Config(private val key: String, private var value: Any? = null) {
|
||||
CACHE_TTL_STATIC_MESSAGE_MINUTES("bot.cache.ttl-minutes.static-messages", 60),
|
||||
CACHE_TTL_ANNOUNCEMENT_MINUTES("bot.cache.ttl-minutes.announcements", 120),
|
||||
CACHE_TTL_CALENDAR_TOKEN_MINUTES("bot.cache.ttl-minutes.calendar", 60),
|
||||
CACHE_TTL_EVENTS_MINUTES("bot.cache.ttl-minutes.event", 15),
|
||||
|
||||
// Security configuration
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.dreamexposure.discal.core.database
|
||||
|
||||
import org.springframework.data.relational.core.mapping.Table
|
||||
|
||||
@Table("events")
|
||||
data class EventMetadataData(
|
||||
val guildId: Long,
|
||||
val eventId: String,
|
||||
val calendarNumber: Int,
|
||||
val eventEnd: Long,
|
||||
val imageLink: String,
|
||||
)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.dreamexposure.discal.core.database
|
||||
|
||||
import org.springframework.data.r2dbc.repository.Query
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
interface EventMetadataRepository : R2dbcRepository<EventMetadataData, Long> {
|
||||
fun findByGuildIdAndEventId(guildId: Long, eventId: String): Mono<EventMetadataData>
|
||||
|
||||
fun findAllByGuildIdAndEventIdIn(guildId: Long, eventIds: Collection<String>): Flux<EventMetadataData>
|
||||
|
||||
@Query("""
|
||||
UPDATE events
|
||||
SET calendar_number = :calendarNumber,
|
||||
event_end = :eventEnd,
|
||||
image_link = :imageLink
|
||||
WHERE guild_id = :guildId AND event_id = :eventId
|
||||
""")
|
||||
fun updateByGuildIdAndEventId(
|
||||
guildId: Long,
|
||||
eventId: String,
|
||||
calendarNumber: Int,
|
||||
eventEnd: Long,
|
||||
imageLink: String,
|
||||
): Mono<Long>
|
||||
|
||||
fun deleteAllByGuildIdAndCalendarNumber(guildId: Long, calendarNumber: Int): Mono<Void>
|
||||
|
||||
@Query("""
|
||||
UPDATE events
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >= :calendarNumber AND guild_id = :guildId
|
||||
""")
|
||||
fun decrementCalendarsByGuildIdAndCalendarNumber(guildId: Long, calendarNumber: Int): Mono<Long>
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.dreamexposure.discal.core.`object`.new
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
data class Event(
|
||||
val id: String,
|
||||
val guildId: Snowflake,
|
||||
val calendarNumber: Int,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val location: String,
|
||||
val link: String,
|
||||
val color: EventColor,
|
||||
val start: Instant,
|
||||
val end: Instant,
|
||||
val recur: Boolean,
|
||||
val recurrence: Recurrence,
|
||||
val image: String,
|
||||
val timezone: ZoneId,
|
||||
) {
|
||||
// Some helpful functions
|
||||
fun isOngoing(): Boolean = start.isBefore(Instant.now()) && end.isAfter(Instant.now())
|
||||
|
||||
fun isOver(): Boolean = end.isBefore(Instant.now())
|
||||
|
||||
fun isStarted() = start.isBefore(Instant.now())
|
||||
|
||||
fun is24Hours() = Duration.between(start, end).toHours() == 24L
|
||||
|
||||
fun isAllDay(): Boolean {
|
||||
val start = this.start.atZone(timezone)
|
||||
|
||||
return start.hour == 0 && is24Hours()
|
||||
}
|
||||
|
||||
fun isMultiDay(): Boolean {
|
||||
if (isAllDay()) return false // All day events should not count as multi-day events
|
||||
|
||||
val start = this.start.atZone(timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
val end = this.end.atZone(timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
|
||||
return when {
|
||||
start.year != end.year -> true
|
||||
start.month != end.month -> true
|
||||
start.dayOfYear != end.dayOfYear -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.dreamexposure.discal.core.`object`.new
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.database.EventMetadataData
|
||||
import org.dreamexposure.discal.core.extensions.asSnowflake
|
||||
import java.time.Instant
|
||||
|
||||
data class EventMetadata(
|
||||
val id: String,
|
||||
val guildId: Snowflake,
|
||||
val calendarNumber: Int,
|
||||
val eventEnd: Instant,
|
||||
val imageLink: String,
|
||||
) {
|
||||
constructor(data: EventMetadataData): this(
|
||||
id = data.eventId,
|
||||
guildId = data.guildId.asSnowflake(),
|
||||
calendarNumber = data.calendarNumber,
|
||||
eventEnd = Instant.ofEpochMilli(data.eventEnd),
|
||||
imageLink = data.imageLink,
|
||||
)
|
||||
|
||||
constructor(id: String, guildId: Snowflake, calendarNumber: Int): this(
|
||||
id = id,
|
||||
guildId = guildId,
|
||||
calendarNumber = calendarNumber,
|
||||
eventEnd = Instant.now(),
|
||||
imageLink = "",
|
||||
)
|
||||
}
|
||||
@@ -16,3 +16,4 @@ typealias StaticMessageCache = CacheRepository<Snowflake, StaticMessage>
|
||||
typealias AnnouncementCache = CacheRepository<Snowflake, Array<Announcement>>
|
||||
typealias AnnouncementWizardStateCache = CacheRepository<Snowflake, AnnouncementWizardState>
|
||||
typealias CalendarTokenCache = CacheRepository<Int, TokenV1Model>
|
||||
typealias EventCache = CacheRepository<String, Event>
|
||||
|
||||
Reference in New Issue
Block a user