Skip to content

Commit ee7eaa7

Browse files
committed
fix(user): Add conflict-safe for save activities
- Introduce `saveActivityIgnoreConflict` extension for `UserActivityRepository` - Use `ON CONFLICT DO NOTHING` for key user activity types: - PUBLISH_ARTICLE - POST_SERIES - PUBLISH_SERIES_CHAPTER - POST_QUESTION - POST_QUESTION_ANSWER - Bump version to 0.6.2
1 parent 449a668 commit ee7eaa7

8 files changed

Lines changed: 129 additions & 39 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010

1111
group = "me.loghub"
1212

13-
version = "0.6.1-SNAPSHOT"
13+
version = "0.6.2-SNAPSHOT"
1414

1515
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
1616

src/main/kotlin/me/loghub/api/aspect/article/ArticleAspect.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import me.loghub.api.entity.article.Article
55
import me.loghub.api.entity.user.User
66
import me.loghub.api.entity.user.UserActivity
77
import me.loghub.api.repository.user.UserActivityRepository
8+
import me.loghub.api.repository.user.saveActivityIgnoreConflict
89
import org.aspectj.lang.annotation.AfterReturning
910
import org.aspectj.lang.annotation.Aspect
1011
import org.springframework.stereotype.Component
@@ -48,17 +49,16 @@ class ArticleAspect(
4849
}
4950

5051
private fun addUserActivityAfterPublishArticle(postedArticle: Article) {
51-
postedArticle.publishedAt?.let { publishedAt ->
52-
userActivityRepository.save(
53-
UserActivity(
54-
action = UserActivity.Action.PUBLISH_ARTICLE,
55-
createdAt = publishedAt,
56-
createdDate = publishedAt.toLocalDate(),
57-
user = postedArticle.writer,
58-
article = postedArticle
59-
)
52+
val publishedAt = requireNotNull(postedArticle.publishedAt)
53+
userActivityRepository.saveActivityIgnoreConflict(
54+
UserActivity(
55+
action = UserActivity.Action.PUBLISH_ARTICLE,
56+
createdAt = publishedAt,
57+
createdDate = publishedAt.toLocalDate(),
58+
user = postedArticle.writer,
59+
article = postedArticle
6060
)
61-
}
61+
)
6262
}
6363

6464
private fun removeUserActivityAfterUnpublishArticle(editedArticle: Article) =

src/main/kotlin/me/loghub/api/aspect/question/QuestionAnswerAspect.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import me.loghub.api.entity.user.User
77
import me.loghub.api.entity.user.UserActivity
88
import me.loghub.api.lib.redis.key.RedisKeys
99
import me.loghub.api.repository.user.UserActivityRepository
10+
import me.loghub.api.repository.user.saveActivityIgnoreConflict
1011
import me.loghub.api.service.notification.NotificationService
1112
import org.aspectj.lang.annotation.AfterReturning
1213
import org.aspectj.lang.annotation.Aspect
@@ -85,13 +86,16 @@ class QuestionAnswerAspect(
8586
}
8687

8788
private fun addUserActivityAfterPostAnswer(postedAnswer: QuestionAnswer) {
88-
val activity = UserActivity(
89-
action = UserActivity.Action.POST_QUESTION_ANSWER,
90-
user = postedAnswer.writer,
91-
question = postedAnswer.question,
92-
questionAnswer = postedAnswer
89+
userActivityRepository.saveActivityIgnoreConflict(
90+
UserActivity(
91+
action = UserActivity.Action.POST_QUESTION_ANSWER,
92+
createdAt = postedAnswer.createdAt,
93+
createdDate = postedAnswer.createdAt.toLocalDate(),
94+
user = postedAnswer.writer,
95+
question = postedAnswer.question,
96+
questionAnswer = postedAnswer
97+
)
9398
)
94-
userActivityRepository.save(activity)
9599
}
96100

97101
private fun logAfterPostAnswer(answer: QuestionAnswer) =

src/main/kotlin/me/loghub/api/aspect/question/QuestionAspect.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import me.loghub.api.entity.question.Question
55
import me.loghub.api.entity.user.User
66
import me.loghub.api.entity.user.UserActivity
77
import me.loghub.api.repository.user.UserActivityRepository
8+
import me.loghub.api.repository.user.saveActivityIgnoreConflict
89
import org.aspectj.lang.annotation.AfterReturning
910
import org.aspectj.lang.annotation.Aspect
1011
import org.springframework.stereotype.Component
@@ -49,12 +50,15 @@ class QuestionAspect(
4950
}
5051

5152
private fun addUserActivityAfterPostQuestion(postedQuestion: Question) {
52-
val activity = UserActivity(
53-
action = UserActivity.Action.POST_QUESTION,
54-
user = postedQuestion.writer,
55-
question = postedQuestion
53+
userActivityRepository.saveActivityIgnoreConflict(
54+
UserActivity(
55+
action = UserActivity.Action.POST_QUESTION,
56+
createdAt = postedQuestion.createdAt,
57+
createdDate = postedQuestion.createdAt.toLocalDate(),
58+
user = postedQuestion.writer,
59+
question = postedQuestion
60+
)
5661
)
57-
userActivityRepository.save(activity)
5862
}
5963

6064
private fun logAfterPostQuestion(question: Question) =

src/main/kotlin/me/loghub/api/aspect/series/SeriesAspect.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import me.loghub.api.entity.series.Series
55
import me.loghub.api.entity.user.User
66
import me.loghub.api.entity.user.UserActivity
77
import me.loghub.api.repository.user.UserActivityRepository
8+
import me.loghub.api.repository.user.saveActivityIgnoreConflict
89
import org.aspectj.lang.annotation.AfterReturning
910
import org.aspectj.lang.annotation.Aspect
1011
import org.springframework.stereotype.Component
@@ -41,12 +42,15 @@ class SeriesAspect(
4142
}
4243

4344
private fun addUserActivityAfterPostSeries(postedSeries: Series) {
44-
val activity = UserActivity(
45-
action = UserActivity.Action.POST_SERIES,
46-
user = postedSeries.writer,
47-
series = postedSeries
45+
userActivityRepository.saveActivityIgnoreConflict(
46+
UserActivity(
47+
action = UserActivity.Action.POST_SERIES,
48+
createdAt = postedSeries.createdAt,
49+
createdDate = postedSeries.createdAt.toLocalDate(),
50+
user = postedSeries.writer,
51+
series = postedSeries
52+
)
4853
)
49-
userActivityRepository.save(activity)
5054
}
5155

5256
private fun logAfterPostSeries(series: Series) =

src/main/kotlin/me/loghub/api/aspect/series/SeriesChapterAspect.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import me.loghub.api.entity.series.SeriesChapter
55
import me.loghub.api.entity.user.User
66
import me.loghub.api.entity.user.UserActivity
77
import me.loghub.api.repository.user.UserActivityRepository
8+
import me.loghub.api.repository.user.saveActivityIgnoreConflict
89
import org.aspectj.lang.annotation.AfterReturning
910
import org.aspectj.lang.annotation.Aspect
1011
import org.springframework.stereotype.Component
@@ -57,18 +58,17 @@ class SeriesChapterAspect(
5758
logAfterDeleteChapter(seriesId, sequence, writer)
5859

5960
private fun addUserActivityAfterPublishChapter(postedChapter: SeriesChapter) {
60-
postedChapter.publishedAt?.let { publishedAt ->
61-
userActivityRepository.save(
62-
UserActivity(
63-
action = UserActivity.Action.PUBLISH_SERIES_CHAPTER,
64-
createdAt = publishedAt,
65-
createdDate = publishedAt.toLocalDate(),
66-
user = postedChapter.writer,
67-
series = postedChapter.series,
68-
seriesChapter = postedChapter,
69-
)
61+
val publishedAt = requireNotNull(postedChapter.publishedAt)
62+
userActivityRepository.saveActivityIgnoreConflict(
63+
UserActivity(
64+
action = UserActivity.Action.PUBLISH_SERIES_CHAPTER,
65+
createdAt = publishedAt,
66+
createdDate = publishedAt.toLocalDate(),
67+
user = postedChapter.writer,
68+
series = postedChapter.series,
69+
seriesChapter = postedChapter
7070
)
71-
}
71+
)
7272
}
7373

7474
private fun removeUserActivityAfterUnpublishChapter(editedChapter: SeriesChapter) =

src/main/kotlin/me/loghub/api/repository/user/UserActivityRepository.kt

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import me.loghub.api.entity.series.SeriesChapter
77
import me.loghub.api.entity.user.User
88
import me.loghub.api.entity.user.UserActivity
99
import org.springframework.data.jpa.repository.JpaRepository
10+
import org.springframework.data.jpa.repository.Modifying
1011
import org.springframework.data.jpa.repository.Query
12+
import org.springframework.data.repository.query.Param
1113
import java.time.LocalDate
1214

1315
interface UserActivityRepository : JpaRepository<UserActivity, Long> {
@@ -59,7 +61,67 @@ interface UserActivityRepository : JpaRepository<UserActivity, Long> {
5961
@Query(value = NativeQuery.SELECT_PROJECTION_BY_USER_AND_CREATED_DATE, nativeQuery = true)
6062
fun findProjectionByUserIdAndCreatedDate(userId: Long, createdDate: LocalDate): List<UserActivityProjection>
6163

64+
@Modifying
65+
@Query(
66+
value = """
67+
INSERT INTO user_activities (action, created_at, created_date, user_id, article_id)
68+
VALUES (CAST(:#{#a.action.name()} AS user_action_enum), :#{#a.createdAt}, :#{#a.createdDate}, :#{#a.user.id}, :#{#a.article.id})
69+
ON CONFLICT (user_id, article_id) WHERE action = 'PUBLISH_ARTICLE' DO NOTHING
70+
""", nativeQuery = true
71+
)
72+
fun saveArticleActivity(@Param("a") a: UserActivity)
73+
74+
@Modifying
75+
@Query(
76+
value = """
77+
INSERT INTO user_activities (action, created_at, created_date, user_id, series_id)
78+
VALUES (CAST(:#{#a.action.name()} AS user_action_enum), :#{#a.createdAt}, :#{#a.createdDate}, :#{#a.user.id}, :#{#a.series.id})
79+
ON CONFLICT (user_id, series_id) WHERE action = 'POST_SERIES' DO NOTHING
80+
""", nativeQuery = true
81+
)
82+
fun saveSeriesActivity(@Param("a") a: UserActivity)
83+
84+
@Modifying
85+
@Query(
86+
value = """
87+
INSERT INTO user_activities (action, created_at, created_date, user_id, series_id, series_chapter_id)
88+
VALUES (CAST(:#{#a.action.name()} AS user_action_enum), :#{#a.createdAt}, :#{#a.createdDate}, :#{#a.user.id}, :#{#a.series.id}, :#{#a.seriesChapter.id})
89+
ON CONFLICT (user_id, series_chapter_id) WHERE action = 'PUBLISH_SERIES_CHAPTER' DO NOTHING
90+
""", nativeQuery = true
91+
)
92+
fun saveSeriesChapterActivity(@Param("a") a: UserActivity)
93+
94+
@Modifying
95+
@Query(
96+
value = """
97+
INSERT INTO user_activities (action, created_at, created_date, user_id, question_id)
98+
VALUES (CAST(:#{#a.action.name()} AS user_action_enum), :#{#a.createdAt}, :#{#a.createdDate}, :#{#a.user.id}, :#{#a.question.id})
99+
ON CONFLICT (user_id, question_id) WHERE action = 'POST_QUESTION' DO NOTHING
100+
""", nativeQuery = true
101+
)
102+
fun saveQuestionActivity(@Param("a") a: UserActivity)
103+
104+
@Modifying
105+
@Query(
106+
value = """
107+
INSERT INTO user_activities (action, created_at, created_date, user_id, question_id, question_answer_id)
108+
VALUES (CAST(:#{#a.action.name()} AS user_action_enum), :#{#a.createdAt}, :#{#a.createdDate}, :#{#a.user.id}, :#{#a.question.id}, :#{#a.questionAnswer.id})
109+
ON CONFLICT (user_id, question_answer_id) WHERE action = 'POST_QUESTION_ANSWER' DO NOTHING
110+
""", nativeQuery = true
111+
)
112+
fun saveQuestionAnswerActivity(@Param("a") a: UserActivity)
113+
62114
fun deleteByArticle(article: Article)
63115

64116
fun deleteBySeriesChapter(seriesChapter: SeriesChapter)
65-
}
117+
}
118+
119+
fun UserActivityRepository.saveActivityIgnoreConflict(activity: UserActivity) {
120+
when (activity.action) {
121+
UserActivity.Action.PUBLISH_ARTICLE -> saveArticleActivity(activity)
122+
UserActivity.Action.POST_SERIES -> saveSeriesActivity(activity)
123+
UserActivity.Action.PUBLISH_SERIES_CHAPTER -> saveSeriesChapterActivity(activity)
124+
UserActivity.Action.POST_QUESTION -> saveQuestionActivity(activity)
125+
UserActivity.Action.POST_QUESTION_ANSWER -> saveQuestionAnswerActivity(activity)
126+
}
127+
}

src/main/resources/database/public/indexes.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,19 @@ CREATE INDEX IF NOT EXISTS question_answers_question_id_created_at_idx ON public
3131

3232
CREATE INDEX IF NOT EXISTS user_stars_user_id_created_at_idx ON public.user_stars (user_id, created_at DESC);
3333
CREATE INDEX IF NOT EXISTS user_activities_user_id_created_date_idx ON public.user_activities (user_id, created_date);
34+
35+
CREATE UNIQUE INDEX IF NOT EXISTS user_activities_user_id_article_id_uq_idx
36+
ON public.user_activities (user_id, article_id)
37+
WHERE action = 'PUBLISH_ARTICLE';
38+
CREATE UNIQUE INDEX IF NOT EXISTS user_activities_user_id_series_id_uq_idx
39+
ON public.user_activities (user_id, series_id)
40+
WHERE action = 'POST_SERIES';
41+
CREATE UNIQUE INDEX IF NOT EXISTS user_activities_user_id_series_chapter_id_uq_idx
42+
ON public.user_activities (user_id, series_chapter_id)
43+
WHERE action = 'PUBLISH_SERIES_CHAPTER';
44+
CREATE UNIQUE INDEX IF NOT EXISTS user_activities_user_id_question_id_uq_idx
45+
ON public.user_activities (user_id, question_id)
46+
WHERE action = 'POST_QUESTION';
47+
CREATE UNIQUE INDEX IF NOT EXISTS user_activities_user_id_question_answer_id_uq_idx
48+
ON public.user_activities (user_id, question_answer_id)
49+
WHERE action = 'POST_QUESTION_ANSWER';

0 commit comments

Comments
 (0)