Skip to content

Commit

Permalink
Merge pull request #161 from DreamExposure/develop
Browse files Browse the repository at this point in the history
RC 4.2.4
  • Loading branch information
NovaFox161 committed Oct 7, 2023
2 parents 0994b5a + fa9f3ad commit bd8cda5
Show file tree
Hide file tree
Showing 30 changed files with 497 additions and 289 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/gradle.yml
Expand Up @@ -77,10 +77,15 @@ jobs:
run: chmod +x ./gradlew

- name: Publish to CR with Gradle
uses: Wandalen/wretry.action@master
env:
SCW_USER: ${{ secrets.SCW_USER }}
SCW_SECRET: ${{ secrets.SCW_SECRET }}
run: ./gradlew jib -Djib.to.auth.username=${SCW_USER} -Djib.to.auth.password=${SCW_SECRET}
with:
command: ./gradlew jib -Djib.to.auth.username=${SCW_USER} -Djib.to.auth.password=${SCW_SECRET}
attempt_limit: 5
# 1 minute in ms
attempt_delay: 60000
deploy-dev:
name: Deploy dev
runs-on: ubuntu-latest
Expand Down
54 changes: 28 additions & 26 deletions build.gradle.kts
Expand Up @@ -9,7 +9,7 @@ plugins {

// Spring
kotlin("plugin.spring")
id("org.springframework.boot") apply false
id("org.springframework.boot")
id("io.spring.dependency-management")

//Tooling
Expand All @@ -27,7 +27,7 @@ buildscript {
allprojects {
//Project props
group = "org.dreamexposure.discal"
version = "4.2.3"
version = "4.2.4"
description = "DisCal"

//Plugins
Expand All @@ -36,30 +36,22 @@ allprojects {
apply(plugin = "io.spring.dependency-management")

// Versions --- found in gradle.properties
val kotlinVersion: String by properties
// Tool
val kotlinxCoroutinesReactorVersion: String by properties
val reactorKotlinExtensions: String by properties
// Discord
val discord4jVersion: String by properties
val discord4jStoresVersion: String by properties
val discordWebhookVersion: String by properties
// Spring
val springVersion: String by properties
// Database
val flywayVersion: String by properties
// Database\
val mikuR2dbcMySqlVersion: String by properties
val mySqlConnectorJava: String by properties
// Serialization
val kotlinxSerializationJsonVersion: String by properties
val jacksonVersion: String by properties
val jsonVersion: String by properties
// Observability
val logbackContribVersion: String by properties
// Google libs
val googleApiClientVersion: String by properties
val googleServicesCalendarVersion: String by properties
val googleOauthClientVersion: String by properties
// Various libs
val okhttpVersion: String by properties
val copyDownVersion: String by properties
val jsoupVersion: String by properties

Expand All @@ -76,32 +68,38 @@ allprojects {

dependencies {
// Tools
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinxCoroutinesReactorVersion")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions:$reactorKotlinExtensions")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")

// Discord
implementation("com.discord4j:discord4j-core:$discord4jVersion")
implementation("com.discord4j:stores-redis:$discord4jStoresVersion")
implementation("club.minnced:discord-webhooks:$discordWebhookVersion")

// Spring
implementation("org.springframework.boot:spring-boot-starter-data-jdbc:$springVersion")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc:$springVersion")
implementation("org.springframework.boot:spring-boot-starter-data-redis:$springVersion")
implementation("org.springframework.boot:spring-boot-starter-webflux:$springVersion")
implementation("org.springframework.boot:spring-boot-starter-cache:$springVersion")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-actuator")

// Database
implementation("dev.miku:r2dbc-mysql:$mikuR2dbcMySqlVersion")
implementation("mysql:mysql-connector-java:$mySqlConnectorJava")

// Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationJsonVersion")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
implementation("org.json:json:$jsonVersion")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("org.json:json")

// Observability
implementation("ch.qos.logback.contrib:logback-json-classic:$logbackContribVersion")
implementation("ch.qos.logback.contrib:logback-jackson:$logbackContribVersion")
implementation("io.micrometer:micrometer-registry-prometheus")

// Google libs
implementation("com.google.api-client:google-api-client:$googleApiClientVersion")
Expand All @@ -111,7 +109,7 @@ allprojects {
}

// Various Libs
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:okhttp")
implementation("io.github.furstenheim:copy_down:$copyDownVersion")
implementation("org.jsoup:jsoup:$jsoupVersion")
}
Expand Down Expand Up @@ -149,5 +147,9 @@ tasks {
distributionType = ALL
gradleVersion = "8.2.1"
}

bootJar {
enabled = false
}
}

Expand Up @@ -10,10 +10,10 @@ class OauthStateService(
) {
suspend fun generateState(): String {
val state = KeyGenerator.csRandomAlphaNumericString(64)
stateCache.put(state, state)
stateCache.put(key = state, value = state)

return state
}

suspend fun validateState(state: String) = stateCache.getAndRemove(state) != null
suspend fun validateState(state: String) = stateCache.getAndRemove(key = state) != null
}
@@ -1,4 +1,4 @@
package org.dreamexposure.discal.cam.endpoints.v1
package org.dreamexposure.discal.cam.controllers.v1

import discord4j.common.util.Snowflake
import org.dreamexposure.discal.cam.managers.CalendarAuthManager
Expand All @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/v1/")
class GetEndpoint(
@RequestMapping("/v1/token")
class TokenController(
private val calendarAuthManager: CalendarAuthManager,
) {
@Authentication(access = Authentication.AccessLevel.ADMIN)
@GetMapping("token", produces = ["application/json"])
suspend fun get(@RequestParam host: CalendarHost, @RequestParam id: Int, @RequestParam guild: Snowflake?): CredentialData? {
@GetMapping(produces = ["application/json"])
suspend fun getToken(@RequestParam host: CalendarHost, @RequestParam id: Int, @RequestParam guild: Snowflake?): CredentialData? {
return calendarAuthManager.getCredentialData(host, id, guild)
}
}
@@ -1,4 +1,4 @@
package org.dreamexposure.discal.cam.endpoints.v1.oauth2
package org.dreamexposure.discal.cam.controllers.v1.oauth2

import org.dreamexposure.discal.cam.json.discal.LoginResponse
import org.dreamexposure.discal.cam.json.discal.TokenRequest
Expand All @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/oauth2/discord/")
class DiscordOauthEndpoint(
class DiscordOauthController(
private val discordOauthManager: DiscordOauthManager,
) {

Expand Down
Expand Up @@ -12,7 +12,6 @@ import org.dreamexposure.discal.cam.json.google.RefreshData
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.business.CredentialService
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.crypto.AESEncryption
import org.dreamexposure.discal.core.exceptions.AccessRevokedException
import org.dreamexposure.discal.core.exceptions.EmptyNotAllowedException
import org.dreamexposure.discal.core.exceptions.NotFoundException
Expand All @@ -25,6 +24,7 @@ import org.dreamexposure.discal.core.utils.GlobalVal.HTTP_CLIENT
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import java.time.Duration
import java.time.Instant

@Component
Expand All @@ -33,40 +33,35 @@ class GoogleAuth(
private val calendarService: CalendarService,
private val objectMapper: ObjectMapper,
) {
private final val aes: AESEncryption = AESEncryption(Config.SECRET_GOOGLE_CREDENTIAL_KEY.getString())

suspend fun requestNewAccessToken(calendar: Calendar): CredentialData? {
val aes = AESEncryption(calendar.secrets.privateKey)
if (!calendar.secrets.expiresAt.isExpiredTtl()) {
return aes.decrypt(calendar.secrets.encryptedAccessToken)
.map { CredentialData(it, calendar.secrets.expiresAt) }
.awaitSingle()
}

val refreshToken = aes.decrypt(calendar.secrets.encryptedRefreshToken).awaitSingle()
val refreshedCredential = doAccessTokenRequest(refreshToken) ?: return null
if (!calendar.secrets.expiresAt.isExpiredTtl()) return CredentialData(calendar.secrets.accessToken, calendar.secrets.expiresAt)

calendar.secrets.expiresAt = refreshedCredential.validUntil.minusSeconds(60) // Add a minute of wiggle room
calendar.secrets.encryptedAccessToken = aes.encrypt(refreshedCredential.accessToken).awaitSingle()
LOGGER.debug("Refreshing access token | guildId:{} | calendar:{}", calendar.guildId, calendar.number)

val refreshedCredential = doAccessTokenRequest(calendar.secrets.refreshToken) ?: return null
calendar.secrets.accessToken = refreshedCredential.accessToken
calendar.secrets.expiresAt = refreshedCredential.validUntil.minus(Duration.ofMinutes(5)) // Add some wiggle room
calendarService.updateCalendar(calendar)

LOGGER.debug("Refreshing access token | guildId:{} | calendar:{}", calendar.guildId, calendar.number)

return refreshedCredential
}

suspend fun requestNewAccessToken(credentialId: Int): CredentialData {
val credential = credentialService.getCredential(credentialId) ?: throw NotFoundException()
if (!credential.expiresAt.isExpiredTtl()) {
val accessToken = aes.decrypt(credential.encryptedAccessToken).awaitSingle()
return CredentialData(accessToken, credential.expiresAt)
}
if (!credential.expiresAt.isExpiredTtl()) return CredentialData(credential.accessToken, credential.expiresAt)

LOGGER.debug("Refreshing access token | credentialId:$credentialId")

val refreshToken = aes.decrypt(credential.encryptedRefreshToken).awaitSingle()
val refreshedCredentialData = doAccessTokenRequest(refreshToken) ?: throw EmptyNotAllowedException()
credential.encryptedAccessToken = aes.encrypt(refreshedCredentialData.accessToken).awaitSingle()
credential.expiresAt = refreshedCredentialData.validUntil.minusSeconds(60) // Add a minute of wiggle room
val refreshedCredentialData = doAccessTokenRequest(credential.refreshToken) ?: throw EmptyNotAllowedException()
credential.accessToken = refreshedCredentialData.accessToken
credential.expiresAt = refreshedCredentialData.validUntil.minus(Duration.ofMinutes(5)) // Add some wiggle room
credentialService.updateCredential(credential)

LOGGER.debug("Refreshed access token | credentialId:{} | validUntil{}", credentialId, credential.expiresAt)

return refreshedCredentialData
}

Expand Down Expand Up @@ -96,10 +91,11 @@ class GoogleAuth(
CredentialData(body.accessToken, Instant.now().plusSeconds(body.expiresIn.toLong()))
}
STATUS_CODE_BAD_REQUEST -> {
val body = objectMapper.readValue<ErrorData>(response.body!!.string())
val bodyRaw = response.body!!.string()
LOGGER.error("[Google] Access Token Request: $bodyRaw")
val body = objectMapper.readValue<ErrorData>(bodyRaw)
response.close()

LOGGER.error("[Google] Access Token Request: $body")

if (body.error == "invalid_grant") {
LOGGER.debug(DEFAULT, "[Google] Access to resource has been revoked")
Expand Down
Expand Up @@ -4,6 +4,7 @@ import discord4j.common.util.Snowflake
import org.dreamexposure.discal.cam.google.GoogleAuth
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.springframework.stereotype.Component

Expand All @@ -13,17 +14,22 @@ class CalendarAuthManager(
private val googleAuth: GoogleAuth,
) {
suspend fun getCredentialData(host: CalendarHost, id: Int, guild: Snowflake?): CredentialData? {
return when (host) {
CalendarHost.GOOGLE -> {
if (guild == null) {
// Internal (owned by DisCal, should never go bad)
googleAuth.requestNewAccessToken(id)
} else {
// External (owned by user)
val calendar = calendarService.getCalendar(guild, id) ?: return null
googleAuth.requestNewAccessToken(calendar)
return try {
when (host) {
CalendarHost.GOOGLE -> {
if (guild == null) {
// Internal (owned by DisCal, should never go bad)
googleAuth.requestNewAccessToken(id)
} else {
// External (owned by user)
val calendar = calendarService.getCalendar(guild, id) ?: return null
googleAuth.requestNewAccessToken(calendar)
}
}
}
} catch (ex: Exception) {
LOGGER.error("Get CredentialData Exception | guildId:$guild | credentialId:$id | calendarHost:${host.name}", ex)
null
}
}
}
Expand Up @@ -57,7 +57,5 @@ class DiscordOauthManager(
sessionService.removeAndInsertSession(session)

return TokenResponse(session.token, session.expiresAt, authInfo.user)

TODO()
}
}
Expand Up @@ -318,7 +318,7 @@ class AnnouncementCommand(val wizard: Wizard<Announcement>) : SlashCommand {
} else {
event.interaction.guild
.map { AnnouncementEmbed.pre(it, pre, settings) }
.flatMap { event.followupEphemeral(getMessage("error.patronOnly", settings), it) }
.flatMap { event.followupEphemeral(getCommonMsg("error.patronOnly", settings), it) }
}
} else {
event.followupEphemeral(getMessage("error.wizard.notStarted", settings))
Expand Down
Expand Up @@ -8,6 +8,9 @@ import discord4j.core.`object`.entity.channel.GuildMessageChannel
import discord4j.core.spec.MessageCreateSpec
import discord4j.rest.http.client.ClientException
import io.netty.handler.codec.http.HttpResponseStatus
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kotlinx.coroutines.reactor.mono
import org.dreamexposure.discal.client.message.embed.AnnouncementEmbed
import org.dreamexposure.discal.core.database.DatabaseManager
import org.dreamexposure.discal.core.entities.Calendar
Expand Down Expand Up @@ -48,11 +51,14 @@ class AnnouncementService(
// Runner
private fun doAnnouncementCycle(): Mono<Void> {
return discordClient.guilds.flatMap { guild ->
DatabaseManager.getEnabledAnnouncements(guild.id).flatMapMany { Flux.fromIterable(it) }.flatMap { announcement ->
when (announcement.modifier) {
AnnouncementModifier.BEFORE -> handleBeforeModifier(guild, announcement)
AnnouncementModifier.DURING -> handleDuringModifier(guild, announcement)
AnnouncementModifier.END -> handleEndModifier(guild, announcement)
mono {
val announcements = DatabaseManager.getEnabledAnnouncements(guild.id).awaitSingle()
announcements.forEach { announcement ->
when (announcement.modifier) {
AnnouncementModifier.BEFORE -> handleBeforeModifier(guild, announcement).awaitSingleOrNull()
AnnouncementModifier.DURING -> handleDuringModifier(guild, announcement).awaitSingleOrNull()
AnnouncementModifier.END -> handleEndModifier(guild, announcement).awaitSingleOrNull()
}
}
}.doOnError {
LOGGER.error(GlobalVal.DEFAULT, "Announcement error", it)
Expand Down Expand Up @@ -204,12 +210,14 @@ class AnnouncementService(

private fun getEvents(guild: Guild, announcement: Announcement): Flux<Event> {
val cached = getCached(announcement.guildId)

return if (!cached.events.contains(announcement.calendarNumber)) {
getCalendar(guild, announcement).flatMapMany {
it.getUpcomingEvents(20).cache()
}
} else cached.events[announcement.calendarNumber]!!
if (cached.events.contains(announcement.calendarNumber))
return Flux.fromIterable(cached.events[announcement.calendarNumber]!!)

return getCalendar(guild, announcement).flatMapMany {
it.getUpcomingEvents(20)
}.collectList()
.doOnNext { cached.events[announcement.calendarNumber] = it }
.flatMapIterable { it }
}

private fun getCached(guildId: Snowflake): AnnouncementCache {
Expand Down

0 comments on commit bd8cda5

Please sign in to comment.