From 42a7140abc34ea33ad07b6cecb7330a1d3cb3067 Mon Sep 17 00:00:00 2001 From: Sarah Sporck Date: Thu, 16 Mar 2023 17:34:27 +0100 Subject: [PATCH 1/2] 836: send verification emails --- .../components/ApplyController.tsx | 2 +- .../{apply.graphql => addApplication.graphql} | 4 +- .../EakApplicationMutationService.kt | 54 ++++++++++++++++--- specs/backend-api.graphql | 2 +- 4 files changed, 50 insertions(+), 12 deletions(-) rename administration/src/graphql/applications/{apply.graphql => addApplication.graphql} (66%) diff --git a/administration/src/application/components/ApplyController.tsx b/administration/src/application/components/ApplyController.tsx index b59e7f433..8bbb9d46d 100644 --- a/administration/src/application/components/ApplyController.tsx +++ b/administration/src/application/components/ApplyController.tsx @@ -92,7 +92,7 @@ const ApplyController = (): React.ReactElement | null => { const [regionId, application] = validationResult.value addBlueEakApplication({ - variables: { regionId, application }, + variables: { regionId, application, project: projectId }, }) } diff --git a/administration/src/graphql/applications/apply.graphql b/administration/src/graphql/applications/addApplication.graphql similarity index 66% rename from administration/src/graphql/applications/apply.graphql rename to administration/src/graphql/applications/addApplication.graphql index ed6df963c..7c215be98 100644 --- a/administration/src/graphql/applications/apply.graphql +++ b/administration/src/graphql/applications/addApplication.graphql @@ -1,3 +1,3 @@ -mutation addEakApplication($regionId: Int!, $application: ApplicationInput!) { - result: addEakApplication(regionId: $regionId, application: $application) +mutation addEakApplication($regionId: Int!, $application: ApplicationInput!, $project: String!) { + result: addEakApplication(regionId: $regionId, application: $application, project: $project) } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt index 27f88dde9..7204e126f 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt @@ -1,15 +1,19 @@ package app.ehrenamtskarte.backend.application.webservice import app.ehrenamtskarte.backend.application.database.ApplicationEntity +import app.ehrenamtskarte.backend.application.database.ApplicationVerificationEntity import app.ehrenamtskarte.backend.application.database.repos.ApplicationRepository import app.ehrenamtskarte.backend.application.webservice.schema.create.Application import app.ehrenamtskarte.backend.auth.database.AdministratorEntity import app.ehrenamtskarte.backend.auth.service.Authorizer.mayDeleteApplicationsInRegion import app.ehrenamtskarte.backend.common.webservice.GraphQLContext import app.ehrenamtskarte.backend.common.webservice.UnauthorizedException +import app.ehrenamtskarte.backend.mail.Mailer import com.expediagroup.graphql.generator.annotations.GraphQLDescription import graphql.schema.DataFetchingEnvironment import org.jetbrains.exposed.sql.transactions.transaction +import java.net.URLEncoder +import java.nio.charset.StandardCharsets @Suppress("unused") class EakApplicationMutationService { @@ -18,9 +22,13 @@ class EakApplicationMutationService { fun addEakApplication( regionId: Int, application: Application, + project: String, dfe: DataFetchingEnvironment, ): Boolean { val context = dfe.getContext() + val backendConfig = context.backendConfiguration + val projectConfig = backendConfig.projects.first { it.id == project } + // Validate that all files are png, jpeg or pdf files and at most 5MB. val allowedContentTypes = setOf("application/pdf", "image/png", "image/jpeg") val maxFileSizeBytes = 5 * 1000 * 1000 @@ -28,19 +36,49 @@ class EakApplicationMutationService { throw IllegalArgumentException("An uploaded file does not adhere to the file upload requirements.") } - val (applicationEntity, verificationEntities) = ApplicationRepository.persistApplication( - application.toJsonField(), - application.extractApplicationVerifications(), - regionId, - context.applicationData, - context.files, - ) + transaction { + val (applicationEntity, verificationEntities) = ApplicationRepository.persistApplication( + application.toJsonField(), + application.extractApplicationVerifications(), + regionId, + context.applicationData, + context.files, + ) - // TODO: Send mails + for (applicationVerification in verificationEntities) { + Mailer.sendMail( + backendConfig, + projectConfig.smtp, + projectConfig.administrationName, + applicationVerification.contactEmailAddress, + "Antrag Verifizieren", + generateApplicationVerificationMailMessage(projectConfig.administrationName, projectConfig.administrationBaseUrl, applicationVerification) + ) + } + } return true } + private fun generateApplicationVerificationMailMessage( + administrationName: String, + administrationBaseUrl: String, + applicationVerification: ApplicationVerificationEntity + ): String { + return """ + Guten Tag ${applicationVerification.contactName}, + + Sie wurden gebeten, die Angaben eines Antrags auf Ehrenamtskarte zu bestätigen. Die Antragsstellerin oder der + Antragssteller hat Sie als Kontaktperson der Organisation ${applicationVerification.organizationName} angegeben. + Sie können den Antrag unter folgendem Link einsehen und die Angaben bestätigen oder ihnen widersprechen: + $administrationBaseUrl/antrag-verifizieren/${URLEncoder.encode(applicationVerification.accessKey, StandardCharsets.UTF_8)} + + Dies ist eine automatisierte Nachricht. Antworten Sie nicht auf diese Email. + + - $administrationName + """.trimIndent() + } + @GraphQLDescription("Deletes the application with specified id") fun deleteApplication( applicationId: Int, diff --git a/specs/backend-api.graphql b/specs/backend-api.graphql index ef2fd719a..f0bc5a65a 100644 --- a/specs/backend-api.graphql +++ b/specs/backend-api.graphql @@ -98,7 +98,7 @@ type Mutation { "Stores a batch of new digital entitlementcards" addCards(cards: [CardGenerationModelInput!]!): Boolean! "Stores a new application for an EAK" - addEakApplication(application: ApplicationInput!, regionId: Int!): Boolean! + addEakApplication(application: ApplicationInput!, project: String!, regionId: Int!): Boolean! "Changes an administrator's password" changePassword(currentPassword: String!, email: String!, newPassword: String!, project: String!): Boolean! "Creates a new administrator" From b11a4140c437e77456feb4cde5cbc2dc2246cdf4 Mon Sep 17 00:00:00 2001 From: Sarah Sporck Date: Mon, 20 Mar 2023 10:54:37 +0100 Subject: [PATCH 2/2] 836: catch mail exception per email --- .../EakApplicationMutationService.kt | 20 +++++++++++++++---- .../database/repos/RegionsRepository.kt | 8 ++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt index 7204e126f..0909d8c78 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/application/webservice/EakApplicationMutationService.kt @@ -9,9 +9,12 @@ import app.ehrenamtskarte.backend.auth.service.Authorizer.mayDeleteApplicationsI import app.ehrenamtskarte.backend.common.webservice.GraphQLContext import app.ehrenamtskarte.backend.common.webservice.UnauthorizedException import app.ehrenamtskarte.backend.mail.Mailer +import app.ehrenamtskarte.backend.regions.database.repos.RegionsRepository import com.expediagroup.graphql.generator.annotations.GraphQLDescription import graphql.schema.DataFetchingEnvironment import org.jetbrains.exposed.sql.transactions.transaction +import org.simplejavamail.MailException +import org.slf4j.LoggerFactory import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -25,10 +28,16 @@ class EakApplicationMutationService { project: String, dfe: DataFetchingEnvironment, ): Boolean { + val logger = LoggerFactory.getLogger(Mailer::class.java) val context = dfe.getContext() val backendConfig = context.backendConfiguration val projectConfig = backendConfig.projects.first { it.id == project } + val region = transaction { RegionsRepository.findByIdInProject(project, regionId) } + if (region == null) { + throw IllegalArgumentException("The region is not related to the project.") + } + // Validate that all files are png, jpeg or pdf files and at most 5MB. val allowedContentTypes = setOf("application/pdf", "image/png", "image/jpeg") val maxFileSizeBytes = 5 * 1000 * 1000 @@ -36,16 +45,18 @@ class EakApplicationMutationService { throw IllegalArgumentException("An uploaded file does not adhere to the file upload requirements.") } - transaction { - val (applicationEntity, verificationEntities) = ApplicationRepository.persistApplication( + val (applicationEntity, verificationEntities) = transaction { + ApplicationRepository.persistApplication( application.toJsonField(), application.extractApplicationVerifications(), regionId, context.applicationData, context.files, ) + } - for (applicationVerification in verificationEntities) { + for (applicationVerification in verificationEntities) { + try { Mailer.sendMail( backendConfig, projectConfig.smtp, @@ -54,9 +65,10 @@ class EakApplicationMutationService { "Antrag Verifizieren", generateApplicationVerificationMailMessage(projectConfig.administrationName, projectConfig.administrationBaseUrl, applicationVerification) ) + } catch (exception: MailException) { + logger.error(exception.message) } } - return true } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/repos/RegionsRepository.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/repos/RegionsRepository.kt index cb54824f7..10808c13a 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/repos/RegionsRepository.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/repos/RegionsRepository.kt @@ -26,6 +26,14 @@ object RegionsRepository { return RegionEntity.wrapRows(query).sortByKeys({ it.id.value }, ids) } + fun findByIdInProject(project: String, id: Int): RegionEntity? { + val query = (Projects innerJoin Regions) + .slice(Regions.columns) + .select { Projects.project eq project and (Regions.id eq id) } + .single() + return RegionEntity.wrapRow(query) + } + fun findByIds(ids: List) = RegionEntity.find { Regions.id inList ids }.sortByKeys({ it.id.value }, ids)