UserController.java
package com.hwhub.backend.presentation.rest.user;
import com.hwhub.backend.application.service.UserIconService;
import com.hwhub.backend.application.service.UserService;
import com.hwhub.backend.application.service.UserService.NotificationSettingsResult;
import com.hwhub.backend.domain.enums.NotificationGroup;
import com.hwhub.backend.domain.enums.ThemeMode;
import com.hwhub.backend.domain.model.UserModel;
import com.hwhub.backend.presentation.rest.auth.GoogleOAuthLinkHelper;
import com.hwhub.backend.presentation.rest.user.dto.ChangePasswordRequest;
import com.hwhub.backend.presentation.rest.user.dto.CreateIconUploadUrlRequest;
import com.hwhub.backend.presentation.rest.user.dto.CreateIconUploadUrlResponse;
import com.hwhub.backend.presentation.rest.user.dto.NotificationSettingsResponse;
import com.hwhub.backend.presentation.rest.user.dto.OAuthStartResponse;
import com.hwhub.backend.presentation.rest.user.dto.UpdateIconRequest;
import com.hwhub.backend.presentation.rest.user.dto.UpdateNotificationSettingsRequest;
import com.hwhub.backend.presentation.rest.user.dto.UpdateThemeRequest;
import com.hwhub.backend.presentation.rest.user.dto.UpdateUserProfileRequest;
import com.hwhub.backend.presentation.rest.user.dto.UserHouseholdDto;
import com.hwhub.backend.presentation.rest.user.dto.UserProfileResponse;
import com.hwhub.backend.security.CurrentUserId;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
/**
* ユーザー関連の操作(プロフィール管理、所属世帯情報の取得など)を行うAPIコントローラです。
*
* <p>このエンドポイントは、認証されたユーザーのみが利用可能です。 ベースパス: /api/user
*/
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
private final UserIconService userIconService;
private final GoogleOAuthLinkHelper linkHelper;
/**
* 認証ユーザーが所属する全ての世帯(Household)の情報を取得します。
*
* @param userId 認証済みユーザーID
* @return ユーザーが所属する世帯情報(IDと名称)のリスト
*/
@GetMapping("/me/households")
public List<UserHouseholdDto> getUserHouseholds(@CurrentUserId Long userId) {
return userService.getHouseholds(userId).stream().map(UserHouseholdDto::fromModel).toList();
}
/**
* 認証ユーザーの現在のプロフィール情報(表示名、言語設定など)を取得します。
*
* @param userId 認証済みユーザーID
* @return 認証ユーザーのプロフィール情報を含むレスポンスオブジェクト
*/
@GetMapping("/me/profile")
public UserProfileResponse getProfile(@CurrentUserId Long userId) {
UserModel user = userService.getProfile(userId);
return UserProfileResponse.fromModel(user);
}
/**
* 認証ユーザーのプロフィール情報(表示名、言語設定など)を更新します。
*
* @param userId 認証済みユーザーID
* @param request 更新するプロフィール情報(displayName, locale)
* @return 更新後のプロフィール情報を含むレスポンスオブジェクト
*/
@PutMapping("/me/profile")
public UserProfileResponse updateProfile(
@CurrentUserId Long userId, @Valid @RequestBody UpdateUserProfileRequest request) {
UserModel updated = userService.updateProfile(userId, request.displayName(), request.locale());
return UserProfileResponse.fromModel(updated);
}
@PostMapping("/me/icon/upload-url")
public CreateIconUploadUrlResponse createIconUploadUrl(
@Valid @RequestBody CreateIconUploadUrlRequest request, @CurrentUserId Long userId) {
var result = userIconService.createUploadUrl(userId, request.fileName(), request.mimeType());
return new CreateIconUploadUrlResponse(result.uploadUrl(), result.fileKey());
}
@PostMapping("/me/icon")
public void updateIcon(
@Valid @RequestBody UpdateIconRequest request, @CurrentUserId Long userId) {
userIconService.updateUserIcon(userId, request.fileKey());
}
@DeleteMapping("/me")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteAccount(@CurrentUserId Long userId) {
userService.deleteAccount(userId);
}
/**
* ログイン中のパスワード変更
*
* @param request 入力値
* @return 204 No Content(成功時は返却なし)
*/
@PutMapping("/me/password")
public ResponseEntity<Void> changePassword(
@CurrentUserId Long userId, @Valid @RequestBody ChangePasswordRequest request) {
userService.changePassword(userId, request.currentPassword(), request.newPassword());
return ResponseEntity.noContent().build();
}
/** Googleアカウント連携開始(ログイン中ユーザーのみ) GET /api/users/me/google/link/start */
@GetMapping("/me/google/link/start")
public ResponseEntity<OAuthStartResponse> startGoogleLink(
@CurrentUserId Long userId, HttpServletResponse response) {
String state = linkHelper.generateStateForLink(userId);
linkHelper.setStateCookie(response, state);
String url = linkHelper.buildAuthorizationUrl(state);
return ResponseEntity.ok(new OAuthStartResponse(url));
}
/**
* ログイン中のユーザーのテーマモードを更新する。
*
* @param userId 認証済みユーザーID
* @param request テーマモード(SYSTEM / LIGHT / DARK)
* @return 204 No Content
*/
@PatchMapping("/me/theme")
public ResponseEntity<Void> updateTheme(
@CurrentUserId Long userId, @Valid @RequestBody UpdateThemeRequest request) {
ThemeMode themeMode = ThemeMode.fromCode(request.themeMode());
userService.updateThemeMode(userId, themeMode);
return ResponseEntity.noContent().build();
}
@GetMapping("/me/notification-settings")
public NotificationSettingsResponse getNotificationSettings(@CurrentUserId Long userId) {
NotificationSettingsResult result = userService.getSettings(userId);
return toNotificationSettingsResponse(result);
}
@PutMapping("/me/notification-settings")
public NotificationSettingsResponse updateNotificationSettings(
@RequestBody UpdateNotificationSettingsRequest req, @CurrentUserId Long userId) {
// Mapの詰め替え(Request -> Service)
Map<NotificationGroup, Boolean> groupSettings = new LinkedHashMap<>();
if (req.groupSettings() != null) {
for (var e : req.groupSettings().entrySet()) {
if (e.getKey() == null || e.getValue() == null) continue;
groupSettings.put(NotificationGroup.fromCode(e.getKey()), e.getValue());
}
}
NotificationSettingsResult result =
userService.updateNotificationEnabled(userId, req.notificationEnabled(), groupSettings);
return toNotificationSettingsResponse(result);
}
/**
* NotificationSettingsResultをNotificationSettingsResponseに変換する。
*
* @param result NotificationSettingsResult
* @return NotificationSettingsResponse
*/
private NotificationSettingsResponse toNotificationSettingsResponse(
NotificationSettingsResult result) {
Map<String, Boolean> responseGroupSettings = new LinkedHashMap<>();
for (var e : result.groupSettings().entrySet()) {
responseGroupSettings.put(e.getKey().getCode(), e.getValue());
}
return new NotificationSettingsResponse(result.notificationEnabled(), responseGroupSettings);
}
}