From c51f55152d38face782ef130b1c01ab608674002 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 13 Aug 2024 16:01:50 +0800 Subject: [PATCH] Add mail notification & hk common holiday --- .../java/com/ffii/core/utils/LocaleUtils.java | 42 ++++ .../com/ffii/tsms/modules/common/MailSMTP.kt | 38 +++ .../tsms/modules/common/SettingNames.java | 8 + .../common/holiday/models/HolidayRequest.kt | 29 +++ .../common/holiday/models/HolidayResponse.kt | 8 + .../common/holiday/service/HolidayService.kt | 60 +++++ .../common/holiday/web/HolidayController.kt | 17 ++ .../modules/common/mail/pojo/MailRequest.kt | 235 ++++++++++++++++++ .../mail/service/MailReminderService.kt | 75 ++++++ .../common/mail/service/MailSenderService.kt | 46 ++++ .../common/mail/service/MailService.kt | 207 +++++++++++++++ .../modules/common/mail/web/MailController.kt | 38 +++ .../common/mail/web/models/MailSave.kt | 21 ++ .../timesheet/web/TimesheetsController.kt | 2 +- .../20240807_01_cyril/01_update_settings.sql | 21 ++ .../20240813_01_cyril/01_update_authority.sql | 12 + .../templates/mail/TimesheetNotification.ftl | 28 +++ 17 files changed, 886 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/ffii/core/utils/LocaleUtils.java create mode 100644 src/main/java/com/ffii/tsms/modules/common/MailSMTP.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayRequest.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayResponse.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/holiday/service/HolidayService.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/holiday/web/HolidayController.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/pojo/MailRequest.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/service/MailSenderService.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/service/MailService.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt create mode 100644 src/main/java/com/ffii/tsms/modules/common/mail/web/models/MailSave.kt create mode 100644 src/main/resources/db/changelog/changes/20240807_01_cyril/01_update_settings.sql create mode 100644 src/main/resources/db/changelog/changes/20240813_01_cyril/01_update_authority.sql create mode 100644 src/main/resources/templates/mail/TimesheetNotification.ftl diff --git a/src/main/java/com/ffii/core/utils/LocaleUtils.java b/src/main/java/com/ffii/core/utils/LocaleUtils.java new file mode 100644 index 0000000..93b3383 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/LocaleUtils.java @@ -0,0 +1,42 @@ +package com.ffii.core.utils; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.i18n.LocaleContextHolder; + +import java.util.Locale; + +/** this utils follow "-" standard ("zh-TW", no "zh_TW") */ +public abstract class LocaleUtils { + + public static Locale getLocale() { + return LocaleContextHolder.getLocale(); + } + + public static String getLocaleStr() { + return toLocaleStr(LocaleContextHolder.getLocale()); + } + + public static String toLocaleStr(Locale locale) { + String language = locale.getLanguage(); + String country = locale.getCountry(); + + if (StringUtils.isNotBlank(country)) { + return language + "-" + country; + } else { + return language; + } + } + + /** + * @param localeStr + * e.g. zh-TW + */ + public static Locale from(String localeStr) { + String[] localeArr = localeStr.split("-"); + if (localeArr.length == 1) { + return new Locale(localeArr[0]); + } else { + return new Locale(localeArr[0], localeArr[1]); + } + } +} diff --git a/src/main/java/com/ffii/tsms/modules/common/MailSMTP.kt b/src/main/java/com/ffii/tsms/modules/common/MailSMTP.kt new file mode 100644 index 0000000..c087d2c --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/MailSMTP.kt @@ -0,0 +1,38 @@ +package com.ffii.tsms.modules.common + +import com.ffii.tsms.modules.settings.service.SettingsService +import org.apache.commons.lang3.StringUtils + +open class MailSMTP(settingsService: SettingsService) { + + open var host: String? = null + open var port: Int? = null + open var username: String? = null + open var password: String? = null + open var auth: Boolean? = null + + init { + this.host = settingsService.getString(SettingNames.MAIL_SMTP_HOST) + this.port = settingsService.getInt(SettingNames.MAIL_SMTP_PORT) + this.username = settingsService.getString(SettingNames.MAIL_SMTP_USERNAME) + this.password = settingsService.getString(SettingNames.MAIL_SMTP_PASSWORD) + this.auth = settingsService.getBoolean(SettingNames.MAIL_SMTP_AUTH) + } + + final override fun equals(other: Any?): Boolean { + if (other == null || other !is MailSMTP) return false + + val o = other as MailSMTP + if (StringUtils.equals( + this.host, + o.host + ) && this.port == o.port && + StringUtils.equals(this.username, o.username) && + StringUtils.equals(this.password, o.password) + ) { + return true + } + + return false + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/SettingNames.java b/src/main/java/com/ffii/tsms/modules/common/SettingNames.java index cef470c..e3b3141 100644 --- a/src/main/java/com/ffii/tsms/modules/common/SettingNames.java +++ b/src/main/java/com/ffii/tsms/modules/common/SettingNames.java @@ -40,6 +40,14 @@ public abstract class SettingNames { public static final String MAIL_SMTP_AUTH = "MAIL.smtp.auth"; + public static final String TIMESHEET_MAIL_CC = "TIMESHEET.mail.cc"; + + public static final String TIMESHEET_MAIL_BCC = "TIMESHEET.mail.bcc"; + + public static final String TIMESHEET_MAIL_SUBJECT = "TIMESHEET.mail.subject"; + + public static final String TIMESHEET_MAIL_TEMPLATE = "TIMESHEET.mail.template"; + public static final String JS_VERSION = "JS.version"; public static final String REPORT_DAILYMAINT_RECIPIENTS_MECH = "REPORT.dailyMaint.recipients.mech"; diff --git a/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayRequest.kt b/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayRequest.kt new file mode 100644 index 0000000..575545b --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayRequest.kt @@ -0,0 +1,29 @@ +package com.ffii.tsms.modules.common.holiday.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VCalendarRequest( + val vcalendar: List +) + +@JsonClass(generateAdapter = true) +data class VCalendar( + val prodid: String, + val version: String, + val calscale: String, + @Json(name = "x-wr-timezone") val xWrTimezone: String, + @Json(name = "x-wr-calname") val xWrCalname: String, + @Json(name = "x-wr-caldesc") val xWrCaldesc: String, + val vevent: List +) + +@JsonClass(generateAdapter = true) +data class VEvent( + val dtstart: List, // Can be a String or a Map + val dtend: List, // Can be a String or a Map + val transp: String, + val uid: String, + val summary: String +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayResponse.kt b/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayResponse.kt new file mode 100644 index 0000000..11f1ad3 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/holiday/models/HolidayResponse.kt @@ -0,0 +1,8 @@ +package com.ffii.tsms.modules.common.holiday.models + +import java.time.LocalDate + +data class CommonHolidayResponse( + val date: LocalDate, + val name: String, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/holiday/service/HolidayService.kt b/src/main/java/com/ffii/tsms/modules/common/holiday/service/HolidayService.kt new file mode 100644 index 0000000..375d868 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/holiday/service/HolidayService.kt @@ -0,0 +1,60 @@ +package com.ffii.tsms.modules.common.holiday.service + +import com.ffii.tsms.modules.common.holiday.models.CommonHolidayResponse +import com.ffii.tsms.modules.common.holiday.models.VCalendarRequest +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import okhttp3.Request +import okhttp3.OkHttpClient +import okhttp3.ResponseBody +import org.apache.commons.logging.Log +import org.apache.commons.logging.LogFactory +import org.springframework.stereotype.Service +import java.io.IOException +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Service +open class HolidayService() { + private val logger: Log = LogFactory.getLog(javaClass) + private val HOLIDAY_URL = "https://www.1823.gov.hk/common/ical/tc.json" + + open fun commonHolidayList(): List { + val client = OkHttpClient() + val request = Request.Builder() + .url(HOLIDAY_URL) + .build() + + client.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw IOException("Unexpected code: $response") + +// logger.info(response.body.toString()) + + val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + val jsonAdapter = moshi.adapter(VCalendarRequest::class.java) + + val vCalendarResponse = jsonAdapter.fromJson(response.body?.string() ?: "") + val vCalendar = vCalendarResponse?.vcalendar?.get(0) + + if (vCalendar != null) { + val commonHolidayResponse = mutableListOf() + val formatter = DateTimeFormatter.ofPattern("yyyyMMdd") + vCalendar.vevent.forEach {vevent -> + commonHolidayResponse.add( + CommonHolidayResponse( + date = LocalDate.parse(vevent.dtstart[0] as String, formatter), + name = vevent.summary + ) + ) + } + + return commonHolidayResponse + } else { + return emptyList() + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/holiday/web/HolidayController.kt b/src/main/java/com/ffii/tsms/modules/common/holiday/web/HolidayController.kt new file mode 100644 index 0000000..b8385f0 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/holiday/web/HolidayController.kt @@ -0,0 +1,17 @@ +package com.ffii.tsms.modules.common.holiday.web + +import com.ffii.tsms.modules.common.holiday.models.CommonHolidayResponse +import com.ffii.tsms.modules.common.holiday.service.HolidayService +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/holiday") +class HolidayController(private val holidayService: HolidayService) { + + @GetMapping("/common") + fun commonHolidays(): List{ + return holidayService.commonHolidayList() + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/pojo/MailRequest.kt b/src/main/java/com/ffii/tsms/modules/common/mail/pojo/MailRequest.kt new file mode 100644 index 0000000..e62fff2 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/pojo/MailRequest.kt @@ -0,0 +1,235 @@ +package com.ffii.tsms.modules.common.mail.pojo + +import jakarta.mail.internet.InternetAddress + +open class MailRequest() { + + val PRIORITY_HIGHEST: Int = 1 + val PRIORITY_HIGH: Int = 2 + val PRIORITY_NORMAL: Int = 3 + val PRIORITY_LOW: Int = 4 + val PRIORITY_LOWEST: Int = 5 + + open var from: InternetAddress? = null + + open var to: List = mutableListOf() + + open var subject: String? = null + + open var template: String? = null + + open var templateContent: String? = null + + open var args: Map = mapOf() + + open var priority: Int? = null + + open var replyTo: InternetAddress? = null + + open var cc: List = mutableListOf() + + open var bcc: List = mutableListOf() + + open var attachments: Map = mapOf() + + open fun builder(): Builder { + return Builder() + } + + open fun addAttachment(attachmentFilename: String, byteArray: ByteArray) { + this.attachments += Pair(attachmentFilename, byteArray) + } + + open fun addTo(to: InternetAddress) { + this.to += to + } + + open fun addCc(cc: InternetAddress) { + this.cc += cc + } + + open fun addBcc(bcc: InternetAddress) { + this.bcc += bcc + } + + open class Builder() { + open var mailRequest:MailRequest = MailRequest() + + init { + this.mailRequest = MailRequest() + } + + open fun build(): MailRequest { + return this.mailRequest + } + + open fun addAttachment(attachmentFilename: String, byteArray: ByteArray): Builder { + mailRequest.addAttachment(attachmentFilename, byteArray) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addToInternetAddress") + open fun addTo(to: InternetAddress): Builder { + mailRequest.addTo(to) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addToString") + open fun addTo(to: String): Builder { + mailRequest.addTo(InternetAddress(to)) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addCcInternetAddress") + open fun addCc(cc: InternetAddress): Builder { + mailRequest.addCc(cc) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addCcString") + open fun addCc(cc: String): Builder { + mailRequest.addCc(InternetAddress(cc)) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addCcStringList") + open fun addCc(cc: List): Builder { + cc.forEach { + if (it.isNotEmpty()) { + mailRequest.addCc(InternetAddress(it)) + } + } + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addBccInternetAddress") + open fun addBcc(bcc: InternetAddress): Builder { + mailRequest.addBcc(bcc) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addBccString") + open fun addBcc(bcc: String): Builder { + mailRequest.addBcc(InternetAddress(bcc)) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("addBccStringList") + open fun addBcc(bcc: List): Builder { + bcc.forEach { + if (it.isNotEmpty()) { + mailRequest.addBcc(InternetAddress(it)) + } + } + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fromInternetAddress") + open fun from(from: InternetAddress): Builder { + mailRequest.from = from + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fromString") + open fun from(from: String): Builder { + mailRequest.from = InternetAddress(from) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("toListInternetAddress") + open fun to(to: List): Builder { + mailRequest.to = to + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("toListString") + open fun to(to: List): Builder { + mailRequest.to = to.map { InternetAddress(it) } + return this + } + + open fun subject(subject: String?): Builder { + mailRequest.subject = subject + return this + } + + open fun template(template: String?): Builder { + mailRequest.template = template + return this + } + + open fun templateContent(templateContent: String?): Builder { + mailRequest.templateContent = templateContent + return this + } + + open fun args(args: Map): Builder { + mailRequest.args = args + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("replyToInternetAddress") + open fun replyTo(replyTo: InternetAddress?): Builder { + mailRequest.replyTo = replyTo + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("replyToString") + open fun replyTo(replyTo: String?): Builder { + mailRequest.replyTo = InternetAddress(replyTo) + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("ccListInternetAddress") + open fun cc(cc: List): Builder { + mailRequest.cc = cc + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("ccListString") + open fun cc(cc: List): Builder { + mailRequest.cc = cc.map { InternetAddress(it) } + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("bccListInternetAddress") + open fun bcc(bcc: List): Builder { + mailRequest.bcc = bcc + return this + } + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("bccListString") + open fun bcc(bcc: List): Builder { + mailRequest.bcc = bcc.map { InternetAddress(it) } + return this + } + + open fun attachments(attachments: Map): Builder { + mailRequest.attachments = attachments + return this + } + + open fun priority(priority: Int?): Builder { + mailRequest.priority = priority + return this + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt new file mode 100644 index 0000000..4dc5d36 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailReminderService.kt @@ -0,0 +1,75 @@ +package com.ffii.tsms.modules.common.mail.service + +import com.ffii.tsms.modules.common.SecurityUtils +import com.ffii.tsms.modules.common.SettingNames +import com.ffii.tsms.modules.common.mail.pojo.MailRequest +import com.ffii.tsms.modules.settings.service.SettingsService +import com.ffii.tsms.modules.user.service.UserService +import jakarta.mail.internet.InternetAddress +import org.apache.commons.logging.Log +import org.apache.commons.logging.LogFactory +import org.springframework.stereotype.Service +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@Service +open class MailReminderService ( + val mailService: MailService, + val userService: UserService, + val settingsService: SettingsService, +) { + protected val logger: Log = LogFactory.getLog(javaClass) + + private val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + +// @Scheduled(cron = "0 0 6 * * ?") // Runs at 06:00 AM every day + open fun sendTimesheetReminder() { + val inputDate = LocalDate.now().minusDays(4).format(dateFormat) + + val subject = settingsService.findByName(SettingNames.TIMESHEET_MAIL_SUBJECT).orElseThrow().value + val template = settingsService.findByName(SettingNames.TIMESHEET_MAIL_TEMPLATE).orElseThrow().value + val cc = settingsService.findByName(SettingNames.TIMESHEET_MAIL_CC).orElseThrow().value.split(",") + val bcc = settingsService.findByName(SettingNames.TIMESHEET_MAIL_BCC).orElseThrow().value.split(",") + val mailRequest = MailRequest.Builder() + .subject(subject) + .template("mail/TimesheetNotification") + .args(mapOf( + Pair("date", inputDate), + Pair("template", template) + )) + .addTo(InternetAddress("cyril.tsui@2fi-solutions.com.hk")) + .addCc(cc) + .addBcc(bcc) + .build() + + val mailRequestList = mutableListOf() + mailRequestList += mailRequest + mailService.send(mailRequestList) + } + + open fun sendTimesheetReminderTest() { + val inputDate = LocalDate.now().minusDays(4).format(dateFormat) + + val subject = settingsService.findByName(SettingNames.TIMESHEET_MAIL_SUBJECT).orElseThrow().value + val template = settingsService.findByName(SettingNames.TIMESHEET_MAIL_TEMPLATE).orElseThrow().value + val cc = settingsService.findByName(SettingNames.TIMESHEET_MAIL_CC).orElseThrow().value.split(",") + logger.info(cc) + val bcc = settingsService.findByName(SettingNames.TIMESHEET_MAIL_BCC).orElseThrow().value.split(",") + val mailRequest = MailRequest.Builder() + .subject(subject) +// .template("mail/TimesheetNotification") + .templateContent(template) + .args(mapOf( + Pair("date", inputDate), +// Pair("template", template) + )) + .addTo(InternetAddress(SecurityUtils.getUser().orElseThrow().email)) + .addCc(cc) + .addBcc(bcc) + .build() + + val mailRequestList = mutableListOf() + mailRequestList += mailRequest + mailService.send(mailRequestList) + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/service/MailSenderService.kt b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailSenderService.kt new file mode 100644 index 0000000..fce2c2b --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailSenderService.kt @@ -0,0 +1,46 @@ +package com.ffii.tsms.modules.common.mail.service + +import com.ffii.tsms.modules.common.MailSMTP +import com.ffii.tsms.modules.settings.service.SettingsService +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.mail.javamail.JavaMailSenderImpl +import org.springframework.stereotype.Service +import java.util.* + +@Service +open class MailSenderService(private val settingsService: SettingsService) { + private var sender: JavaMailSender? = null + private var mailConfigCachs: MailSMTP? = null + + open fun get(): JavaMailSender { + + val config = MailSMTP(settingsService) + + if (this.sender == null || (mailConfigCachs == null) || !config.equals(this.mailConfigCachs)) { + + this.mailConfigCachs = config + val sender = JavaMailSenderImpl() + + val props = Properties() + val auth = config.auth ?: false + if (auth) { + props["mail.smtp.timeout"] = "20000" + props["mail.smtp.connectiontimeout"] = "10000" + } + props["mail.smtp.auth"] = auth + props["mail.smtp.starttls.enable"] = "true" + + + sender.host = config.host + sender.port = config.port!! + if (auth) { + sender.username = config.username + sender.password = config.password + } + sender.javaMailProperties = props + + this.sender = sender + } + return this.sender ?: JavaMailSenderImpl() + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/service/MailService.kt b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailService.kt new file mode 100644 index 0000000..84540e6 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/service/MailService.kt @@ -0,0 +1,207 @@ +package com.ffii.tsms.modules.common.mail.service + +import com.ffii.core.exception.InternalServerErrorException +import com.ffii.core.utils.LocaleUtils +import com.ffii.tsms.modules.common.ErrorCodes +import com.ffii.tsms.modules.common.SettingNames +import com.ffii.tsms.modules.common.mail.pojo.MailRequest +import com.ffii.tsms.modules.common.mail.web.models.MailSave +import com.ffii.tsms.modules.settings.entity.Settings +import com.ffii.tsms.modules.settings.entity.SettingsRepository +import com.ffii.tsms.modules.settings.service.SettingsService +import freemarker.cache.StringTemplateLoader +import freemarker.template.* +import jakarta.mail.MessagingException +import org.apache.commons.logging.Log +import org.apache.commons.logging.LogFactory +import org.springframework.core.io.ByteArrayResource +import org.springframework.mail.javamail.MimeMessageHelper +import org.springframework.scheduling.annotation.Async +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.stereotype.Service +import org.springframework.ui.freemarker.FreeMarkerTemplateUtils +import java.io.IOException +import java.text.ParseException +import java.util.* + +@Service +open class MailService( + private val mailSenderService: MailSenderService, + private val freemarkerConfig: Configuration, + private val settingsService: SettingsService, + private val passwordEncoder: PasswordEncoder, + private val settingsRepository: SettingsRepository, +) { + protected val logger: Log = LogFactory.getLog(javaClass) + + @Throws( + MessagingException::class, + TemplateNotFoundException::class, + MalformedTemplateNameException::class, + ParseException::class, + IOException::class, + TemplateException::class + ) + open fun doSend(mailRequests: List, locale: Locale) { + + val sender = mailSenderService.get() + + for (mailRequest in mailRequests) { + val mimeMessage = sender.createMimeMessage() + + val helper = MimeMessageHelper(mimeMessage, true, "UTF-8") + mailRequest.subject?.let { helper.setSubject(it) } + + var template: Template? = null + if (mailRequest.template != null) { + template = try { + freemarkerConfig + .getTemplate((mailRequest.template + "_" + LocaleUtils.toLocaleStr(locale)) + ".ftl") + } catch (e: TemplateNotFoundException) { + freemarkerConfig.getTemplate(mailRequest.template + ".ftl") + } + } else { + // Create a FreeMarker configuration + val configuration = Configuration(Configuration.VERSION_2_3_32) + + // Create a template loader and add the template content + val templateLoader = StringTemplateLoader() + templateLoader.putTemplate("myTemplate", mailRequest.templateContent) + configuration.templateLoader = templateLoader + + try { + // Get the template by name + template = configuration.getTemplate("myTemplate") + } catch (e: IOException) { + // Handle any IO exceptions + } + } + + helper.setText( + FreeMarkerTemplateUtils.processTemplateIntoString(template!!, mailRequest.args), + true + ) + if (mailRequest.from != null) { + helper.setFrom(mailRequest.from!!) + } else { + helper.setFrom(settingsService.getString(SettingNames.MAIL_SMTP_USERNAME)) + } + + if (mailRequest.priority != null) helper.setPriority(mailRequest.priority!!) + if (mailRequest.replyTo != null) helper.setReplyTo(mailRequest.replyTo!!) + if (mailRequest.to.isNotEmpty()) helper.setTo( + mailRequest.to.toTypedArray() + ) + if (mailRequest.cc.isNotEmpty()) helper.setCc( + mailRequest.cc.toTypedArray() + ) + if (mailRequest.bcc.isNotEmpty()) helper.setBcc( + mailRequest.bcc.toTypedArray() + ) + + if (mailRequest.attachments.isNotEmpty()) { + for ((key, value) in mailRequest.attachments.entries) { + helper.addAttachment(key, ByteArrayResource(value)) + } + } + sender.send(mimeMessage) + } + } + + @Throws(ParseException::class) + fun send(mailRequests: List, locale: Locale) { + try { + doSend(mailRequests, locale) + } catch (e: MessagingException) { + throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e) + } catch (e: IOException) { + throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e) + } catch (e: TemplateException) { + throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e) + } + } + + @Throws(ParseException::class) + fun send(mailRequests: List) { + send(mailRequests, LocaleUtils.getLocale()) + } + + @Throws(ParseException::class) + fun send(mailRequest: MailRequest, locale: Locale) { + send(mutableListOf(mailRequest), locale) + } + + @Async + @Throws(ParseException::class) + open fun asyncSend(mailRequests: List, locale: Locale) { + try { + doSend(mailRequests, locale) + } catch (e: MessagingException) { + logger.error("send email error", e) + } catch (e: IOException) { + logger.error("send email error", e) + } catch (e: TemplateException) { + logger.error("send email error", e) + } + } + + @Async + @Throws(ParseException::class) + open fun asyncSend(mailRequest: MailRequest, locale: Locale) { + asyncSend(mutableListOf(mailRequest), locale) + } + + @Async + @Throws(ParseException::class) + open fun asyncSend(mailRequests: List) { + asyncSend(mailRequests, LocaleUtils.getLocale()) + } + + @Async + @Throws(ParseException::class) + open fun asyncSend(mailRequest: MailRequest) { + asyncSend(mutableListOf(mailRequest)) + } + + open fun saveMail(mailSave: MailSave): List { + // ------------------ save mail settings ------------------ // + val settings = mutableListOf() + + mailSave.settings.forEach { setting -> + val tempSetting = settingsRepository.findById(setting.id).orElseThrow() + settings += tempSetting.apply { + value = setting.value + } + } + + // ------------------ save timesheet mail details ------------------ // + val mailCc = settingsRepository.findByName("TIMESHEET.mail.cc").orElseThrow() + + settings += mailCc.apply { + value = mailSave.template.cc + } + + val mailBcc = settingsRepository.findByName("TIMESHEET.mail.bcc").orElseThrow() + + settings += mailBcc.apply { + value = mailSave.template.bcc + } + + val mailSubject = settingsRepository.findByName("TIMESHEET.mail.subject").orElseThrow() + + settings += mailSubject.apply { + value = mailSave.template.subject + } + + val mailTemplate = settingsRepository.findByName("TIMESHEET.mail.template").orElseThrow() + + settings += mailTemplate.apply { + value = mailSave.template.template + } + + // ------------------ save all ------------------ // + settingsRepository.saveAll(settings) + + return settings + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt b/src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt new file mode 100644 index 0000000..4a1332a --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/web/MailController.kt @@ -0,0 +1,38 @@ +package com.ffii.tsms.modules.common.mail.web + +import com.ffii.tsms.modules.common.mail.service.MailReminderService +import com.ffii.tsms.modules.common.mail.service.MailService +import com.ffii.tsms.modules.common.mail.web.models.MailSave +import com.ffii.tsms.modules.settings.entity.Settings +import com.ffii.tsms.modules.settings.entity.SettingsRepository +import com.ffii.tsms.modules.settings.service.SettingsService +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/mails") +class MailController( + private val settingsService: SettingsService, + private val mailService: MailService, + private val mailReminderService: MailReminderService, +) { + @GetMapping("/setting") + fun mailSetting(): List { + return settingsService.findAllByCategory("MAIL") + } + + @GetMapping("/timesheet-template") + fun mailTimesheetTemplate(): List { + return settingsService.findAllByCategory("TIMESHEET") + } + + @PostMapping("/save") + fun saveMail(@Valid @RequestBody mailSave: MailSave): List { + return mailService.saveMail(mailSave) + } + + @GetMapping("/test") + fun testMail() { + mailReminderService.sendTimesheetReminderTest() + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/common/mail/web/models/MailSave.kt b/src/main/java/com/ffii/tsms/modules/common/mail/web/models/MailSave.kt new file mode 100644 index 0000000..92d6811 --- /dev/null +++ b/src/main/java/com/ffii/tsms/modules/common/mail/web/models/MailSave.kt @@ -0,0 +1,21 @@ +package com.ffii.tsms.modules.common.mail.web.models + +data class Setting ( + val id: Long, + val name: String, + val value: String, + val category: String, + val type: String, +) + +data class Template ( + val cc: String?, + val bcc: String?, + val subject: String, + val template: String?, +) + +data class MailSave ( + val settings: List, + val template: Template +) \ No newline at end of file diff --git a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt index 2048651..b57ef71 100644 --- a/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt +++ b/src/main/java/com/ffii/tsms/modules/timesheet/web/TimesheetsController.kt @@ -3,7 +3,7 @@ package com.ffii.tsms.modules.timesheet.web import com.ffii.core.exception.BadRequestException import com.ffii.tsms.modules.timesheet.entity.LeaveType import com.ffii.tsms.modules.timesheet.service.LeaveService -//import com.ffii.tsms.modules.timesheet.service.MailReminderService +//import com.ffii.tsms.modules.common.mail.service.MailReminderService import com.ffii.tsms.modules.timesheet.service.TimesheetsService import com.ffii.tsms.modules.timesheet.web.models.* import jakarta.servlet.http.HttpServletRequest diff --git a/src/main/resources/db/changelog/changes/20240807_01_cyril/01_update_settings.sql b/src/main/resources/db/changelog/changes/20240807_01_cyril/01_update_settings.sql new file mode 100644 index 0000000..f017d3e --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240807_01_cyril/01_update_settings.sql @@ -0,0 +1,21 @@ +-- liquibase formatted sql +-- changeset cyril:settings + +INSERT INTO settings (id,name,value,category,`type`) +VALUES (7,'MAIL.smtp.host','smtp.office365.com','MAIL','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (8,'MAIL.smtp.port','587','MAIL','integer'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (9,'MAIL.smtp.username','','MAIL','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (10,'MAIL.smtp.password','','MAIL','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (11,'MAIL.smtp.auth','false','MAIL','boolean'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (12,'TIMESHEET.mail.cc','','TIMESHEET','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (13,'TIMESHEET.mail.bcc','','TIMESHEET','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (14,'TIMESHEET.mail.subject','','TIMESHEET','string'); +INSERT INTO settings (id,name,value,category,`type`) +VALUES (15,'TIMESHEET.mail.template','','TIMESHEET','string'); diff --git a/src/main/resources/db/changelog/changes/20240813_01_cyril/01_update_authority.sql b/src/main/resources/db/changelog/changes/20240813_01_cyril/01_update_authority.sql new file mode 100644 index 0000000..5fa9a3a --- /dev/null +++ b/src/main/resources/db/changelog/changes/20240813_01_cyril/01_update_authority.sql @@ -0,0 +1,12 @@ +-- liquibase formatted sql +-- changeset cyril:authority + +INSERT INTO authority (authority,name) +VALUES ('MAINTAIN_MAIL','Maintain Mail in Master Data'); +INSERT INTO authority (authority,name) +VALUES ('VIEW_MAIL','View Mail in Master Data'); + +INSERT INTO user_authority (userId,authId) +VALUES (1,53); +INSERT INTO user_authority (userId,authId) +VALUES (1,54); \ No newline at end of file diff --git a/src/main/resources/templates/mail/TimesheetNotification.ftl b/src/main/resources/templates/mail/TimesheetNotification.ftl new file mode 100644 index 0000000..a7c75b6 --- /dev/null +++ b/src/main/resources/templates/mail/TimesheetNotification.ftl @@ -0,0 +1,28 @@ + + + + + + + + +
+
+ ${template} +
+ +
+ + \ No newline at end of file