Skip to content

Commit 7dd182a

Browse files
authored
feat: Add comment notifications for posts (#75)
2 parents c0dd4bf + 4d6c6fa commit 7dd182a

File tree

12 files changed

+168
-14
lines changed

12 files changed

+168
-14
lines changed

backend/mail-service/src/main/kotlin/io/blog/mail/mail/controller/EmailController.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class EmailController {
1515

1616
@PostMapping("/send")
1717
fun sendEmail(@RequestBody message: AuthenticationMessage) {
18-
emailService.sendEmail(MessageType.EMAIL, message.email, message.subject, message.authCode)
18+
val params = HashMap<String, String>()
19+
params["authCode"] = message.authCode
20+
emailService.sendEmail(MessageType.EMAIL, message.email, message.subject, params)
1921
}
2022
}

backend/mail-service/src/main/kotlin/io/blog/mail/mail/service/EmailService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ package io.blog.mail.mail.service
33
import io.blog.mail.mail.types.MessageType
44

55
interface EmailService {
6-
fun sendEmail(type: MessageType, email: String, subject: String, authCode: String)
7-
fun setContext(type: MessageType, authCode: String) : String
6+
fun sendEmail(type: MessageType, email: String, subject: String, params: HashMap<String, String>)
7+
fun setContext(type: MessageType, params: HashMap<String, String>) : String
88
}

backend/mail-service/src/main/kotlin/io/blog/mail/mail/service/EmailServiceImpl.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,28 @@ class EmailServiceImpl : EmailService {
1717
@Autowired
1818
private lateinit var templateEngine: SpringTemplateEngine
1919

20-
override fun sendEmail(type: MessageType, email: String, subject: String, authCode: String) {
20+
override fun sendEmail(type: MessageType, email: String, subject: String, params: HashMap<String, String>) {
2121
val mimeMessage: MimeMessage = emailSender.createMimeMessage()
2222

2323
try {
2424
val helper = MimeMessageHelper(mimeMessage, true, "utf-8")
2525
helper.setTo(email)
2626
helper.setSubject(subject)
27-
helper.setText(this.setContext(type, authCode), true)
27+
helper.setText(this.setContext(type, params), true)
2828
emailSender.send(mimeMessage)
2929
} catch (e : Exception) {
3030
println("Failed to send email $e")
3131
}
3232
}
3333

34-
override fun setContext(type: MessageType, authCode: String): String {
34+
override fun setContext(type: MessageType, params: HashMap<String, String>): String {
3535
val context = Context()
36-
context.setVariable("authCode", authCode)
36+
37+
for (entry in params.entries) {
38+
context.setVariable(entry.key, entry.value)
39+
}
40+
41+
// context.setVariable("authCode", authCode)
3742
return templateEngine.process(type.name.lowercase(), context)
3843
}
3944
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package io.blog.mail.mail.types
22

33
enum class MessageType {
4-
EMAIL, PASSWORD
4+
EMAIL, PASSWORD, COMMENT
55
}

backend/mail-service/src/main/kotlin/io/blog/mail/redis/config/RedisConfig.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.blog.mail.redis.config
22

3+
import io.blog.mail.redis.service.CommentEmailSubService
34
import io.blog.mail.redis.service.VerifyEmailSubService
45
import org.springframework.beans.factory.annotation.Autowired
56
import org.springframework.beans.factory.annotation.Value
@@ -17,6 +18,8 @@ import org.springframework.data.redis.listener.adapter.MessageListenerAdapter
1718
class RedisConfig {
1819
@Autowired
1920
private lateinit var verifyEmailSubService: VerifyEmailSubService
21+
@Autowired
22+
private lateinit var commentEmailSubService: CommentEmailSubService
2023

2124
@Value("\${spring.data.redis.host}")
2225
var redisHost: String = "localhost"
@@ -30,21 +33,32 @@ class RedisConfig {
3033
return LettuceConnectionFactory(redisHost, redisPort)
3134
}
3235

36+
@Bean
37+
fun verifyEmailTopic(): ChannelTopic {
38+
return ChannelTopic("VerifyEmail")
39+
}
40+
3341
@Bean
3442
fun verifyEmailMessageListenerAdapter(): MessageListenerAdapter {
3543
return MessageListenerAdapter(verifyEmailSubService)
3644
}
3745

46+
@Bean
47+
fun commentEmailTopic(): ChannelTopic {
48+
return ChannelTopic("CommentEmail")
49+
}
50+
51+
@Bean
52+
fun commentEmailMessageListenerAdapter(): MessageListenerAdapter {
53+
return MessageListenerAdapter(commentEmailSubService)
54+
}
55+
3856
@Bean
3957
fun redisContainer(): RedisMessageListenerContainer {
4058
val container = RedisMessageListenerContainer()
4159
container.setConnectionFactory(redisConnectionFactory())
4260
container.addMessageListener(verifyEmailMessageListenerAdapter(), verifyEmailTopic())
61+
container.addMessageListener(commentEmailMessageListenerAdapter(), commentEmailTopic())
4362
return container
4463
}
45-
46-
@Bean
47-
fun verifyEmailTopic(): ChannelTopic {
48-
return ChannelTopic("VerifyEmail")
49-
}
5064
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.blog.mail.redis.message
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
data class CommentEmailMessage(
6+
@JsonProperty("email") val email: String,
7+
@JsonProperty("subject") val subject: String,
8+
@JsonProperty("postName") val postName: String,
9+
@JsonProperty("postUrl") val postUrl: String,
10+
@JsonProperty("commentContent") val commentContent: String,
11+
@JsonProperty("commentAuthor") val commentAuthor: String
12+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.blog.mail.redis.service
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper
4+
import io.blog.mail.mail.service.EmailService
5+
import io.blog.mail.mail.types.MessageType
6+
import io.blog.mail.redis.message.CommentEmailMessage
7+
import org.springframework.beans.factory.annotation.Autowired
8+
import org.springframework.data.redis.connection.Message
9+
import org.springframework.data.redis.connection.MessageListener
10+
import org.springframework.stereotype.Service
11+
12+
@Service
13+
class CommentEmailSubService : MessageListener {
14+
@Autowired
15+
private lateinit var emailService: EmailService
16+
private val mapper = ObjectMapper()
17+
18+
override fun onMessage(message: Message, pattern: ByteArray?) {
19+
try {
20+
val emailMessage = mapper.readValue(message.body, CommentEmailMessage::class.java)
21+
22+
val params = HashMap<String, String>()
23+
params["postName"] = emailMessage.postName
24+
params["postUrl"] = emailMessage.postUrl
25+
params["commentContent"] = emailMessage.commentContent
26+
params["commentAuthor"] = emailMessage.commentAuthor
27+
28+
emailService.sendEmail(MessageType.COMMENT, emailMessage.email, emailMessage.subject, params)
29+
}
30+
catch (e: Exception) {
31+
e.printStackTrace()
32+
}
33+
}
34+
}

backend/mail-service/src/main/kotlin/io/blog/mail/redis/service/VerifyEmailSubService.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ class VerifyEmailSubService : MessageListener {
2424
println("Subject: ${verifyEmailMessage.subject}")
2525
println("Code: ${verifyEmailMessage.code}")
2626

27-
emailService.sendEmail(MessageType.EMAIL, verifyEmailMessage.email, verifyEmailMessage.subject, verifyEmailMessage.code)
27+
val params = HashMap<String, String>()
28+
params["authCode"] = verifyEmailMessage.code
29+
30+
emailService.sendEmail(MessageType.EMAIL, verifyEmailMessage.email, verifyEmailMessage.subject, params)
2831
}
2932
catch (e: Exception) {
3033
e.printStackTrace()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<html xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>댓글 알림</title>
7+
</head>
8+
<body style="width: 100%; height: 100%;">
9+
<div style="width: 100%; height: 100%; font-family: 'Noto Sans', sans-serif; background-color: #f8f9fa; margin: 0; padding: 0; text-align: center; color: #333;">
10+
<div class="container" style="max-width: 600px; margin: 30px auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; border: 1px solid #e9ecef;">
11+
<h1 style="font-size: 24px; margin-bottom: 10px; color: #007bff;">새로운 댓글이 달렸어요!</h1>
12+
<p style="font-size: 16px; line-height: 1.5; margin: 10px 0;">
13+
<strong th:text="${commentAuthor}">작성자</strong>님이
14+
<strong th:text="${postName}">게시글 제목</strong>에 댓글을 남겼습니다.
15+
</p>
16+
<div style="font-size: 16px; background-color: #f1f3f5; padding: 15px; border-radius: 4px; margin: 15px 0;" th:text="${commentContent}">
17+
댓글 내용
18+
</div>
19+
<p style="font-size: 14px; line-height: 1.5; color: #6c757d;">
20+
더 많은 내용을 확인하려면 게시글을 방문해 주세요.
21+
</p>
22+
<!-- 방문 버튼 -->
23+
<a th:href="${postUrl}"
24+
style="display: inline-block; padding: 12px 24px; background-color: #007bff; color: #ffffff; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: bold; box-shadow: 0 2px 6px rgba(0, 123, 255, 0.2);">
25+
댓글 보러 가기
26+
</a>
27+
<div class="footer" style="font-size: 12px; color: #6c757d; margin-top: 20px;">
28+
<p>이 메일은 자동 발송된 메일입니다. 회신하지 마세요.</p>
29+
</div>
30+
</div>
31+
</div>
32+
</body>
33+
</html>

backend/main-service/src/main/java/io/blog/devlog/domain/comment/controller/CommentController.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import io.blog.devlog.domain.user.service.UserService;
1313
import io.blog.devlog.global.exception.NoPermissionException;
1414
import io.blog.devlog.global.exception.NotFoundException;
15+
import io.blog.devlog.global.redis.message.CommentEmailMessage;
16+
import io.blog.devlog.global.redis.service.CommentEmailPubService;
1517
import lombok.RequiredArgsConstructor;
1618
import lombok.extern.slf4j.Slf4j;
1719
import org.springframework.http.ResponseEntity;
@@ -27,9 +29,12 @@
2729
@RequestMapping("/comments")
2830
@Slf4j
2931
public class CommentController {
32+
3033
private final UserService userService;
3134
private final PostService postService;
3235
private final CommentService commentService;
36+
private final CommentEmailPubService commentEmailPubService;
37+
3338
@PostMapping
3439
public void uploadComment(@RequestBody RequestCommentDto requestCommentDto) {
3540
log.info("RequestCommentDto : " + requestCommentDto);
@@ -42,6 +47,16 @@ public void uploadComment(@RequestBody RequestCommentDto requestCommentDto) {
4247
throw new NoPermissionException("댓글을 작성할 권한이 없습니다.");
4348
}
4449
commentService.saveComment(user, requestCommentDto, postDetail.getPost());
50+
51+
CommentEmailMessage emailMessage = new CommentEmailMessage(
52+
postDetail.getPost().getUser().getEmail(),
53+
String.format("[devLog] {%s} 게시글에 댓글이 달렸습니다.", postDetail.getPost().getTitle()),
54+
postDetail.getPost().getTitle(),
55+
postDetail.getPost().getUrl(),
56+
requestCommentDto.getContent(),
57+
user.getUsername()
58+
);
59+
commentEmailPubService.sendEmail(emailMessage);
4560
}
4661

4762
@PostMapping("/{commentId}")

0 commit comments

Comments
 (0)