Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-session-jdbc'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
testImplementation 'org.springframework.boot:spring-boot-starter-session-jdbc-test'
testCompileOnly 'org.projectlombok:lombok'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testAnnotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-security'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.user.controller;

import com.example.user.dto.request.SignInRequest;
import com.example.user.dto.response.UserAuthResponse;
import com.example.user.service.InternalUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/internal/users")
public class InternalUserController {

private final InternalUserService internalUserService;

@PostMapping("/authenticate")
public ResponseEntity<UserAuthResponse> authenticate(
@RequestBody SignInRequest request
) {
UserAuthResponse response = internalUserService.authenticate(request);

return ResponseEntity.ok(response);
}

}
64 changes: 64 additions & 0 deletions src/main/java/com/example/user/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.example.user.controller;

import com.example.user.dto.request.SignUpRequest;
import com.example.user.dto.request.UserUpdateRequest;
import com.example.user.dto.response.SignUpResponse;
import com.example.user.dto.response.UserSearchResponse;
import com.example.user.global.dto.ApiResponse;
import com.example.user.global.security.UserPrincipal;
import com.example.user.global.util.ResponseUtil;
import com.example.user.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {

private final UserService userService;

@PostMapping("/signup")
public ResponseEntity<ApiResponse<SignUpResponse>> signup(
@Valid @RequestBody SignUpRequest request
) {
SignUpResponse response = userService.signup(request);

ApiResponse<SignUpResponse> apiResponse = ResponseUtil.success("create user", response);

return ResponseEntity.status(201).body(apiResponse);
}

@GetMapping("/me")
public ResponseEntity<ApiResponse<UserSearchResponse>> getMyProfile(
@AuthenticationPrincipal UserPrincipal principal
) {
UserSearchResponse response = userService.getMyProfile(principal.getUserId());

ApiResponse<UserSearchResponse> apiResponse = ResponseUtil.success("select my profile", response);

return ResponseEntity.ok(apiResponse);
}

@PatchMapping("/me")
public ResponseEntity<Void> update(
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody UserUpdateRequest request
) {
userService.update(principal.getUserId(), request);

return ResponseEntity.noContent().build();
}

@DeleteMapping("/me")
public ResponseEntity<Void> delete(
@AuthenticationPrincipal UserPrincipal principal
) {
userService.delete(principal.getUserId());

return ResponseEntity.noContent().build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/example/user/dto/request/SignInRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.user.dto.request;

public record SignInRequest(
String email,
String password
) {
}
17 changes: 17 additions & 0 deletions src/main/java/com/example/user/dto/request/SignUpRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.user.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public record SignUpRequest(
@NotBlank
@Email
String email,

@NotBlank
String password,

@NotBlank
String name
) {
}
11 changes: 11 additions & 0 deletions src/main/java/com/example/user/dto/request/UserUpdateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.user.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;

public record UserUpdateRequest(
String name,

@JsonProperty("image_url")
String imageUrl
) {
}
13 changes: 13 additions & 0 deletions src/main/java/com/example/user/dto/response/SignUpResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.user.dto.response;

import com.example.user.entity.User;
import com.fasterxml.jackson.annotation.JsonProperty;

public record SignUpResponse(
@JsonProperty("user_id")
String userId
) {
public static SignUpResponse from(User user) {
return new SignUpResponse(user.getId());
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/example/user/dto/response/UserAuthResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.user.dto.response;

import com.example.user.entity.Role;
import com.example.user.entity.User;

public record UserAuthResponse(
String userId,
String name,
String role
) {
public static UserAuthResponse from(User user) {
return new UserAuthResponse(
user.getId(),
user.getName(),
user.getRole().name()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.user.dto.response;

import com.example.user.entity.User;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.time.LocalDateTime;

public record UserSearchResponse(
@JsonProperty("image_url")
String imageUrl,

String email,

String name,

@JsonProperty("created_at")
LocalDateTime createdAt
) {
public static UserSearchResponse from(User user) {
return new UserSearchResponse(
user.getImageUrl(),
user.getEmail(),
user.getName(),
user.getCreatedAt()
);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/example/user/entity/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.user.entity;

public enum Role {
USER,
ADMIN
}
77 changes: 77 additions & 0 deletions src/main/java/com/example/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.example.user.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(access = AccessLevel.PRIVATE)
@Getter
@Table(name = "users")
public class User {
@Id
private String id;

@Column(nullable = false, unique = true)
private String email;

private String password;

@Column(nullable = false)
private String name;

@Column(name = "image_url")
private String imageUrl;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role;

@Column(name = "auth_provider")
private String authProvider;

@Column(name = "provider_id")
private String providerId;

@CreationTimestamp
@Column(nullable = false, name = "created_at")
private LocalDateTime createdAt;

@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

public static User create(String email, String password, String name) {
return User.builder()
.id(UUID.randomUUID().toString())
.email(email)
.password(password)
.name(name)
.role(Role.USER)
.build();
}

public void update(String name, String imageUrl) {
if (this.deletedAt != null) {
throw new IllegalStateException("삭제된 유저");
}
if (name != null) this.name = name;
if (imageUrl != null) this.imageUrl = imageUrl;
}

public void delete() {
if (this.deletedAt != null) {
throw new IllegalStateException("삭제된 유저");
}
this.deletedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.user.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
37 changes: 37 additions & 0 deletions src/main/java/com/example/user/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.example.user.global.config;

import com.example.user.global.security.JwtFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtFilter jwtFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.formLogin(formLogin -> formLogin.disable())
.httpBasic(httpBasic -> httpBasic.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/users/signup").permitAll()
.requestMatchers("/users/me").authenticated()
.anyRequest().permitAll()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/example/user/global/dto/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.user.global.dto;

public record ApiResponse<T>(
String message,
T data
) {
}
Loading