diff --git a/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthApi.java b/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthApi.java index 5e7aae83..05a964f8 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthApi.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthApi.java @@ -5,10 +5,11 @@ import jakarta.validation.Valid; import org.ioteatime.meonghanyangserver.auth.dto.reponse.LoginResponse; import org.ioteatime.meonghanyangserver.auth.dto.reponse.RefreshResponse; +import org.ioteatime.meonghanyangserver.auth.dto.request.EmailRequest; import org.ioteatime.meonghanyangserver.auth.dto.request.LoginRequest; -import org.ioteatime.meonghanyangserver.auth.dto.request.SendEmailRequest; import org.ioteatime.meonghanyangserver.common.api.Api; import org.ioteatime.meonghanyangserver.user.dto.UserDto; +import org.ioteatime.meonghanyangserver.user.dto.response.UserSimpleResponse; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @@ -18,11 +19,14 @@ public interface AuthApi { Api registerUser(@Valid @RequestBody UserDto userDto); @Operation(summary = "인증 메일 전송") - Api verifyEmail(@Valid @RequestBody SendEmailRequest email); + Api verifyEmail(@Valid @RequestBody EmailRequest email); @Operation(summary = "로그인을 합니다.") Api login(@RequestBody @Valid LoginRequest loginRequest); + @Operation(summary = "이메일 중복을 확인 합니다.") + Api duplicateEmail(@Valid @RequestBody EmailRequest email); + @Operation(summary = "토큰을 다시 생성합니다.") Api refreshToken(@RequestHeader("Authorization") String authorizationHeader); } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthController.java b/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthController.java index 2b6cbf4c..17aad109 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthController.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/auth/controller/AuthController.java @@ -4,11 +4,12 @@ import lombok.RequiredArgsConstructor; import org.ioteatime.meonghanyangserver.auth.dto.reponse.LoginResponse; import org.ioteatime.meonghanyangserver.auth.dto.reponse.RefreshResponse; +import org.ioteatime.meonghanyangserver.auth.dto.request.EmailRequest; import org.ioteatime.meonghanyangserver.auth.dto.request.LoginRequest; -import org.ioteatime.meonghanyangserver.auth.dto.request.SendEmailRequest; import org.ioteatime.meonghanyangserver.auth.service.AuthService; import org.ioteatime.meonghanyangserver.common.api.Api; import org.ioteatime.meonghanyangserver.user.dto.UserDto; +import org.ioteatime.meonghanyangserver.user.dto.response.UserSimpleResponse; import org.springframework.web.bind.annotation.*; @RestController @@ -24,11 +25,18 @@ public Api registerUser(@Valid @RequestBody UserDto userDto) { } @PostMapping("/email-verification") - public Api verifyEmail(@Valid @RequestBody SendEmailRequest sendEmailReq) { - authService.send(sendEmailReq.email()); + public Api verifyEmail(@Valid @RequestBody EmailRequest emailReq) { + authService.send(emailReq.email()); return Api.OK(); } + // Email 중복 확인 + @PostMapping("/check-email") + public Api duplicateEmail(@Valid @RequestBody EmailRequest emailReq) { + UserSimpleResponse response = authService.verifyEmail(emailReq.email()); + return Api.OK(response); + } + @Override @PostMapping("/sign-in") public Api login(LoginRequest loginRequest) { diff --git a/src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/SendEmailRequest.java b/src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/EmailRequest.java similarity index 91% rename from src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/SendEmailRequest.java rename to src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/EmailRequest.java index 16bfebdf..39d8c8a8 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/SendEmailRequest.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/auth/dto/request/EmailRequest.java @@ -5,6 +5,6 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; -public record SendEmailRequest( +public record EmailRequest( @Valid @Email @NotNull @Schema(description = "이메일", example = "example@gmail.com") String email) {} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java b/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java index ce218b32..4e9acc67 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/auth/service/AuthService.java @@ -77,6 +77,14 @@ public void send(String email) { googleMailClient.sendMail(email, "hello", "world"); } + public UserSimpleResponse verifyEmail(String email) { + UserEntity userEntity = + userRepository + .findByEmail(email) + .orElseThrow(() -> new ApiException(ErrorTypeCode.NULL_POINT)); + return UserSimpleResponse.from(userEntity); + } + public RefreshResponse reissueAccessToken(String authorizationHeader) { String refreshToken = jwtUtils.extractTokenFromHeader(authorizationHeader); diff --git a/src/main/java/org/ioteatime/meonghanyangserver/cctv/domain/CctvEntity.java b/src/main/java/org/ioteatime/meonghanyangserver/cctv/domain/CctvEntity.java new file mode 100644 index 00000000..1dad7384 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/cctv/domain/CctvEntity.java @@ -0,0 +1,24 @@ +package org.ioteatime.meonghanyangserver.cctv.domain; + +import jakarta.persistence.*; +import lombok.Data; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; + +@Data +@Entity +@Table(name = "cctv") +public class CctvEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "group_id", nullable = false) + private GroupEntity group; + + @Column(nullable = false, length = 20) + private String cctvNickname; + + @Column(nullable = false, length = 100) + private String kvsChannelName; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/common/utils/LoginMember.java b/src/main/java/org/ioteatime/meonghanyangserver/common/utils/LoginMember.java new file mode 100644 index 00000000..3f949451 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/common/utils/LoginMember.java @@ -0,0 +1,12 @@ +package org.ioteatime.meonghanyangserver.common.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.security.core.annotation.AuthenticationPrincipal; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@AuthenticationPrincipal(expression = "id == null ? 0L : id") +public @interface LoginMember {} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/config/OpenApiConfig.java b/src/main/java/org/ioteatime/meonghanyangserver/config/OpenApiConfig.java index 45b45a60..50df160b 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/config/OpenApiConfig.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/config/OpenApiConfig.java @@ -3,6 +3,13 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import java.util.List; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @OpenAPIDefinition( @@ -13,4 +20,48 @@ version = "v1", contact = @Contact(name = "서유진", email = "seoyoujin97@gmail.com"))) @Configuration -public class OpenApiConfig {} +public class OpenApiConfig { + @Bean + public OpenAPI openAPI() { + SecurityScheme apiKey = + new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + + SecurityRequirement securityRequirement = new SecurityRequirement().addList("Bearer Token"); + + Server productionServer = new Server(); + productionServer.setDescription("Production Server"); + productionServer.setUrl("https://my-server-name.com"); + + Server localServer = new Server(); + localServer.setDescription("Local Server"); + localServer.setUrl("http://localhost:8080"); + + return new OpenAPI() + .addSecurityItem(getSecurityRequirement()) + .components(getAuthComponent()) + .servers(List.of(productionServer, localServer)) + .components(new Components().addSecuritySchemes("Bearer Token", apiKey)) + .addSecurityItem(securityRequirement); + } + + private SecurityRequirement getSecurityRequirement() { + String jwt = "JWT"; + return new SecurityRequirement().addList(jwt); + } + + private Components getAuthComponent() { + return new Components() + .addSecuritySchemes( + "JWT", + new SecurityScheme() + .name("JWT") + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name("Authorization")); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/config/SecurityConfig.java b/src/main/java/org/ioteatime/meonghanyangserver/config/SecurityConfig.java index e28b1df0..5cc5feb9 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/config/SecurityConfig.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/config/SecurityConfig.java @@ -1,5 +1,7 @@ package org.ioteatime.meonghanyangserver.config; +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.filter.JwtRequestFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -7,10 +9,14 @@ import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { + private final JwtRequestFilter jwtRequestFilter; + @Bean BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); @@ -19,14 +25,18 @@ BCryptPasswordEncoder bCryptPasswordEncoder() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(CsrfConfigurer::disable); - http.authorizeHttpRequests( (auth) -> - auth.requestMatchers("/open-api/**", "/swagger-ui/**", "/v3/**", "/error") + auth.requestMatchers( + "/open-api/**", + "/swagger-ui/**", + "/v3/**", + "/error", + "/api/**") .permitAll() .anyRequest() .authenticated()); - + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupApi.java b/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupApi.java new file mode 100644 index 00000000..dccbf7cb --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupApi.java @@ -0,0 +1,13 @@ +package org.ioteatime.meonghanyangserver.group.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.ioteatime.meonghanyangserver.common.api.Api; +import org.ioteatime.meonghanyangserver.group.dto.response.CreateGroupResponse; +import org.springframework.security.core.Authentication; + +@Tag(name = "Group Api", description = "Group 관련 API 목록입니다.") +public interface GroupApi { + @Operation(summary = "그룹을 생성합니다.") + Api createGroup(Authentication authentication); +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupController.java b/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupController.java new file mode 100644 index 00000000..097c32a7 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/controller/GroupController.java @@ -0,0 +1,24 @@ +package org.ioteatime.meonghanyangserver.group.controller; + +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.common.api.Api; +import org.ioteatime.meonghanyangserver.group.dto.response.CreateGroupResponse; +import org.ioteatime.meonghanyangserver.group.service.GroupService; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/group") +public class GroupController implements GroupApi { + private final GroupService groupService; + + @Override + @PostMapping + public Api createGroup(Authentication authentication) { + CreateGroupResponse createGroupResponse = groupService.createGroup(authentication); + return Api.OK(createGroupResponse); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupEntity.java b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupEntity.java new file mode 100644 index 00000000..b5cd0b89 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupEntity.java @@ -0,0 +1,35 @@ +package org.ioteatime.meonghanyangserver.group.domain; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ioteatime.meonghanyangserver.cctv.domain.CctvEntity; +import org.ioteatime.meonghanyangserver.video.entity.VideoEntity; + +@Data +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "`group`") +public class GroupEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String groupName; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @OneToMany(mappedBy = "group", cascade = CascadeType.ALL) + private List cctvs; + + @OneToMany(mappedBy = "group", cascade = CascadeType.ALL) + private List videos; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserEntity.java b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserEntity.java new file mode 100644 index 00000000..c00cc67a --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserEntity.java @@ -0,0 +1,33 @@ +package org.ioteatime.meonghanyangserver.group.domain; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ioteatime.meonghanyangserver.group.domain.enums.GroupUserRole; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; + +@Data +@Entity +@Table(name = "group_user") +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class GroupUserEntity { + @EmbeddedId private GroupUserId id; + + @Column(nullable = false, length = 10) + @Enumerated(value = EnumType.STRING) + private GroupUserRole role; + + @ManyToOne + @MapsId("groupId") + @JoinColumn(name = "group_id") + private GroupEntity group; + + @ManyToOne + @MapsId("userId") + @JoinColumn(name = "user_id", unique = true) + private UserEntity user; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserId.java b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserId.java new file mode 100644 index 00000000..698f5e84 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/GroupUserId.java @@ -0,0 +1,16 @@ +package org.ioteatime.meonghanyangserver.group.domain; + +import jakarta.persistence.Embeddable; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Embeddable +@AllArgsConstructor +@NoArgsConstructor +public class GroupUserId implements Serializable { + private Long groupId; + private Long userId; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/domain/enums/GroupUserRole.java b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/enums/GroupUserRole.java new file mode 100644 index 00000000..8a80ed3c --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/domain/enums/GroupUserRole.java @@ -0,0 +1,11 @@ +package org.ioteatime.meonghanyangserver.group.domain.enums; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum GroupUserRole { + ROLE_USER("USER"), + ROLE_MASTER("MASTER"); + + private final String description; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/dto/response/CreateGroupResponse.java b/src/main/java/org/ioteatime/meonghanyangserver/group/dto/response/CreateGroupResponse.java new file mode 100644 index 00000000..aac3864c --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/dto/response/CreateGroupResponse.java @@ -0,0 +1,5 @@ +package org.ioteatime.meonghanyangserver.group.dto.response; + +import java.time.LocalDateTime; + +public record CreateGroupResponse(Long groupId, String groupName, LocalDateTime createdAt) {} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupEntityMapper.java b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupEntityMapper.java new file mode 100644 index 00000000..d5b74487 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupEntityMapper.java @@ -0,0 +1,10 @@ +package org.ioteatime.meonghanyangserver.group.mapper.group; + +import java.time.LocalDateTime; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; + +public class GroupEntityMapper { + public static GroupEntity toEntity(String groupName) { + return GroupEntity.builder().groupName(groupName).createdAt(LocalDateTime.now()).build(); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupResponseMapper.java b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupResponseMapper.java new file mode 100644 index 00000000..38a46c7d --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/group/GroupResponseMapper.java @@ -0,0 +1,11 @@ +package org.ioteatime.meonghanyangserver.group.mapper.group; + +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.ioteatime.meonghanyangserver.group.dto.response.CreateGroupResponse; + +public class GroupResponseMapper { + public static CreateGroupResponse toResponse(GroupEntity groupEntity) { + return new CreateGroupResponse( + groupEntity.getId(), groupEntity.getGroupName(), groupEntity.getCreatedAt()); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/groupuser/GroupUserEntityMapper.java b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/groupuser/GroupUserEntityMapper.java new file mode 100644 index 00000000..7e615951 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/mapper/groupuser/GroupUserEntityMapper.java @@ -0,0 +1,22 @@ +package org.ioteatime.meonghanyangserver.group.mapper.groupuser; + +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.ioteatime.meonghanyangserver.group.domain.GroupUserEntity; +import org.ioteatime.meonghanyangserver.group.domain.GroupUserId; +import org.ioteatime.meonghanyangserver.group.domain.enums.GroupUserRole; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; + +public class GroupUserEntityMapper { + + // master mapper + public static GroupUserEntity toEntity( + GroupEntity groupEntity, UserEntity userEntity, GroupUserRole groupUserRole) { + return GroupUserEntity.builder() + .id(new GroupUserId(groupEntity.getId(), userEntity.getId())) + .user(userEntity) + .role(groupUserRole) + .group(groupEntity) + .user(userEntity) + .build(); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepository.java new file mode 100644 index 00000000..871c3f2c --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepository.java @@ -0,0 +1,7 @@ +package org.ioteatime.meonghanyangserver.group.repository.group; + +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; + +public interface GroupRepository { + GroupEntity createGroup(GroupEntity groupEntity); +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepositoryImpl.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepositoryImpl.java new file mode 100644 index 00000000..6da5012b --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/GroupRepositoryImpl.java @@ -0,0 +1,16 @@ +package org.ioteatime.meonghanyangserver.group.repository.group; + +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class GroupRepositoryImpl implements GroupRepository { + private final JpaGroupRepository jpaGroupRepository; + + @Override + public GroupEntity createGroup(GroupEntity groupEntity) { + return jpaGroupRepository.save(groupEntity); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/JpaGroupRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/JpaGroupRepository.java new file mode 100644 index 00000000..379a500c --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/group/JpaGroupRepository.java @@ -0,0 +1,6 @@ +package org.ioteatime.meonghanyangserver.group.repository.group; + +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaGroupRepository extends JpaRepository {} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepository.java new file mode 100644 index 00000000..61a3ab08 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepository.java @@ -0,0 +1,10 @@ +package org.ioteatime.meonghanyangserver.group.repository.groupuser; + +import org.ioteatime.meonghanyangserver.group.domain.GroupUserEntity; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; + +public interface GroupUserRepository { + GroupUserEntity createGroupUser(GroupUserEntity groupUserEntity); + + boolean findGroupUser(UserEntity userEntity); +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepositoryImpl.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepositoryImpl.java new file mode 100644 index 00000000..fa79b6f8 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/GroupUserRepositoryImpl.java @@ -0,0 +1,23 @@ +package org.ioteatime.meonghanyangserver.group.repository.groupuser; + +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.group.domain.GroupUserEntity; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class GroupUserRepositoryImpl implements GroupUserRepository { + private final JpaGroupUserRepository jpaGroupUserRepository; + + @Override + public GroupUserEntity createGroupUser(GroupUserEntity groupUserEntity) { + return jpaGroupUserRepository.save(groupUserEntity); + } + + @Override + public boolean findGroupUser(UserEntity userEntity) { + boolean groupUser = jpaGroupUserRepository.existsByUser(userEntity); + return groupUser; + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/JpaGroupUserRepository.java b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/JpaGroupUserRepository.java new file mode 100644 index 00000000..ad0d23b7 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/repository/groupuser/JpaGroupUserRepository.java @@ -0,0 +1,10 @@ +package org.ioteatime.meonghanyangserver.group.repository.groupuser; + +import org.ioteatime.meonghanyangserver.group.domain.GroupUserEntity; +import org.ioteatime.meonghanyangserver.group.domain.GroupUserId; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaGroupUserRepository extends JpaRepository { + boolean existsByUser(UserEntity userEntity); +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupService.java b/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupService.java new file mode 100644 index 00000000..3165fa3c --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupService.java @@ -0,0 +1,47 @@ +package org.ioteatime.meonghanyangserver.group.service; + +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.common.error.ErrorTypeCode; +import org.ioteatime.meonghanyangserver.common.exception.ApiException; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.ioteatime.meonghanyangserver.group.domain.enums.GroupUserRole; +import org.ioteatime.meonghanyangserver.group.dto.response.CreateGroupResponse; +import org.ioteatime.meonghanyangserver.group.mapper.group.GroupEntityMapper; +import org.ioteatime.meonghanyangserver.group.mapper.group.GroupResponseMapper; +import org.ioteatime.meonghanyangserver.group.repository.group.GroupRepository; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; +import org.ioteatime.meonghanyangserver.user.dto.CustomUserDetail; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GroupService { + private final GroupRepository groupRepository; + private final GroupUserService groupUserService; + + // create group + public CreateGroupResponse createGroup(Authentication authentication) { + CustomUserDetail userDetails = (CustomUserDetail) authentication.getPrincipal(); + + UserEntity userEntity = userDetails.getUserEntity(); + + boolean groupUserEntity = groupUserService.findGroupUser(userEntity); + + if (groupUserEntity) { + throw new ApiException(ErrorTypeCode.BAD_REQUEST); + } + + String roomName = userEntity.getNickname() + " 그룹"; + + GroupEntity groupEntity = GroupEntityMapper.toEntity(roomName); + + GroupEntity newGroupEntity = groupRepository.createGroup(groupEntity); + + groupUserService.createGroupUser(newGroupEntity, userEntity, GroupUserRole.ROLE_MASTER); + + CreateGroupResponse createGroupResponse = GroupResponseMapper.toResponse(newGroupEntity); + + return createGroupResponse; + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupUserService.java b/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupUserService.java new file mode 100644 index 00000000..ff66d15d --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/group/service/GroupUserService.java @@ -0,0 +1,29 @@ +package org.ioteatime.meonghanyangserver.group.service; + +import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; +import org.ioteatime.meonghanyangserver.group.domain.GroupUserEntity; +import org.ioteatime.meonghanyangserver.group.domain.enums.GroupUserRole; +import org.ioteatime.meonghanyangserver.group.mapper.groupuser.GroupUserEntityMapper; +import org.ioteatime.meonghanyangserver.group.repository.groupuser.GroupUserRepository; +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class GroupUserService { + private final GroupUserRepository groupUserRepository; + + // input user + public void createGroupUser( + GroupEntity groupEntity, UserEntity userEntity, GroupUserRole groupUserRole) { + GroupUserEntity groupUserEntity = + GroupUserEntityMapper.toEntity(groupEntity, userEntity, groupUserRole); + groupUserRepository.createGroupUser(groupUserEntity); + } + + public boolean findGroupUser(UserEntity userEntity) { + boolean groupUser = groupUserRepository.findGroupUser(userEntity); + return groupUser; + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserApi.java b/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserApi.java index 0cef54b9..4dc43ddb 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserApi.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserApi.java @@ -2,9 +2,13 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import org.ioteatime.meonghanyangserver.common.api.Api; +import org.ioteatime.meonghanyangserver.common.utils.LoginMember; +import org.ioteatime.meonghanyangserver.user.dto.request.ChangePasswordRequest; import org.ioteatime.meonghanyangserver.user.dto.response.UserDetailResponse; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; @Tag(name = "User Api", description = "유저 관련 API 목록입니다.") public interface UserApi { @@ -13,5 +17,9 @@ public interface UserApi { Api getUserDetail(@PathVariable("userId") Long userId); @Operation(summary = "회원 정보를 삭제합니다.") - Api deleteUser(@PathVariable("userId") Long userId); + Api deleteUser(@LoginMember Long userId); + + @Operation(summary = "회원의 비밀번호를 변경합니다.") + Api changeUserPassword( + @LoginMember Long userId, @RequestBody @Valid ChangePasswordRequest request); } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserController.java b/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserController.java index bd6accc2..fb0f438f 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserController.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/controller/UserController.java @@ -1,13 +1,13 @@ package org.ioteatime.meonghanyangserver.user.controller; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.ioteatime.meonghanyangserver.common.api.Api; +import org.ioteatime.meonghanyangserver.common.utils.LoginMember; +import org.ioteatime.meonghanyangserver.user.dto.request.ChangePasswordRequest; import org.ioteatime.meonghanyangserver.user.dto.response.UserDetailResponse; import org.ioteatime.meonghanyangserver.user.service.UserService; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -16,14 +16,21 @@ public class UserController implements UserApi { private final UserService userService; @GetMapping("/{userId}") - public Api getUserDetail(Long userId) { + public Api getUserDetail(@PathVariable("userId") Long userId) { UserDetailResponse userDto = userService.getUserDetail(userId); return Api.OK(userDto); } - @DeleteMapping("/{userId}") - public Api deleteUser(Long userId) { + @DeleteMapping + public Api deleteUser(@LoginMember Long userId) { userService.deleteUser(userId); return Api.OK(); } + + @PutMapping("/password") + public Api changeUserPassword( + @LoginMember Long userId, @RequestBody @Valid ChangePasswordRequest request) { + userService.changeUserPassword(userId, request); + return Api.OK(); + } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/domain/UserEntity.java b/src/main/java/org/ioteatime/meonghanyangserver/user/domain/UserEntity.java index f2e9adf0..60af3e89 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/domain/UserEntity.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/domain/UserEntity.java @@ -4,6 +4,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.ioteatime.meonghanyangserver.common.error.ErrorTypeCode; +import org.ioteatime.meonghanyangserver.common.exception.ApiException; @Getter @Entity @@ -26,10 +28,19 @@ public class UserEntity { @Column private String profileImgUrl; @Builder - public UserEntity(String nickname, String email, String password, String profileImgUrl) { - this.nickname = nickname; + public UserEntity( + Long id, String email, String password, String nickname, String profileImgUrl) { + this.id = id; this.email = email; this.password = password; + this.nickname = nickname; this.profileImgUrl = profileImgUrl; } + + public void setPassword(String encodedPassword) { + if (encodedPassword == null || encodedPassword.isBlank()) { + throw new ApiException(ErrorTypeCode.BAD_REQUEST, "Password is empty"); + } + this.password = encodedPassword; + } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/CustomUserDetail.java b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/CustomUserDetail.java index b5f0a225..cda2c663 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/CustomUserDetail.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/CustomUserDetail.java @@ -12,10 +12,10 @@ @Data @RequiredArgsConstructor public class CustomUserDetail implements UserDetails { - private UserEntity usersEntity; + private UserEntity userEntity; - public CustomUserDetail(UserEntity usersEntity) { - this.usersEntity = usersEntity; + public CustomUserDetail(UserEntity userEntity) { + this.userEntity = userEntity; } @Override @@ -25,11 +25,15 @@ public Collection getAuthorities() { @Override public String getPassword() { - return usersEntity.getPassword(); + return userEntity.getPassword(); } @Override public String getUsername() { - return usersEntity.getEmail(); + return userEntity.getEmail(); + } + + public Long getId() { + return userEntity.getId(); } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/ChangePasswordRequest.java b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/ChangePasswordRequest.java new file mode 100644 index 00000000..e3373c41 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/ChangePasswordRequest.java @@ -0,0 +1,18 @@ +package org.ioteatime.meonghanyangserver.user.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ChangePasswordRequest { + + @NotBlank(message = "현재 비밀번호는 필수 입력값") + private String currentPassword; + + @NotBlank(message = "새 비밀번호는 필수 입력값") + @Size(min = 8, max = 20, message = "비밀번호는 8자 이상 20자 이하여야 합니다.") + private String newPassword; +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/UserDto.java b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/UserDto.java new file mode 100644 index 00000000..ad1708f5 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/request/UserDto.java @@ -0,0 +1,46 @@ +package org.ioteatime.meonghanyangserver.user.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserDto { + + @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "이메일 형식으로 입력해주세요.") + @Schema(description = "사용자의 이메일", example = "example@gmail.com") + private String email; + + @NotBlank(message = "비밀번호를 입력해주세요.") + @Schema(description = "사용자의 비밀번호", example = "testpassword") + private String password; + + @NotBlank(message = "비밀번호를 확인해주세요.") + @Schema(description = "비밀번호 확인", example = "testpassword") + private String passwordConfirm; + + @NotBlank(message = "닉네임을 입력해주세요.") + @Schema(description = "사용자의 닉네임") + private String nickname; + + @Schema(description = "사용자의 프로필 이미지 URL", example = "http://example.com/example.jpg") + private String profileImgUrl; + + @Builder + public UserDto( + String email, + String password, + String passwordConfirm, + String nickname, + String profileImgUrl) { + this.email = email; + this.password = password; + this.passwordConfirm = passwordConfirm; + this.nickname = nickname; + this.profileImgUrl = profileImgUrl; + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/response/UserSimpleResponse.java b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/response/UserSimpleResponse.java index fcb97239..eee86b71 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/dto/response/UserSimpleResponse.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/dto/response/UserSimpleResponse.java @@ -1,3 +1,9 @@ package org.ioteatime.meonghanyangserver.user.dto.response; -public record UserSimpleResponse(Long id, String email) {} +import org.ioteatime.meonghanyangserver.user.domain.UserEntity; + +public record UserSimpleResponse(Long id, String email) { + public static UserSimpleResponse from(UserEntity user) { + return new UserSimpleResponse(user.getId(), user.getEmail()); + } +} diff --git a/src/main/java/org/ioteatime/meonghanyangserver/user/service/UserService.java b/src/main/java/org/ioteatime/meonghanyangserver/user/service/UserService.java index 8c65361f..ea0aed1f 100644 --- a/src/main/java/org/ioteatime/meonghanyangserver/user/service/UserService.java +++ b/src/main/java/org/ioteatime/meonghanyangserver/user/service/UserService.java @@ -1,16 +1,22 @@ package org.ioteatime.meonghanyangserver.user.service; import lombok.RequiredArgsConstructor; +import org.ioteatime.meonghanyangserver.common.error.ErrorTypeCode; +import org.ioteatime.meonghanyangserver.common.exception.ApiException; import org.ioteatime.meonghanyangserver.user.domain.UserEntity; +import org.ioteatime.meonghanyangserver.user.dto.request.ChangePasswordRequest; import org.ioteatime.meonghanyangserver.user.dto.response.UserDetailResponse; import org.ioteatime.meonghanyangserver.user.repository.UserRepository; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final BCryptPasswordEncoder bCryptPasswordEncoder; public UserDetailResponse getUserDetail(Long userId) { UserEntity userEntity = @@ -24,4 +30,25 @@ public UserDetailResponse getUserDetail(Long userId) { public void deleteUser(Long userId) { userRepository.deleteById(userId); } + + @Transactional + public void changeUserPassword(Long userId, ChangePasswordRequest request) { + String currentPassword = request.getCurrentPassword(); + String newPassword = request.getNewPassword(); + + UserEntity userEntity = + userRepository + .findById(userId) + .orElseThrow( + () -> + new ApiException( + ErrorTypeCode.BAD_REQUEST, "User not found")); + + if (!bCryptPasswordEncoder.matches(currentPassword, userEntity.getPassword())) { + throw new ApiException(ErrorTypeCode.BAD_REQUEST, "현재 비밀번호가 일치하지 않습니다."); + } + + // Dirty-Checking Password Change + userEntity.setPassword(bCryptPasswordEncoder.encode(newPassword)); + } } diff --git a/src/main/java/org/ioteatime/meonghanyangserver/video/entity/VideoEntity.java b/src/main/java/org/ioteatime/meonghanyangserver/video/entity/VideoEntity.java new file mode 100644 index 00000000..8316ff27 --- /dev/null +++ b/src/main/java/org/ioteatime/meonghanyangserver/video/entity/VideoEntity.java @@ -0,0 +1,28 @@ +package org.ioteatime.meonghanyangserver.video.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import lombok.Data; +import org.ioteatime.meonghanyangserver.group.domain.GroupEntity; + +@Data +@Entity +@Table(name = "video") +public class VideoEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String videoName; + + @ManyToOne + @JoinColumn(name = "group_id", nullable = false) + private GroupEntity group; + + @Column(nullable = false, length = 100) + private String videoPath; + + @Column(nullable = false) + private LocalDateTime createdAt; +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 99ad2669..23634c97 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -13,7 +13,7 @@ spring: default_batch_fetch_size: 100 servlet: multipart: - location: D:\\shieldus\\interviewPartner\\uploads\\ + location: C:\\multipart\\uploads\\ max-file-size: 10MB max-request-size: 10MB data: