| @@ -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]); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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 | |||||
| } | |||||
| } | |||||
| @@ -40,6 +40,14 @@ public abstract class SettingNames { | |||||
| public static final String MAIL_SMTP_AUTH = "MAIL.smtp.auth"; | 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 JS_VERSION = "JS.version"; | ||||
| public static final String REPORT_DAILYMAINT_RECIPIENTS_MECH = "REPORT.dailyMaint.recipients.mech"; | public static final String REPORT_DAILYMAINT_RECIPIENTS_MECH = "REPORT.dailyMaint.recipients.mech"; | ||||
| @@ -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<VCalendar> | |||||
| ) | |||||
| @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<VEvent> | |||||
| ) | |||||
| @JsonClass(generateAdapter = true) | |||||
| data class VEvent( | |||||
| val dtstart: List<Any>, // Can be a String or a Map | |||||
| val dtend: List<Any>, // Can be a String or a Map | |||||
| val transp: String, | |||||
| val uid: String, | |||||
| val summary: String | |||||
| ) | |||||
| @@ -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, | |||||
| ) | |||||
| @@ -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<CommonHolidayResponse> { | |||||
| 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<CommonHolidayResponse>() | |||||
| 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() | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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<CommonHolidayResponse>{ | |||||
| return holidayService.commonHolidayList() | |||||
| } | |||||
| } | |||||
| @@ -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<InternetAddress> = mutableListOf() | |||||
| open var subject: String? = null | |||||
| open var template: String? = null | |||||
| open var templateContent: String? = null | |||||
| open var args: Map<String, Any?> = mapOf() | |||||
| open var priority: Int? = null | |||||
| open var replyTo: InternetAddress? = null | |||||
| open var cc: List<InternetAddress> = mutableListOf() | |||||
| open var bcc: List<InternetAddress> = mutableListOf() | |||||
| open var attachments: Map<String, ByteArray> = 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<String>): 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<String>): 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<InternetAddress>): Builder { | |||||
| mailRequest.to = to | |||||
| return this | |||||
| } | |||||
| @Suppress("INAPPLICABLE_JVM_NAME") | |||||
| @JvmName("toListString") | |||||
| open fun to(to: List<String>): 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<String, Any?>): 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<InternetAddress>): Builder { | |||||
| mailRequest.cc = cc | |||||
| return this | |||||
| } | |||||
| @Suppress("INAPPLICABLE_JVM_NAME") | |||||
| @JvmName("ccListString") | |||||
| open fun cc(cc: List<String>): Builder { | |||||
| mailRequest.cc = cc.map { InternetAddress(it) } | |||||
| return this | |||||
| } | |||||
| @Suppress("INAPPLICABLE_JVM_NAME") | |||||
| @JvmName("bccListInternetAddress") | |||||
| open fun bcc(bcc: List<InternetAddress>): Builder { | |||||
| mailRequest.bcc = bcc | |||||
| return this | |||||
| } | |||||
| @Suppress("INAPPLICABLE_JVM_NAME") | |||||
| @JvmName("bccListString") | |||||
| open fun bcc(bcc: List<String>): Builder { | |||||
| mailRequest.bcc = bcc.map { InternetAddress(it) } | |||||
| return this | |||||
| } | |||||
| open fun attachments(attachments: Map<String, ByteArray>): Builder { | |||||
| mailRequest.attachments = attachments | |||||
| return this | |||||
| } | |||||
| open fun priority(priority: Int?): Builder { | |||||
| mailRequest.priority = priority | |||||
| return this | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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("[email protected]")) | |||||
| .addCc(cc) | |||||
| .addBcc(bcc) | |||||
| .build() | |||||
| val mailRequestList = mutableListOf<MailRequest>() | |||||
| 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<MailRequest>() | |||||
| mailRequestList += mailRequest | |||||
| mailService.send(mailRequestList) | |||||
| } | |||||
| } | |||||
| @@ -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() | |||||
| } | |||||
| } | |||||
| @@ -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<MailRequest>, 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<MailRequest>, 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<MailRequest>) { | |||||
| 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<MailRequest>, 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<MailRequest>) { | |||||
| asyncSend(mailRequests, LocaleUtils.getLocale()) | |||||
| } | |||||
| @Async | |||||
| @Throws(ParseException::class) | |||||
| open fun asyncSend(mailRequest: MailRequest) { | |||||
| asyncSend(mutableListOf(mailRequest)) | |||||
| } | |||||
| open fun saveMail(mailSave: MailSave): List<Settings> { | |||||
| // ------------------ save mail settings ------------------ // | |||||
| val settings = mutableListOf<Settings>() | |||||
| 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 | |||||
| } | |||||
| } | |||||
| @@ -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<Settings> { | |||||
| return settingsService.findAllByCategory("MAIL") | |||||
| } | |||||
| @GetMapping("/timesheet-template") | |||||
| fun mailTimesheetTemplate(): List<Settings> { | |||||
| return settingsService.findAllByCategory("TIMESHEET") | |||||
| } | |||||
| @PostMapping("/save") | |||||
| fun saveMail(@Valid @RequestBody mailSave: MailSave): List<Settings> { | |||||
| return mailService.saveMail(mailSave) | |||||
| } | |||||
| @GetMapping("/test") | |||||
| fun testMail() { | |||||
| mailReminderService.sendTimesheetReminderTest() | |||||
| } | |||||
| } | |||||
| @@ -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<Setting>, | |||||
| val template: Template | |||||
| ) | |||||
| @@ -3,7 +3,7 @@ package com.ffii.tsms.modules.timesheet.web | |||||
| import com.ffii.core.exception.BadRequestException | import com.ffii.core.exception.BadRequestException | ||||
| import com.ffii.tsms.modules.timesheet.entity.LeaveType | import com.ffii.tsms.modules.timesheet.entity.LeaveType | ||||
| import com.ffii.tsms.modules.timesheet.service.LeaveService | 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.service.TimesheetsService | ||||
| import com.ffii.tsms.modules.timesheet.web.models.* | import com.ffii.tsms.modules.timesheet.web.models.* | ||||
| import jakarta.servlet.http.HttpServletRequest | import jakarta.servlet.http.HttpServletRequest | ||||
| @@ -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'); | |||||
| @@ -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); | |||||
| @@ -0,0 +1,28 @@ | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |||||
| </head> | |||||
| <style> | |||||
| body { | |||||
| margin: 0; | |||||
| } | |||||
| .content { | |||||
| padding-top: 2rem; | |||||
| display: flex; | |||||
| } | |||||
| .container { | |||||
| padding: 2px 16px; | |||||
| } | |||||
| </style> | |||||
| <body> | |||||
| <main class="content"> | |||||
| <div class="container"> | |||||
| ${template} | |||||
| </div> | |||||
| </main> | |||||
| </body> | |||||
| </html> | |||||