Remove calendar v2 API, replaced with v3 and rip out related code that is now unused

This commit is contained in:
NovaFox161
2024-11-29 20:51:50 -06:00
parent 81ff1c6447
commit 24df723195
19 changed files with 99 additions and 360 deletions
@@ -7,8 +7,8 @@ import discord4j.core.`object`.entity.Message
import kotlinx.coroutines.reactor.awaitSingle
import org.dreamexposure.discal.client.commands.SlashCommand
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.business.PermissionService
import org.dreamexposure.discal.core.business.StaticMessageService
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
import org.dreamexposure.discal.core.`object`.new.GuildSettings
import org.dreamexposure.discal.core.utils.getCommonMsg
import org.springframework.stereotype.Component
@@ -17,6 +17,7 @@ import org.springframework.stereotype.Component
class DisplayCalendarCommand(
private val staticMessageService: StaticMessageService,
private val calendarService: CalendarService,
private val permissionService: PermissionService,
) : SlashCommand {
override val name = "displaycal"
override val hasSubcommands = true
@@ -42,7 +43,7 @@ class DisplayCalendarCommand(
.orElse(1)
// Validate control role
val hasElevatedPerms = event.interaction.member.get().hasElevatedPermissions().awaitSingle()
val hasElevatedPerms = permissionService.hasElevatedPermissions(settings.guildId, event.interaction.user.id)
if (!hasElevatedPerms)
return event.createFollowup(getCommonMsg("error.perms.elevated", settings.locale))
.withEphemeral(ephemeral)
@@ -10,7 +10,6 @@ import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.business.EmbedService
import org.dreamexposure.discal.core.business.PermissionService
import org.dreamexposure.discal.core.business.RsvpService
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
import org.dreamexposure.discal.core.`object`.new.GuildSettings
import org.dreamexposure.discal.core.utils.getCommonMsg
import org.springframework.stereotype.Component
@@ -361,7 +360,7 @@ class RsvpCommand(
// Validate control role first to reduce work
val hasElevatedPerms = event.interaction.member.get().hasElevatedPermissions().awaitSingle()
val hasElevatedPerms = permissionService.hasElevatedPermissions(settings.guildId, event.interaction.user.id)
if (!hasElevatedPerms)
return event.createFollowup(getCommonMsg("error.perms.elevated", settings.locale))
.withEphemeral(ephemeral)
@@ -8,8 +8,8 @@ import kotlinx.coroutines.reactor.awaitSingle
import org.dreamexposure.discal.client.commands.SlashCommand
import org.dreamexposure.discal.core.business.EmbedService
import org.dreamexposure.discal.core.business.GuildSettingsService
import org.dreamexposure.discal.core.business.PermissionService
import org.dreamexposure.discal.core.enums.time.TimeFormat
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
import org.dreamexposure.discal.core.`object`.new.GuildSettings
import org.dreamexposure.discal.core.utils.getCommonMsg
import org.springframework.stereotype.Component
@@ -19,6 +19,7 @@ import java.util.*
class SettingsCommand(
private val settingsService: GuildSettingsService,
private val embedService: EmbedService,
private val permissionService: PermissionService,
) : SlashCommand {
override val name = "settings"
override val hasSubcommands = true
@@ -26,7 +27,7 @@ class SettingsCommand(
override suspend fun suspendHandle(event: ChatInputInteractionEvent, settings: GuildSettings): Message {
// Validate permissions
val hasElevatedPerms = event.interaction.member.get().hasElevatedPermissions().awaitSingle()
val hasElevatedPerms = permissionService.hasElevatedPermissions(settings.guildId, event.interaction.user.id)
if (!hasElevatedPerms) return event.createFollowup(getCommonMsg("error.perms.elevated", settings.locale))
.withEphemeral(ephemeral)
.awaitSingle()
@@ -5,8 +5,8 @@ import discord4j.core.`object`.entity.Message
import kotlinx.coroutines.reactor.awaitSingle
import org.dreamexposure.discal.client.commands.SlashCommand
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.business.PermissionService
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
import org.dreamexposure.discal.core.`object`.new.GuildSettings
import org.dreamexposure.discal.core.utils.getCommonMsg
import org.springframework.stereotype.Component
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Component
@Component
class AddCalCommand(
private val calendarService: CalendarService,
private val permissionService: PermissionService,
) : SlashCommand {
override val name = "addcal"
override val hasSubcommands = false
@@ -26,7 +27,7 @@ class AddCalCommand(
.awaitSingle()
// Validate permissions
val hasElevatedPerms = event.interaction.member.get().hasElevatedPermissions().awaitSingle()
val hasElevatedPerms = permissionService.hasElevatedPermissions(settings.guildId, event.interaction.user.id)
if (!hasElevatedPerms)
return event.createFollowup(getCommonMsg("error.perms.elevated", settings.locale))
.withEphemeral(ephemeral)
@@ -141,6 +141,11 @@ class CalendarService(
return calendar
}
suspend fun getAllCalendars(guildId: Snowflake): List<Calendar> {
return getAllCalendarMetadata(guildId).map { getCalendar(guildId, it.number)!! }
}
suspend fun createCalendar(guildId: Snowflake, spec: Calendar.CreateSpec): Calendar {
val calendar = calendarProviders
.first { it.host == spec.host }
@@ -1,16 +0,0 @@
package org.dreamexposure.discal.core.entities.spec.create
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
import java.time.ZoneId
data class CreateCalendarSpec(
val host: CalendarHost,
val calNumber: Int,
val name: String,
val description: String? = null,
val timezone: ZoneId,
)
@@ -1,9 +0,0 @@
package org.dreamexposure.discal.core.extensions.discord4j
import discord4j.core.`object`.entity.Member
import discord4j.rest.entity.RestMember
@Deprecated("Prefer to use PermissionService impl")
fun Member.hasElevatedPermissions() = getRestMember().hasElevatedPermissions()
fun Member.getRestMember(): RestMember = client.rest().restMember(guildId, memberData)
@@ -5,7 +5,6 @@ import discord4j.rest.entity.RestGuild
import org.dreamexposure.discal.core.cache.DiscalCache
import org.dreamexposure.discal.core.database.DatabaseManager
import org.dreamexposure.discal.core.entities.Calendar
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
//Calendars
@@ -37,21 +36,3 @@ fun RestGuild.getCalendar(calNumber: Int): Mono<Calendar> {
.flatMap(Calendar.Companion::from)
.doOnNext(DiscalCache::putCalendar)
}
/**
* Attempts to retrieve all [calendars][Calendar] belonging to this [Guild].
* If an error occurs, it is emitted through the [Flux]
*
* @return A [Flux] containing all the [calendars][Calendar] belonging to this [Guild].
*/
@Deprecated("Prefer to use new CalendarService")
fun RestGuild.getAllCalendars(): Flux<Calendar> {
//check cache first
val cals = DiscalCache.getAllCalendars(id)
if (cals != null) return Flux.fromIterable(cals)
return DatabaseManager.getAllCalendars(this.id)
.flatMapMany { Flux.fromIterable(it) }
.flatMap(Calendar.Companion::from)
.doOnNext(DiscalCache::putCalendar)
}
@@ -1,34 +0,0 @@
package org.dreamexposure.discal.core.extensions.discord4j
import discord4j.discordjson.json.RoleData
import discord4j.rest.entity.RestMember
import discord4j.rest.util.Permission
import discord4j.rest.util.PermissionSet
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.function.Predicate
@Deprecated("Prefer to use PermissionService implementation")
fun RestMember.hasPermissions(pred: Predicate<PermissionSet>): Mono<Boolean> {
return this.guild().data.flatMap { guildData ->
if (guildData.ownerId().asLong() == this.id.asLong()) {
Mono.just(true)
} else {
this.data.flatMap { memberData ->
Flux.fromIterable(guildData.roles())
.filter { memberData.roles().contains(it.id()) }
.map(RoleData::permissions)
.reduce(0L) { perm: Long, accumulator: Long -> accumulator or perm }
.map(PermissionSet::of)
.map(pred::test)
}
}
}
}
@Deprecated("Prefer to use PermissionService implementation")
fun RestMember.hasElevatedPermissions(): Mono<Boolean> {
return hasPermissions() {
it.contains(Permission.MANAGE_GUILD) || it.contains(Permission.ADMINISTRATOR)
}
}
@@ -1,11 +0,0 @@
package org.dreamexposure.discal.core.`object`.calendar
import com.google.api.services.calendar.model.Calendar
import discord4j.core.`object`.entity.Message
data class CalendarCreatorResponse(
val successful: Boolean,
val edited: Boolean,
val creatorMessage: Message?,
val calendar: Calendar?
)
@@ -10,6 +10,7 @@ import org.dreamexposure.discal.core.serializers.SnowflakeAsStringSerializer
import java.time.Instant
@Serializable
@Deprecated("Use new CalendarMetadata implementation instead")
data class CalendarData(
@Serializable(with = SnowflakeAsStringSerializer::class)
@SerialName("guild_id")
@@ -0,0 +1,34 @@
package org.dreamexposure.discal.core.`object`.new.model.discal.cam
import discord4j.common.util.Snowflake
import org.dreamexposure.discal.core.`object`.new.Calendar
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata
import java.time.ZoneId
data class CalendarV3Model(
val number: Int,
val guildId: Snowflake,
val name: String,
val description: String,
val timezone: ZoneId,
val link: String,
val host: CalendarMetadata.Host,
val hostLink: String,
val external: Boolean,
) {
constructor(calendar: Calendar): this(
number = calendar.metadata.number,
guildId = calendar.metadata.guildId,
name = calendar.name,
description = calendar.description,
timezone = calendar.timezone,
link = calendar.link,
host = calendar.metadata.host,
hostLink = calendar.hostLink,
external = calendar.metadata.external,
)
}
@@ -3,6 +3,9 @@ package org.dreamexposure.discal.core.`object`.new.security
enum class Scope {
CALENDAR_TOKEN_READ,
CALENDAR_READ,
CALENDAR_WRITE,
GUILD_SETTINGS_READ,
GUILD_SETTINGS_WRITE,
@@ -1,62 +0,0 @@
package org.dreamexposure.discal.server.endpoints.v2.calendar
import discord4j.common.util.Snowflake
import discord4j.core.DiscordClient
import kotlinx.serialization.encodeToString
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.entities.Calendar
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.utils.GlobalVal
import org.dreamexposure.discal.server.utils.Authentication
import org.dreamexposure.discal.server.utils.responseMessage
import org.json.JSONException
import org.json.JSONObject
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/v2/calendar")
class DeleteCalendarEndpoint(val client: DiscordClient) {
@PostMapping("/delete", produces = ["application/json"])
@SecurityRequirement(disableSecurity = true, scopes = [])
fun deleteCalendar(swe: ServerWebExchange, response: ServerHttpResponse, @RequestBody rBody: String): Mono<String> {
return Authentication.authenticate(swe).flatMap { authState ->
if (!authState.success) {
response.rawStatusCode = authState.status
return@flatMap Mono.just(GlobalVal.JSON_FORMAT.encodeToString(authState))
} else if (authState.readOnly) {
response.rawStatusCode = GlobalVal.STATUS_AUTHORIZATION_DENIED
return@flatMap responseMessage("Read-Only key not allowed")
}
//Handle request
val body = JSONObject(rBody)
val guildId = Snowflake.of(body.getString("guild_id"))
val calendarNumber = body.getInt("calendar_number")
return@flatMap client.getGuildById(guildId).getCalendar(calendarNumber)
.flatMap(Calendar::delete)
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_SUCCESS }
.then(responseMessage("Success"))
.switchIfEmpty(responseMessage("Calendar not found")
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_NOT_FOUND }
)
}.onErrorResume(JSONException::class.java) {
LOGGER.trace("[API-v2] JSON error. Bad request?", it)
response.rawStatusCode = GlobalVal.STATUS_BAD_REQUEST
return@onErrorResume responseMessage("Bad Request")
}.onErrorResume {
LOGGER.error(GlobalVal.DEFAULT, "[API-v2] delete calendar error", it)
response.rawStatusCode = GlobalVal.STATUS_INTERNAL_ERROR
return@onErrorResume responseMessage("Internal Server Error")
}
}
}
@@ -1,58 +0,0 @@
package org.dreamexposure.discal.server.endpoints.v2.calendar
import discord4j.common.util.Snowflake
import discord4j.core.DiscordClient
import kotlinx.serialization.encodeToString
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.entities.Calendar
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.utils.GlobalVal
import org.dreamexposure.discal.server.utils.Authentication
import org.dreamexposure.discal.server.utils.responseMessage
import org.json.JSONException
import org.json.JSONObject
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/v2/calendar")
class GetCalendarEndpoint(val client: DiscordClient) {
@PostMapping("/get", produces = ["application/json"])
@SecurityRequirement(disableSecurity = true, scopes = [])
fun getCalendar(swe: ServerWebExchange, response: ServerHttpResponse, @RequestBody rBody: String): Mono<String> {
return Authentication.authenticate(swe).flatMap { authState ->
if (!authState.success) {
response.rawStatusCode = authState.status
return@flatMap Mono.just(GlobalVal.JSON_FORMAT.encodeToString(authState))
}
//Handle request
val body = JSONObject(rBody)
val guildId = Snowflake.of(body.getString("guild_id"))
val calNumber = body.getInt("calendar_number")
return@flatMap client.getGuildById(guildId).getCalendar(calNumber)
.map(Calendar::toJson)
.map(JSONObject::toString)
.switchIfEmpty(responseMessage("Calendar not found")
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_NOT_FOUND }
)
}.onErrorResume(JSONException::class.java) {
LOGGER.trace("[API-v2] JSON error. Bad request?", it)
response.rawStatusCode = GlobalVal.STATUS_BAD_REQUEST
return@onErrorResume responseMessage("Bad Request")
}.onErrorResume {
LOGGER.error(GlobalVal.DEFAULT, "[API-v2] get calendar error", it)
response.rawStatusCode = GlobalVal.STATUS_INTERNAL_ERROR
return@onErrorResume responseMessage("Internal Server Error")
}
}
}
@@ -1,58 +0,0 @@
package org.dreamexposure.discal.server.endpoints.v2.calendar
import discord4j.common.util.Snowflake
import discord4j.core.DiscordClient
import kotlinx.serialization.encodeToString
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.entities.Calendar
import org.dreamexposure.discal.core.extensions.discord4j.getAllCalendars
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.utils.GlobalVal
import org.dreamexposure.discal.server.utils.Authentication
import org.dreamexposure.discal.server.utils.responseMessage
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
@RestController
@RequestMapping("/v2/calendar")
class ListCalendarEndpoint(val client: DiscordClient) {
@PostMapping("/list", produces = ["application/json"])
@SecurityRequirement(disableSecurity = true, scopes = [])
fun listCalendars(swe: ServerWebExchange, response: ServerHttpResponse, @RequestBody rBody: String): Mono<String> {
return Authentication.authenticate(swe).flatMap { authState ->
if (!authState.success) {
response.rawStatusCode = authState.status
return@flatMap Mono.just(GlobalVal.JSON_FORMAT.encodeToString(authState))
}
//Handle request
val body = JSONObject(rBody)
val guildId = Snowflake.of(body.getString("guild_id"))
return@flatMap client.getGuildById(guildId).getAllCalendars()
.map(Calendar::toJson)
.collectList()
.map { JSONArray(it) }
.map { JSONObject().put("calendars", it).toString() }
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_SUCCESS }
}.onErrorResume(JSONException::class.java) {
LOGGER.trace("[API-v2] JSON error. Bad request?", it)
response.rawStatusCode = GlobalVal.STATUS_BAD_REQUEST
return@onErrorResume responseMessage("Bad Request")
}.onErrorResume {
LOGGER.error(GlobalVal.DEFAULT, "[API-v2] list calendars error", it)
response.rawStatusCode = GlobalVal.STATUS_INTERNAL_ERROR
return@onErrorResume responseMessage("Internal Server Error")
}
}
}
@@ -1,83 +0,0 @@
package org.dreamexposure.discal.server.endpoints.v2.calendar
import discord4j.common.util.Snowflake
import discord4j.core.DiscordClient
import kotlinx.serialization.encodeToString
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.entities.spec.update.UpdateCalendarSpec
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
import org.dreamexposure.discal.core.extensions.isValidTimezone
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.utils.GlobalVal
import org.dreamexposure.discal.server.utils.Authentication
import org.dreamexposure.discal.server.utils.responseMessage
import org.json.JSONException
import org.json.JSONObject
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import java.time.ZoneId
@RestController
@RequestMapping("/v2/calendar")
class UpdateCalendarEndpoint(val client: DiscordClient) {
@PostMapping("/update", produces = ["application/json"])
@SecurityRequirement(disableSecurity = true, scopes = [])
fun updateCalendar(swe: ServerWebExchange, response: ServerHttpResponse, @RequestBody rBody: String): Mono<String> {
return Authentication.authenticate(swe).flatMap { authState ->
if (!authState.success) {
response.rawStatusCode = authState.status
return@flatMap Mono.just(GlobalVal.JSON_FORMAT.encodeToString(authState))
} else if (authState.readOnly) {
response.rawStatusCode = GlobalVal.STATUS_AUTHORIZATION_DENIED
return@flatMap responseMessage("Read-Only key not allowed")
}
//Handle request
val body = JSONObject(rBody)
val guildId = Snowflake.of(body.getString("guild_id"))
val calendarNumber = body.getInt("calendar_number")
client.getGuildById(guildId).getCalendar(calendarNumber).flatMap { calendar ->
var spec = UpdateCalendarSpec()
if (body.has("name")) {
spec = spec.copy(name = body.getString("name"))
}
if (body.has("description")) {
spec = spec.copy(description = body.getString("description"))
}
if (body.has("timezone")) {
val tzRaw = body.getString("timezone")
if (tzRaw.isValidTimezone())
spec = spec.copy(timezone = ZoneId.of(tzRaw))
}
calendar.update(spec)
.filter { it.success }
.map { JSONObject().put("calendar", it.new?.toJson()).put("message", "Success") }
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_SUCCESS }
.map(JSONObject::toString)
.switchIfEmpty(responseMessage("Update failed")
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_INTERNAL_ERROR }
)
}.switchIfEmpty(responseMessage("Calendar not found")
.doOnNext { response.rawStatusCode = GlobalVal.STATUS_NOT_FOUND }
)
}.onErrorResume(JSONException::class.java) {
LOGGER.trace("[API-v2] JSON error. Bad request?", it)
response.rawStatusCode = GlobalVal.STATUS_BAD_REQUEST
return@onErrorResume responseMessage("Bad Request")
}.onErrorResume {
LOGGER.error(GlobalVal.DEFAULT, "[API-v2] update calendar error", it)
response.rawStatusCode = GlobalVal.STATUS_INTERNAL_ERROR
return@onErrorResume responseMessage("Internal Server Error")
}
}
}
@@ -9,7 +9,6 @@ import org.dreamexposure.discal.core.business.GuildSettingsService
import org.dreamexposure.discal.core.business.PermissionService
import org.dreamexposure.discal.core.enums.announcement.AnnouncementStyle
import org.dreamexposure.discal.core.exceptions.BotNotInGuildException
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.`object`.GuildSettings
import org.dreamexposure.discal.core.`object`.web.WebGuild
@@ -71,7 +70,7 @@ class GetWebGuildEndpoint(
settingsMono.flatMap { WebGuild.fromGuild(g, it) }.flatMap { wg ->
val member = g.member(userId)
val elevatedMono = member.hasElevatedPermissions()
val elevatedMono = mono { permissionService.hasElevatedPermissions(guildId, userId) }
val discalRoleMono = mono { permissionService.hasControlRole(guildId, userId) }
Mono.zip(elevatedMono, discalRoleMono)
@@ -0,0 +1,45 @@
package org.dreamexposure.discal.server.endpoints.v3
import discord4j.common.util.Snowflake
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.`object`.new.Calendar
import org.dreamexposure.discal.core.`object`.new.model.discal.cam.CalendarV3Model
import org.dreamexposure.discal.core.`object`.new.security.Scope
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/v3/guilds/{guildId}/calendars")
class CalendarController(
private val calendarService: CalendarService,
) {
// TODO: Need a way to check if authenticated user has access to the guild...
// TODO: Create calendar endpoint???
@SecurityRequirement(scopes = [Scope.CALENDAR_READ])
@GetMapping(produces = ["application/json"])
suspend fun getAllCalendars(@PathVariable("guildId") guildId: Snowflake): List<CalendarV3Model> {
return calendarService.getAllCalendars(guildId).map(::CalendarV3Model)
}
@SecurityRequirement(scopes = [Scope.CALENDAR_READ])
@GetMapping("/{calendarNumber}")
suspend fun getCalendar(@PathVariable guildId: Snowflake, @PathVariable calendarNumber: Int): CalendarV3Model? {
val calendar = calendarService.getCalendar(guildId, calendarNumber) ?: return null
return CalendarV3Model(calendar)
}
@SecurityRequirement(scopes = [Scope.CALENDAR_WRITE])
@PatchMapping("/{calendarNumber}", produces = ["application/json"], consumes = ["application/json"])
suspend fun updateCalendar(@PathVariable guildId: Snowflake, @PathVariable calendarNumber: Int, @RequestBody spec: Calendar.UpdateSpec): CalendarV3Model {
val calendar = calendarService.updateCalendar(guildId, calendarNumber, spec)
return CalendarV3Model(calendar)
}
@SecurityRequirement(scopes = [Scope.CALENDAR_WRITE])
@DeleteMapping("/{calendarNumber}")
suspend fun deleteCalendar(@PathVariable guildId: Snowflake, @PathVariable calendarNumber: Int) {
calendarService.deleteCalendar(guildId, calendarNumber)
}
}