package com.xuqm.im.controller; import com.xuqm.common.model.ApiResponse; import com.xuqm.im.entity.ImAccountEntity; import com.xuqm.im.entity.ImGlobalMuteEntity; import com.xuqm.im.entity.ImGroupEntity; import com.xuqm.im.entity.ImMessageEntity; import com.xuqm.im.entity.KeywordFilterEntity; import com.xuqm.im.entity.WebhookConfigEntity; import com.xuqm.im.repository.ImAccountRepository; import com.xuqm.im.repository.ImGroupRepository; import com.xuqm.im.repository.ImMessageRepository; import com.xuqm.im.service.ImAccountService; import com.xuqm.im.service.ImGroupService; import com.xuqm.im.service.GlobalMuteService; import com.xuqm.im.service.KeywordFilterService; import com.xuqm.im.service.MessageService; import com.xuqm.im.service.OperationLogService; import com.xuqm.im.service.WebhookConfigService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/im/admin") public class ImAdminController { private final ImAccountRepository accountRepository; private final ImGroupRepository groupRepository; private final ImMessageRepository messageRepository; private final ImAccountService accountService; private final ImGroupService groupService; private final MessageService messageService; private final WebhookConfigService webhookConfigService; private final KeywordFilterService keywordFilterService; private final GlobalMuteService globalMuteService; private final OperationLogService operationLogService; public ImAdminController(ImAccountRepository accountRepository, ImGroupRepository groupRepository, ImMessageRepository messageRepository, ImAccountService accountService, ImGroupService groupService, MessageService messageService, WebhookConfigService webhookConfigService, KeywordFilterService keywordFilterService, GlobalMuteService globalMuteService, OperationLogService operationLogService) { this.accountRepository = accountRepository; this.groupRepository = groupRepository; this.messageRepository = messageRepository; this.accountService = accountService; this.groupService = groupService; this.messageService = messageService; this.webhookConfigService = webhookConfigService; this.keywordFilterService = keywordFilterService; this.globalMuteService = globalMuteService; this.operationLogService = operationLogService; } /** List all registered IM users for the given appId. */ @GetMapping("/users") public ResponseEntity>> listUsers( @RequestParam String appId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return ResponseEntity.ok(ApiResponse.success( accountRepository.findByAppId(appId, PageRequest.of(page, size)))); } /** Ban or unban a user. */ @PutMapping("/users/{userId}/status") public ResponseEntity> updateUserStatus( @RequestParam String appId, @PathVariable String userId, @AuthenticationPrincipal String operatorId, @RequestBody Map body) { ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId) .orElseThrow(() -> new RuntimeException("User not found")); account.setStatus(ImAccountEntity.Status.valueOf(body.get("status").toUpperCase())); ImAccountEntity saved = accountRepository.save(account); operationLogService.record(appId, operatorId, "UPDATE_USER_STATUS", "ACCOUNT", userId, body.get("status")); return ResponseEntity.ok(ApiResponse.success(saved)); } /** List all groups for the given appId. */ @GetMapping("/groups") public ResponseEntity>> listGroups(@RequestParam String appId) { return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppId(appId))); } /** Admin registers a new IM user (or returns existing). */ @PostMapping("/users") public ResponseEntity> registerUser( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestBody RegisterUserRequest req) { accountService.loginOrRegister(appId, req.userId(), req.nickname(), req.avatar()); ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, req.userId()) .orElseThrow(); operationLogService.record(appId, operatorId, "REGISTER_USER", "ACCOUNT", req.userId(), req.nickname()); return ResponseEntity.ok(ApiResponse.success(account)); } /** Admin creates a group. */ @PostMapping("/groups") public ResponseEntity> createGroup( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestBody CreateGroupRequest req) { ImGroupEntity group = groupService.create(appId, req.name(), req.creatorId(), req.memberIds(), "WORK"); operationLogService.record(appId, operatorId, "CREATE_GROUP", "GROUP", group.getId(), group.getName()); return ResponseEntity.ok(ApiResponse.success(group)); } /** Fuzzy search users by userId or nickname. */ @GetMapping("/users/search") public ResponseEntity>> searchUsers( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestParam String keyword, @RequestParam(defaultValue = "20") int size) { List results = accountRepository.searchByKeyword(appId, keyword, PageRequest.of(0, size)); operationLogService.record(appId, operatorId, "SEARCH_USERS", "ACCOUNT", null, keyword); return ResponseEntity.ok(ApiResponse.success(results)); } /** Fuzzy search groups by id, name, creator or announcement. */ @GetMapping("/groups/search") public ResponseEntity>> searchGroups( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestParam String keyword, @RequestParam(defaultValue = "20") int size) { List results = groupRepository.searchByKeyword(appId, keyword, PageRequest.of(0, size)); operationLogService.record(appId, operatorId, "SEARCH_GROUPS", "GROUP", null, keyword); return ResponseEntity.ok(ApiResponse.success(results)); } /** Search messages across the application. */ @GetMapping("/messages/search") public ResponseEntity>> searchMessages( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestParam(required = false) ImMessageEntity.ChatType chatType, @RequestParam(required = false) ImMessageEntity.MsgType msgType, @RequestParam(required = false) String keyword, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { operationLogService.record(appId, operatorId, "SEARCH_MESSAGES", "MESSAGE", null, keyword); return ResponseEntity.ok(ApiResponse.success( messageRepository.searchByKeyword( appId, chatType, msgType, keyword, startTime, endTime, PageRequest.of(page, size)))); } /** Message statistics for the given appId. */ @GetMapping("/stats") public ResponseEntity>> stats( @RequestParam String appId, @AuthenticationPrincipal String operatorId) { long totalMessages = messageRepository.countByAppId(appId); long totalUsers = accountRepository.countByAppId(appId); long totalGroups = groupRepository.countByAppId(appId); long todayMessages = messageRepository.countTodayByAppId(appId); operationLogService.record(appId, operatorId, "VIEW_STATS", "STATS", null, "summary"); return ResponseEntity.ok(ApiResponse.success(Map.of( "totalMessages", totalMessages, "totalUsers", totalUsers, "totalGroups", totalGroups, "todayMessages", todayMessages ))); } /** Admin queries conversation history between any two users. */ @GetMapping("/messages") public ResponseEntity>> history( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestParam String userA, @RequestParam String userB, @RequestParam(required = false) com.xuqm.im.entity.ImMessageEntity.MsgType msgType, @RequestParam(required = false) String keyword, @RequestParam(required = false) LocalDateTime startTime, @RequestParam(required = false) LocalDateTime endTime, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { operationLogService.record(appId, operatorId, "VIEW_HISTORY", "MESSAGE", userA + "," + userB, keyword); return ResponseEntity.ok(ApiResponse.success( messageRepository.findSingleConversationFiltered( appId, userA, userB, msgType, keyword, startTime, endTime, PageRequest.of(page, size)))); } /** Admin revokes an arbitrary message. */ @PostMapping("/messages/{messageId}/revoke") public ResponseEntity> adminRevoke( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @PathVariable String messageId) { ImMessageEntity revoked = messageService.adminRevoke(appId, messageId); operationLogService.record(appId, operatorId, "ADMIN_REVOKE_MESSAGE", "MESSAGE", messageId, null); return ResponseEntity.ok(ApiResponse.success(revoked)); } /** Admin force dismisses a group. */ @DeleteMapping("/groups/{groupId}") public ResponseEntity> adminDismissGroup( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @PathVariable String groupId) { groupService.adminDismiss(groupId); operationLogService.record(appId, operatorId, "ADMIN_DISMISS_GROUP", "GROUP", groupId, null); return ResponseEntity.ok(ApiResponse.ok()); } @GetMapping("/webhooks") public ResponseEntity>> listWebhooks(@RequestParam String appId) { return ResponseEntity.ok(ApiResponse.success(webhookConfigService.list(appId))); } @PostMapping("/webhooks") public ResponseEntity> createWebhook( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestBody WebhookConfigRequest req) { WebhookConfigEntity saved = webhookConfigService.create(appId, req.url(), req.secret(), req.enabled()); operationLogService.record(appId, operatorId, "CREATE_WEBHOOK", "WEBHOOK", saved.getId(), req.url()); return ResponseEntity.ok(ApiResponse.success(saved)); } @PutMapping("/webhooks/{id}") public ResponseEntity> updateWebhook( @RequestParam String appId, @PathVariable String id, @AuthenticationPrincipal String operatorId, @RequestBody WebhookConfigRequest req) { WebhookConfigEntity saved = webhookConfigService.update(appId, id, req.url(), req.secret(), req.enabled()); operationLogService.record(appId, operatorId, "UPDATE_WEBHOOK", "WEBHOOK", id, req.url()); return ResponseEntity.ok(ApiResponse.success(saved)); } @DeleteMapping("/webhooks/{id}") public ResponseEntity> deleteWebhook( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @PathVariable String id) { webhookConfigService.delete(appId, id); operationLogService.record(appId, operatorId, "DELETE_WEBHOOK", "WEBHOOK", id, null); return ResponseEntity.ok(ApiResponse.ok()); } @GetMapping("/keyword-filters") public ResponseEntity>> listKeywordFilters(@RequestParam String appId) { return ResponseEntity.ok(ApiResponse.success(keywordFilterService.list(appId))); } @PostMapping("/keyword-filters") public ResponseEntity> createKeywordFilter( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestBody KeywordFilterRequest req) { KeywordFilterEntity saved = keywordFilterService.add(appId, req.pattern(), req.replacement(), req.action()); operationLogService.record(appId, operatorId, "CREATE_KEYWORD_FILTER", "KEYWORD_FILTER", saved.getId(), req.pattern()); return ResponseEntity.ok(ApiResponse.success(saved)); } @PutMapping("/keyword-filters/{id}") public ResponseEntity> updateKeywordFilter( @RequestParam String appId, @PathVariable String id, @AuthenticationPrincipal String operatorId, @RequestBody KeywordFilterRequest req) { KeywordFilterEntity saved = keywordFilterService.update(appId, id, req.pattern(), req.replacement(), req.action(), req.enabled()); operationLogService.record(appId, operatorId, "UPDATE_KEYWORD_FILTER", "KEYWORD_FILTER", id, req.pattern()); return ResponseEntity.ok(ApiResponse.success(saved)); } @DeleteMapping("/keyword-filters/{id}") public ResponseEntity> deleteKeywordFilter( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @PathVariable String id) { keywordFilterService.delete(appId, id); operationLogService.record(appId, operatorId, "DELETE_KEYWORD_FILTER", "KEYWORD_FILTER", id, null); return ResponseEntity.ok(ApiResponse.ok()); } @GetMapping("/global-mute") public ResponseEntity> getGlobalMute(@RequestParam String appId) { return ResponseEntity.ok(ApiResponse.success(globalMuteService.get(appId))); } @PutMapping("/global-mute") public ResponseEntity> setGlobalMute( @RequestParam String appId, @AuthenticationPrincipal String operatorId, @RequestParam boolean enabled) { ImGlobalMuteEntity saved = globalMuteService.setEnabled(appId, enabled); operationLogService.record(appId, operatorId, "SET_GLOBAL_MUTE", "GLOBAL_MUTE", saved.getId(), String.valueOf(enabled)); return ResponseEntity.ok(ApiResponse.success(saved)); } @GetMapping("/operation-logs") public ResponseEntity>> operationLogs( @RequestParam String appId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { return ResponseEntity.ok(ApiResponse.success( operationLogService.list(appId, PageRequest.of(page, size)))); } public record RegisterUserRequest(String userId, String nickname, String avatar) {} public record CreateGroupRequest(String name, String creatorId, List memberIds) {} public record WebhookConfigRequest(String url, String secret, Boolean enabled) {} public record KeywordFilterRequest(String pattern, String replacement, String action, Boolean enabled) {} }