diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..6da268b Binary files /dev/null and b/.DS_Store differ diff --git a/build.gradle.kts b/build.gradle.kts index 1539fd5..8da95f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -86,4 +86,5 @@ dependencies { runtimeOnly("org.postgresql:postgresql:42.3.6") implementation("com.google.code.gson:gson:2.9.1") implementation("aws.sdk.kotlin:s3:0.17.5-beta") + implementation("org.simplejavamail:simple-java-mail:7.5.0") } diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..7b0d367 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 0000000..53fb051 Binary files /dev/null and b/src/main/.DS_Store differ diff --git a/src/main/kotlin/com/example/Application.kt b/src/main/kotlin/com/example/Application.kt index 2251186..2d6e120 100644 --- a/src/main/kotlin/com/example/Application.kt +++ b/src/main/kotlin/com/example/Application.kt @@ -7,6 +7,7 @@ import com.example.plugins.configureHikariDataSource import com.example.plugins.configureRouting import com.example.plugins.configureSecurity import com.example.plugins.performDBMigration +import com.example.routes.mailSender.SmtpMailClient import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import io.ktor.http.ContentType @@ -24,6 +25,10 @@ const val ENV_GITHUB_CLIENT_ID = "GITHUB_CLIENT_ID" const val ENV_GITHUB_CLIENT_SECRET = "GITHUB_CLIENT_SECRET" const val ENV_CLIENT_URL = "CLIENT_URL" const val ENV_DATA_BUCKET_NAME = "DATA_BUCKET_NAME" +const val ENV_EMAIL = "EMAIL" +const val ENV_EMAIL_PASSWORD = "EMAIL_PASSWORD" +const val ENV_EMAIL_HOST = "EMAIL_HOST" +const val ENV_EMAIL_PORT = "EMAIL_PORT" fun configureGithubClient( clientId: String = System.getenv(ENV_GITHUB_CLIENT_ID), @@ -43,6 +48,10 @@ fun main() { val context = DSL.using(datasource, SQLDialect.POSTGRES) val clientUrl = System.getenv(ENV_CLIENT_URL) val dataBucketName = System.getenv(ENV_DATA_BUCKET_NAME) + val smtpEmail = System.getenv(ENV_EMAIL) + val smtpPassword = System.getenv(ENV_EMAIL_PASSWORD) + val smtpHost = System.getenv(ENV_EMAIL_HOST) + val smtpPort = System.getenv(ENV_EMAIL_PORT).toInt() performDBMigration(datasource) configureSecurity(context) @@ -51,7 +60,8 @@ fun main() { context, configureGithubClient(), clientUrl, - dataBucketName + dataBucketName, + SmtpMailClient(smtpHost,smtpPort,smtpEmail,smtpPassword) ) install(ContentNegotiation) { diff --git a/src/main/kotlin/com/example/plugins/Routing.kt b/src/main/kotlin/com/example/plugins/Routing.kt index 263bae3..ed0eb10 100644 --- a/src/main/kotlin/com/example/plugins/Routing.kt +++ b/src/main/kotlin/com/example/plugins/Routing.kt @@ -7,6 +7,8 @@ import com.example.routes.badges.badge import com.example.routes.dataBucket.dataBucket import com.example.routes.githubEvents.githubEvents import com.example.routes.leaderboard.leaderboard +import com.example.routes.mailSender.MailClient +import com.example.routes.mailSender.SmtpMailClient import com.example.routes.pullRequests.pullRequests import com.example.routes.session.session import com.example.routes.sprints.sprints @@ -69,7 +71,8 @@ fun Application.configureRouting( context: DSLContext? = null, githubClient: GithubClient? = null, clientUrl: String? = null, - dataBucketName: String? = null + dataBucketName: String? = null, + mailClient: SmtpMailClient? = null ) { routing { oidc(context!!, githubClient!!, clientUrl!!) @@ -77,7 +80,7 @@ fun Application.configureRouting( user(context) githubEvents(context) tasks(context) - sprints(context) + sprints(context, mailClient!!) leaderboard(context) badge(context) dataBucket(dataBucketName!!) diff --git a/src/main/kotlin/com/example/routes/mailSender/EmailDto.kt b/src/main/kotlin/com/example/routes/mailSender/EmailDto.kt new file mode 100644 index 0000000..a06ae46 --- /dev/null +++ b/src/main/kotlin/com/example/routes/mailSender/EmailDto.kt @@ -0,0 +1,21 @@ +package com.example.routes.mailSender + +import java.time.OffsetDateTime + + +open class PartialEmailDto( + open val subject: String, + open val emailAddresses: String, + open val htmlBody: String +) + +class EmailDto( + subject: String, + emailAddresses: String, + htmlBody: String, + val createdAt: OffsetDateTime, +): PartialEmailDto( + subject, + emailAddresses, + htmlBody +) diff --git a/src/main/kotlin/com/example/routes/mailSender/MailClient.kt b/src/main/kotlin/com/example/routes/mailSender/MailClient.kt new file mode 100644 index 0000000..e250001 --- /dev/null +++ b/src/main/kotlin/com/example/routes/mailSender/MailClient.kt @@ -0,0 +1,7 @@ +package com.example.routes.mailSender + +import java.util.concurrent.CompletableFuture + +interface MailClient { + fun sendEmail (emailDto: EmailDto): CompletableFuture +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/routes/mailSender/SmtpMailClient.kt b/src/main/kotlin/com/example/routes/mailSender/SmtpMailClient.kt new file mode 100644 index 0000000..1a7b40b --- /dev/null +++ b/src/main/kotlin/com/example/routes/mailSender/SmtpMailClient.kt @@ -0,0 +1,27 @@ +package com.example.routes.mailSender + +import org.simplejavamail.api.mailer.Mailer +import org.simplejavamail.email.EmailBuilder +import org.simplejavamail.mailer.MailerBuilder +import java.util.concurrent.CompletableFuture + + +class SmtpMailClient( + private val host: String, + private val port: Int, + private val username: String, + private val password: String, +) : MailClient { + + private val mailer: Mailer = MailerBuilder.withSMTPServer(host, port, username, password).buildMailer() + + override fun sendEmail(emailDto: EmailDto): CompletableFuture { + val email = EmailBuilder.startingBlank() + .from(username) + .to(emailDto.emailAddresses) + .withSubject(emailDto.subject) + .withHTMLText(emailDto.htmlBody) + .buildEmail() + return mailer.sendMail(email) + } +} diff --git a/src/main/kotlin/com/example/routes/sprints/sprintRouter.kt b/src/main/kotlin/com/example/routes/sprints/sprintRouter.kt index d649dc2..2d705e1 100644 --- a/src/main/kotlin/com/example/routes/sprints/sprintRouter.kt +++ b/src/main/kotlin/com/example/routes/sprints/sprintRouter.kt @@ -1,11 +1,16 @@ package com.example.routes.sprints +import aws.smithy.kotlin.runtime.http.response.dumpResponse import com.example.plugins.UserPrinciple +import com.example.routes.mailSender.EmailDto +import com.example.routes.mailSender.SmtpMailClient +import com.example.routes.tasks.DevTaskDetails +import com.example.routes.tasks.QuizAnswerPayload +import com.example.routes.tasks.toDto import com.wehuddle.db.enums.IssueState +import com.wehuddle.db.enums.TaskType import com.wehuddle.db.enums.UserRole -import com.wehuddle.db.tables.Issue -import com.wehuddle.db.tables.Sprint -import com.wehuddle.db.tables.SprintIssue +import com.wehuddle.db.tables.* import io.ktor.http.HttpStatusCode import io.ktor.server.application.call import io.ktor.server.auth.authenticate @@ -19,8 +24,11 @@ import org.jooq.DSLContext private val SPRINT = Sprint.SPRINT private val ISSUE = Issue.ISSUE +private val PROFILE = Profile.PROFILE +private val ISSUE_ASSIGNMENT = IssueAssignment.ISSUE_ASSIGNMENT +private val SPRINT_ISSUE = SprintIssue.SPRINT_ISSUE -fun Route.sprints(context: DSLContext) { +fun Route.sprints(context: DSLContext, mailClient: SmtpMailClient) { authenticate { route("/sprints") { post { @@ -54,6 +62,32 @@ fun Route.sprints(context: DSLContext) { } route("/{sprintId}") { + route("/send-reminder") { + post { + val userPrinciple = call.principal()!! + if (userPrinciple.profile.role != UserRole.HUDDLE_AGENT) { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + val sprint = call.receive() + val emailAddresses = context + .select(PROFILE.EMAIL) + .from(PROFILE) + .join(ISSUE_ASSIGNMENT) + .on(PROFILE.ID.eq(ISSUE_ASSIGNMENT.PROFILE_ID)) + .join(SPRINT_ISSUE) + .on(SPRINT_ISSUE.SPRINT_ID.eq(sprint.id)) + .fetch(PROFILE.EMAIL) + .toList() + println(emailAddresses) + emailAddresses.forEach(){ email -> + val emailToSend = EmailDto(sprint.title, email, sprint.description, OffsetDateTime.now()) + mailClient.sendEmail(emailToSend) + } + } + } + get { val sprintId = UUID.fromString(call.parameters["sprintId"]!!) val existingSprint = context.fetchOne( diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store new file mode 100644 index 0000000..b1ea27f Binary files /dev/null and b/src/main/resources/.DS_Store differ diff --git a/src/main/resources/migrations/.DS_Store b/src/main/resources/migrations/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/src/main/resources/migrations/.DS_Store differ