QPAT commited on
Commit
c2446b7
·
0 Parent(s):

Deploy to HF with Git LFS for all media files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .PVS-Studio/State/WindowState.json +23 -0
  2. .PVS-Studio/Temp/.PVS-Studio/.gitignore +2 -0
  3. .PVS-Studio/nonAnalyzedProjects.log +1 -0
  4. .PVS-Studio/project.json +329 -0
  5. .gitattributes +7 -0
  6. .gitignore +33 -0
  7. .mvn/wrapper/maven-wrapper.properties +3 -0
  8. DockerFile +13 -0
  9. README.md +1 -0
  10. TEST_CASES.md +104 -0
  11. mvnw +295 -0
  12. mvnw.cmd +189 -0
  13. pom.xml +130 -0
  14. src/main/java/com/darkfantasy/DarkFantasyApplication.java +19 -0
  15. src/main/java/com/darkfantasy/annotation/ChangePasswordMatch.java +23 -0
  16. src/main/java/com/darkfantasy/annotation/RegisterPasswordMatch.java +23 -0
  17. src/main/java/com/darkfantasy/annotation/ResetPasswordMatch.java +23 -0
  18. src/main/java/com/darkfantasy/config/SchedulingConfig.java +5 -0
  19. src/main/java/com/darkfantasy/config/WebConfig.java +27 -0
  20. src/main/java/com/darkfantasy/constant/CmsMenu.java +16 -0
  21. src/main/java/com/darkfantasy/constant/Routes.java +18 -0
  22. src/main/java/com/darkfantasy/controller/AdminController.java +109 -0
  23. src/main/java/com/darkfantasy/controller/ArticleController.java +195 -0
  24. src/main/java/com/darkfantasy/controller/ContactMessageController.java +62 -0
  25. src/main/java/com/darkfantasy/controller/ContributorController.java +181 -0
  26. src/main/java/com/darkfantasy/controller/DashboardController.java +46 -0
  27. src/main/java/com/darkfantasy/controller/FaqController.java +161 -0
  28. src/main/java/com/darkfantasy/controller/GameCharacterController.java +163 -0
  29. src/main/java/com/darkfantasy/controller/ImageController.java +42 -0
  30. src/main/java/com/darkfantasy/controller/InternalController.java +26 -0
  31. src/main/java/com/darkfantasy/controller/PageController.java +206 -0
  32. src/main/java/com/darkfantasy/controller/StoryController.java +171 -0
  33. src/main/java/com/darkfantasy/controller/UserController.java +321 -0
  34. src/main/java/com/darkfantasy/controller/WorldController.java +162 -0
  35. src/main/java/com/darkfantasy/dto/article/ArticleResponse.java +58 -0
  36. src/main/java/com/darkfantasy/dto/article/CreateArticleRequest.java +40 -0
  37. src/main/java/com/darkfantasy/dto/article/UpdateArticleRequest.java +32 -0
  38. src/main/java/com/darkfantasy/dto/contact/ContactMessageResponse.java +42 -0
  39. src/main/java/com/darkfantasy/dto/contact/CreateContactMessageRequest.java +49 -0
  40. src/main/java/com/darkfantasy/dto/contributor/ContributorResponse.java +41 -0
  41. src/main/java/com/darkfantasy/dto/contributor/CreateContributorRequest.java +44 -0
  42. src/main/java/com/darkfantasy/dto/contributor/UpdateContributorRequest.java +34 -0
  43. src/main/java/com/darkfantasy/dto/faq/CreateFaqRequest.java +41 -0
  44. src/main/java/com/darkfantasy/dto/faq/FaqResponse.java +30 -0
  45. src/main/java/com/darkfantasy/dto/faq/UpdateFaqRequest.java +29 -0
  46. src/main/java/com/darkfantasy/dto/gamecharacter/CreateGameCharacterRequest.java +52 -0
  47. src/main/java/com/darkfantasy/dto/gamecharacter/GameCharacterResponse.java +36 -0
  48. src/main/java/com/darkfantasy/dto/gamecharacter/UpdateGameCharacterRequest.java +48 -0
  49. src/main/java/com/darkfantasy/dto/log/AuditLogResponse.java +37 -0
  50. src/main/java/com/darkfantasy/dto/story/CreateStoryRequest.java +50 -0
.PVS-Studio/State/WindowState.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "isDataChanged": false,
3
+ "filters": {
4
+ "code": "",
5
+ "cwe": "",
6
+ "sastId": "",
7
+ "message": "",
8
+ "files": "",
9
+ "level": [
10
+ 0,
11
+ 1,
12
+ 2
13
+ ],
14
+ "isColumnFiltersVisible": false,
15
+ "isLevelFiltersVisible": true,
16
+ "isGroupFiltersVisible": true,
17
+ "type": [
18
+ "General",
19
+ "Optimization"
20
+ ]
21
+ },
22
+ "loadableProcessIds": []
23
+ }
.PVS-Studio/Temp/.PVS-Studio/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ logs/
2
+ models/
.PVS-Studio/nonAnalyzedProjects.log ADDED
@@ -0,0 +1 @@
 
 
1
+ []
.PVS-Studio/project.json ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "modules": [
3
+ {
4
+ "name": "DarkMoon1",
5
+ "path": "f:\\DarkMoon1\\",
6
+ "sources": [
7
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\DarkFantasyApplication.java",
8
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\ChangePasswordMatch.java",
9
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\RegisterPasswordMatch.java",
10
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\ResetPasswordMatch.java",
11
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\config\\SchedulingConfig.java",
12
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\config\\WebConfig.java",
13
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\constant\\CmsMenu.java",
14
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\constant\\Routes.java",
15
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\AdminController.java",
16
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ArticleController.java",
17
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ContactMessageController.java",
18
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\DashboardController.java",
19
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\FaqController.java",
20
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\GameCharacterController.java",
21
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ImageController.java",
22
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\InternalController.java",
23
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\PageController.java",
24
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\StoryController.java",
25
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\UserController.java",
26
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\WorldController.java",
27
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\ArticleResponse.java",
28
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\CreateArticleRequest.java",
29
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\UpdateArticleRequest.java",
30
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\contact\\ContactMessageResponse.java",
31
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\contact\\CreateContactMessageRequest.java",
32
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\CreateFaqRequest.java",
33
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\FaqResponse.java",
34
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\UpdateFaqRequest.java",
35
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\CreateGameCharacterRequest.java",
36
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\GameCharacterResponse.java",
37
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\UpdateGameCharacterRequest.java",
38
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\log\\AuditLogResponse.java",
39
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\CreateStoryRequest.java",
40
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\StoryResponse.java",
41
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\UpdateStoryRequest.java",
42
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ChangePasswordRequest.java",
43
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ForgotPasswordRequest.java",
44
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\LoginRequest.java",
45
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\RegisterRequest.java",
46
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ResetPasswordRequest.java",
47
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\UserResponse.java",
48
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\VerifyOtpRequest.java",
49
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\CreateWorldRequest.java",
50
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\UpdateWorldRequest.java",
51
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\WorldResponse.java",
52
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Article.java",
53
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\AuditLog.java",
54
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\ContactMessage.java",
55
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Faq.java",
56
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\GameCharacter.java",
57
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\PasswordResetToken.java",
58
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Story.java",
59
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\User.java",
60
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\World.java",
61
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ArticleTypeConverter.java",
62
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ContactCategoryConverter.java",
63
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ContactPlatformConverter.java",
64
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\LogActionConverter.java",
65
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\LogEntityTypeConverter.java",
66
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\RoleConverter.java",
67
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ArticleType.java",
68
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ContactCategory.java",
69
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ContactPlatform.java",
70
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\LogAction.java",
71
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\LogEntityType.java",
72
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\Role.java",
73
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\exception\\GlobalExceptionHandler.java",
74
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\ArticleRepository.java",
75
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\AuditLogRepository.java",
76
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\ContactMessageRepository.java",
77
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\FaqRepository.java",
78
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\GameCharacterRepository.java",
79
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\PasswordResetTokenRepository.java",
80
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\StoryRepository.java",
81
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\UserRepository.java",
82
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\WorldRepository.java",
83
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\security\\ForceChangePasswordInterceptor.java",
84
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\security\\SecurityConfig.java",
85
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\ArticleService.java",
86
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\AuditLogService.java",
87
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\ContactMessageService.java",
88
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\CustomUserDetailsService.java",
89
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\FaqService.java",
90
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\FileStorageService.java",
91
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\GameCharacterService.java",
92
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\StoryService.java",
93
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\UserService.java",
94
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\WorldService.java",
95
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\ArticleServiceImpl.java",
96
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\AuditLogServiceImpl.java",
97
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\ContactMessageServiceImpl.java",
98
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\FaqServiceImpl.java",
99
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\FileStorageServiceImpl.java",
100
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\GameCharacterServiceImpl.java",
101
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\StoryServiceImpl.java",
102
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\UserServiceImpl.java",
103
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\WorldServiceImpl.java",
104
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\util\\SecurityUtil.java",
105
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\util\\TimeUtil.java",
106
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\PasswordChangeMatchValidator.java",
107
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\PasswordRegisterMatchValidator.java",
108
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\ResetPasswordMatchValidator.java",
109
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\DarkFantasyApplication.java",
110
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\ChangePasswordMatch.java",
111
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\RegisterPasswordMatch.java",
112
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\annotation\\ResetPasswordMatch.java",
113
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\config\\SchedulingConfig.java",
114
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\config\\WebConfig.java",
115
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\constant\\CmsMenu.java",
116
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\constant\\Routes.java",
117
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\AdminController.java",
118
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ArticleController.java",
119
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ContactMessageController.java",
120
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\DashboardController.java",
121
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\FaqController.java",
122
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\GameCharacterController.java",
123
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\ImageController.java",
124
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\InternalController.java",
125
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\PageController.java",
126
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\StoryController.java",
127
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\UserController.java",
128
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\controller\\WorldController.java",
129
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\ArticleResponse.java",
130
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\CreateArticleRequest.java",
131
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\article\\UpdateArticleRequest.java",
132
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\contact\\ContactMessageResponse.java",
133
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\contact\\CreateContactMessageRequest.java",
134
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\CreateFaqRequest.java",
135
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\FaqResponse.java",
136
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\faq\\UpdateFaqRequest.java",
137
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\CreateGameCharacterRequest.java",
138
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\GameCharacterResponse.java",
139
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\gamecharacter\\UpdateGameCharacterRequest.java",
140
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\log\\AuditLogResponse.java",
141
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\CreateStoryRequest.java",
142
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\StoryResponse.java",
143
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\story\\UpdateStoryRequest.java",
144
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ChangePasswordRequest.java",
145
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ForgotPasswordRequest.java",
146
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\LoginRequest.java",
147
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\RegisterRequest.java",
148
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\ResetPasswordRequest.java",
149
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\UserResponse.java",
150
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\user\\VerifyOtpRequest.java",
151
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\CreateWorldRequest.java",
152
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\UpdateWorldRequest.java",
153
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\dto\\world\\WorldResponse.java",
154
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Article.java",
155
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\AuditLog.java",
156
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\ContactMessage.java",
157
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Faq.java",
158
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\GameCharacter.java",
159
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\PasswordResetToken.java",
160
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\Story.java",
161
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\User.java",
162
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\World.java",
163
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ArticleTypeConverter.java",
164
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ContactCategoryConverter.java",
165
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\ContactPlatformConverter.java",
166
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\LogActionConverter.java",
167
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\LogEntityTypeConverter.java",
168
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\converter\\RoleConverter.java",
169
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ArticleType.java",
170
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ContactCategory.java",
171
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\ContactPlatform.java",
172
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\LogAction.java",
173
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\LogEntityType.java",
174
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\entity\\enums\\Role.java",
175
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\exception\\GlobalExceptionHandler.java",
176
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\ArticleRepository.java",
177
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\AuditLogRepository.java",
178
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\ContactMessageRepository.java",
179
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\FaqRepository.java",
180
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\GameCharacterRepository.java",
181
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\PasswordResetTokenRepository.java",
182
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\StoryRepository.java",
183
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\UserRepository.java",
184
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\repository\\WorldRepository.java",
185
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\security\\ForceChangePasswordInterceptor.java",
186
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\security\\SecurityConfig.java",
187
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\ArticleService.java",
188
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\AuditLogService.java",
189
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\ContactMessageService.java",
190
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\CustomUserDetailsService.java",
191
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\FaqService.java",
192
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\FileStorageService.java",
193
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\GameCharacterService.java",
194
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\StoryService.java",
195
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\UserService.java",
196
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\WorldService.java",
197
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\ArticleServiceImpl.java",
198
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\AuditLogServiceImpl.java",
199
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\ContactMessageServiceImpl.java",
200
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\FaqServiceImpl.java",
201
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\FileStorageServiceImpl.java",
202
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\GameCharacterServiceImpl.java",
203
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\StoryServiceImpl.java",
204
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\UserServiceImpl.java",
205
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\service\\impl\\WorldServiceImpl.java",
206
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\util\\SecurityUtil.java",
207
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\util\\TimeUtil.java",
208
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\PasswordChangeMatchValidator.java",
209
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\PasswordRegisterMatchValidator.java",
210
+ "f:\\DarkMoon1\\src\\main\\java\\com\\darkfantasy\\validator\\ResetPasswordMatchValidator.java",
211
+ "f:\\DarkMoon1\\target\\"
212
+ ],
213
+ "testSources": [],
214
+ "generatedSources": [
215
+ "f:\\DarkMoon1\\target\\"
216
+ ],
217
+ "classpath": [
218
+ "F:\\DarkMoon1\\target\\classes",
219
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-mail\\3.2.5\\spring-boot-starter-mail-3.2.5.jar",
220
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter\\3.2.5\\spring-boot-starter-3.2.5.jar",
221
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-logging\\3.2.5\\spring-boot-starter-logging-3.2.5.jar",
222
+ "C:\\Users\\Admin\\.m2\\repository\\ch\\qos\\logback\\logback-classic\\1.4.14\\logback-classic-1.4.14.jar",
223
+ "C:\\Users\\Admin\\.m2\\repository\\ch\\qos\\logback\\logback-core\\1.4.14\\logback-core-1.4.14.jar",
224
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\logging\\log4j\\log4j-to-slf4j\\2.21.1\\log4j-to-slf4j-2.21.1.jar",
225
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\logging\\log4j\\log4j-api\\2.21.1\\log4j-api-2.21.1.jar",
226
+ "C:\\Users\\Admin\\.m2\\repository\\org\\slf4j\\jul-to-slf4j\\2.0.13\\jul-to-slf4j-2.0.13.jar",
227
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-core\\6.1.6\\spring-core-6.1.6.jar",
228
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-jcl\\6.1.6\\spring-jcl-6.1.6.jar",
229
+ "C:\\Users\\Admin\\.m2\\repository\\org\\yaml\\snakeyaml\\2.2\\snakeyaml-2.2.jar",
230
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-context-support\\6.1.6\\spring-context-support-6.1.6.jar",
231
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-beans\\6.1.6\\spring-beans-6.1.6.jar",
232
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-context\\6.1.6\\spring-context-6.1.6.jar",
233
+ "C:\\Users\\Admin\\.m2\\repository\\org\\eclipse\\angus\\jakarta.mail\\2.0.3\\jakarta.mail-2.0.3.jar",
234
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\activation\\jakarta.activation-api\\2.1.3\\jakarta.activation-api-2.1.3.jar",
235
+ "C:\\Users\\Admin\\.m2\\repository\\org\\eclipse\\angus\\angus-activation\\2.0.2\\angus-activation-2.0.2.jar",
236
+ "C:\\Users\\Admin\\.m2\\repository\\org\\thymeleaf\\extras\\thymeleaf-extras-springsecurity6\\3.1.2.RELEASE\\thymeleaf-extras-springsecurity6-3.1.2.RELEASE.jar",
237
+ "C:\\Users\\Admin\\.m2\\repository\\org\\thymeleaf\\thymeleaf-spring6\\3.1.2.RELEASE\\thymeleaf-spring6-3.1.2.RELEASE.jar",
238
+ "C:\\Users\\Admin\\.m2\\repository\\org\\thymeleaf\\thymeleaf\\3.1.2.RELEASE\\thymeleaf-3.1.2.RELEASE.jar",
239
+ "C:\\Users\\Admin\\.m2\\repository\\org\\attoparser\\attoparser\\2.0.7.RELEASE\\attoparser-2.0.7.RELEASE.jar",
240
+ "C:\\Users\\Admin\\.m2\\repository\\org\\unbescape\\unbescape\\1.1.6.RELEASE\\unbescape-1.1.6.RELEASE.jar",
241
+ "C:\\Users\\Admin\\.m2\\repository\\org\\slf4j\\slf4j-api\\2.0.13\\slf4j-api-2.0.13.jar",
242
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-thymeleaf\\3.2.5\\spring-boot-starter-thymeleaf-3.2.5.jar",
243
+ "C:\\Users\\Admin\\.m2\\repository\\org\\mariadb\\jdbc\\mariadb-java-client\\3.3.3\\mariadb-java-client-3.3.3.jar",
244
+ "C:\\Users\\Admin\\.m2\\repository\\com\\github\\waffle\\waffle-jna\\3.3.0\\waffle-jna-3.3.0.jar",
245
+ "C:\\Users\\Admin\\.m2\\repository\\net\\java\\dev\\jna\\jna\\5.13.0\\jna-5.13.0.jar",
246
+ "C:\\Users\\Admin\\.m2\\repository\\net\\java\\dev\\jna\\jna-platform\\5.13.0\\jna-platform-5.13.0.jar",
247
+ "C:\\Users\\Admin\\.m2\\repository\\org\\slf4j\\jcl-over-slf4j\\2.0.13\\jcl-over-slf4j-2.0.13.jar",
248
+ "C:\\Users\\Admin\\.m2\\repository\\com\\github\\ben-manes\\caffeine\\caffeine\\3.1.8\\caffeine-3.1.8.jar",
249
+ "C:\\Users\\Admin\\.m2\\repository\\com\\google\\errorprone\\error_prone_annotations\\2.21.1\\error_prone_annotations-2.21.1.jar",
250
+ "C:\\Users\\Admin\\.m2\\repository\\org\\checkerframework\\checker-qual\\3.32.0\\checker-qual-3.32.0.jar",
251
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-validation\\3.2.5\\spring-boot-starter-validation-3.2.5.jar",
252
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-el\\10.1.20\\tomcat-embed-el-10.1.20.jar",
253
+ "C:\\Users\\Admin\\.m2\\repository\\org\\hibernate\\validator\\hibernate-validator\\8.0.1.Final\\hibernate-validator-8.0.1.Final.jar",
254
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\validation\\jakarta.validation-api\\3.0.2\\jakarta.validation-api-3.0.2.jar",
255
+ "C:\\Users\\Admin\\.m2\\repository\\org\\jboss\\logging\\jboss-logging\\3.5.3.Final\\jboss-logging-3.5.3.Final.jar",
256
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\classmate\\1.6.0\\classmate-1.6.0.jar",
257
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-data-jpa\\3.2.5\\spring-boot-starter-data-jpa-3.2.5.jar",
258
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-aop\\3.2.5\\spring-boot-starter-aop-3.2.5.jar",
259
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-aop\\6.1.6\\spring-aop-6.1.6.jar",
260
+ "C:\\Users\\Admin\\.m2\\repository\\org\\aspectj\\aspectjweaver\\1.9.22\\aspectjweaver-1.9.22.jar",
261
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-jdbc\\3.2.5\\spring-boot-starter-jdbc-3.2.5.jar",
262
+ "C:\\Users\\Admin\\.m2\\repository\\com\\zaxxer\\HikariCP\\5.0.1\\HikariCP-5.0.1.jar",
263
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-jdbc\\6.1.6\\spring-jdbc-6.1.6.jar",
264
+ "C:\\Users\\Admin\\.m2\\repository\\org\\hibernate\\orm\\hibernate-core\\6.4.4.Final\\hibernate-core-6.4.4.Final.jar",
265
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\persistence\\jakarta.persistence-api\\3.1.0\\jakarta.persistence-api-3.1.0.jar",
266
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\transaction\\jakarta.transaction-api\\2.0.1\\jakarta.transaction-api-2.0.1.jar",
267
+ "C:\\Users\\Admin\\.m2\\repository\\org\\hibernate\\common\\hibernate-commons-annotations\\6.0.6.Final\\hibernate-commons-annotations-6.0.6.Final.jar",
268
+ "C:\\Users\\Admin\\.m2\\repository\\io\\smallrye\\jandex\\3.1.2\\jandex-3.1.2.jar",
269
+ "C:\\Users\\Admin\\.m2\\repository\\net\\bytebuddy\\byte-buddy\\1.14.13\\byte-buddy-1.14.13.jar",
270
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\xml\\bind\\jakarta.xml.bind-api\\4.0.2\\jakarta.xml.bind-api-4.0.2.jar",
271
+ "C:\\Users\\Admin\\.m2\\repository\\org\\glassfish\\jaxb\\jaxb-runtime\\4.0.5\\jaxb-runtime-4.0.5.jar",
272
+ "C:\\Users\\Admin\\.m2\\repository\\org\\glassfish\\jaxb\\jaxb-core\\4.0.5\\jaxb-core-4.0.5.jar",
273
+ "C:\\Users\\Admin\\.m2\\repository\\org\\glassfish\\jaxb\\txw2\\4.0.5\\txw2-4.0.5.jar",
274
+ "C:\\Users\\Admin\\.m2\\repository\\com\\sun\\istack\\istack-commons-runtime\\4.1.2\\istack-commons-runtime-4.1.2.jar",
275
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\inject\\jakarta.inject-api\\2.0.1\\jakarta.inject-api-2.0.1.jar",
276
+ "C:\\Users\\Admin\\.m2\\repository\\org\\antlr\\antlr4-runtime\\4.13.0\\antlr4-runtime-4.13.0.jar",
277
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\data\\spring-data-jpa\\3.2.5\\spring-data-jpa-3.2.5.jar",
278
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\data\\spring-data-commons\\3.2.5\\spring-data-commons-3.2.5.jar",
279
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-orm\\6.1.6\\spring-orm-6.1.6.jar",
280
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-tx\\6.1.6\\spring-tx-6.1.6.jar",
281
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-aspects\\6.1.6\\spring-aspects-6.1.6.jar",
282
+ "C:\\Users\\Admin\\.m2\\repository\\org\\projectlombok\\lombok\\1.18.32\\lombok-1.18.32.jar",
283
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-oauth2-client\\3.2.5\\spring-boot-starter-oauth2-client-3.2.5.jar",
284
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-config\\6.2.4\\spring-security-config-6.2.4.jar",
285
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-core\\6.2.4\\spring-security-core-6.2.4.jar",
286
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-crypto\\6.2.4\\spring-security-crypto-6.2.4.jar",
287
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-expression\\6.1.6\\spring-expression-6.1.6.jar",
288
+ "C:\\Users\\Admin\\.m2\\repository\\io\\micrometer\\micrometer-observation\\1.12.5\\micrometer-observation-1.12.5.jar",
289
+ "C:\\Users\\Admin\\.m2\\repository\\io\\micrometer\\micrometer-commons\\1.12.5\\micrometer-commons-1.12.5.jar",
290
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-oauth2-client\\6.2.4\\spring-security-oauth2-client-6.2.4.jar",
291
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-oauth2-core\\6.2.4\\spring-security-oauth2-core-6.2.4.jar",
292
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-web\\6.2.4\\spring-security-web-6.2.4.jar",
293
+ "C:\\Users\\Admin\\.m2\\repository\\com\\nimbusds\\oauth2-oidc-sdk\\9.43.3\\oauth2-oidc-sdk-9.43.3.jar",
294
+ "C:\\Users\\Admin\\.m2\\repository\\com\\github\\stephenc\\jcip\\jcip-annotations\\1.0-1\\jcip-annotations-1.0-1.jar",
295
+ "C:\\Users\\Admin\\.m2\\repository\\com\\nimbusds\\content-type\\2.2\\content-type-2.2.jar",
296
+ "C:\\Users\\Admin\\.m2\\repository\\net\\minidev\\json-smart\\2.5.1\\json-smart-2.5.1.jar",
297
+ "C:\\Users\\Admin\\.m2\\repository\\net\\minidev\\accessors-smart\\2.5.1\\accessors-smart-2.5.1.jar",
298
+ "C:\\Users\\Admin\\.m2\\repository\\org\\ow2\\asm\\asm\\9.6\\asm-9.6.jar",
299
+ "C:\\Users\\Admin\\.m2\\repository\\com\\nimbusds\\lang-tag\\1.7\\lang-tag-1.7.jar",
300
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\security\\spring-security-oauth2-jose\\6.2.4\\spring-security-oauth2-jose-6.2.4.jar",
301
+ "C:\\Users\\Admin\\.m2\\repository\\com\\nimbusds\\nimbus-jose-jwt\\9.24.4\\nimbus-jose-jwt-9.24.4.jar",
302
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-web\\3.2.5\\spring-boot-starter-web-3.2.5.jar",
303
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-json\\3.2.5\\spring-boot-starter-json-3.2.5.jar",
304
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\core\\jackson-databind\\2.15.4\\jackson-databind-2.15.4.jar",
305
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\core\\jackson-annotations\\2.15.4\\jackson-annotations-2.15.4.jar",
306
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\core\\jackson-core\\2.15.4\\jackson-core-2.15.4.jar",
307
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\datatype\\jackson-datatype-jdk8\\2.15.4\\jackson-datatype-jdk8-2.15.4.jar",
308
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\datatype\\jackson-datatype-jsr310\\2.15.4\\jackson-datatype-jsr310-2.15.4.jar",
309
+ "C:\\Users\\Admin\\.m2\\repository\\com\\fasterxml\\jackson\\module\\jackson-module-parameter-names\\2.15.4\\jackson-module-parameter-names-2.15.4.jar",
310
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-web\\6.1.6\\spring-web-6.1.6.jar",
311
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\spring-webmvc\\6.1.6\\spring-webmvc-6.1.6.jar",
312
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-starter-tomcat\\3.2.5\\spring-boot-starter-tomcat-3.2.5.jar",
313
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\annotation\\jakarta.annotation-api\\2.1.1\\jakarta.annotation-api-2.1.1.jar",
314
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-core\\10.1.20\\tomcat-embed-core-10.1.20.jar",
315
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-websocket\\10.1.20\\tomcat-embed-websocket-10.1.20.jar",
316
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-devtools\\3.2.5\\spring-boot-devtools-3.2.5.jar",
317
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot\\3.2.5\\spring-boot-3.2.5.jar",
318
+ "C:\\Users\\Admin\\.m2\\repository\\org\\springframework\\boot\\spring-boot-autoconfigure\\3.2.5\\spring-boot-autoconfigure-3.2.5.jar",
319
+ "C:\\Users\\Admin\\.m2\\repository\\org\\apache\\tomcat\\embed\\tomcat-embed-jasper\\10.1.20\\tomcat-embed-jasper-10.1.20.jar",
320
+ "C:\\Users\\Admin\\.m2\\repository\\org\\eclipse\\jdt\\ecj\\3.33.0\\ecj-3.33.0.jar",
321
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\servlet\\jsp\\jstl\\jakarta.servlet.jsp.jstl-api\\3.0.0\\jakarta.servlet.jsp.jstl-api-3.0.0.jar",
322
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\servlet\\jakarta.servlet-api\\6.0.0\\jakarta.servlet-api-6.0.0.jar",
323
+ "C:\\Users\\Admin\\.m2\\repository\\jakarta\\el\\jakarta.el-api\\5.0.0\\jakarta.el-api-5.0.0.jar",
324
+ "C:\\Users\\Admin\\.m2\\repository\\org\\glassfish\\web\\jakarta.servlet.jsp.jstl\\3.0.1\\jakarta.servlet.jsp.jstl-3.0.1.jar",
325
+ "C:\\Users\\Admin\\.m2\\repository\\com\\mysql\\mysql-connector-j\\8.3.0\\mysql-connector-j-8.3.0.jar"
326
+ ]
327
+ }
328
+ ]
329
+ }
.gitattributes ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /mvnw text eol=lf
2
+ *.cmd text eol=crlf
3
+ *.zip filter=lfs diff=lfs merge=lfs -text
4
+ *.png filter=lfs diff=lfs merge=lfs -text
5
+ *.jpg filter=lfs diff=lfs merge=lfs -text
6
+ *.webp filter=lfs diff=lfs merge=lfs -text
7
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HELP.md
2
+ target/
3
+ .mvn/wrapper/maven-wrapper.jar
4
+ !**/src/main/**/target/
5
+ !**/src/test/**/target/
6
+
7
+ ### STS ###
8
+ .apt_generated
9
+ .classpath
10
+ .factorypath
11
+ .project
12
+ .settings
13
+ .springBeans
14
+ .sts4-cache
15
+
16
+ ### IntelliJ IDEA ###
17
+ .idea
18
+ *.iws
19
+ *.iml
20
+ *.ipr
21
+
22
+ ### NetBeans ###
23
+ /nbproject/private/
24
+ /nbbuild/
25
+ /dist/
26
+ /nbdist/
27
+ /.nb-gradle/
28
+ build/
29
+ !**/src/main/**/build/
30
+ !**/src/test/**/build/
31
+
32
+ ### VS Code ###
33
+ .vscode/
.mvn/wrapper/maven-wrapper.properties ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ wrapperVersion=3.3.4
2
+ distributionType=only-script
3
+ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip
DockerFile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ---Build code ---
2
+ FROM maven:3.9.6-eclipse-temurin-21-alpine AS build
3
+ WORKDIR /app
4
+ COPY . .
5
+ # Build run
6
+ RUN mvn clean package -DskipTests
7
+
8
+ FROM eclipse-temurin:21-jdk-alpine
9
+ WORKDIR /app
10
+ COPY --from=build /app/target/*.war app.war
11
+
12
+ EXPOSE 8080
13
+ ENTRYPOINT ["java","-jar","app.war"]
README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # DarkMoon
TEST_CASES.md ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Test Cases - DarkMoon / Moon Blight
2
+
3
+ | Test Case ID | Mô tả | Pre-condition | Steps | Expected Result | Actual Result | Status |
4
+ |---|---|---|---|---|---|---|
5
+ | TC-PUB-001 | Kiểm tra trang chủ hiển thị đúng dữ liệu nổi bật | Ứng dụng chạy, có dữ liệu article/character/story/world chưa xóa | 1. Mở `/` 2. Quan sát banner, latest articles, characters, top story, top world | Trang chủ load thành công, hiển thị tối đa 3 bài viết mới, 4 nhân vật, story/world ưu tiên cao nhất | | Chưa chạy |
6
+ | TC-PUB-002 | Kiểm tra trang chủ khi chưa có dữ liệu nổi bật | Database không có dữ liệu public hoặc toàn bộ dữ liệu đã soft-delete | 1. Mở `/` 2. Quan sát các khu vực dữ liệu động | Trang không lỗi 500, các khu vực trống/ẩn hợp lý | | Chưa chạy |
7
+ | TC-PUB-003 | Kiểm tra trang Story public | Có ít nhất 2 story chưa xóa với priority khác nhau | 1. Mở `/story` 2. Quan sát danh sách story | Chỉ hiển thị story chưa xóa, sắp xếp priority giảm dần | | Chưa chạy |
8
+ | TC-PUB-004 | Kiểm tra trang Characters public | Có nhân vật chưa xóa và nhân vật đã xóa | 1. Mở `/characters` 2. Quan sát danh sách | Chỉ hiển thị nhân vật chưa xóa, sắp xếp priority giảm dần | | Chưa chạy |
9
+ | TC-PUB-005 | Kiểm tra trang World public | Có world chưa xóa và world đã xóa | 1. Mở `/world` 2. Quan sát danh sách | Chỉ hiển thị world chưa xóa, sắp xếp priority giảm dần | | Chưa chạy |
10
+ | TC-PUB-006 | Kiểm tra trang News public mặc định | Có ít nhất 7 bài viết chưa xóa | 1. Mở `/news` 2. Quan sát số bài viết và nút load thêm | Hiển thị 6 bài viết, `hasMore` đúng, có nextLimit = 12 | | Chưa chạy |
11
+ | TC-PUB-007 | Kiểm tra trang News với tham số limit | Có ít nhất 12 bài viết chưa xóa | 1. Mở `/news?limit=12` 2. Đếm số bài viết | Hiển thị tối đa 12 bài viết, nextLimit = 18 nếu còn dữ liệu | | Chưa chạy |
12
+ | TC-PUB-008 | Kiểm tra bài viết quan trọng nổi bật trong News | Có bài viết type IMPORTANT chưa xóa mới nhất | 1. Mở `/news` 2. Kiểm tra khu vực featured article | Featured article là bài IMPORTANT mới nhất chưa xóa | | Chưa chạy |
13
+ | TC-PUB-009 | Kiểm tra chi tiết tin tức hợp lệ | Có bài viết chưa xóa với ID hợp lệ | 1. Mở `/news/{id}` 2. Quan sát nội dung và bài liên quan | Hiển thị đúng bài viết, danh sách liên quan không chứa chính bài đang xem | | Chưa chạy |
14
+ | TC-PUB-010 | Kiểm tra chi tiết tin tức ID không tồn tại | Không có article với ID test | 1. Mở `/news/999999` | Hệ thống xử lý lỗi hợp lý, không lộ stack trace cho người dùng | | Chưa chạy |
15
+ | TC-CON-001 | Kiểm tra trang Support hiển thị form và FAQ | Có FAQ chưa xóa | 1. Mở `/support` 2. Quan sát form liên hệ và FAQ | Trang hiển thị categories, platforms và FAQ chưa xóa | | Chưa chạy |
16
+ | TC-CON-002 | Gửi contact message hợp lệ | Người dùng ở trang `/support` | 1. Nhập tên, email hợp lệ, category, platform, message 2. Submit | Lưu contact message với processed = false, redirect `/support`, hiển thị thông báo thành công | | Chưa chạy |
17
+ | TC-CON-003 | Gửi contact thiếu tên | Người dùng ở trang `/support` | 1. Để trống tên 2. Nhập các trường còn lại hợp lệ 3. Submit | Không lưu dữ liệu, ở lại trang support, hiển thị lỗi kiểm tra thông tin | | Chưa chạy |
18
+ | TC-CON-004 | Gửi contact email sai định dạng | Người dùng ở trang `/support` | 1. Nhập email `abc` 2. Nhập các trường khác hợp lệ 3. Submit | Không lưu dữ liệu, hiển thị lỗi email không hợp lệ | | Chưa chạy |
19
+ | TC-CON-005 | Gửi contact thiếu category | Người dùng ở trang `/support` | 1. Không chọn category 2. Nhập các trường khác hợp lệ 3. Submit | Không lưu dữ liệu, hiển thị lỗi chọn danh mục | | Chưa chạy |
20
+ | TC-CON-007 | Gửi contact thiếu nội dung | Người dùng ở trang `/support` | 1. Để trống message 2. Nhập các trường khác hợp lệ 3. Submit | Không lưu dữ liệu, hiển thị lỗi nhập nội dung | | Chưa chạy |
21
+ | TC-CON-008 | Gửi contact message vượt quá 5000 ký tự | Người dùng ở trang `/support` | 1. Nhập message 5001 ký tự 2. Submit | Không lưu dữ liệu, hiển thị lỗi độ dài nội dung | | Chưa chạy |
22
+ | TC-CON-009 | Xem danh sách contact trong CMS | Có tài khoản STAFF đã đăng nhập, có contact messages | 1. Mở `/contact/blightmoon/list` | Hiển thị danh sách contact phân trang size 20 | | Chưa chạy |
23
+ | TC-CON-010 | Kiểm tra phân quyền contact CMS khi chưa đăng nhập | Chưa đăng nhập | 1. Mở `/contact/blightmoon/list` | Kỳ vọng bảo mật: bị chặn hoặc chuyển login. Nếu truy cập được là lỗi cấu hình route | | Chưa chạy |
24
+ | TC-AUTH-002 | Đăng nhập STAFF hợp lệ | Có tài khoản STAFF active, mật khẩu đúng | 1. Nhập username/email và password 2. Submit login | Đăng nhập thành công, tạo session, redirect `/dashboard/moonblight`, ghi audit LOGIN | | Chưa chạy |
25
+ | TC-AUTH-003 | Đăng nhập ADMIN hợp lệ | Có tài khoản ADMIN active, mật khẩu đúng | 1. Nhập credentials admin 2. Submit login | Đăng nhập thành công, redirect `/admin/moonblight/dashboard`, ghi audit LOGIN | | Chưa chạy |
26
+ | TC-AUTH-004 | Đăng nhập sai mật khẩu | Có tài khoản hợp lệ | 1. Nhập username/email đúng 2. Nhập password sai 3. Submit | Ở lại login, hiển thị `Tên đăng nhập hoặc mật khẩu không đúng.` | | Chưa chạy |
27
+ | TC-AUTH-005 | Đăng nhập thiếu username/email | Chưa đăng nhập | 1. Để trống login 2. Nhập password hợp lệ 3. Submit | Validation lỗi, không gọi authenticate, ở lại login | | Chưa chạy |
28
+ | TC-AUTH-007 | Đăng nhập mật khẩu dưới 8 ký tự | Chưa đăng nhập | 1. Nhập login 2. Nhập password 7 ký tự 3. Submit | Validation lỗi mật khẩu tối thiểu 8 ký tự | | Chưa chạy |
29
+ | TC-AUTH-008 | Đăng nhập tài khoản bị khóa | Có STAFF isActive = false | 1. Nhập credentials tài khoản bị khóa 2. Submit | Đăng nhập thất bại hoặc bị Spring Security từ chối, không vào dashboard | | Chưa chạy |
30
+ | TC-AUTH-010 | Đăng ký tài khoản STAFF hợp lệ | Username/email chưa tồn tại | 1. Nhập username 3-30 ký tự chữ/số, email hợp lệ, password >= 8, rePassword khớp, fullName 2. Submit | Tạo user role STAFF, isActive = false, password BCrypt, redirect login, thông báo thành công | | Chưa chạy |
31
+ | TC-AUTH-011 | Đăng ký username đã tồn tại | Database có username test | 1. Nhập username đã tồn tại 2. Nhập thông tin khác hợp lệ 3. Submit | Không tạo user, hiển thị `Tên đăng nhập đã tồn tại!`, xóa password trên form | | Chưa chạy |
32
+ | TC-AUTH-012 | Đăng ký email đã tồn tại | Database có email test | 1. Nhập email đã tồn tại 2. Nhập thông tin khác hợp lệ 3. Submit | Không tạo user, hiển thị `Email này đã được đăng ký!`, xóa password trên form | | Chưa chạy |
33
+ | TC-AUTH-017 | Đăng ký mật khẩu và xác nhận không khớp | Chưa đăng nhập | 1. Nhập password và rePassword khác nhau 2. Submit | Validation lỗi password confirmation, không tạo user | | Chưa chạy |
34
+ | TC-AUTH-018 | Đăng xuất khi đã đăng nhập | User đã đăng nhập | 1. POST `/user/moonblight/logout` | Session bị clear, redirect login, ghi audit LOGOUT | | Chưa chạy |
35
+ | TC-AUTH-020 | Gửi OTP cho email tồn tại | Có user với email hợp lệ | 1. Nhập email tồn tại 2. Submit forgot-password | Tạo OTP 6 chữ số, hết hạn sau 5 phút, gửi email, session `OTP_EMAIL`, redirect verify-otp | | Chưa chạy |
36
+ | TC-AUTH-021 | Gửi OTP cho email không tồn tại | Email chưa có trong DB | 1. Nhập email chưa đăng ký 2. Submit | Ở lại forgot-password, hiển thị `Email không tồn tại` | | Chưa chạy |
37
+ | TC-AUTH-022 | Verify OTP hợp lệ | Đã gửi OTP, OTP chưa hết hạn | 1. Mở verify-otp 2. Nhập OTP đúng 3. Submit | Session lưu RESET_EMAIL và RESET_OTP, redirect reset-password | | Chưa chạy |
38
+ | TC-AUTH-023 | Verify OTP sai | Đã gửi OTP | 1. Nhập OTP sai 2. Submit | Ở lại verify-otp, hiển thị `OTP không hợp lệ` | | Chưa chạy |
39
+ | TC-AUTH-024 | Verify OTP hết hạn | OTP đã quá 5 phút | 1. Nhập OTP hết hạn 2. Submit | Ở lại verify-otp, hiển thị `OTP đã hết hạn` | | Chưa chạy |
40
+ | TC-AUTH-025 | Reset password hợp lệ sau OTP | Session có RESET_EMAIL và RESET_OTP hợp lệ | 1. Mở reset-password 2. Nhập newPassword >= 8 và confirm khớp 3. Submit | Password được mã hóa mới, OTP marked used, session reset bị xóa, redirect login | | Chưa chạy |
41
+ | TC-AUTH-027 | Reset password xác nhận không khớp | Session OTP hợp lệ | 1. Nhập newPassword và confirmPassword khác nhau 2. Submit | Validation lỗi, ở lại reset-password | | Chưa chạy |
42
+ | TC-AUTH-028 | Reset password dưới 8 ký tự | Session OTP hợp lệ | 1. Nhập newPassword 7 ký tự 2. Submit | Validation lỗi min 8, không đổi mật khẩu | | Chưa chạy |
43
+ | TC-AUTH-029 | Đổi mật khẩu current user hợp lệ | User đã đăng nhập | 1. Mở `/user/moonblight/change-password` 2. Nhập oldPassword đúng, newPassword >= 8, confirm khớp 3. Submit | Password đổi thành công, mustChangePassword = false, redirect theo role, ghi audit UPDATE | | Chưa chạy |
44
+ | TC-AUTH-031 | Đổi mật khẩu xác nhận không khớp | User đã đăng nhập | 1. Nhập oldPassword đúng 2. Nhập newPassword và confirm khác nhau 3. Submit | Validation/lỗi xác nhận không khớp, không đổi mật khẩu | | Chưa chạy |
45
+ | TC-SEC-001 | Chặn truy cập dashboard STAFF khi chưa đăng nhập | Chưa đăng nhập | 1. Mở `/dashboard/moonblight` | Không cho xem dashboard, chuyển tới trang login hoặc trả 403 theo cấu hình Spring Security | | Chưa chạy |
46
+ | TC-SEC-002 | Chặn truy cập CMS Article khi chưa đăng nhập | Chưa đăng nhập | 1. Mở `/article/moonblight/list` | Không cho xem danh sách article CMS | | Chưa chạy |
47
+ | TC-SEC-003 | Chặn STAFF truy cập admin dashboard | Đăng nhập STAFF active | 1. Mở `/admin/moonblight/dashboard` | Bị từ chối 403 hoặc chuyển trang lỗi, không thấy dữ liệu admin | | Chưa chạy |
48
+ | TC-SEC-004 | Cho ADMIN truy cập admin dashboard | Đăng nhập ADMIN active | 1. Mở `/admin/moonblight/dashboard` | Hiển thị dashboard admin với totalUsers, activeUsers, inActiveUsers | | Chưa chạy |
49
+ | TC-SEC-005 | Cho STAFF truy cập CMS content | Đăng nhập STAFF active | 1. Mở `/article/moonblight/list` 2. Mở `/story/moonblight/list` | STAFF truy cập được các trang CMS content | | Chưa chạy |
50
+ | TC-SEC-007 | Force change password redirect | User đăng nhập có mustChangePassword = true | 1. Mở `/dashboard/moonblight` | Bị redirect tới `/user/moonblight/change-password` | | Chưa chạy |
51
+ | TC-SEC-008 | Force change password cho phép mở trang change-password | User đăng nhập có mustChangePassword = true | 1. Mở `/user/moonblight/change-password` | Trang đổi mật khẩu hiển thị, không bị redirect vòng lặp | | Chưa chạy |
52
+ | TC-SEC-010 | Kiểm tra API hash password internal public | Chưa đăng nhập | 1. Gọi `/api/moonblight/internal/hash?password=12345678` | Kỳ vọng bảo mật: API nội bộ không nên public. Nếu trả BCrypt khi chưa đăng nhập là rủi ro cần ghi nhận | | Chưa chạy |
53
+ | TC-ADM-001 | Xem danh sách tài khoản | Đăng nhập ADMIN | 1. Mở `/admin/moonblight/list` | Hiển thị accounts phân trang size 10, sắp xếp id tăng dần | | Chưa chạy |
54
+ | TC-ADM-002 | Xem chi tiết tài khoản hợp lệ | Đăng nhập ADMIN, có user ID hợp lệ | 1. Mở `/admin/moonblight/account/{id}` | Hiển thị đúng thông tin account | | Chưa chạy |
55
+ | TC-ADM-004 | Approve tài khoản STAFF chờ duyệt | Đăng nhập ADMIN, STAFF isActive = false | 1. POST `/admin/moonblight/account/{id}/approve` | User isActive = true, redirect detail, thông báo duyệt thành công, ghi audit UNLOCK | | Chưa chạy |
56
+ | TC-ADM-006 | Khóa tài khoản STAFF | Đăng nhập ADMIN, STAFF active | 1. POST `/admin/moonblight/account/{id}/lock` | User isActive = false, thông báo khóa thành công, ghi audit LOCK | | Chưa chạy |
57
+ | TC-ADM-007 | Mở khóa tài khoản STAFF | Đăng nhập ADMIN, STAFF inactive | 1. POST `/admin/moonblight/account/{id}/unlock` | User isActive = true, thông báo mở khóa thành công, ghi audit UNLOCK | | Chưa chạy |
58
+ | TC-ADM-008 | Không cho khóa tài khoản ADMIN | Đăng nhập ADMIN, target user role ADMIN | 1. POST `/admin/moonblight/account/{adminId}/lock` | Không đổi trạng thái admin, hiển thị lỗi `Không có thẩm quyền` hoặc xử lý lỗi phù hợp | | Chưa chạy |
59
+ | TC-ADM-009 | Xem audit log | Đăng nhập ADMIN, có audit logs | 1. Mở `/admin/moonblight/log` | Hiển thị logs size 20, sort createdAt giảm dần, activeMenu audit-log | | Chưa chạy |
60
+ | TC-ADM-010 | Dashboard admin thống kê user | Đăng nhập ADMIN, DB có active/inactive users | 1. Mở `/admin/moonblight/dashboard` | totalUsers = activeUsers + inActiveUsers, số liệu đúng DB | | Chưa chạy |
61
+ | TC-DASH-001 | Dashboard STAFF hiển thị thống kê content | Đăng nhập STAFF active | 1. Mở `/dashboard/moonblight` | Hiển thị count article/story/world/character/faq và contact chưa xử lý | | Chưa chạy |
62
+ | TC-ART-001 | Xem danh sách bài viết CMS | Đăng nhập STAFF, có articles | 1. Mở `/article/moonblight/list` | Hiển thị tất cả article kể cả đã xóa, phân trang size 10 | | Chưa chạy |
63
+ | TC-ART-003 | Tạo bài viết hợp lệ không thumbnail | Đăng nhập STAFF | 1. Nhập title, content, type 2. Không chọn thumbnail 3. Submit | Tạo article, createdBy là current user, redirect list, flash success, ghi audit CREATE | | Chưa chạy |
64
+ | TC-ART-004 | Tạo bài viết hợp lệ có thumbnail | Đăng nhập STAFF, có file ảnh hợp lệ | 1. Chọn thumbnail 2. Nhập thông tin hợp lệ 3. Submit | File lưu vào `uploads/articles`, thumbnailUrl được set, tạo article thành công | | Chưa chạy |
65
+ | TC-ART-005 | Tạo bài viết thiếu title | Đăng nhập STAFF | 1. Để trống title 2. Nhập content/type 3. Submit | Không tạo article, ở lại form, hiển thị lỗi title không được trống | | Chưa chạy |
66
+ | TC-ART-006 | Tạo bài viết title trên 255 ký tự | Đăng nhập STAFF | 1. Nhập title 256 ký tự 2. Nhập content/type 3. Submit | Không tạo article, hiển thị lỗi tiêu đề quá dài | | Chưa chạy |
67
+ | TC-ART-007 | Tạo bài viết thiếu content | Đăng nhập STAFF | 1. Nhập title/type 2. Để trống content 3. Submit | Không tạo article, hiển thị lỗi nội dung không được trống | | Chưa chạy |
68
+ | TC-ART-008 | Tạo bài viết thiếu type | Đăng nhập STAFF | 1. Nhập title/content 2. Không chọn type 3. Submit | Không tạo article, hiển thị lỗi loại bài viết không được trống | | Chưa chạy |
69
+ | TC-ART-009 | Sửa bài viết hợp lệ không đổi thumbnail | Đăng nhập STAFF, có article | 1. Mở `/article/moonblight/{id}/edit` 2. Sửa title/content/type 3. Không chọn thumbnail 4. Submit | Article cập nhật, thumbnailUrl cũ được giữ, updatedBy set, audit UPDATE | | Chưa chạy |
70
+ | TC-ART-011 | Soft-delete bài viết | Đăng nhập STAFF, có article chưa xóa | 1. POST `/article/moonblight/{id}/delete` | Article deleted = true, public news không còn hiển thị, CMS vẫn thấy, audit DELETE | | Chưa chạy |
71
+ | TC-ART-012 | Restore bài viết | Đăng nhập STAFF, có article deleted = true | 1. POST `/article/moonblight/{id}/restore` | Article deleted = false, public news hiển thị lại, audit RESTORE | | Chưa chạy |
72
+ | TC-STO-001 | Xem danh sách story CMS | Đăng nhập STAFF | 1. Mở `/story/moonblight/list` | Hiển thị stories phân trang size 10, activeMenu STORY | | Chưa chạy |
73
+ | TC-STO-002 | Tạo story hợp lệ có ảnh | Đăng nhập STAFF, có file ảnh | 1. Mở create 2. Nhập tag, title, content, quoteContent, quoteAuthor, priority 3. Chọn image 4. Submit | Tạo story thành công, lưu ảnh `uploads/stories`, audit CREATE | | Chưa chạy |
74
+ | TC-STO-005 | Tạo story priority nhỏ hơn 0 | Đăng nhập STAFF | 1. Nhập priority = -1 2. Submit | Không tạo story, hiển thị lỗi priority 0-100 | | Chưa chạy |
75
+ | TC-STO-006 | Tạo story priority lớn hơn 100 | Đăng nhập STAFF | 1. Nhập priority = 101 2. Submit | Không tạo story, hiển thị lỗi priority 0-100 | | Chưa chạy |
76
+ | TC-STO-007 | Sửa story không đổi ảnh | Đăng nhập STAFF, có story | 1. Mở edit 2. Sửa title/content/priority 3. Không chọn image 4. Submit | Story cập nhật, giữ image cũ, audit UPDATE | | Chưa chạy |
77
+ | TC-STO-008 | Soft-delete story | Đăng nhập STAFF, có story chưa xóa | 1. POST `/story/moonblight/{id}/delete` | Story deleted = true, public `/story` không hiển thị, audit DELETE | | Chưa chạy |
78
+ | TC-STO-009 | Restore story | Đăng nhập STAFF, có story deleted = true | 1. POST `/story/moonblight/{id}/restore` | Story deleted = false, public `/story` hiển thị lại, audit RESTORE | | Chưa chạy |
79
+ | TC-WLD-001 | Xem danh sách world CMS | Đăng nhập STAFF | 1. Mở `/world/moonblight/list` | Hiển thị worlds phân trang size 10, activeMenu WORLD | | Chưa chạy |
80
+ | TC-WLD-002 | Tạo world hợp lệ có ảnh | Đăng nhập STAFF, có file ảnh | 1. Mở create 2. Nhập tag, title, content, priority 3. Chọn image 4. Submit | Tạo world thành công, lưu ảnh `uploads/worlds`, audit CREATE | | Chưa chạy |
81
+ | TC-WLD-003 | Tạo world thiếu title | Đăng nhập STAFF | 1. Để trống title 2. Nhập các trường khác hợp lệ 3. Submit | Không tạo world, hiển thị lỗi tiêu đề không được trống | | Chưa chạy |
82
+ | TC-WLD-005 | Tạo world thiếu content | Đăng nhập STAFF | 1. Để trống content 2. Submit form | Không tạo world, hiển thị lỗi nội dung không được trống | | Chưa chạy |
83
+ | TC-WLD-007 | Sửa world hợp lệ không đổi ảnh | Đăng nhập STAFF, có world | 1. Mở edit 2. Sửa tag/title/content/priority 3. Không chọn image 4. Submit | World cập nhật, giữ image cũ, audit UPDATE | | Chưa chạy |
84
+ | TC-WLD-008 | Soft-delete world | Đăng nhập STAFF, có world chưa xóa | 1. POST `/world/moonblight/{id}/delete` | World deleted = true, public `/world` không hiển thị, audit DELETE | | Chưa chạy |
85
+ | TC-WLD-009 | Restore world | Đăng nhập STAFF, có world deleted = true | 1. POST `/world/moonblight/{id}/restore` | World deleted = false, public `/world` hiển thị lại, audit RESTORE | | Chưa chạy |
86
+ | TC-CHR-001 | Xem danh sách character CMS | Đăng nhập STAFF | 1. Mở `/character/moonblight/list` | Hiển thị characters phân trang size 10, activeMenu CHARACTER | | Chưa chạy |
87
+ | TC-CHR-002 | Tạo character hợp lệ có ảnh | Đăng nhập STAFF, có file ảnh | 1. Mở create 2. Nhập name, race, description, quote, priority 3. Chọn image 4. Submit | Tạo character thành công, lưu ảnh `uploads/characters`, audit CREATE | | Chưa chạy |
88
+ | TC-CHR-003 | Tạo character thiếu name | Đăng nhập STAFF | 1. Để trống name 2. Submit form hợp lệ còn lại | Không tạo character, hiển thị lỗi tên nhân v��t không được trống | | Chưa chạy |
89
+ | TC-CHR-005 | Tạo character thiếu race | Đăng nhập STAFF | 1. Để trống race 2. Submit | Không tạo character, hiển thị lỗi chủng tộc không được trống | | Chưa chạy |
90
+ | TC-CHR-008 | Tạo character priority ngoài biên | Đăng nhập STAFF | 1. Nhập priority = 101 hoặc -1 2. Submit | Không tạo character, hiển thị lỗi priority 0-100 | | Chưa chạy |
91
+ | TC-CHR-009 | Sửa character không đổi ảnh | Đăng nhập STAFF, có character | 1. Mở edit 2. Sửa name/race/description/priority 3. Không chọn image 4. Submit | Character cập nhật, giữ image cũ, audit UPDATE | | Chưa chạy |
92
+ | TC-CHR-010 | Soft-delete character | Đăng nhập STAFF, có character chưa xóa | 1. POST `/character/moonblight/{id}/delete` | Character deleted = true, public `/characters` không hiển thị, audit DELETE | | Chưa chạy |
93
+ | TC-CHR-011 | Restore character | Đăng nhập STAFF, có character deleted = true | 1. POST `/character/moonblight/{id}/restore` | Character deleted = false, public `/characters` hiển thị lại, audit RESTORE | | Chưa chạy |
94
+ | TC-FAQ-001 | Xem danh sách FAQ CMS | Đăng nhập STAFF | 1. Mở `/faq/moonblight/list` | Hiển thị FAQs phân trang size 10, activeMenu FAQ | | Chưa chạy |
95
+ | TC-FAQ-002 | Tạo FAQ hợp lệ | Đăng nhập STAFF | 1. Mở create 2. Nhập title, content, priority 3. Submit | Tạo FAQ thành công, redirect list, audit CREATE | | Chưa chạy |
96
+ | TC-FAQ-003 | Tạo FAQ thiếu title | Đăng nhập STAFF | 1. Để trống title 2. Nhập content/priority 3. Submit | Không tạo FAQ, hiển thị lỗi tiêu đề không được trống | | Chưa chạy |
97
+ | TC-FAQ-006 | Sửa FAQ hợp lệ | Đăng nhập STAFF, có FAQ | 1. Mở `/faq/moonblight/{id}/edit` 2. Sửa title/content/priority 3. Submit | FAQ cập nhật, redirect list, audit UPDATE | | Chưa chạy |
98
+ | TC-FAQ-007 | Soft-delete FAQ | Đăng nhập STAFF, có FAQ chưa xóa | 1. POST `/faq/moonblight/{id}/delete` | FAQ deleted = true, public support không hiển thị FAQ đó, audit DELETE | | Chưa chạy |
99
+ | TC-FAQ-008 | Restore FAQ | Đăng nhập STAFF, có FAQ deleted = true | 1. POST `/faq/moonblight/{id}/restore` | FAQ deleted = false, public support hiển thị lại, audit RESTORE | | Chưa chạy |
100
+ | TC-UPL-001 | Upload ảnh editor hợp lệ | Đăng nhập STAFF hoặc user được phép truy cập editor, có file ảnh | 1. POST `/api/upload` với multipart field `image` | Trả JSON `{url: ...}`, file lưu trong `uploads/editor` | | Chưa chạy |
101
+ | TC-UPL-002 | Upload ảnh editor file rỗng | Có quyền gọi API upload | 1. POST `/api/upload` với file rỗng | Trả HTTP 400 và JSON error `File upload không hợp lệ` | | Chưa chạy |
102
+ | TC-AUD-003 | Đánh dấu contact đã xử lý | Đăng nhập STAFF, có contact chưa xử lý | 1. POST `/contact/blightmoon/{id}/process` | Contact processed = true, redirect list, flash success, audit PROCESS | | Chưa chạy |
103
+ | TC-AUD-004 | Đánh dấu contact chưa xử lý | Đăng nhập STAFF, có contact đã xử lý | 1. POST `/contact/blightmoon/{id}/unprocess` | Contact processed = false, redirect list, flash success | | Chưa chạy |
104
+ | TC-AUD-005 | Xem chi tiết contact | Đăng nhập STAFF, có contact ID hợp lệ | 1. Mở `/contact/blightmoon/{id}` | Hiển thị đúng visitorName, visitorEmail, category, platform, message, processed | | Chưa chạy |
mvnw ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # ----------------------------------------------------------------------------
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+ # ----------------------------------------------------------------------------
20
+
21
+ # ----------------------------------------------------------------------------
22
+ # Apache Maven Wrapper startup batch script, version 3.3.4
23
+ #
24
+ # Optional ENV vars
25
+ # -----------------
26
+ # JAVA_HOME - location of a JDK home dir, required when download maven via java source
27
+ # MVNW_REPOURL - repo url base for downloading maven distribution
28
+ # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
29
+ # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
30
+ # ----------------------------------------------------------------------------
31
+
32
+ set -euf
33
+ [ "${MVNW_VERBOSE-}" != debug ] || set -x
34
+
35
+ # OS specific support.
36
+ native_path() { printf %s\\n "$1"; }
37
+ case "$(uname)" in
38
+ CYGWIN* | MINGW*)
39
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
40
+ native_path() { cygpath --path --windows "$1"; }
41
+ ;;
42
+ esac
43
+
44
+ # set JAVACMD and JAVACCMD
45
+ set_java_home() {
46
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
47
+ if [ -n "${JAVA_HOME-}" ]; then
48
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
49
+ # IBM's JDK on AIX uses strange locations for the executables
50
+ JAVACMD="$JAVA_HOME/jre/sh/java"
51
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
52
+ else
53
+ JAVACMD="$JAVA_HOME/bin/java"
54
+ JAVACCMD="$JAVA_HOME/bin/javac"
55
+
56
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
57
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
58
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
59
+ return 1
60
+ fi
61
+ fi
62
+ else
63
+ JAVACMD="$(
64
+ 'set' +e
65
+ 'unset' -f command 2>/dev/null
66
+ 'command' -v java
67
+ )" || :
68
+ JAVACCMD="$(
69
+ 'set' +e
70
+ 'unset' -f command 2>/dev/null
71
+ 'command' -v javac
72
+ )" || :
73
+
74
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
75
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
76
+ return 1
77
+ fi
78
+ fi
79
+ }
80
+
81
+ # hash string like Java String::hashCode
82
+ hash_string() {
83
+ str="${1:-}" h=0
84
+ while [ -n "$str" ]; do
85
+ char="${str%"${str#?}"}"
86
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
87
+ str="${str#?}"
88
+ done
89
+ printf %x\\n $h
90
+ }
91
+
92
+ verbose() { :; }
93
+ [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
94
+
95
+ die() {
96
+ printf %s\\n "$1" >&2
97
+ exit 1
98
+ }
99
+
100
+ trim() {
101
+ # MWRAPPER-139:
102
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
103
+ # Needed for removing poorly interpreted newline sequences when running in more
104
+ # exotic environments such as mingw bash on Windows.
105
+ printf "%s" "${1}" | tr -d '[:space:]'
106
+ }
107
+
108
+ scriptDir="$(dirname "$0")"
109
+ scriptName="$(basename "$0")"
110
+
111
+ # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
112
+ while IFS="=" read -r key value; do
113
+ case "${key-}" in
114
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
115
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
116
+ esac
117
+ done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
118
+ [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
119
+
120
+ case "${distributionUrl##*/}" in
121
+ maven-mvnd-*bin.*)
122
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
123
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
124
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
125
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
126
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
127
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
128
+ *)
129
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
130
+ distributionPlatform=linux-amd64
131
+ ;;
132
+ esac
133
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
134
+ ;;
135
+ maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
136
+ *) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
137
+ esac
138
+
139
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
140
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
141
+ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
142
+ distributionUrlName="${distributionUrl##*/}"
143
+ distributionUrlNameMain="${distributionUrlName%.*}"
144
+ distributionUrlNameMain="${distributionUrlNameMain%-bin}"
145
+ MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
146
+ MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
147
+
148
+ exec_maven() {
149
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
150
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
151
+ }
152
+
153
+ if [ -d "$MAVEN_HOME" ]; then
154
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
155
+ exec_maven "$@"
156
+ fi
157
+
158
+ case "${distributionUrl-}" in
159
+ *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
160
+ *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
161
+ esac
162
+
163
+ # prepare tmp dir
164
+ if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
165
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
166
+ trap clean HUP INT TERM EXIT
167
+ else
168
+ die "cannot create temp dir"
169
+ fi
170
+
171
+ mkdir -p -- "${MAVEN_HOME%/*}"
172
+
173
+ # Download and Install Apache Maven
174
+ verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
175
+ verbose "Downloading from: $distributionUrl"
176
+ verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
177
+
178
+ # select .zip or .tar.gz
179
+ if ! command -v unzip >/dev/null; then
180
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
181
+ distributionUrlName="${distributionUrl##*/}"
182
+ fi
183
+
184
+ # verbose opt
185
+ __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
186
+ [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
187
+
188
+ # normalize http auth
189
+ case "${MVNW_PASSWORD:+has-password}" in
190
+ '') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
191
+ has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
192
+ esac
193
+
194
+ if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
195
+ verbose "Found wget ... using wget"
196
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
197
+ elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
198
+ verbose "Found curl ... using curl"
199
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
200
+ elif set_java_home; then
201
+ verbose "Falling back to use Java to download"
202
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
203
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
204
+ cat >"$javaSource" <<-END
205
+ public class Downloader extends java.net.Authenticator
206
+ {
207
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
208
+ {
209
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
210
+ }
211
+ public static void main( String[] args ) throws Exception
212
+ {
213
+ setDefault( new Downloader() );
214
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
215
+ }
216
+ }
217
+ END
218
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
219
+ verbose " - Compiling Downloader.java ..."
220
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
221
+ verbose " - Running Downloader.java ..."
222
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
223
+ fi
224
+
225
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
226
+ if [ -n "${distributionSha256Sum-}" ]; then
227
+ distributionSha256Result=false
228
+ if [ "$MVN_CMD" = mvnd.sh ]; then
229
+ echo "Checksum validation is not supported for maven-mvnd." >&2
230
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
231
+ exit 1
232
+ elif command -v sha256sum >/dev/null; then
233
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
234
+ distributionSha256Result=true
235
+ fi
236
+ elif command -v shasum >/dev/null; then
237
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
238
+ distributionSha256Result=true
239
+ fi
240
+ else
241
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
242
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
243
+ exit 1
244
+ fi
245
+ if [ $distributionSha256Result = false ]; then
246
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
247
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
248
+ exit 1
249
+ fi
250
+ fi
251
+
252
+ # unzip and move
253
+ if command -v unzip >/dev/null; then
254
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
255
+ else
256
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
257
+ fi
258
+
259
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
260
+ actualDistributionDir=""
261
+
262
+ # First try the expected directory name (for regular distributions)
263
+ if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
264
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
265
+ actualDistributionDir="$distributionUrlNameMain"
266
+ fi
267
+ fi
268
+
269
+ # If not found, search for any directory with the Maven executable (for snapshots)
270
+ if [ -z "$actualDistributionDir" ]; then
271
+ # enable globbing to iterate over items
272
+ set +f
273
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
274
+ if [ -d "$dir" ]; then
275
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
276
+ actualDistributionDir="$(basename "$dir")"
277
+ break
278
+ fi
279
+ fi
280
+ done
281
+ set -f
282
+ fi
283
+
284
+ if [ -z "$actualDistributionDir" ]; then
285
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
286
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
287
+ die "Could not find Maven distribution directory in extracted archive"
288
+ fi
289
+
290
+ verbose "Found extracted Maven distribution directory: $actualDistributionDir"
291
+ printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
292
+ mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
293
+
294
+ clean || :
295
+ exec_maven "$@"
mvnw.cmd ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <# : batch portion
2
+ @REM ----------------------------------------------------------------------------
3
+ @REM Licensed to the Apache Software Foundation (ASF) under one
4
+ @REM or more contributor license agreements. See the NOTICE file
5
+ @REM distributed with this work for additional information
6
+ @REM regarding copyright ownership. The ASF licenses this file
7
+ @REM to you under the Apache License, Version 2.0 (the
8
+ @REM "License"); you may not use this file except in compliance
9
+ @REM with the License. You may obtain a copy of the License at
10
+ @REM
11
+ @REM http://www.apache.org/licenses/LICENSE-2.0
12
+ @REM
13
+ @REM Unless required by applicable law or agreed to in writing,
14
+ @REM software distributed under the License is distributed on an
15
+ @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ @REM KIND, either express or implied. See the License for the
17
+ @REM specific language governing permissions and limitations
18
+ @REM under the License.
19
+ @REM ----------------------------------------------------------------------------
20
+
21
+ @REM ----------------------------------------------------------------------------
22
+ @REM Apache Maven Wrapper startup batch script, version 3.3.4
23
+ @REM
24
+ @REM Optional ENV vars
25
+ @REM MVNW_REPOURL - repo url base for downloading maven distribution
26
+ @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
27
+ @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
28
+ @REM ----------------------------------------------------------------------------
29
+
30
+ @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
31
+ @SET __MVNW_CMD__=
32
+ @SET __MVNW_ERROR__=
33
+ @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
34
+ @SET PSModulePath=
35
+ @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
36
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
37
+ )
38
+ @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
39
+ @SET __MVNW_PSMODULEP_SAVE=
40
+ @SET __MVNW_ARG0_NAME__=
41
+ @SET MVNW_USERNAME=
42
+ @SET MVNW_PASSWORD=
43
+ @IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
44
+ @echo Cannot start maven from wrapper >&2 && exit /b 1
45
+ @GOTO :EOF
46
+ : end batch / begin powershell #>
47
+
48
+ $ErrorActionPreference = "Stop"
49
+ if ($env:MVNW_VERBOSE -eq "true") {
50
+ $VerbosePreference = "Continue"
51
+ }
52
+
53
+ # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
54
+ $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
55
+ if (!$distributionUrl) {
56
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
57
+ }
58
+
59
+ switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
60
+ "maven-mvnd-*" {
61
+ $USE_MVND = $true
62
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
63
+ $MVN_CMD = "mvnd.cmd"
64
+ break
65
+ }
66
+ default {
67
+ $USE_MVND = $false
68
+ $MVN_CMD = $script -replace '^mvnw','mvn'
69
+ break
70
+ }
71
+ }
72
+
73
+ # apply MVNW_REPOURL and calculate MAVEN_HOME
74
+ # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
75
+ if ($env:MVNW_REPOURL) {
76
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
77
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
78
+ }
79
+ $distributionUrlName = $distributionUrl -replace '^.*/',''
80
+ $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
81
+
82
+ $MAVEN_M2_PATH = "$HOME/.m2"
83
+ if ($env:MAVEN_USER_HOME) {
84
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
85
+ }
86
+
87
+ if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
88
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
89
+ }
90
+
91
+ $MAVEN_WRAPPER_DISTS = $null
92
+ if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
93
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
94
+ } else {
95
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
96
+ }
97
+
98
+ $MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
99
+ $MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
100
+ $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
101
+
102
+ if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
103
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
104
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
105
+ exit $?
106
+ }
107
+
108
+ if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
109
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
110
+ }
111
+
112
+ # prepare tmp dir
113
+ $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
114
+ $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
115
+ $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
116
+ trap {
117
+ if ($TMP_DOWNLOAD_DIR.Exists) {
118
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
119
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
120
+ }
121
+ }
122
+
123
+ New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
124
+
125
+ # Download and Install Apache Maven
126
+ Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
127
+ Write-Verbose "Downloading from: $distributionUrl"
128
+ Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
129
+
130
+ $webclient = New-Object System.Net.WebClient
131
+ if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
132
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
133
+ }
134
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
135
+ $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
136
+
137
+ # If specified, validate the SHA-256 sum of the Maven distribution zip file
138
+ $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
139
+ if ($distributionSha256Sum) {
140
+ if ($USE_MVND) {
141
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
142
+ }
143
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
144
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
145
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
146
+ }
147
+ }
148
+
149
+ # unzip and move
150
+ Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
151
+
152
+ # Find the actual extracted directory name (handles snapshots where filename != directory name)
153
+ $actualDistributionDir = ""
154
+
155
+ # First try the expected directory name (for regular distributions)
156
+ $expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
157
+ $expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
158
+ if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
159
+ $actualDistributionDir = $distributionUrlNameMain
160
+ }
161
+
162
+ # If not found, search for any directory with the Maven executable (for snapshots)
163
+ if (!$actualDistributionDir) {
164
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
165
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
166
+ if (Test-Path -Path $testPath -PathType Leaf) {
167
+ $actualDistributionDir = $_.Name
168
+ }
169
+ }
170
+ }
171
+
172
+ if (!$actualDistributionDir) {
173
+ Write-Error "Could not find Maven distribution directory in extracted archive"
174
+ }
175
+
176
+ Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
177
+ Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
178
+ try {
179
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
180
+ } catch {
181
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
182
+ Write-Error "fail to move MAVEN_HOME"
183
+ }
184
+ } finally {
185
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
186
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
187
+ }
188
+
189
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
pom.xml ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+ <modelVersion>4.0.0</modelVersion>
6
+
7
+ <parent>
8
+ <groupId>org.springframework.boot</groupId>
9
+ <artifactId>spring-boot-starter-parent</artifactId>
10
+ <version>3.2.5</version>
11
+ <relativePath />
12
+ </parent>
13
+
14
+ <groupId>com.darkfantasy</groupId>
15
+ <artifactId>darkfantasy-landing</artifactId>
16
+ <version>1.0.0</version>
17
+ <packaging>war</packaging>
18
+ <name>Dark Fantasy Landing Page</name>
19
+ <description>Gothic Horror Dark Fantasy Landing Page - Spring Boot + JSP</description>
20
+
21
+ <properties>
22
+ <java.version>21</java.version>
23
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
24
+ </properties>
25
+
26
+ <dependencies>
27
+ <dependency>
28
+ <groupId>org.springframework.boot</groupId>
29
+ <artifactId>spring-boot-starter-mail</artifactId>
30
+ </dependency>
31
+ <dependency>
32
+ <groupId>org.thymeleaf.extras</groupId>
33
+ <artifactId>thymeleaf-extras-springsecurity6</artifactId>
34
+ </dependency>
35
+ <dependency>
36
+ <groupId>org.springframework.boot</groupId>
37
+ <artifactId>spring-boot-starter-thymeleaf</artifactId>
38
+ </dependency>
39
+ <dependency>
40
+ <groupId>org.mariadb.jdbc</groupId>
41
+ <artifactId>mariadb-java-client</artifactId>
42
+ </dependency>
43
+ <dependency>
44
+ <groupId>org.springframework.boot</groupId>
45
+ <artifactId>spring-boot-starter-validation</artifactId>
46
+ </dependency>
47
+ <dependency>
48
+ <groupId>org.springframework.boot</groupId>
49
+ <artifactId>spring-boot-starter-data-jpa</artifactId>
50
+ </dependency>
51
+ <dependency>
52
+ <groupId>org.projectlombok</groupId>
53
+ <artifactId>lombok</artifactId>
54
+ <!-- <version>1.18.32</version> -->
55
+ <optional>true</optional>
56
+ </dependency>
57
+ <!-- OAuth2 Google Login -->
58
+ <dependency>
59
+ <groupId>org.springframework.boot</groupId>
60
+ <artifactId>spring-boot-starter-oauth2-client</artifactId>
61
+ </dependency>
62
+ <!-- Spring Boot Web -->
63
+ <dependency>
64
+ <groupId>org.springframework.boot</groupId>
65
+ <artifactId>spring-boot-starter-web</artifactId>
66
+ </dependency>
67
+
68
+ <!-- Embedded Tomcat (not provided so java -jar works) -->
69
+ <dependency>
70
+ <groupId>org.springframework.boot</groupId>
71
+ <artifactId>spring-boot-starter-tomcat</artifactId>
72
+ </dependency>
73
+ <dependency>
74
+ <groupId>org.springframework.boot</groupId>
75
+ <artifactId>spring-boot-devtools</artifactId>
76
+ <scope>runtime</scope>
77
+ <optional>true</optional>
78
+ </dependency>
79
+ <!-- JSP support -->
80
+ <dependency>
81
+ <groupId>org.apache.tomcat.embed</groupId>
82
+ <artifactId>tomcat-embed-jasper</artifactId>
83
+ </dependency>
84
+
85
+ <!-- JSTL -->
86
+ <dependency>
87
+ <groupId>jakarta.servlet.jsp.jstl</groupId>
88
+ <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
89
+ </dependency>
90
+ <dependency>
91
+ <groupId>org.glassfish.web</groupId>
92
+ <artifactId>jakarta.servlet.jsp.jstl</artifactId>
93
+ </dependency>
94
+ <dependency>
95
+ <groupId>com.mysql</groupId>
96
+ <artifactId>mysql-connector-j</artifactId>
97
+ <scope>runtime</scope>
98
+ </dependency>
99
+
100
+ </dependencies>
101
+
102
+ <build>
103
+ <plugins>
104
+
105
+
106
+ <plugin>
107
+ <groupId>org.springframework.boot</groupId>
108
+ <artifactId>spring-boot-maven-plugin</artifactId>
109
+ </plugin>
110
+ <plugin>
111
+ <groupId>org.apache.maven.plugins</groupId>
112
+ <artifactId>maven-compiler-plugin</artifactId>
113
+ <version>3.11.0</version>
114
+
115
+ <configuration>
116
+ <release>21</release>
117
+
118
+ <annotationProcessorPaths>
119
+ <path>
120
+ <groupId>org.projectlombok</groupId>
121
+ <artifactId>lombok</artifactId>
122
+ <version>1.18.32</version>
123
+ </path>
124
+ </annotationProcessorPaths>
125
+
126
+ </configuration>
127
+ </plugin>
128
+ </plugins>
129
+ </build>
130
+ </project>
src/main/java/com/darkfantasy/DarkFantasyApplication.java ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy;
2
+
3
+ import org.springframework.boot.SpringApplication;
4
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
6
+ import org.springframework.boot.builder.SpringApplicationBuilder;
7
+
8
+ @SpringBootApplication
9
+ public class DarkFantasyApplication extends SpringBootServletInitializer {
10
+
11
+ @Override
12
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
13
+ return application.sources(DarkFantasyApplication.class);
14
+ }
15
+
16
+ public static void main(String[] args) {
17
+ SpringApplication.run(DarkFantasyApplication.class, args);
18
+ }
19
+ }
src/main/java/com/darkfantasy/annotation/ChangePasswordMatch.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.annotation;
2
+
3
+ import java.lang.annotation.ElementType;
4
+ import java.lang.annotation.Retention;
5
+ import java.lang.annotation.RetentionPolicy;
6
+ import java.lang.annotation.Target;
7
+
8
+ import com.darkfantasy.validator.PasswordChangeMatchValidator;
9
+
10
+ import jakarta.validation.Constraint;
11
+ import jakarta.validation.Payload;
12
+
13
+ @Target(ElementType.TYPE)
14
+ @Retention(RetentionPolicy.RUNTIME)
15
+ @Constraint(validatedBy = PasswordChangeMatchValidator.class)
16
+ public @interface ChangePasswordMatch {
17
+
18
+ String message() default "Mật khẩu xác nhận không khớp";
19
+
20
+ Class<?>[] groups() default {};
21
+
22
+ Class<? extends Payload>[] payload() default {};
23
+ }
src/main/java/com/darkfantasy/annotation/RegisterPasswordMatch.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.annotation;
2
+
3
+ import java.lang.annotation.ElementType;
4
+ import java.lang.annotation.Retention;
5
+ import java.lang.annotation.RetentionPolicy;
6
+ import java.lang.annotation.Target;
7
+
8
+ import com.darkfantasy.validator.PasswordRegisterMatchValidator;
9
+
10
+ import jakarta.validation.Constraint;
11
+ import jakarta.validation.Payload;
12
+
13
+ @Target(ElementType.TYPE)
14
+ @Retention(RetentionPolicy.RUNTIME)
15
+ @Constraint(validatedBy = PasswordRegisterMatchValidator.class)
16
+ public @interface RegisterPasswordMatch {
17
+
18
+ String message() default "Mật khẩu xác nhận không khớp";
19
+
20
+ Class<?>[] groups() default {};
21
+
22
+ Class<? extends Payload>[] payload() default {};
23
+ }
src/main/java/com/darkfantasy/annotation/ResetPasswordMatch.java ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.annotation;
2
+
3
+ import java.lang.annotation.ElementType;
4
+ import java.lang.annotation.Retention;
5
+ import java.lang.annotation.RetentionPolicy;
6
+ import java.lang.annotation.Target;
7
+
8
+ import com.darkfantasy.validator.ResetPasswordMatchValidator;
9
+
10
+ import jakarta.validation.Constraint;
11
+ import jakarta.validation.Payload;
12
+
13
+ @Target(ElementType.TYPE)
14
+ @Retention(RetentionPolicy.RUNTIME)
15
+ @Constraint(validatedBy = ResetPasswordMatchValidator.class)
16
+ public @interface ResetPasswordMatch {
17
+
18
+ String message() default "Mật khẩu xác nhận không khớp";
19
+
20
+ Class<?>[] groups() default {};
21
+
22
+ Class<? extends Payload>[] payload() default {};
23
+ }
src/main/java/com/darkfantasy/config/SchedulingConfig.java ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // package com.darkfantasy.config;
2
+
3
+ // public class SchedulingConfig {
4
+
5
+ // }
src/main/java/com/darkfantasy/config/WebConfig.java ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.config;
2
+
3
+ import org.springframework.context.annotation.Configuration;
4
+ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
5
+ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
6
+ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7
+
8
+ import com.darkfantasy.security.ForceChangePasswordInterceptor;
9
+
10
+ import lombok.RequiredArgsConstructor;
11
+
12
+ @Configuration
13
+ @RequiredArgsConstructor
14
+ public class WebConfig implements WebMvcConfigurer {
15
+ private final ForceChangePasswordInterceptor interceptor;
16
+
17
+ @Override
18
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
19
+ registry.addResourceHandler("/uploads/**")
20
+ .addResourceLocations("file:uploads/");
21
+ }
22
+
23
+ @Override
24
+ public void addInterceptors(InterceptorRegistry registry) {
25
+ registry.addInterceptor(interceptor);
26
+ }
27
+ }
src/main/java/com/darkfantasy/constant/CmsMenu.java ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.constant;
2
+
3
+ import lombok.AccessLevel;
4
+ import lombok.NoArgsConstructor;
5
+
6
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
7
+ public final class CmsMenu {
8
+ public static final String DASHBOARD = "dashboard";
9
+ public static final String ARTICLE = "article";
10
+ public static final String STORY = "story";
11
+ public static final String WORLD = "world";
12
+ public static final String CHARACTER = "character";
13
+ public static final String FAQ = "faq";
14
+ public static final String CONTRIBUTOR = "contributor";
15
+ public static final String CONTACT = "contact";
16
+ }
src/main/java/com/darkfantasy/constant/Routes.java ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.constant;
2
+
3
+ import lombok.AccessLevel;
4
+ import lombok.NoArgsConstructor;
5
+
6
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
7
+ public final class Routes {
8
+ public static final String USER = "/user/moonblight";
9
+ public static final String ADMIN = "/admin/moonblight";
10
+ public static final String DASHBOARD = "/dashboard/moonblight";
11
+ public static final String ARTICLE = "/article/moonblight";
12
+ public static final String CHARACTER = "/character/moonblight";
13
+ public static final String WORLD = "/world/moonblight";
14
+ public static final String STORY = "/story/moonblight";
15
+ public static final String FAQ = "/faq/moonblight";
16
+ public static final String CONTRIBUTOR = "/contributor/moonblight";
17
+ public static final String CONTACT = "/contact/moonblight";
18
+ }
src/main/java/com/darkfantasy/controller/AdminController.java ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.domain.Sort.Direction;
5
+ import org.springframework.data.web.PageableDefault;
6
+ import org.springframework.stereotype.Controller;
7
+ import org.springframework.ui.Model;
8
+ import org.springframework.web.bind.annotation.GetMapping;
9
+ import org.springframework.web.bind.annotation.PathVariable;
10
+ import org.springframework.web.bind.annotation.RequestMapping;
11
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
12
+
13
+ import lombok.RequiredArgsConstructor;
14
+
15
+ import com.darkfantasy.constant.Routes;
16
+ import com.darkfantasy.dto.user.UserResponse;
17
+ import com.darkfantasy.service.AuditLogService;
18
+ import com.darkfantasy.service.UserService;
19
+ import org.springframework.web.bind.annotation.PostMapping;
20
+
21
+ @Controller
22
+ @RequiredArgsConstructor
23
+ @RequestMapping(Routes.ADMIN + "/")
24
+ public class AdminController {
25
+ private final AuditLogService auditLogService;
26
+ private final UserService userService;
27
+
28
+ @GetMapping("dashboard")
29
+ public String toDashboard(Model model) {
30
+ model.addAttribute("totalUsers", userService.count());
31
+ model.addAttribute("activeUsers", userService.countActiveUsers());
32
+ model.addAttribute("inActiveUsers", userService.countInactiveUsers());
33
+ return "cms/admin/admin-dashboard";
34
+ }
35
+
36
+ @GetMapping("list")
37
+ public String toAccountList(
38
+ @PageableDefault(size = 10) Pageable pageable,
39
+ Model model) {
40
+ model.addAttribute("accounts", userService.getAccounts(pageable));
41
+ return "cms/admin/account-list";
42
+ }
43
+
44
+ @GetMapping("account/{id}")
45
+ public String toDetailAccount(
46
+ @PathVariable("id") Long id,
47
+ Model model) {
48
+
49
+ UserResponse response = userService.findAccountById(id);
50
+
51
+ model.addAttribute("account", response);
52
+ return "cms/admin/account-detail";
53
+ }
54
+
55
+ @GetMapping("log")
56
+ public String toLogList(
57
+ @PageableDefault(size = 20, sort = "createdAt", direction = Direction.DESC) Pageable pageable,
58
+ Model model) {
59
+
60
+ model.addAttribute("logs", auditLogService.getLogs(pageable));
61
+ model.addAttribute("activeMenu", "audit-log");
62
+ return "cms/admin/audit-log";
63
+ }
64
+
65
+ @PostMapping("account/{id}/approve")
66
+ public String approveAccount(
67
+ @PathVariable("id") Long id,
68
+ RedirectAttributes redirectAttributes) {
69
+
70
+ userService.unlockUser(id);
71
+
72
+ redirectAttributes.addFlashAttribute("successMessage", "Duyệt tài khoản thành công");
73
+ return "redirect:" + Routes.ADMIN + "/account/" + id;
74
+ }
75
+
76
+ @PostMapping("account/{id}/reject")
77
+ public String rejectAccount(
78
+ @PathVariable("id") Long id,
79
+ RedirectAttributes redirectAttributes) {
80
+
81
+ userService.lockUser(id);
82
+
83
+ redirectAttributes.addFlashAttribute("successMessage", "Từ chối tài khoản thành công");
84
+ return "redirect:" + Routes.ADMIN + "/account/" + id;
85
+ }
86
+
87
+ @PostMapping("account/{id}/lock")
88
+ public String lockAccount(
89
+ @PathVariable("id") Long id,
90
+ RedirectAttributes redirectAttributes) {
91
+
92
+ userService.lockUser(id);
93
+
94
+ redirectAttributes.addFlashAttribute("successMessage", "Khóa tài khoản thành công");
95
+ return "redirect:" + Routes.ADMIN + "/account/" + id;
96
+ }
97
+
98
+ @PostMapping("account/{id}/unlock")
99
+ public String unlockAccount(
100
+ @PathVariable("id") Long id,
101
+ RedirectAttributes redirectAttributes) {
102
+
103
+ userService.unlockUser(id);
104
+
105
+ redirectAttributes.addFlashAttribute("successMessage", "Mở khóa tài khoản thành công");
106
+ return "redirect:" + Routes.ADMIN + "/account/" + id;
107
+ }
108
+
109
+ }
src/main/java/com/darkfantasy/controller/ArticleController.java ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.validation.BindingResult;
8
+ import org.springframework.web.bind.annotation.GetMapping;
9
+ import org.springframework.web.bind.annotation.ModelAttribute;
10
+ import org.springframework.web.bind.annotation.PathVariable;
11
+ import org.springframework.web.bind.annotation.PostMapping;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RequestParam;
14
+ import org.springframework.web.multipart.MultipartFile;
15
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16
+
17
+ import com.darkfantasy.constant.CmsMenu;
18
+ import com.darkfantasy.constant.Routes;
19
+ import com.darkfantasy.dto.article.ArticleResponse;
20
+ import com.darkfantasy.dto.article.CreateArticleRequest;
21
+ import com.darkfantasy.dto.article.UpdateArticleRequest;
22
+ import com.darkfantasy.entity.enums.ArticleType;
23
+ import com.darkfantasy.service.ArticleService;
24
+ import com.darkfantasy.service.FileStorageService;
25
+
26
+ import jakarta.validation.Valid;
27
+ import lombok.RequiredArgsConstructor;
28
+
29
+ @Controller
30
+ @RequestMapping(Routes.ARTICLE + "/")
31
+ @RequiredArgsConstructor
32
+ public class ArticleController {
33
+
34
+ private final ArticleService articleService;
35
+ private final FileStorageService fileStorageService;
36
+
37
+ @ModelAttribute("articleTypes")
38
+ public ArticleType[] articleTypes() {
39
+ return ArticleType.values();
40
+ }
41
+
42
+ @GetMapping({ "/list", "/list/" })
43
+ public String articleList(
44
+ @PageableDefault(size = 10) Pageable pageable,
45
+ Model model) {
46
+ model.addAttribute(
47
+ "articles",
48
+ articleService.getArticles(pageable));
49
+ model.addAttribute("activeMenu", CmsMenu.ARTICLE);
50
+ return "cms/article/article-list";
51
+ }
52
+
53
+ @GetMapping({ "/create", "/create/" })
54
+ public String toCreatePage(Model model) {
55
+ model.addAttribute("createArticleRequest", new CreateArticleRequest());
56
+ return "cms/article/article-create";
57
+ }
58
+
59
+ @PostMapping("/create")
60
+ public String createArticle(
61
+ @Valid @ModelAttribute("createArticleRequest") CreateArticleRequest request,
62
+ BindingResult result,
63
+ @RequestParam("thumbnail") MultipartFile thumbnailFile,
64
+ Model model,
65
+ RedirectAttributes redirectAttributes) {
66
+
67
+ if (result.hasErrors()) {
68
+ return "cms/article/article-create";
69
+ }
70
+
71
+ try {
72
+
73
+ if (!thumbnailFile.isEmpty()) {
74
+ request.setThumbnailUrl(fileStorageService.saveFile(thumbnailFile, "articles"));
75
+ }
76
+
77
+ ArticleResponse response = articleService.createArticle(request);
78
+
79
+ redirectAttributes.addFlashAttribute(
80
+ "successMessage",
81
+ "Tạo bài viết thành công: "
82
+ + response.getTitle());
83
+
84
+ return "redirect:"+ Routes.ARTICLE +"/list";
85
+
86
+ } catch (Exception e) {
87
+
88
+ model.addAttribute(
89
+ "errorMessage",
90
+ e.getMessage());
91
+
92
+ return "cms/article/article-create";
93
+ }
94
+ }
95
+
96
+ @PostMapping("{id}/delete")
97
+ public String deleteArticle(
98
+ @PathVariable("id") Long id,
99
+ RedirectAttributes redirectAttributes) {
100
+
101
+ try {
102
+ articleService.deleteArticle(id);
103
+
104
+ redirectAttributes.addFlashAttribute(
105
+ "successMessage",
106
+ "Xóa bài viết thành công");
107
+
108
+ } catch (Exception e) {
109
+ redirectAttributes.addFlashAttribute(
110
+ "errorMessage",
111
+ e.getMessage());
112
+ }
113
+
114
+ return "redirect:"+ Routes.ARTICLE +"/list";
115
+ }
116
+
117
+ @PostMapping("{id}/restore")
118
+ public String restoreArticle(
119
+ @PathVariable("id") Long id,
120
+ RedirectAttributes redirectAttributes) {
121
+ try {
122
+ articleService.restoreArticle(id);
123
+ redirectAttributes.addFlashAttribute(
124
+ "successMessage",
125
+ "Khôi phục bài viết thành công");
126
+ } catch (Exception e) {
127
+ redirectAttributes.addFlashAttribute(
128
+ "errorMessage",
129
+ e.getMessage());
130
+ }
131
+ return "redirect:"+ Routes.ARTICLE +"/list";
132
+ }
133
+
134
+ @GetMapping({ "/{id}/edit", "/{id}/edit/" })
135
+ public String editArticlePage(
136
+ @PathVariable Long id,
137
+ Model model) {
138
+
139
+ ArticleResponse article = articleService.getArticleById(id);
140
+
141
+ model.addAttribute("article", article);
142
+
143
+ return "cms/article/article-edit";
144
+ }
145
+
146
+ @PostMapping("/{id}/edit")
147
+ public String editArticle(
148
+ @PathVariable Long id,
149
+ @Valid @ModelAttribute("article") UpdateArticleRequest request,
150
+ BindingResult result,
151
+ @RequestParam("thumbnail") MultipartFile thumbnailFile,
152
+ Model model) {
153
+
154
+ if (result.hasErrors()) {
155
+ ArticleResponse oldArticle = articleService.getArticleById(id);
156
+
157
+ request.setThumbnailUrl(
158
+ oldArticle.getThumbnailUrl());
159
+
160
+ model.addAttribute(
161
+ "articleTypes",
162
+ ArticleType.values());
163
+
164
+ return "cms/article/article-edit";
165
+ }
166
+
167
+ request.setId(id);
168
+
169
+ try {
170
+
171
+ if (!thumbnailFile.isEmpty()) {
172
+
173
+ request.setThumbnailUrl(
174
+ fileStorageService.saveFile(thumbnailFile, "articles"));
175
+ }
176
+
177
+ articleService.updateArticle(request);
178
+
179
+ return "redirect:"+ Routes.ARTICLE +"/list";
180
+
181
+ } catch (Exception e) {
182
+
183
+ model.addAttribute(
184
+ "articleTypes",
185
+ ArticleType.values());
186
+
187
+ model.addAttribute(
188
+ "errorMessage",
189
+ e.getMessage());
190
+
191
+ return "cms/article/article-edit";
192
+ }
193
+ }
194
+
195
+ }
src/main/java/com/darkfantasy/controller/ContactMessageController.java ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.web.bind.annotation.GetMapping;
8
+ import org.springframework.web.bind.annotation.PathVariable;
9
+ import org.springframework.web.bind.annotation.PostMapping;
10
+ import org.springframework.web.bind.annotation.RequestMapping;
11
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
12
+
13
+ import com.darkfantasy.constant.CmsMenu;
14
+ import com.darkfantasy.constant.Routes;
15
+ import com.darkfantasy.service.ContactMessageService;
16
+
17
+ import lombok.RequiredArgsConstructor;
18
+
19
+ @Controller
20
+ @RequiredArgsConstructor
21
+ @RequestMapping(Routes.CONTACT + "/")
22
+ public class ContactMessageController {
23
+ private final ContactMessageService contactMessageService;
24
+ @GetMapping("list")
25
+ public String toList(
26
+ @PageableDefault(size = 20) Pageable pageable,
27
+ Model model) {
28
+ model.addAttribute("messages", contactMessageService.getMessages(pageable));
29
+ model.addAttribute("activeMenu", CmsMenu.CONTACT);
30
+
31
+ return "cms/contact/contact-list";
32
+ }
33
+
34
+ @GetMapping("{id}")
35
+ public String detail(
36
+ @PathVariable Long id,
37
+ Model model) {
38
+
39
+ model.addAttribute("message", contactMessageService.getMessageById(id));
40
+ model.addAttribute("activeMenu", CmsMenu.CONTACT);
41
+ return "cms/contact/contact-detail";
42
+ }
43
+
44
+ @PostMapping("{id}/process")
45
+ public String process(@PathVariable Long id, RedirectAttributes redirectAttributes) {
46
+ contactMessageService.markAsProcessed(id);
47
+ redirectAttributes.addFlashAttribute("successMessage", "Đã đánh dấu xử lý.");
48
+ return "redirect:" + Routes.CONTACT + "/list";
49
+ }
50
+
51
+ @PostMapping("{id}/unprocess")
52
+ public String unprocess(
53
+ @PathVariable Long id,
54
+ RedirectAttributes redirectAttributes) {
55
+
56
+ contactMessageService.markAsUnprocessed(id);
57
+
58
+ redirectAttributes.addFlashAttribute("successMessage", "Đã đánh dấu chưa xử lý.");
59
+
60
+ return "redirect:" + Routes.CONTACT + "/list";
61
+ }
62
+ }
src/main/java/com/darkfantasy/controller/ContributorController.java ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.validation.BindingResult;
8
+ import org.springframework.web.bind.annotation.GetMapping;
9
+ import org.springframework.web.bind.annotation.ModelAttribute;
10
+ import org.springframework.web.bind.annotation.PathVariable;
11
+ import org.springframework.web.bind.annotation.PostMapping;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RequestParam;
14
+ import org.springframework.web.multipart.MultipartFile;
15
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16
+
17
+ import com.darkfantasy.constant.CmsMenu;
18
+ import com.darkfantasy.constant.Routes;
19
+ import com.darkfantasy.dto.contributor.ContributorResponse;
20
+ import com.darkfantasy.dto.contributor.CreateContributorRequest;
21
+ import com.darkfantasy.dto.contributor.UpdateContributorRequest;
22
+ import com.darkfantasy.service.ContributorService;
23
+ import com.darkfantasy.service.FileStorageService;
24
+
25
+ import jakarta.validation.Valid;
26
+ import lombok.RequiredArgsConstructor;
27
+
28
+ @Controller
29
+ @RequestMapping(Routes.CONTRIBUTOR + "/")
30
+ @RequiredArgsConstructor
31
+ public class ContributorController {
32
+
33
+ private final ContributorService contributorService;
34
+ private final FileStorageService fileStorageService;
35
+
36
+ @GetMapping({ "/list", "/list/" })
37
+ public String contributorList(
38
+ @PageableDefault(size = 10) Pageable pageable,
39
+ Model model) {
40
+ model.addAttribute(
41
+ "contributors",
42
+ contributorService.getContributors(pageable));
43
+ model.addAttribute("activeMenu", CmsMenu.CONTRIBUTOR);
44
+ return "cms/contributor/contributor-list";
45
+ }
46
+
47
+ @GetMapping({ "/create", "/create/" })
48
+ public String toCreatePage(Model model) {
49
+ model.addAttribute("createContributorRequest", new CreateContributorRequest());
50
+ return "cms/contributor/contributor-create";
51
+ }
52
+
53
+ @PostMapping("/create")
54
+ public String createContributor(
55
+ @Valid @ModelAttribute("createContributorRequest") CreateContributorRequest request,
56
+ BindingResult result,
57
+ @RequestParam("thumbnail") MultipartFile thumbnailFile,
58
+ Model model,
59
+ RedirectAttributes redirectAttributes) {
60
+
61
+ if (result.hasErrors()) {
62
+ return "cms/contributor/contributor-create";
63
+ }
64
+
65
+ try {
66
+
67
+ if (!thumbnailFile.isEmpty()) {
68
+ request.setAvatar(fileStorageService.saveFile(thumbnailFile, "contributors"));
69
+ }
70
+
71
+ ContributorResponse response = contributorService.createContributor(request);
72
+
73
+ redirectAttributes.addFlashAttribute(
74
+ "successMessage",
75
+ "Tạo người đóng góp thành công: "
76
+ + response.getName());
77
+
78
+ return "redirect:" + Routes.CONTRIBUTOR + "/list";
79
+
80
+ } catch (Exception e) {
81
+
82
+ model.addAttribute(
83
+ "errorMessage",
84
+ e.getMessage());
85
+
86
+ return "cms/contributor/contributor-create";
87
+ }
88
+ }
89
+
90
+ @PostMapping("{id}/delete")
91
+ public String deleteContributor(
92
+ @PathVariable("id") Long id,
93
+ RedirectAttributes redirectAttributes) {
94
+
95
+ try {
96
+ contributorService.deleteContributor(id);
97
+
98
+ redirectAttributes.addFlashAttribute(
99
+ "successMessage",
100
+ "Xóa người đóng góp thành công");
101
+
102
+ } catch (Exception e) {
103
+ redirectAttributes.addFlashAttribute(
104
+ "errorMessage",
105
+ e.getMessage());
106
+ }
107
+
108
+ return "redirect:" + Routes.CONTRIBUTOR + "/list";
109
+ }
110
+
111
+ @PostMapping("{id}/restore")
112
+ public String restoreContributor(
113
+ @PathVariable("id") Long id,
114
+ RedirectAttributes redirectAttributes) {
115
+ try {
116
+ contributorService.restoreContributor(id);
117
+ redirectAttributes.addFlashAttribute(
118
+ "successMessage",
119
+ "Khôi phục người đóng góp thành công");
120
+ } catch (Exception e) {
121
+ redirectAttributes.addFlashAttribute(
122
+ "errorMessage",
123
+ e.getMessage());
124
+ }
125
+ return "redirect:" + Routes.CONTRIBUTOR + "/list";
126
+ }
127
+
128
+ @GetMapping({ "/{id}/edit", "/{id}/edit/" })
129
+ public String editContributorPage(
130
+ @PathVariable Long id,
131
+ Model model) {
132
+
133
+ ContributorResponse contributor = contributorService.getContributorById(id);
134
+
135
+ model.addAttribute("contributor", contributor);
136
+
137
+ return "cms/contributor/contributor-edit";
138
+ }
139
+
140
+ @PostMapping("/{id}/edit")
141
+ public String editContributor(
142
+ @PathVariable Long id,
143
+ @Valid @ModelAttribute("contributor") UpdateContributorRequest request,
144
+ BindingResult result,
145
+ @RequestParam("thumbnail") MultipartFile thumbnailFile,
146
+ Model model) {
147
+
148
+ if (result.hasErrors()) {
149
+ ContributorResponse oldContributor = contributorService.getContributorById(id);
150
+
151
+ request.setAvatar(
152
+ oldContributor.getAvatar());
153
+
154
+ return "cms/contributor/contributor-edit";
155
+ }
156
+
157
+ request.setId(id);
158
+
159
+ try {
160
+
161
+ if (!thumbnailFile.isEmpty()) {
162
+
163
+ request.setAvatar(
164
+ fileStorageService.saveFile(thumbnailFile, "contributors"));
165
+ }
166
+
167
+ contributorService.updateContributor(request);
168
+
169
+ return "redirect:" + Routes.CONTRIBUTOR + "/list";
170
+
171
+ } catch (Exception e) {
172
+
173
+ model.addAttribute(
174
+ "errorMessage",
175
+ e.getMessage());
176
+
177
+ return "cms/contributor/contributor-edit";
178
+ }
179
+ }
180
+
181
+ }
src/main/java/com/darkfantasy/controller/DashboardController.java ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.stereotype.Controller;
4
+ import org.springframework.ui.Model;
5
+ import org.springframework.util.RouteMatcher.Route;
6
+ import org.springframework.web.bind.annotation.GetMapping;
7
+ import org.springframework.web.bind.annotation.RequestMapping;
8
+
9
+ import com.darkfantasy.constant.CmsMenu;
10
+ import com.darkfantasy.constant.Routes;
11
+ import com.darkfantasy.service.ArticleService;
12
+ import com.darkfantasy.service.ContactMessageService;
13
+ import com.darkfantasy.service.ContributorService;
14
+ import com.darkfantasy.service.FaqService;
15
+ import com.darkfantasy.service.GameCharacterService;
16
+ import com.darkfantasy.service.StoryService;
17
+ import com.darkfantasy.service.WorldService;
18
+
19
+ import lombok.RequiredArgsConstructor;
20
+
21
+ @Controller
22
+ @RequiredArgsConstructor
23
+ @RequestMapping(Routes.DASHBOARD)
24
+
25
+ public class DashboardController {
26
+ private final ArticleService articleService;
27
+ private final GameCharacterService gameCharacterService;
28
+ private final WorldService worldService;
29
+ private final StoryService storyService;
30
+ private final FaqService faqService;
31
+ private final ContributorService contributorService;
32
+ private final ContactMessageService contactMessageService;
33
+
34
+ @GetMapping({ "", "/" })
35
+ public String toDashboard(Model model) {
36
+ model.addAttribute("activeMenu", CmsMenu.DASHBOARD);
37
+ model.addAttribute("articleCount", articleService.count());
38
+ model.addAttribute("storyCount", storyService.count());
39
+ model.addAttribute("worldCount", worldService.count());
40
+ model.addAttribute("characterCount", gameCharacterService.count());
41
+ model.addAttribute("faqCount", faqService.count());
42
+ model.addAttribute("contributorCount", contributorService.count());
43
+ model.addAttribute("unprocessedContactCount", contactMessageService.countByProcessedFalse());
44
+ return "cms/dashboard";
45
+ }
46
+ }
src/main/java/com/darkfantasy/controller/FaqController.java ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.util.RouteMatcher.Route;
8
+ import org.springframework.validation.BindingResult;
9
+ import org.springframework.web.bind.annotation.GetMapping;
10
+ import org.springframework.web.bind.annotation.ModelAttribute;
11
+ import org.springframework.web.bind.annotation.PathVariable;
12
+ import org.springframework.web.bind.annotation.PostMapping;
13
+ import org.springframework.web.bind.annotation.RequestMapping;
14
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
15
+
16
+ import com.darkfantasy.constant.CmsMenu;
17
+ import com.darkfantasy.constant.Routes;
18
+ import com.darkfantasy.dto.faq.CreateFaqRequest;
19
+ import com.darkfantasy.dto.faq.FaqResponse;
20
+ import com.darkfantasy.dto.faq.UpdateFaqRequest;
21
+ import com.darkfantasy.service.FaqService;
22
+
23
+ import jakarta.validation.Valid;
24
+ import lombok.RequiredArgsConstructor;
25
+
26
+ @Controller
27
+ @RequiredArgsConstructor
28
+ @RequestMapping(Routes.FAQ + "/")
29
+ public class FaqController {
30
+ private final FaqService faqService;
31
+
32
+ @GetMapping({ "list", "list/" })
33
+ public String toList(
34
+ @PageableDefault(size = 10) Pageable pageable,
35
+ Model model) {
36
+
37
+ model.addAttribute(
38
+ "faqs",
39
+ faqService.getFaqs(pageable));
40
+ model.addAttribute("activeMenu", CmsMenu.FAQ);
41
+ return "cms/faq/faq-list";
42
+ }
43
+
44
+ @GetMapping({ "create", "create/" })
45
+ public String toCreatePage(Model model) {
46
+ model.addAttribute("createFaqRequest", new CreateFaqRequest());
47
+ return "cms/faq/faq-create";
48
+ }
49
+
50
+ @PostMapping({ "create", "create/" })
51
+ public String createFaq(
52
+ @Valid @ModelAttribute("createFaqRequest") CreateFaqRequest request,
53
+ BindingResult result,
54
+ Model model,
55
+ RedirectAttributes redirectAttributes) {
56
+
57
+ if (result.hasErrors()) {
58
+ model.addAttribute(
59
+ "errorMessage",
60
+ "Vui lòng kiểm tra lại thông tin FAQ.");
61
+ return "cms/faq/faq-create";
62
+ }
63
+
64
+ try {
65
+
66
+ FaqResponse response = faqService.createFaq(request);
67
+
68
+ redirectAttributes.addFlashAttribute(
69
+ "successMessage",
70
+ "Tạo FAQ thành công: "
71
+ + response.getTitle());
72
+
73
+ return "redirect:" + Routes.FAQ + "/list";
74
+
75
+ } catch (Exception e) {
76
+
77
+ model.addAttribute(
78
+ "errorMessage",
79
+ e.getMessage());
80
+
81
+ return "cms/faq/faq-create";
82
+ }
83
+ }
84
+
85
+ @GetMapping({ "{id}/edit", "{id}/edit/" })
86
+ public String toEditPage(@PathVariable("id") Long id, Model model) {
87
+ FaqResponse faq = faqService.getFaqById(id);
88
+ UpdateFaqRequest request = UpdateFaqRequest.builder()
89
+ .id(faq.getId())
90
+ .title(faq.getTitle())
91
+ .content(faq.getContent())
92
+ .priority(faq.getPriority())
93
+ .build();
94
+
95
+ model.addAttribute(
96
+ "updateFaqRequest",
97
+ request);
98
+ model.addAttribute("faq", faq);
99
+ return "cms/faq/faq-edit";
100
+ }
101
+
102
+ @PostMapping("{id}/edit")
103
+ public String editFaq(@PathVariable Long id,
104
+ @Valid @ModelAttribute("faq") UpdateFaqRequest request,
105
+ BindingResult result,
106
+ Model model) {
107
+ if (result.hasErrors()) {
108
+ return "cms/faq/faq-edit";
109
+ }
110
+
111
+ request.setId(id);
112
+
113
+ try {
114
+ faqService.updateFaq(request);
115
+
116
+ return "redirect:" + Routes.FAQ + "/list";
117
+ } catch (Exception e) {
118
+ model.addAttribute(
119
+ "errorMessage",
120
+ e.getMessage());
121
+
122
+ return "cms/faq/faq-edit";
123
+ }
124
+ }
125
+
126
+ @PostMapping("{id}/delete")
127
+ public String deleteFaq(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
128
+ try {
129
+ faqService.deleteFaq(id);
130
+
131
+ redirectAttributes.addFlashAttribute(
132
+ "successMessage",
133
+ "Xóa faq thành công");
134
+
135
+ } catch (Exception e) {
136
+ redirectAttributes.addFlashAttribute(
137
+ "errorMessage",
138
+ e.getMessage());
139
+ }
140
+
141
+ return "redirect:" + Routes.FAQ + "/list";
142
+ }
143
+
144
+ @PostMapping("{id}/restore")
145
+ public String restoreFaq(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
146
+ try {
147
+ faqService.restoreFaq(id);
148
+
149
+ redirectAttributes.addFlashAttribute(
150
+ "successMessage",
151
+ "Khôi phục faq thành công");
152
+
153
+ } catch (Exception e) {
154
+ redirectAttributes.addFlashAttribute(
155
+ "errorMessage",
156
+ e.getMessage());
157
+ }
158
+
159
+ return "redirect:" + Routes.FAQ + "/list";
160
+ }
161
+ }
src/main/java/com/darkfantasy/controller/GameCharacterController.java ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.validation.BindingResult;
8
+ import org.springframework.web.bind.annotation.RequestMapping;
9
+ import org.springframework.web.bind.annotation.RequestParam;
10
+ import org.springframework.web.multipart.MultipartFile;
11
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
12
+
13
+ import com.darkfantasy.constant.CmsMenu;
14
+ import com.darkfantasy.constant.Routes;
15
+ import com.darkfantasy.dto.gamecharacter.CreateGameCharacterRequest;
16
+ import com.darkfantasy.dto.gamecharacter.GameCharacterResponse;
17
+ import com.darkfantasy.dto.gamecharacter.UpdateGameCharacterRequest;
18
+ import com.darkfantasy.service.FileStorageService;
19
+ import com.darkfantasy.service.GameCharacterService;
20
+
21
+ import jakarta.validation.Valid;
22
+ import lombok.RequiredArgsConstructor;
23
+ import org.springframework.web.bind.annotation.GetMapping;
24
+ import org.springframework.web.bind.annotation.ModelAttribute;
25
+ import org.springframework.web.bind.annotation.PathVariable;
26
+ import org.springframework.web.bind.annotation.PostMapping;
27
+
28
+ @Controller
29
+ @RequestMapping(Routes.CHARACTER + "/")
30
+ @RequiredArgsConstructor
31
+ public class GameCharacterController {
32
+ private final GameCharacterService gameCharacterService;
33
+ private final FileStorageService fileStorageService;
34
+
35
+ @GetMapping({ "list", "list/" })
36
+ public String toList(
37
+ @PageableDefault(size = 10) Pageable pageable,
38
+ Model model) {
39
+
40
+ model.addAttribute(
41
+ "characters",
42
+ gameCharacterService.getCharacters(pageable));
43
+ model.addAttribute("activeMenu", CmsMenu.CHARACTER);
44
+ return "cms/character/character-list";
45
+ }
46
+
47
+ @GetMapping({ "create", "create/" })
48
+ public String toCreatePage(Model model) {
49
+ model.addAttribute("createGameCharacterRequest", new CreateGameCharacterRequest());
50
+ return "cms/character/character-create";
51
+ }
52
+
53
+ @PostMapping({ "create", "create/" })
54
+ public String createCharacter(
55
+ @Valid @ModelAttribute("createGameCharacterRequest") CreateGameCharacterRequest request,
56
+ BindingResult result,
57
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
58
+ Model model,
59
+ RedirectAttributes redirectAttributes) {
60
+
61
+ if (result.hasErrors()) {
62
+ model.addAttribute("errorMessage", "Vui lòng kiểm tra lại thông tin nhân vật.");
63
+ return "cms/character/character-create";
64
+
65
+ }
66
+ try {
67
+ if (imageFile != null && !imageFile.isEmpty()) {
68
+ request.setImage(fileStorageService.saveFile(imageFile, "characters"));
69
+ }
70
+ GameCharacterResponse response = gameCharacterService.createGameCharacter(request);
71
+
72
+ redirectAttributes.addFlashAttribute(
73
+ "successMessage",
74
+ "Tạo nhân vật thành công: "
75
+ + response.getName());
76
+ return "redirect:" + Routes.CHARACTER + "/list";
77
+ } catch (Exception e) {
78
+ model.addAttribute(
79
+ "errorMessage",
80
+ e.getMessage());
81
+
82
+ return "cms/character/character-create";
83
+ }
84
+ }
85
+
86
+ @GetMapping({ "{id}/edit", "{id}/edit/" })
87
+ public String toEditPage(@PathVariable("id") Long id, Model model) {
88
+ GameCharacterResponse character = gameCharacterService.getGameCharacterById(id);
89
+ model.addAttribute("character", character);
90
+ return "cms/character/character-edit";
91
+ }
92
+
93
+ @PostMapping("{id}/edit")
94
+ public String editCharacter(@PathVariable Long id,
95
+ @Valid @ModelAttribute("character") UpdateGameCharacterRequest request,
96
+ BindingResult result,
97
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
98
+ Model model) {
99
+ if (result.hasErrors()) {
100
+ GameCharacterResponse oldCharacter = gameCharacterService.getGameCharacterById(id);
101
+
102
+ request.setImage(
103
+ oldCharacter.getImage());
104
+
105
+ return "cms/character/character-edit";
106
+ }
107
+
108
+ request.setId(id);
109
+
110
+ try {
111
+ if (imageFile != null && !imageFile.isEmpty()) {
112
+ request.setImage(
113
+ fileStorageService.saveFile(imageFile, "characters"));
114
+ }
115
+ gameCharacterService.updateGameCharacter(request);
116
+
117
+ return "redirect:" + Routes.CHARACTER + "/list";
118
+ } catch (Exception e) {
119
+ model.addAttribute(
120
+ "errorMessage",
121
+ e.getMessage());
122
+
123
+ return "cms/character/character-edit";
124
+ }
125
+ }
126
+
127
+ @PostMapping("{id}/delete")
128
+ public String deleteCharacter(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
129
+ try {
130
+ gameCharacterService.deleteCharacter(id);
131
+
132
+ redirectAttributes.addFlashAttribute(
133
+ "successMessage",
134
+ "Xóa nhân vật thành công");
135
+
136
+ } catch (Exception e) {
137
+ redirectAttributes.addFlashAttribute(
138
+ "errorMessage",
139
+ e.getMessage());
140
+ }
141
+
142
+ return "redirect:" + Routes.CHARACTER + "/list";
143
+ }
144
+
145
+ @PostMapping("{id}/restore")
146
+ public String restoreCharacter(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
147
+ try {
148
+ gameCharacterService.restoreCharacter(id);
149
+
150
+ redirectAttributes.addFlashAttribute(
151
+ "successMessage",
152
+ "Khôi phục nhân vật thành công");
153
+
154
+ } catch (Exception e) {
155
+ redirectAttributes.addFlashAttribute(
156
+ "errorMessage",
157
+ e.getMessage());
158
+ }
159
+
160
+ return "redirect:" + Routes.CHARACTER + "/list";
161
+ }
162
+
163
+ }
src/main/java/com/darkfantasy/controller/ImageController.java ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import java.io.IOException;
4
+ import java.util.Map;
5
+
6
+ import org.springframework.http.HttpStatus;
7
+ import org.springframework.http.ResponseEntity;
8
+ import org.springframework.stereotype.Controller;
9
+ import org.springframework.web.bind.annotation.PostMapping;
10
+ import org.springframework.web.bind.annotation.RequestParam;
11
+ import org.springframework.web.bind.annotation.ResponseBody;
12
+ import org.springframework.web.multipart.MultipartFile;
13
+
14
+ import com.darkfantasy.service.FileStorageService;
15
+
16
+ import lombok.RequiredArgsConstructor;
17
+
18
+ @Controller
19
+ @RequiredArgsConstructor
20
+ public class ImageController {
21
+ private final FileStorageService fileStorageService;
22
+
23
+ @PostMapping("/api/upload")
24
+ @ResponseBody
25
+ public ResponseEntity<?> uploadImage(@RequestParam("image") MultipartFile file) {
26
+ try {
27
+ // Gọi Service để lưu file vào thư mục "editor" (nó sẽ tạo ra uploads/editor/)
28
+ String fileUrl = fileStorageService.saveFile(file, "editors");
29
+
30
+ // Trả về JSON cho Quill Frontend: {"url": "/uploads/editor/abc123hash.png"}
31
+ return ResponseEntity.ok(Map.of("url", fileUrl));
32
+
33
+ } catch (IllegalArgumentException e) {
34
+ // Xử lý lỗi file rỗng theo Exception đã ném ở Service
35
+ return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
36
+ } catch (IOException e) {
37
+ // Xử lý lỗi đọc ghi file
38
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
39
+ .body(Map.of("error", "Lỗi xử lý file trên server"));
40
+ }
41
+ }
42
+ }
src/main/java/com/darkfantasy/controller/InternalController.java ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.security.crypto.password.PasswordEncoder;
4
+ import org.springframework.stereotype.Controller;
5
+ import org.springframework.web.bind.annotation.GetMapping;
6
+ import org.springframework.web.bind.annotation.RequestMapping;
7
+ import org.springframework.web.bind.annotation.RequestParam;
8
+ import org.springframework.web.bind.annotation.ResponseBody;
9
+
10
+ import lombok.RequiredArgsConstructor;
11
+
12
+ @Controller
13
+ @RequestMapping("/api/moonblight/internal")
14
+ @RequiredArgsConstructor
15
+ public class InternalController {
16
+
17
+ private final PasswordEncoder passwordEncoder;
18
+
19
+ @GetMapping("/hash")
20
+ @ResponseBody
21
+ public String hash(
22
+ @RequestParam String password) {
23
+
24
+ return passwordEncoder.encode(password);
25
+ }
26
+ }
src/main/java/com/darkfantasy/controller/PageController.java ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Page;
4
+ import org.springframework.data.domain.PageRequest;
5
+ import org.springframework.data.domain.Pageable;
6
+ import org.springframework.stereotype.Controller;
7
+ import org.springframework.ui.Model;
8
+ import org.springframework.validation.BindingResult;
9
+ import org.springframework.web.bind.annotation.GetMapping;
10
+ import org.springframework.web.bind.annotation.ModelAttribute;
11
+ import org.springframework.web.bind.annotation.PathVariable;
12
+ import org.springframework.web.bind.annotation.PostMapping;
13
+ import org.springframework.web.bind.annotation.RequestMapping;
14
+ import org.springframework.web.bind.annotation.RequestParam;
15
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16
+
17
+ import com.darkfantasy.dto.article.ArticleResponse;
18
+ import com.darkfantasy.dto.contact.CreateContactMessageRequest;
19
+ import com.darkfantasy.dto.contributor.ContributorResponse;
20
+ import com.darkfantasy.entity.enums.ContactCategory;
21
+ import com.darkfantasy.entity.enums.ContactPlatform;
22
+ import com.darkfantasy.service.ArticleService;
23
+ import com.darkfantasy.service.ContactMessageService;
24
+ import com.darkfantasy.service.ContributorService;
25
+ import com.darkfantasy.service.FaqService;
26
+ import com.darkfantasy.service.GameCharacterService;
27
+ import com.darkfantasy.service.StoryService;
28
+ import com.darkfantasy.service.WorldService;
29
+
30
+ import jakarta.validation.Valid;
31
+ import lombok.RequiredArgsConstructor;
32
+
33
+ @Controller
34
+ @RequiredArgsConstructor
35
+ @RequestMapping("/")
36
+ public class PageController {
37
+ private final ArticleService articleService;
38
+ private final GameCharacterService gameCharacterService;
39
+ private final WorldService worldService;
40
+ private final StoryService storyService;
41
+ private final FaqService faqService;
42
+ private final ContactMessageService contactMessageService;
43
+ private final ContributorService contributorService;
44
+ @GetMapping
45
+ public String index(Model model) {
46
+
47
+ model.addAttribute("activePage", "home");
48
+ model.addAttribute("pageTitle", "Moon Blight - Enter the Darkness");
49
+
50
+ model.addAttribute("articles", articleService.getLatestArticles(3));
51
+ model.addAttribute("characters", gameCharacterService.getCharactersLimit(4));
52
+ model.addAttribute("topWorld", worldService.getWorldDeletedFalseHighestPriority());
53
+ model.addAttribute("topStory", storyService.getStoryDeletedFalseHighestPriority());
54
+
55
+ return "forward:/WEB-INF/views/index.jsp";
56
+ // return "index";
57
+ }
58
+
59
+ @GetMapping("story")
60
+ public String story(Model model) {
61
+ model.addAttribute("activePage", "story");
62
+ model.addAttribute("pageTitle", "The Story - Moon Blight");
63
+
64
+ model.addAttribute("stories", storyService.getStoriesDeletedFalse());
65
+ return "forward:/WEB-INF/views/storys/story.jsp";
66
+ // return "story/story";
67
+ }
68
+
69
+ @GetMapping("characters")
70
+ public String characters(Model model) {
71
+ model.addAttribute("activePage", "characters");
72
+ model.addAttribute("pageTitle", "Characters - Moon Blight");
73
+
74
+ model.addAttribute("characters", gameCharacterService.getCharactersDeletedFalse());
75
+ return "forward:/WEB-INF/views/characters/characters.jsp";
76
+ }
77
+
78
+ @GetMapping("enemies")
79
+ public String enemies(Model model) {
80
+ model.addAttribute("activePage", "enemies");
81
+ model.addAttribute("pageTitle", "Enemies - Moon Blight");
82
+ return "forward:/WEB-INF/views/enemies/enemies.jsp";
83
+ }
84
+
85
+ @GetMapping("weapons")
86
+ public String weapons(Model model) {
87
+ model.addAttribute("activePage", "weapons");
88
+ model.addAttribute("pageTitle", "Weapons - Moon Blight");
89
+ return "forward:/WEB-INF/views/weapons/weapons.jsp";
90
+ }
91
+
92
+ @GetMapping("world")
93
+ public String world(Model model) {
94
+
95
+ model.addAttribute("activePage", "world");
96
+ model.addAttribute("pageTitle", "The World - Moon Blight");
97
+
98
+ model.addAttribute("worlds", worldService.getWorldsDeletedFalse());
99
+
100
+ return "forward:/WEB-INF/views/worlds/world.jsp";
101
+ }
102
+
103
+ @GetMapping("news")
104
+ public String news(
105
+ @RequestParam(defaultValue = "6") int limit,
106
+ Model model) {
107
+
108
+ Pageable pageable = PageRequest.of(0, limit);
109
+
110
+ Page<ArticleResponse> page = articleService.getArticlesDeletedFalse(pageable);
111
+
112
+ model.addAttribute(
113
+ "articles",
114
+ page.getContent());
115
+
116
+ model.addAttribute(
117
+ "hasMore",
118
+ page.getTotalElements() > limit);
119
+
120
+ model.addAttribute(
121
+ "nextLimit",
122
+ limit + 6);
123
+ model.addAttribute(
124
+ "featuredArticle",
125
+ articleService.getLatestImportantArticle());
126
+
127
+ return "forward:/WEB-INF/views/news/news.jsp";
128
+ }
129
+
130
+ @GetMapping("aboutUs")
131
+ public String community(
132
+ @RequestParam(defaultValue = "4") int limit,
133
+ Model model) {
134
+
135
+ Pageable pageable = PageRequest.of(0, limit);
136
+
137
+ Page<ContributorResponse> page = contributorService.getContributorsDeletedFalse(pageable);
138
+
139
+ model.addAttribute(
140
+ "contributors",
141
+ page.getContent());
142
+
143
+ model.addAttribute(
144
+ "hasMore",
145
+ page.getTotalElements() > limit);
146
+
147
+ model.addAttribute(
148
+ "nextLimit",
149
+ limit + 4);
150
+
151
+ model.addAttribute(
152
+ "activePage",
153
+ "community");
154
+
155
+ model.addAttribute(
156
+ "pageTitle",
157
+ "Community - Moon Blight");
158
+
159
+ return "forward:/WEB-INF/views/community/aboutUs.jsp";
160
+ }
161
+
162
+ @GetMapping("news/{id}")
163
+ public String newsDetail(@PathVariable("id") Long id,
164
+ Model model) {
165
+ model.addAttribute("article", articleService.getPublicArticleById(id));
166
+ model.addAttribute("articles", articleService.getLatestArticlesExcept(id, 3));
167
+ return "forward:/WEB-INF/views/news/newDetail.jsp";
168
+ }
169
+
170
+ @GetMapping("support")
171
+ public String support(Model model) {
172
+ populateSupportModel(model);
173
+ model.addAttribute("contactRequest", new CreateContactMessageRequest());
174
+ return "forward:/WEB-INF/views/supports/support.jsp";
175
+ }
176
+
177
+ @PostMapping("support")
178
+ public String submitContactMessage(
179
+ @Valid @ModelAttribute("contactRequest") CreateContactMessageRequest request,
180
+ BindingResult result,
181
+ Model model,
182
+ RedirectAttributes redirectAttributes) {
183
+
184
+ if (result.hasErrors()) {
185
+ populateSupportModel(model);
186
+ model.addAttribute("errorMessage", "Vui lòng kiểm tra lại thông tin liên hệ.");
187
+ return "forward:/WEB-INF/views/supports/support.jsp";
188
+ }
189
+
190
+ contactMessageService.createMessage(request);
191
+
192
+ redirectAttributes.addFlashAttribute(
193
+ "successMessage",
194
+ "Tin nhắn đã được gửi thành công.");
195
+
196
+ return "redirect:/support";
197
+ }
198
+
199
+ private void populateSupportModel(Model model) {
200
+ model.addAttribute("activePage", "support");
201
+ model.addAttribute("pageTitle", "Support - Moon Blight");
202
+ model.addAttribute("faqs", faqService.getFaqsDeletedFalse());
203
+ model.addAttribute("categories", ContactCategory.values());
204
+ model.addAttribute("platforms", ContactPlatform.values());
205
+ }
206
+ }
src/main/java/com/darkfantasy/controller/StoryController.java ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.validation.BindingResult;
8
+ import org.springframework.web.bind.annotation.GetMapping;
9
+ import org.springframework.web.bind.annotation.ModelAttribute;
10
+ import org.springframework.web.bind.annotation.PathVariable;
11
+ import org.springframework.web.bind.annotation.PostMapping;
12
+ import org.springframework.web.bind.annotation.RequestMapping;
13
+ import org.springframework.web.bind.annotation.RequestParam;
14
+ import org.springframework.web.multipart.MultipartFile;
15
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16
+
17
+ import com.darkfantasy.constant.CmsMenu;
18
+ import com.darkfantasy.constant.Routes;
19
+ import com.darkfantasy.dto.story.CreateStoryRequest;
20
+ import com.darkfantasy.dto.story.StoryResponse;
21
+ import com.darkfantasy.dto.story.UpdateStoryRequest;
22
+ import com.darkfantasy.service.FileStorageService;
23
+ import com.darkfantasy.service.StoryService;
24
+
25
+ import jakarta.validation.Valid;
26
+ import lombok.RequiredArgsConstructor;
27
+
28
+ @Controller
29
+ @RequiredArgsConstructor
30
+ @RequestMapping(Routes.STORY + "/")
31
+ public class StoryController {
32
+ private final StoryService storyService;
33
+ private final FileStorageService fileStorageService;
34
+
35
+ @GetMapping({ "list", "list/" })
36
+ public String toList(
37
+ @PageableDefault(size = 10) Pageable pageable,
38
+ Model model) {
39
+
40
+ model.addAttribute(
41
+ "stories",
42
+ storyService.getStories(pageable));
43
+ model.addAttribute("activeMenu", CmsMenu.STORY);
44
+ return "cms/story/story-list";
45
+ }
46
+
47
+ @GetMapping({ "create", "create/" })
48
+ public String toCreatePage(Model model) {
49
+ model.addAttribute("createStoryRequest", new CreateStoryRequest());
50
+ return "cms/story/story-create";
51
+ }
52
+
53
+ @PostMapping({ "create", "create/" })
54
+ public String createStory(
55
+ @Valid @ModelAttribute("createStoryRequest") CreateStoryRequest request,
56
+ BindingResult result,
57
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
58
+ Model model,
59
+ RedirectAttributes redirectAttributes) {
60
+
61
+ if (result.hasErrors()) {
62
+ return "cms/story/story-create";
63
+
64
+ }
65
+ try {
66
+ if (imageFile != null && !imageFile.isEmpty()) {
67
+ request.setImage(fileStorageService.saveFile(imageFile, "stories"));
68
+ }
69
+ StoryResponse response = storyService.createStory(request);
70
+
71
+ redirectAttributes.addFlashAttribute(
72
+ "successMessage",
73
+ "Tạo câu chuyện thành công: "
74
+ + response.getTitle());
75
+ return "redirect:" + Routes.STORY + "/list";
76
+ } catch (Exception e) {
77
+ model.addAttribute(
78
+ "errorMessage",
79
+ e.getMessage());
80
+
81
+ return "cms/story/story-create";
82
+ }
83
+ }
84
+
85
+ @GetMapping({ "{id}/edit", "{id}/edit/" })
86
+ public String toEditPage(@PathVariable("id") Long id, Model model) {
87
+ StoryResponse story = storyService.getStoryById(id);
88
+
89
+ model.addAttribute("story", story);
90
+ return "cms/story/story-edit";
91
+ }
92
+
93
+ @PostMapping("{id}/edit")
94
+ public String editStory(@PathVariable Long id,
95
+ @Valid @ModelAttribute("story") UpdateStoryRequest request,
96
+ BindingResult result,
97
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
98
+ Model model) {
99
+ if (result.hasErrors()) {
100
+ StoryResponse oldStory = storyService.getStoryById(id);
101
+
102
+ request.setImage(
103
+ oldStory.getImage());
104
+
105
+ return "cms/story/story-edit";
106
+ }
107
+
108
+ request.setId(id);
109
+
110
+ try {
111
+ StoryResponse oldStory = storyService.getStoryById(id);
112
+
113
+ if (imageFile != null && !imageFile.isEmpty()) {
114
+
115
+ request.setImage(
116
+ fileStorageService.saveFile(
117
+ imageFile,
118
+ "stories"));
119
+
120
+ } else {
121
+ request.setImage(
122
+ oldStory.getImage());
123
+ }
124
+ storyService.updateStory(request);
125
+
126
+ return "redirect:" + Routes.STORY + "/list";
127
+ } catch (Exception e) {
128
+ model.addAttribute(
129
+ "errorMessage",
130
+ e.getMessage());
131
+
132
+ return "cms/story/story-edit";
133
+ }
134
+ }
135
+
136
+ @PostMapping("{id}/delete")
137
+ public String deleteStory(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
138
+ try {
139
+ storyService.deleteStory(id);
140
+
141
+ redirectAttributes.addFlashAttribute(
142
+ "successMessage",
143
+ "Xóa câu chuyện thành công");
144
+
145
+ } catch (Exception e) {
146
+ redirectAttributes.addFlashAttribute(
147
+ "errorMessage",
148
+ e.getMessage());
149
+ }
150
+
151
+ return "redirect:" + Routes.STORY + "/list";
152
+ }
153
+
154
+ @PostMapping("{id}/restore")
155
+ public String restoreStory(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
156
+ try {
157
+ storyService.restoreStory(id);
158
+
159
+ redirectAttributes.addFlashAttribute(
160
+ "successMessage",
161
+ "Khôi phục câu chuyện thành công");
162
+
163
+ } catch (Exception e) {
164
+ redirectAttributes.addFlashAttribute(
165
+ "errorMessage",
166
+ e.getMessage());
167
+ }
168
+
169
+ return "redirect:" + Routes.STORY + "/list";
170
+ }
171
+ }
src/main/java/com/darkfantasy/controller/UserController.java ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.security.core.Authentication;
4
+ import org.springframework.security.core.AuthenticationException;
5
+ import org.springframework.security.core.context.SecurityContextHolder;
6
+ import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
7
+ import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
8
+ import org.springframework.stereotype.Controller;
9
+ import org.springframework.ui.Model;
10
+ import org.springframework.validation.BindingResult;
11
+ import org.springframework.web.bind.annotation.GetMapping;
12
+ import org.springframework.web.bind.annotation.ModelAttribute;
13
+ import org.springframework.web.bind.annotation.PostMapping;
14
+ import org.springframework.web.bind.annotation.RequestMapping;
15
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
16
+
17
+ import com.darkfantasy.constant.Routes;
18
+ import com.darkfantasy.dto.user.ChangePasswordRequest;
19
+ import com.darkfantasy.dto.user.ForgotPasswordRequest;
20
+ import com.darkfantasy.dto.user.LoginRequest;
21
+ import com.darkfantasy.dto.user.RegisterRequest;
22
+ import com.darkfantasy.dto.user.ResetPasswordRequest;
23
+ import com.darkfantasy.dto.user.UserResponse;
24
+ import com.darkfantasy.dto.user.VerifyOtpRequest;
25
+ import com.darkfantasy.entity.enums.LogAction;
26
+ import com.darkfantasy.entity.enums.LogEntityType;
27
+ import com.darkfantasy.service.AuditLogService;
28
+ import com.darkfantasy.service.UserService;
29
+
30
+ import jakarta.servlet.http.HttpServletRequest;
31
+ import jakarta.servlet.http.HttpServletResponse;
32
+ import jakarta.servlet.http.HttpSession;
33
+ import jakarta.validation.Valid;
34
+ import lombok.RequiredArgsConstructor;
35
+
36
+ @Controller
37
+ @RequiredArgsConstructor
38
+ @RequestMapping(Routes.USER + "/")
39
+ public class UserController {
40
+ private final UserService userService;
41
+ private final AuditLogService auditLogService;
42
+
43
+ @GetMapping({ "login", "login/" })
44
+ public String toLoginPage(Model model) {
45
+ model.addAttribute("loginRequest", new LoginRequest());
46
+ return "cms/auth/login";
47
+ }
48
+
49
+ @GetMapping({ "register", "register/" })
50
+ public String toRegisterPage(Model model) {
51
+ model.addAttribute("registerRequest", new RegisterRequest());
52
+ return "cms/auth/register";
53
+ }
54
+
55
+ @GetMapping({ "reset", "reset/" })
56
+ public String toResetPage() {
57
+ return "cms/auth/reset";
58
+ }
59
+
60
+ @GetMapping({ "change", "change/" })
61
+ public String toChangePasswordPage() {
62
+ return "cms/auth/password-change";
63
+ }
64
+
65
+ @PostMapping("login")
66
+ public String login(
67
+ @Valid @ModelAttribute LoginRequest request,
68
+ BindingResult result,
69
+ Model model,
70
+ HttpServletRequest req) {
71
+
72
+ if (result.hasErrors()) {
73
+ return "cms/auth/login";
74
+ }
75
+
76
+ try {
77
+
78
+ UserResponse user = userService.login(request);
79
+
80
+ req.getSession(true).setAttribute(
81
+ HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
82
+ SecurityContextHolder.getContext());
83
+ return switch (user.getRole()) {
84
+ case "ADMIN" -> "redirect:" + Routes.ADMIN + "/dashboard";
85
+ case "STAFF" -> "redirect:" + Routes.DASHBOARD;
86
+ default -> "redirect:/";
87
+ };
88
+
89
+ } catch (AuthenticationException e) {
90
+
91
+ model.addAttribute(
92
+ "errorMessage",
93
+ "Tên đăng nhập hoặc mật khẩu không đúng.");
94
+
95
+ return "cms/auth/login";
96
+ }
97
+ }
98
+
99
+ @PostMapping("logout")
100
+ public String logout(
101
+ HttpServletRequest request,
102
+ HttpServletResponse response,
103
+ Authentication authentication) {
104
+ if (authentication != null) {
105
+ String username = authentication.getName();
106
+ UserResponse user = userService.findByUsername(username);
107
+ auditLogService.log(
108
+ LogEntityType.USER,
109
+ user.getId(),
110
+ LogAction.LOGOUT,
111
+ "Đăng xuất");
112
+ }
113
+
114
+ new SecurityContextLogoutHandler()
115
+ .logout(request, response, authentication);
116
+
117
+ return "redirect:" + Routes.USER + "/login";
118
+ }
119
+
120
+ @PostMapping("register")
121
+ public String registerAccount(@Valid @ModelAttribute RegisterRequest request,
122
+ BindingResult result,
123
+ Model model,
124
+ RedirectAttributes redirectAttributes) {
125
+ if (result.hasErrors()) {
126
+ return "cms/auth/register";
127
+ }
128
+ try {
129
+ userService.register(request);
130
+ redirectAttributes.addFlashAttribute("successMessage", "Đăng ký thành công! Chờ admin xác nhận");
131
+ return "redirect:" + Routes.USER + "/register";
132
+ } catch (IllegalArgumentException e) {
133
+ request.setPassword("");
134
+ request.setRePassword("");
135
+ model.addAttribute("errorMessage", e.getMessage());
136
+ return "cms/auth/register";
137
+ }
138
+ }
139
+
140
+ @GetMapping("change-password")
141
+ public String changePasswordPage(
142
+ Model model) {
143
+
144
+ model.addAttribute(
145
+ "changePasswordRequest",
146
+ new ChangePasswordRequest());
147
+ return "cms/auth/change-password";
148
+ }
149
+
150
+ @PostMapping("change-password")
151
+ public String changePassword(
152
+ @Valid @ModelAttribute("changePasswordRequest") ChangePasswordRequest request,
153
+ BindingResult result,
154
+ Model model,
155
+ RedirectAttributes redirectAttributes) {
156
+
157
+ if (result.hasErrors()) {
158
+ return "cms/auth/change-password";
159
+ }
160
+
161
+ try {
162
+
163
+ userService.changeCurrentUserPassword(request);
164
+
165
+ redirectAttributes.addFlashAttribute(
166
+ "successMessage",
167
+ "Đổi mật khẩu thành công");
168
+
169
+ return "redirect:" + Routes.USER + "/change-password";
170
+
171
+ } catch (Exception e) {
172
+
173
+ model.addAttribute(
174
+ "errorMessage",
175
+ e.getMessage());
176
+
177
+ return "cms/auth/change-password";
178
+ }
179
+ }
180
+ // @PostMapping("reset/enter")
181
+ // public String resetPassword(@Valid @ModelAttribute ResetPasswordRequest
182
+ // request, BindingResult result,
183
+ // Model model, RedirectAttributes redirectAttributes) {
184
+ // if (result.hasErrors()) {
185
+ // return "reset";
186
+ // }
187
+ // try {
188
+ // userService.changeForgottenPassword(request);
189
+ // redirectAttributes.addAttribute("successMessage", "Thay đổi mật khẩu thành
190
+ // công!");
191
+ // return "redirect:/user/login";
192
+ // } catch (IllegalArgumentException e) {
193
+ // model.addAttribute("errorMessage", e.getMessage());
194
+ // return "reset";
195
+ // }
196
+
197
+ // }
198
+ @GetMapping("forgot-password")
199
+ public String forgotPasswordPage(Model model) {
200
+ model.addAttribute("forgotPasswordRequest", new ForgotPasswordRequest());
201
+ return "cms/auth/forgot-password";
202
+ }
203
+
204
+ @PostMapping("forgot-password")
205
+ public String forgotPassword(
206
+ @Valid @ModelAttribute ForgotPasswordRequest request,
207
+ BindingResult result, HttpSession session,
208
+ Model model) {
209
+ if (result.hasErrors()) {
210
+ return "cms/auth/forgot-password";
211
+ }
212
+ try {
213
+ userService.sendOtp(request);
214
+ session.setAttribute("OTP_EMAIL", request.getEmail());
215
+ return "redirect:" + Routes.USER + "/verify-otp";
216
+ } catch (Exception e) {
217
+ model.addAttribute("errorMessage", e.getMessage());
218
+ return "cms/auth/forgot-password";
219
+ }
220
+ }
221
+
222
+ @GetMapping("verify-otp")
223
+ public String verifyOtpPage(HttpSession session, Model model) {
224
+ VerifyOtpRequest request = new VerifyOtpRequest();
225
+ request.setEmail((String) session.getAttribute("OTP_EMAIL"));
226
+
227
+ model.addAttribute("verifyOtpRequest", request);
228
+ return "cms/auth/verify-otp";
229
+ }
230
+
231
+ @PostMapping("verify-otp")
232
+ public String verifyOtp(
233
+ @Valid @ModelAttribute VerifyOtpRequest request,
234
+ BindingResult result,
235
+ HttpSession session,
236
+ Model model) {
237
+
238
+ if (result.hasErrors()) {
239
+ return "cms/auth/verify-otp";
240
+ }
241
+
242
+ try {
243
+
244
+ userService.verifyOtp(request);
245
+ session.setAttribute(
246
+ "RESET_OTP",
247
+ request.getOtp());
248
+ session.setAttribute(
249
+ "RESET_EMAIL",
250
+ request.getEmail());
251
+ return "redirect:" + Routes.USER + "/reset-password";
252
+
253
+ } catch (Exception e) {
254
+ model.addAttribute(
255
+ "errorMessage", e.getMessage());
256
+
257
+ return "cms/auth/verify-otp";
258
+ }
259
+ }
260
+
261
+ @GetMapping("/reset-password")
262
+ public String resetPasswordPage(
263
+ HttpSession session,
264
+ Model model) {
265
+ ResetPasswordRequest request = new ResetPasswordRequest();
266
+ request.setEmail(
267
+ (String) session.getAttribute("RESET_EMAIL"));
268
+
269
+ request.setOtp(
270
+ (String) session.getAttribute("RESET_OTP"));
271
+
272
+ model.addAttribute("resetPasswordRequest", request);
273
+ if (request.getEmail() == null || request.getOtp() == null) {
274
+ return "redirect:" + Routes.USER + "/forgot-password";
275
+ }
276
+ return "cms/auth/reset-password";
277
+ }
278
+
279
+ @PostMapping("/reset-password")
280
+ public String resetPassword(
281
+ @Valid @ModelAttribute ResetPasswordRequest request,
282
+ BindingResult result,
283
+ HttpSession session,
284
+ Model model,
285
+ RedirectAttributes redirectAttributes) {
286
+ if (result.hasErrors()) {
287
+ return "cms/auth/reset-password";
288
+ }
289
+
290
+ try {
291
+ String email = (String) session.getAttribute("RESET_EMAIL");
292
+ String otp = (String) session.getAttribute("RESET_OTP");
293
+ if (email == null || otp == null) {
294
+ return "redirect:" + Routes.USER + "/forgot-password";
295
+ }
296
+ request.setEmail(email);
297
+ request.setOtp(otp);
298
+ userService.resetPassword(request);
299
+
300
+ session.removeAttribute(
301
+ "RESET_EMAIL");
302
+
303
+ session.removeAttribute(
304
+ "RESET_OTP");
305
+
306
+ redirectAttributes.addFlashAttribute(
307
+ "successMessage",
308
+ "Đổi mật khẩu thành công");
309
+
310
+ return "redirect:" + Routes.USER + "/login";
311
+
312
+ } catch (Exception e) {
313
+ e.printStackTrace();
314
+ model.addAttribute(
315
+ "errorMessage",
316
+ e.getMessage());
317
+
318
+ return "cms/auth/reset-password";
319
+ }
320
+ }
321
+ }
src/main/java/com/darkfantasy/controller/WorldController.java ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.controller;
2
+
3
+ import org.springframework.data.domain.Pageable;
4
+ import org.springframework.data.web.PageableDefault;
5
+ import org.springframework.stereotype.Controller;
6
+ import org.springframework.ui.Model;
7
+ import org.springframework.validation.BindingResult;
8
+ import org.springframework.web.bind.annotation.RequestMapping;
9
+ import org.springframework.web.bind.annotation.RequestParam;
10
+ import org.springframework.web.multipart.MultipartFile;
11
+ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
12
+
13
+ import com.darkfantasy.constant.CmsMenu;
14
+ import com.darkfantasy.constant.Routes;
15
+ import com.darkfantasy.dto.world.CreateWorldRequest;
16
+ import com.darkfantasy.dto.world.UpdateWorldRequest;
17
+ import com.darkfantasy.dto.world.WorldResponse;
18
+ import com.darkfantasy.service.FileStorageService;
19
+ import com.darkfantasy.service.WorldService;
20
+
21
+ import jakarta.validation.Valid;
22
+ import lombok.RequiredArgsConstructor;
23
+ import org.springframework.web.bind.annotation.GetMapping;
24
+ import org.springframework.web.bind.annotation.ModelAttribute;
25
+ import org.springframework.web.bind.annotation.PathVariable;
26
+ import org.springframework.web.bind.annotation.PostMapping;
27
+
28
+ @Controller
29
+ @RequiredArgsConstructor
30
+ @RequestMapping(Routes.WORLD + "/")
31
+ public class WorldController {
32
+ private final WorldService worldService;
33
+ private final FileStorageService fileStorageService;
34
+
35
+ @GetMapping({ "list", "list/" })
36
+ public String toList(
37
+ @PageableDefault(size = 10) Pageable pageable,
38
+ Model model) {
39
+
40
+ model.addAttribute(
41
+ "worlds",
42
+ worldService.getWorlds(pageable));
43
+ model.addAttribute("activeMenu", CmsMenu.WORLD);
44
+ return "cms/world/world-list";
45
+ }
46
+
47
+ @GetMapping({ "create", "create/" })
48
+ public String toCreatePage(Model model) {
49
+ model.addAttribute("createWorldRequest", new CreateWorldRequest());
50
+ return "cms/world/world-create";
51
+ }
52
+
53
+ @PostMapping({ "create", "create/" })
54
+ public String createWorld(
55
+ @Valid @ModelAttribute("createWorldRequest") CreateWorldRequest request,
56
+ BindingResult result,
57
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
58
+ Model model,
59
+ RedirectAttributes redirectAttributes) {
60
+
61
+ if (result.hasErrors()) {
62
+ model.addAttribute("errorMessage", "Vui lòng kiểm tra lại thông tin thế giới.");
63
+ return "cms/world/world-create";
64
+
65
+ }
66
+ try {
67
+ if (imageFile != null && !imageFile.isEmpty()) {
68
+ request.setImage(fileStorageService.saveFile(imageFile, "worlds"));
69
+ }
70
+ WorldResponse response = worldService.createWorld(request);
71
+
72
+ redirectAttributes.addFlashAttribute(
73
+ "successMessage",
74
+ "Tạo thế giới thành công: "
75
+ + response.getTitle());
76
+ return "redirect:" + Routes.WORLD + "/list";
77
+ } catch (Exception e) {
78
+ model.addAttribute(
79
+ "errorMessage",
80
+ e.getMessage());
81
+
82
+ return "cms/world/world-create";
83
+ }
84
+ }
85
+
86
+ @GetMapping({ "{id}/edit", "{id}/edit/" })
87
+ public String toEditPage(@PathVariable("id") Long id, Model model) {
88
+ WorldResponse world = worldService.getWorldById(id);
89
+ model.addAttribute("world", world);
90
+ return "cms/world/world-edit";
91
+ }
92
+
93
+ @PostMapping("{id}/edit")
94
+ public String editWorld(@PathVariable Long id,
95
+ @Valid @ModelAttribute("world") UpdateWorldRequest request,
96
+ BindingResult result,
97
+ @RequestParam(value = "imageFile", required = false) MultipartFile imageFile,
98
+ Model model) {
99
+ if (result.hasErrors()) {
100
+ WorldResponse oldWorld = worldService.getWorldById(id);
101
+
102
+ request.setImage(
103
+ oldWorld.getImage());
104
+
105
+ return "cms/world/world-edit";
106
+ }
107
+
108
+ request.setId(id);
109
+
110
+ try {
111
+ if (imageFile != null && !imageFile.isEmpty()) {
112
+ request.setImage(
113
+ fileStorageService.saveFile(imageFile, "worlds"));
114
+ }
115
+ worldService.updateWorld(request);
116
+
117
+ return "redirect:" + Routes.WORLD + "/list";
118
+ } catch (Exception e) {
119
+ model.addAttribute(
120
+ "errorMessage",
121
+ e.getMessage());
122
+
123
+ return "cms/world/world-edit";
124
+ }
125
+ }
126
+
127
+ @PostMapping("{id}/delete")
128
+ public String deleteWorld(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
129
+ try {
130
+ worldService.deleteWorld(id);
131
+
132
+ redirectAttributes.addFlashAttribute(
133
+ "successMessage",
134
+ "Xóa thế giới thành công");
135
+
136
+ } catch (Exception e) {
137
+ redirectAttributes.addFlashAttribute(
138
+ "errorMessage",
139
+ e.getMessage());
140
+ }
141
+
142
+ return "redirect:" + Routes.WORLD + "/list";
143
+ }
144
+
145
+ @PostMapping("{id}/restore")
146
+ public String restoreWorld(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
147
+ try {
148
+ worldService.restoreWorld(id);
149
+
150
+ redirectAttributes.addFlashAttribute(
151
+ "successMessage",
152
+ "Khôi phục thế giới thành công");
153
+
154
+ } catch (Exception e) {
155
+ redirectAttributes.addFlashAttribute(
156
+ "errorMessage",
157
+ e.getMessage());
158
+ }
159
+
160
+ return "redirect:" + Routes.WORLD + "/list";
161
+ }
162
+ }
src/main/java/com/darkfantasy/dto/article/ArticleResponse.java ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.article;
2
+
3
+ import com.darkfantasy.entity.Article;
4
+ import com.darkfantasy.entity.User;
5
+ import com.darkfantasy.entity.enums.ArticleType;
6
+ import com.darkfantasy.util.TimeUtil;
7
+
8
+ import lombok.AllArgsConstructor;
9
+ import lombok.Builder;
10
+ import lombok.Data;
11
+ import lombok.NoArgsConstructor;
12
+
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+
18
+ public class ArticleResponse {
19
+ private Long id;
20
+ private String title;
21
+ private String content;
22
+ private String authorUsername;
23
+ private String thumbnailUrl;
24
+ private String type;
25
+ private String createdByUserName;
26
+ private String createdAt;
27
+ private String updatedByUserName;
28
+ private String updatedAt;
29
+ private boolean deleted;
30
+
31
+ public static ArticleResponse fromEntity(Article article) {
32
+ return ArticleResponse.builder()
33
+ .id(article.getId())
34
+ .title(article.getTitle())
35
+ .content(article.getContent())
36
+ .authorUsername(article.getCreatedBy().getUsername())
37
+ .thumbnailUrl(article.getThumbnailUrl())
38
+ .type(article.getType().getDisplayName())
39
+ .createdByUserName(article.getCreatedBy()!= null? article.getCreatedBy().getUsername() : null)
40
+ .createdAt(TimeUtil.formatInstant(article.getCreatedAt()))
41
+ .updatedByUserName(article.getUpdatedBy()!= null? article.getUpdatedBy().getUsername() : null)
42
+ .updatedAt(TimeUtil.formatInstant(article.getUpdatedAt()))
43
+ .deleted(article.isDeleted())
44
+ .build();
45
+ }
46
+
47
+ public String getSummary() {
48
+ if (content == null) {
49
+ return "";
50
+ }
51
+
52
+ if (content.length() <= 150) {
53
+ return content;
54
+ }
55
+
56
+ return content.substring(0, 150) + "...";
57
+ }
58
+ }
src/main/java/com/darkfantasy/dto/article/CreateArticleRequest.java ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.article;
2
+
3
+ import com.darkfantasy.entity.Article;
4
+ import com.darkfantasy.entity.enums.ArticleType;
5
+
6
+ import jakarta.validation.constraints.NotBlank;
7
+ import jakarta.validation.constraints.NotNull;
8
+ import jakarta.validation.constraints.Size;
9
+ import lombok.AllArgsConstructor;
10
+ import lombok.Builder;
11
+ import lombok.Data;
12
+ import lombok.NoArgsConstructor;
13
+
14
+ @Data
15
+ @NoArgsConstructor
16
+ @AllArgsConstructor
17
+ @Builder
18
+ public class CreateArticleRequest {
19
+
20
+ @NotBlank(message = "Tiêu đề không được để trống")
21
+ @Size(max = 255, message = "Tiêu đề quá dài")
22
+ private String title;
23
+
24
+ @NotBlank(message = "Nội dung không được để trống")
25
+ private String content;
26
+
27
+ private String thumbnailUrl;
28
+
29
+ @NotNull(message = "Loại bài viết không được để trống")
30
+ private ArticleType type;
31
+
32
+ public Article toEntity() {
33
+ return Article.builder()
34
+ .title(this.title)
35
+ .content(this.content)
36
+ .thumbnailUrl(this.thumbnailUrl)
37
+ .type(this.type)
38
+ .build();
39
+ }
40
+ }
src/main/java/com/darkfantasy/dto/article/UpdateArticleRequest.java ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.article;
2
+
3
+ import com.darkfantasy.entity.enums.ArticleType;
4
+
5
+ import jakarta.validation.constraints.NotBlank;
6
+ import jakarta.validation.constraints.NotNull;
7
+ import jakarta.validation.constraints.Size;
8
+ import lombok.AllArgsConstructor;
9
+ import lombok.Builder;
10
+ import lombok.Data;
11
+ import lombok.NoArgsConstructor;
12
+
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+ public class UpdateArticleRequest {
18
+ @NotNull
19
+ private Long id;
20
+ @NotBlank(message = "Tiêu đề không được để trống")
21
+ @Size(max = 255)
22
+ private String title;
23
+
24
+ @NotBlank(message = "Nội dung không được để trống")
25
+ private String content;
26
+
27
+ private String thumbnailUrl;
28
+
29
+ @NotNull(message = "Loại bài viết không được để trống")
30
+ private ArticleType type;
31
+
32
+ }
src/main/java/com/darkfantasy/dto/contact/ContactMessageResponse.java ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.contact;
2
+
3
+
4
+ import com.darkfantasy.entity.ContactMessage;
5
+ import com.darkfantasy.entity.enums.ContactCategory;
6
+ import com.darkfantasy.entity.enums.ContactPlatform;
7
+
8
+ import lombok.AllArgsConstructor;
9
+ import lombok.Builder;
10
+ import lombok.Data;
11
+ import lombok.NoArgsConstructor;
12
+
13
+ @Data
14
+ @Builder
15
+ @NoArgsConstructor
16
+ @AllArgsConstructor
17
+ public class ContactMessageResponse {
18
+ private Long id;
19
+ private String visitorName;
20
+
21
+ private String visitorEmail;
22
+
23
+ private ContactCategory category;
24
+
25
+ private ContactPlatform platform;
26
+
27
+ private String message;
28
+
29
+ private boolean processed;
30
+
31
+ public static ContactMessageResponse fromEntity(ContactMessage message) {
32
+ return ContactMessageResponse.builder()
33
+ .id(message.getId())
34
+ .visitorName(message.getVisitorName())
35
+ .visitorEmail(message.getVisitorEmail())
36
+ .category(message.getCategory())
37
+ .platform(message.getPlatform())
38
+ .message(message.getMessage())
39
+ .processed(message.isProcessed())
40
+ .build();
41
+ }
42
+ }
src/main/java/com/darkfantasy/dto/contact/CreateContactMessageRequest.java ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.contact;
2
+
3
+ import com.darkfantasy.entity.ContactMessage;
4
+ import com.darkfantasy.entity.enums.ContactCategory;
5
+ import com.darkfantasy.entity.enums.ContactPlatform;
6
+
7
+ import jakarta.validation.constraints.Email;
8
+ import jakarta.validation.constraints.NotBlank;
9
+ import jakarta.validation.constraints.NotNull;
10
+ import jakarta.validation.constraints.Size;
11
+ import lombok.AllArgsConstructor;
12
+ import lombok.Builder;
13
+ import lombok.Data;
14
+ import lombok.NoArgsConstructor;
15
+
16
+ @Data
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ @Builder
20
+ public class CreateContactMessageRequest {
21
+
22
+ @NotBlank(message = "Vui lòng nhập tên")
23
+ private String visitorName;
24
+
25
+ @NotBlank(message = "Vui lòng nhập email")
26
+ @Email(message = "Email không hợp lệ")
27
+ private String visitorEmail;
28
+
29
+ @NotNull(message = "Vui lòng chọn danh mục")
30
+ private ContactCategory category;
31
+
32
+ @NotNull(message = "Vui lòng chọn nền tảng")
33
+ private ContactPlatform platform;
34
+
35
+ @NotBlank(message = "Vui lòng nhập nội dung")
36
+ @Size(max = 5000)
37
+ private String message;
38
+
39
+ public ContactMessage toEntity() {
40
+ return ContactMessage.builder()
41
+ .visitorName(visitorName)
42
+ .visitorEmail(visitorEmail)
43
+ .category(category)
44
+ .platform(platform)
45
+ .message(message)
46
+ .processed(false)
47
+ .build();
48
+ }
49
+ }
src/main/java/com/darkfantasy/dto/contributor/ContributorResponse.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.contributor;
2
+
3
+ import com.darkfantasy.entity.Contributor;
4
+ import com.darkfantasy.util.TimeUtil;
5
+
6
+ import lombok.AllArgsConstructor;
7
+ import lombok.Builder;
8
+ import lombok.Data;
9
+ import lombok.NoArgsConstructor;
10
+
11
+ @Data
12
+ @NoArgsConstructor
13
+ @AllArgsConstructor
14
+ @Builder
15
+ public class ContributorResponse {
16
+ private Long id;
17
+ private String name;
18
+ private String position;
19
+ private String avatar;
20
+ private Integer priority;
21
+ private boolean deleted;
22
+ private String createdByUserName;
23
+ private String createdAt;
24
+ private String updatedByUserName;
25
+ private String updatedAt;
26
+
27
+ public static ContributorResponse fromEntity(Contributor contributor) {
28
+ return ContributorResponse.builder()
29
+ .id(contributor.getId())
30
+ .name(contributor.getName())
31
+ .position(contributor.getPosition())
32
+ .avatar(contributor.getAvatar())
33
+ .priority(contributor.getPriority())
34
+ .deleted(contributor.isDeleted())
35
+ .createdByUserName(contributor.getCreatedBy() != null ? contributor.getCreatedBy().getUsername() : null)
36
+ .createdAt(TimeUtil.formatInstant(contributor.getCreatedAt()))
37
+ .updatedByUserName(contributor.getUpdatedBy() != null ? contributor.getUpdatedBy().getUsername() : null)
38
+ .updatedAt(TimeUtil.formatInstant(contributor.getUpdatedAt()))
39
+ .build();
40
+ }
41
+ }
src/main/java/com/darkfantasy/dto/contributor/CreateContributorRequest.java ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.contributor;
2
+
3
+ import com.darkfantasy.entity.Article;
4
+ import com.darkfantasy.entity.Contributor;
5
+
6
+ import jakarta.validation.constraints.Max;
7
+ import jakarta.validation.constraints.Min;
8
+ import jakarta.validation.constraints.NotBlank;
9
+ import jakarta.validation.constraints.NotNull;
10
+ import jakarta.validation.constraints.Size;
11
+ import lombok.AllArgsConstructor;
12
+ import lombok.Builder;
13
+ import lombok.Data;
14
+ import lombok.NoArgsConstructor;
15
+
16
+ @Data
17
+ @NoArgsConstructor
18
+ @AllArgsConstructor
19
+ @Builder
20
+ public class CreateContributorRequest {
21
+ @NotBlank(message = "Tên người đóng góp không được để trống")
22
+ @Size(max = 255, message = "Tên người đóng góp không được vượt quá 255 ký tự")
23
+ private String name;
24
+
25
+ @NotBlank(message = "Chức danh không được để trống")
26
+ @Size(max = 255, message = "Chức danh không được vượt quá 255 ký tự")
27
+ private String position;
28
+
29
+ private String avatar;
30
+
31
+ @NotNull(message = "Độ ưu tiên không được để trống")
32
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100")
33
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100")
34
+ private Integer priority;
35
+
36
+ public Contributor toEntity() {
37
+ return Contributor.builder()
38
+ .name(this.name)
39
+ .position(this.position)
40
+ .avatar(this.avatar)
41
+ .priority(this.priority)
42
+ .build();
43
+ }
44
+ }
src/main/java/com/darkfantasy/dto/contributor/UpdateContributorRequest.java ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.contributor;
2
+
3
+ import jakarta.validation.constraints.Max;
4
+ import jakarta.validation.constraints.Min;
5
+ import jakarta.validation.constraints.NotBlank;
6
+ import jakarta.validation.constraints.NotNull;
7
+ import jakarta.validation.constraints.Size;
8
+ import lombok.AllArgsConstructor;
9
+ import lombok.Builder;
10
+ import lombok.Data;
11
+ import lombok.NoArgsConstructor;
12
+
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+ public class UpdateContributorRequest {
18
+ private Long id;
19
+ @NotBlank(message = "Tên người đóng góp không được để trống")
20
+ @Size(max = 255, message = "Tên người đóng góp không được vượt quá 255 ký tự")
21
+ private String name;
22
+
23
+ @NotBlank(message = "Chức danh không được để trống")
24
+ @Size(max = 255, message = "Chức danh không được vượt quá 255 ký tự")
25
+ private String position;
26
+
27
+ private String avatar;
28
+
29
+ @NotNull(message = "Độ ưu tiên không được để trống")
30
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100")
31
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100")
32
+ private Integer priority;
33
+
34
+ }
src/main/java/com/darkfantasy/dto/faq/CreateFaqRequest.java ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.faq;
2
+
3
+ import com.darkfantasy.entity.Faq;
4
+
5
+ import jakarta.validation.constraints.Max;
6
+ import jakarta.validation.constraints.Min;
7
+ import jakarta.validation.constraints.NotBlank;
8
+ import jakarta.validation.constraints.NotNull;
9
+ import jakarta.validation.constraints.Size;
10
+ import lombok.AllArgsConstructor;
11
+ import lombok.Builder;
12
+ import lombok.Data;
13
+ import lombok.NoArgsConstructor;
14
+
15
+ @Data
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ @Builder
19
+ public class CreateFaqRequest {
20
+
21
+ @NotBlank(message = "Tiêu đề không được để trống!")
22
+ @Size(max = 255, message = "Tiêu đề không được vượt quá 255 ký tự!")
23
+ private String title;
24
+ @NotBlank(message = "Nội dung không được để trống!")
25
+ private String content;
26
+
27
+ @NotNull(message = "Độ ưu tiên không được để trống!")
28
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100!")
29
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100!")
30
+ private Integer priority;
31
+ private boolean deleted;
32
+
33
+ public Faq toEntity() {
34
+ return Faq.builder()
35
+ .title(this.title)
36
+ .content(this.content)
37
+ .priority(this.priority)
38
+ .deleted(this.deleted)
39
+ .build();
40
+ }
41
+ }
src/main/java/com/darkfantasy/dto/faq/FaqResponse.java ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.faq;
2
+
3
+ import com.darkfantasy.entity.Faq;
4
+
5
+ import lombok.AllArgsConstructor;
6
+ import lombok.Builder;
7
+ import lombok.Data;
8
+ import lombok.NoArgsConstructor;
9
+
10
+ @Data
11
+ @NoArgsConstructor
12
+ @AllArgsConstructor
13
+ @Builder
14
+ public class FaqResponse {
15
+ private Long id;
16
+ private String title;
17
+ private String content;
18
+ private Integer priority;
19
+ private boolean deleted;
20
+
21
+ public static FaqResponse fromEntity(Faq faq) {
22
+ return FaqResponse.builder()
23
+ .id(faq.getId())
24
+ .title(faq.getTitle())
25
+ .content(faq.getContent())
26
+ .priority(faq.getPriority())
27
+ .deleted(faq.isDeleted())
28
+ .build();
29
+ }
30
+ }
src/main/java/com/darkfantasy/dto/faq/UpdateFaqRequest.java ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.faq;
2
+
3
+ import jakarta.validation.constraints.Max;
4
+ import jakarta.validation.constraints.Min;
5
+ import jakarta.validation.constraints.NotBlank;
6
+ import jakarta.validation.constraints.NotNull;
7
+ import jakarta.validation.constraints.Size;
8
+ import lombok.AllArgsConstructor;
9
+ import lombok.Builder;
10
+ import lombok.Data;
11
+ import lombok.NoArgsConstructor;
12
+
13
+ @Data
14
+ @NoArgsConstructor
15
+ @AllArgsConstructor
16
+ @Builder
17
+ public class UpdateFaqRequest {
18
+ private Long id;
19
+
20
+ @NotBlank(message = "Tiêu đề không được để trống!")
21
+ @Size(max = 255, message = "Tiêu đề không được vượt quá 255 ký tự!")
22
+ private String title;
23
+ @NotBlank(message = "Nội dung không được để trống!")
24
+ private String content;
25
+ @NotNull(message = "Độ ưu tiên không được để trống!")
26
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100!")
27
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100!")
28
+ private Integer priority;
29
+ }
src/main/java/com/darkfantasy/dto/gamecharacter/CreateGameCharacterRequest.java ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.gamecharacter;
2
+
3
+ import com.darkfantasy.entity.GameCharacter;
4
+
5
+ import jakarta.validation.constraints.Max;
6
+ import jakarta.validation.constraints.Min;
7
+ import jakarta.validation.constraints.NotBlank;
8
+ import jakarta.validation.constraints.NotNull;
9
+ import jakarta.validation.constraints.Size;
10
+ import lombok.AllArgsConstructor;
11
+ import lombok.Builder;
12
+ import lombok.Data;
13
+ import lombok.NoArgsConstructor;
14
+
15
+ @Data
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ @Builder
19
+ public class CreateGameCharacterRequest {
20
+
21
+ @NotBlank(message = "Tên nhân vật không được để trống")
22
+ @Size(max = 255, message = "Tên nhân vật không được vượt quá 255 ký tự")
23
+ private String name;
24
+
25
+ @NotBlank(message = "Chủng tộc không được để trống")
26
+ @Size(max = 100, message = "Chủng tộc không được vượt quá 100 ký tự")
27
+ private String race;
28
+
29
+ @NotBlank(message = "Mô tả không được để trống")
30
+ private String description;
31
+
32
+ @Size(max = 255, message = "Quote không được vượt quá 255 ký tự")
33
+ private String quote;
34
+
35
+ private String image;
36
+
37
+ @NotNull(message = "Độ ưu tiên không được để trống")
38
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100")
39
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100")
40
+ private Integer priority;
41
+
42
+ public GameCharacter toEntity() {
43
+ return GameCharacter.builder()
44
+ .name(this.name)
45
+ .race(this.race)
46
+ .description(this.description)
47
+ .quote(this.quote)
48
+ .image(this.image)
49
+ .priority(this.priority)
50
+ .build();
51
+ }
52
+ }
src/main/java/com/darkfantasy/dto/gamecharacter/GameCharacterResponse.java ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.gamecharacter;
2
+
3
+ import com.darkfantasy.entity.GameCharacter;
4
+
5
+ import lombok.AllArgsConstructor;
6
+ import lombok.Builder;
7
+ import lombok.Data;
8
+ import lombok.NoArgsConstructor;
9
+
10
+ @Data
11
+ @NoArgsConstructor
12
+ @AllArgsConstructor
13
+ @Builder
14
+ public class GameCharacterResponse {
15
+ private Long id;
16
+ private String name;
17
+ private String race;
18
+ private String description;
19
+ private String quote;
20
+ private String image;
21
+ private Integer priority;
22
+ private boolean deleted;
23
+
24
+ public static GameCharacterResponse fromEntity(GameCharacter character) {
25
+ return GameCharacterResponse.builder()
26
+ .id(character.getId())
27
+ .name(character.getName())
28
+ .race(character.getRace())
29
+ .description(character.getDescription())
30
+ .quote(character.getQuote())
31
+ .image(character.getImage())
32
+ .priority(character.getPriority())
33
+ .deleted(character.isDeleted())
34
+ .build();
35
+ }
36
+ }
src/main/java/com/darkfantasy/dto/gamecharacter/UpdateGameCharacterRequest.java ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.gamecharacter;
2
+
3
+ import com.darkfantasy.entity.GameCharacter;
4
+
5
+ import jakarta.validation.constraints.Max;
6
+ import jakarta.validation.constraints.Min;
7
+ import jakarta.validation.constraints.NotBlank;
8
+ import jakarta.validation.constraints.NotNull;
9
+ import jakarta.validation.constraints.Size;
10
+ import lombok.AllArgsConstructor;
11
+ import lombok.Builder;
12
+ import lombok.Data;
13
+ import lombok.NoArgsConstructor;
14
+
15
+ @Data
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ @Builder
19
+ public class UpdateGameCharacterRequest {
20
+ private Long id;
21
+ @NotBlank(message = "Tên nhân vật không được để trống")
22
+ @Size(max = 255, message = "Tên nhân vật không được vượt quá 255 ký tự")
23
+ private String name;
24
+ @NotBlank(message = "Chủng tộc không được để trống")
25
+ @Size(max = 100, message = "Chủng tộc không được vượt quá 100 ký tự")
26
+ private String race;
27
+ @NotBlank(message = "Mô tả không được để trống")
28
+ private String description;
29
+ @Size(max = 255, message = "Quote không được vượt quá 255 ký tự")
30
+ private String quote;
31
+ private String image;
32
+ @NotNull(message = "Độ ưu tiên không được để trống")
33
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100")
34
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100")
35
+ private Integer priority;
36
+
37
+ public GameCharacter toEntity() {
38
+ return GameCharacter.builder()
39
+ .id(this.id)
40
+ .name(this.name)
41
+ .race(this.race)
42
+ .description(this.description)
43
+ .quote(this.quote)
44
+ .image(this.image)
45
+ .priority(this.priority)
46
+ .build();
47
+ }
48
+ }
src/main/java/com/darkfantasy/dto/log/AuditLogResponse.java ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.log;
2
+
3
+ import java.time.Instant;
4
+
5
+ import com.darkfantasy.entity.AuditLog;
6
+ import com.darkfantasy.entity.enums.LogAction;
7
+ import com.darkfantasy.entity.enums.LogEntityType;
8
+ import com.darkfantasy.util.TimeUtil;
9
+
10
+ import lombok.Builder;
11
+ import lombok.Data;
12
+
13
+ @Data
14
+ @Builder
15
+ public class AuditLogResponse {
16
+ private Long id;
17
+ private Long userId;
18
+ private String username;
19
+ private LogEntityType entityType;
20
+ private Long entityId;
21
+ private LogAction action;
22
+ private String description;
23
+ private String createdAt;
24
+
25
+ public static AuditLogResponse fromEntity(AuditLog auditLog) {
26
+ return AuditLogResponse.builder()
27
+ .id(auditLog.getId())
28
+ .userId(auditLog.getUser().getId())
29
+ .username(auditLog.getUser().getUsername())
30
+ .entityType(auditLog.getEntityType())
31
+ .entityId(auditLog.getEntityId())
32
+ .action(auditLog.getAction())
33
+ .description(auditLog.getDescription())
34
+ .createdAt(TimeUtil.formatInstant(auditLog.getCreatedAt()))
35
+ .build();
36
+ }
37
+ }
src/main/java/com/darkfantasy/dto/story/CreateStoryRequest.java ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.darkfantasy.dto.story;
2
+
3
+ import com.darkfantasy.entity.Story;
4
+
5
+ import jakarta.validation.constraints.Max;
6
+ import jakarta.validation.constraints.Min;
7
+ import jakarta.validation.constraints.NotBlank;
8
+ import jakarta.validation.constraints.NotNull;
9
+ import jakarta.validation.constraints.Size;
10
+ import lombok.AllArgsConstructor;
11
+ import lombok.Builder;
12
+ import lombok.Data;
13
+ import lombok.NoArgsConstructor;
14
+
15
+ @Data
16
+ @NoArgsConstructor
17
+ @AllArgsConstructor
18
+ @Builder
19
+ public class CreateStoryRequest {
20
+ @NotBlank(message = "Tag không được để trống!")
21
+ @Size(max = 255, message = "Tag không được vượt quá 255 ký tự!")
22
+ private String tag;
23
+
24
+ @NotBlank(message = "Tiêu đề không được để trống!")
25
+ @Size(max = 255, message = "Tiêu đề không được vượt quá 255 ký tự!")
26
+ private String title;
27
+ @NotBlank(message = "Nội dung không được để trống!")
28
+ private String content;
29
+ private String image;
30
+ @NotBlank(message = "Nội dung quote không được để trống!")
31
+ private String quoteContent;
32
+ @NotBlank(message = "Tác giả trích dẫn không được để trống!")
33
+ private String quoteAuthor;
34
+ @NotNull(message = "Độ ưu tiên không được để trống!")
35
+ @Min(value = 0, message = "Độ ưu tiên phải từ 0 đến 100!")
36
+ @Max(value = 100, message = "Độ ưu tiên phải từ 0 đến 100!")
37
+ private Integer priority;
38
+
39
+ public Story toEntity() {
40
+ return Story.builder()
41
+ .tag(this.tag)
42
+ .title(this.title)
43
+ .content(this.content)
44
+ .image(this.image)
45
+ .quoteContent(this.quoteContent)
46
+ .quoteAuthor(this.quoteAuthor)
47
+ .priority(this.priority)
48
+ .build();
49
+ }
50
+ }