Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dependencies {
implementation("software.amazon.awssdk:ses")
implementation("software.amazon.awssdk:auth")
implementation("software.amazon.awssdk:regions")
implementation("org.springframework.boot:spring-boot-starter-mail")

implementation "com.microsoft.sqlserver:mssql-jdbc:${mssqlJdbcVersion}"
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class SecurityConfig() {
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("http://localhost:5173", "http://localhost:3000", "https://autobank-frontend.vercel.app") // Add your frontend URL(s)
configuration.allowedOrigins = listOf("http://localhost:5173", "http://localhost:3000", "https://autobank-frontend.vercel.app", "https://autobank.online.ntnu.no") // Add your frontend URL(s)
configuration.allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS")
configuration.allowedHeaders = listOf("*")
configuration.allowCredentials = true
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/example/autobank/security/WebConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class WebConfig : WebMvcConfigurer {

override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "https://autobank-frontend.vercel.app")
.allowedOrigins("http://localhost:3000", "https://autobank-frontend.vercel.app", "https://autobank.online.ntnu.no")
.allowedMethods("GET", "POST")
.allowedHeaders("*")
.allowCredentials(true)
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/com/example/autobank/service/BlobService.kt
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤨

Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ class BlobService(
if (file.size > maxFileSize) {
throw Exception("File size is too large")
} else {
val filename = UUID.randomUUID().toString() +"."+ mimeType.replace('/', ':')
val blobClient = blobContainerClient.getBlobClient("$filename.");
val extension = mimeType.substringAfter("/")
val filename = "${UUID.randomUUID()}.$extension".replace("\\s".toRegex(), "_")

val blobClient = blobContainerClient.getBlobClient(filename)
blobClient.upload(file.inputStream(), file.size.toLong())

println("Uploaded image to $filename")
Expand Down
75 changes: 54 additions & 21 deletions src/main/kotlin/com/example/autobank/service/MailService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider
import software.amazon.awssdk.core.SdkBytes
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.ses.SesClient
import software.amazon.awssdk.services.ses.model.*
import jakarta.mail.Message
import jakarta.mail.Session
import jakarta.mail.internet.InternetAddress
import jakarta.mail.internet.MimeBodyPart
import jakarta.mail.internet.MimeMessage
import jakarta.mail.internet.MimeMultipart
import jakarta.mail.internet.MimeUtility
import java.io.ByteArrayOutputStream
import java.util.Properties

@Service
class MailService(
Expand All @@ -23,36 +33,59 @@ class MailService(
)
.build()

fun sendEmail(toEmail: String, subject: String, htmlBody: String) {
val destination = Destination.builder()
.toAddresses(toEmail)
.build()
/**
* Sends an email via AWS SES.
* If [attachments] is provided, it sends a Raw MIME message.
*/
fun sendEmail(
toEmail: String,
subject: String,
htmlBody: String,
attachments: List<Pair<String, ByteArray>> = emptyList()
) {
val session = Session.getDefaultInstance(Properties())
val message = MimeMessage(session)

val content = Content.builder()
.data(subject)
.charset("UTF-8")
.build()
message.setSubject(subject, "UTF-8")
message.setFrom(InternetAddress("kvittering@online.ntnu.no"))
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail))

val body = Body.builder()
.html(Content.builder().data(htmlBody).charset("UTF-8").build())
.build()
// Create a multipart container to hold the text and the files
val multipart = MimeMultipart()

// 1. Add the HTML content
val htmlPart = MimeBodyPart()
htmlPart.setContent(htmlBody, "text/html; charset=UTF-8")
multipart.addBodyPart(htmlPart)

// 2. Add any attachments
attachments.forEach { (fileName, data) ->
val attachmentPart = MimeBodyPart()
val encodedFileName = MimeUtility.encodeText(fileName, "UTF-8", null)
attachmentPart.fileName = encodedFileName
attachmentPart.setContent(data, "application/octet-stream")
multipart.addBodyPart(attachmentPart)
}

message.setContent(multipart)

// Convert the MimeMessage to a format AWS SES understands
val outputStream = ByteArrayOutputStream()
message.writeTo(outputStream)

val message = Message.builder()
.subject(content)
.body(body)
val rawMessage = RawMessage.builder()
.data(SdkBytes.fromByteArray(outputStream.toByteArray()))
.build()

val request = SendEmailRequest.builder()
.destination(destination)
.message(message)
.source("kvittering@online.ntnu.no")
val request = SendRawEmailRequest.builder()
.rawMessage(rawMessage)
.build()

try {
sesClient.sendEmail(request)
println("Email sent successfully to $toEmail")
sesClient.sendRawEmail(request)
println("Email sent successfully to $toEmail ${if(attachments.isNotEmpty()) "with attachments" else ""}")
} catch (e: SesException) {
println("Failed to send email: ${e.awsErrorDetails().errorMessage()}")
println("Failed to send email via SES: ${e.awsErrorDetails().errorMessage()}")
throw e
}
}
Expand Down
100 changes: 60 additions & 40 deletions src/main/kotlin/com/example/autobank/service/ReceiptService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,52 +61,72 @@ class ReceiptService(

)

val storedReceipt = receiptRepository.save(receipt)

/**
* Save attachments and prepare for email
*/
val attachments = receiptRequestBody.attachments
val attachmentsForEmail = mutableListOf<Pair<String, ByteArray>>()

try {
attachments.forEach { file64 ->
// 1. Upload to storage (this returns the generated filename with UUID)
val imgname = blobService.uploadFile(file64)
attachmentService.createAttachment(Attachment("", storedReceipt, imgname))

val pureBase64 = if (file64.contains(",")) file64.split(",")[1] else file64
val bytearray = java.util.Base64.getDecoder().decode(pureBase64)

attachmentsForEmail.add(imgname to bytearray)
}
} catch (e: Exception) {
receiptRepository.delete(storedReceipt)

throw e
}

val emailContent = """
<h2>Detaljer for innsendt kvittering</h2>
<p><strong>Bruker:</strong> ${user.fullname}</p>
<p><strong>Brukerens e-post:</strong> ${user.email}</p>
<p><strong>Kvitterings-ID:</strong> ${storedReceipt.id}</p>
<p><strong>Beløp:</strong> ${storedReceipt.amount}</p>
<p><strong>Komité-ID:</strong> ${storedReceipt.committee.name}</p>
<p><strong>Anledning:</strong> ${storedReceipt.name}</p>
<p><strong>Beskrivelse:</strong> ${storedReceipt.description}</p>
<p><strong>Betalingsmetode:</strong> ${
if (receiptRequestBody.receiptPaymentInformation?.usedOnlineCard == true) "Online-kort" else "Bankoverføring"
}</p>
<p><strong>Kontonummer:</strong> ${
receiptRequestBody.receiptPaymentInformation?.accountnumber ?: "Ikke oppgitt"
}</p>
""".trimIndent()

// 3. Send email with the collected attachments
mailService.sendEmail(
toEmail = user.email,
subject = "Receipt Submission Details",
htmlBody = emailContent,
attachments = attachmentsForEmail
)

val storedReceipt = receiptRepository.save(receipt);

/**
* Save attachments
*/

val attachments = receiptRequestBody.attachments
val attachmentsnames = mutableListOf<String>()
try {
attachments.forEach { attachment ->
val imgname = blobService.uploadFile(attachment)
attachmentService.createAttachment(Attachment("", storedReceipt, imgname))
println("Attachent name: $imgname")
attachmentsnames.add(imgname)
}
} catch (e: Exception) {
receiptRepository.delete(storedReceipt)
throw e;
// if (environment == "prod") {
// mailService.sendEmail(
// toEmail = "online-linjeforeningen-for-informatikk1@bilag.fiken.no",
// subject = "Kvittering: ${user.fullname} - ${storedReceipt.name}",
// attachments = attachmentsForEmail,
// htmlBody = emailContent
// )
// println("Email sent to Fiken")
// }

return ReceiptResponseBody()
}


val emailContent = """
<h2>Detaljer for innsendt kvittering</h2>
<p><strong>Bruker:</strong> ${user.fullname}</p>
<p><strong>Brukerens e-post:</strong> ${user.email}</p>
<p><strong>Kvitterings-ID:</strong> ${storedReceipt.id}</p>
<p><strong>Beløp:</strong> ${storedReceipt.amount}</p>
<p><strong>Komité-ID:</strong> ${storedReceipt.committee.name}</p>
<p><strong>Anledning:</strong> ${storedReceipt.name}</p>
<p><strong>Beskrivelse:</strong> ${storedReceipt.description}</p>
<p><strong>Betalingsmetode:</strong> ${
if (receiptRequestBody.receiptPaymentInformation?.usedOnlineCard == true)
"Online-kort"
else
"Bankoverføring"
}</p>
<p><strong>Kontonummer:</strong> ${
receiptRequestBody.receiptPaymentInformation?.accountnumber ?: "Ikke oppgitt"
}</p>
""".trimIndent()

mailService.sendEmail(user.email, "Receipt Submission Details", emailContent)
return ReceiptResponseBody()

}

fun getAllReceiptsFromUser(from: Int, count: Int, status: String?, committeeName: String?, search: String?, sortField: String?, sortOrder: String?): ReceiptListResponseBody? {

Expand Down