Merge "user module API" into main
diff --git a/src/main/java/com/g9/g9backend/controller/UserController.java b/src/main/java/com/g9/g9backend/controller/UserController.java
index 1be4222..48ec0cd 100644
--- a/src/main/java/com/g9/g9backend/controller/UserController.java
+++ b/src/main/java/com/g9/g9backend/controller/UserController.java
@@ -7,6 +7,7 @@
 import com.g9.g9backend.service.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
@@ -25,10 +26,13 @@
 
     private final SubscriptionService subscriptionService;
 
-    public UserController(UserService userService, InvitationService invitationService, SubscriptionService subscriptionService) {
+    private final SearchHistoryService searchHistoryService;
+
+    public UserController(UserService userService, InvitationService invitationService, SubscriptionService subscriptionService, SearchHistoryService searchHistoryService) {
         this.userService = userService;
         this.invitationService = invitationService;
         this.subscriptionService = subscriptionService;
+        this.searchHistoryService = searchHistoryService;
     }
 
     private final Logger logger = LoggerFactory.getLogger(UserController.class);
@@ -82,9 +86,9 @@
         User userGetId = userService.getOne(userQuery);
         int newUserId = userGetId.getUserId();
 
-        UpdateWrapper<Invitation> updateWrapper = new UpdateWrapper<>();
-        updateWrapper.eq("invitation_code", invitationCode).set("invitee_id", newUserId);
-        invitationService.update(updateWrapper);
+        UpdateWrapper<Invitation> invitationUpdate = new UpdateWrapper<>();
+        invitationUpdate.eq("invitation_code", invitationCode).set("invitee_id", newUserId);
+        invitationService.update(invitationUpdate);
 
         // 生成五个邀请码并分配给新用户
         String[] invitationCodes = invitationService.generateInvitationCode();
@@ -143,4 +147,60 @@
         subscriptionService.save(subscription);
         return ResponseEntity.ok("");
     }
+
+    /**
+     * 注销账户
+     *
+     * @param userId   用户id
+     * @param password 密码
+     * @return 注销结果
+     */
+    @DeleteMapping
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<String> userDelete(@RequestParam int userId, @RequestParam String password) {
+        logger.warn("Delete user with id: {}", userId);
+        // 根据用户id查询该用户
+        QueryWrapper<User> userQuery = new QueryWrapper<>();
+        userQuery.eq("user_id", userId);
+        User userCheck = userService.getOne(userQuery);
+
+        if (userCheck.getPassword().equals(password)) {
+            // 注销账户(自动清除与该用户相关的记录,注意会回收分配给该用户的邀请码)
+            userService.remove(userQuery);
+
+            return ResponseEntity.noContent().build();
+        } else {
+            logger.warn("Delete failed. Incorrect password for account: {}", password);
+            return ResponseEntity.status(408).body("");
+        }
+    }
+
+    /**
+     * 删除用户搜索历史
+     *
+     * @param searchHistoryId 搜索历史id
+     * @return 删除用户搜索历史结果
+     */
+    @DeleteMapping("/search")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<String> searchDelete(@RequestParam int searchHistoryId) {
+        searchHistoryService.removeById(searchHistoryId);
+        return ResponseEntity.noContent().build();
+    }
+
+    /**
+     * 取消关注
+     *
+     * @param userId     被关注者id
+     * @param followerId 关注被人者id
+     * @return 取消关注结果
+     */
+    @DeleteMapping("/subscription")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public ResponseEntity<String> subscriptionDelete(@RequestParam int userId, @RequestParam int followerId) {
+        QueryWrapper<Subscription> subscriptionQuery = new QueryWrapper<>();
+        subscriptionQuery.eq("user_id", userId).eq("follower_id", followerId);
+        subscriptionService.remove(subscriptionQuery);
+        return ResponseEntity.noContent().build();
+    }
 }
\ No newline at end of file
diff --git a/src/test/java/com/g9/g9backend/controller/UserControllerTest.java b/src/test/java/com/g9/g9backend/controller/UserControllerTest.java
index 208645c..3f06b32 100644
--- a/src/test/java/com/g9/g9backend/controller/UserControllerTest.java
+++ b/src/test/java/com/g9/g9backend/controller/UserControllerTest.java
@@ -34,6 +34,9 @@
     @Mock
     private SubscriptionService subscriptionService;
 
+    @Mock
+    private SearchHistoryService searchHistoryService;
+
     private final ObjectMapper objectMapper = new ObjectMapper();
 
     @BeforeEach
@@ -169,4 +172,50 @@
                         .content(objectMapper.writeValueAsString(new Subscription(1, 2))))
                 .andExpect(status().isOk());
     }
+
+    // 注销
+    @Test
+    public void testUserDelete_success() throws Exception {
+
+        when(userService.getOne(any())).thenReturn(new User(1, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+
+        mockMvc.perform(delete("/user")
+                        .param("userId", "1")
+                        .param("password", "123"))
+                .andExpect(status().isNoContent());
+    }
+
+    @Test
+    public void testUserDelete_wrongPassword() throws Exception {
+
+        when(userService.getOne(any())).thenReturn(new User(1, "hcy", "123", null, 0, 0, null, 0, 0, 0));
+
+        mockMvc.perform(delete("/user")
+                        .param("userId", "1")
+                        .param("password", "wrong"))
+                .andExpect(status().is(408));
+    }
+
+    // 删除搜索历史
+    @Test
+    public void testSearchDelete() throws Exception {
+
+        when(searchHistoryService.removeById(anyInt())).thenReturn(true);
+
+        mockMvc.perform(delete("/user/search")
+                        .param("searchHistoryId", "1"))
+                .andExpect(status().isNoContent());
+    }
+
+    // 取消关注
+    @Test
+    public void testSubscriptionDelete() throws Exception {
+
+        when(subscriptionService.remove(any())).thenReturn(true);
+
+        mockMvc.perform(delete("/user/subscription")
+                        .param("userId", "1")
+                        .param("followerId", "2"))
+                .andExpect(status().isNoContent());
+    }
 }
\ No newline at end of file