[#173] fix : 디렉토리구조 변경

package com.speech.up.auth.provider;  
  
import org.springframework.security.oauth2.core.user.OAuth2User;  
  
import com.speech.up.auth.service.implement.UserAuthorizationType;  
import com.speech.up.auth.service.servicetype.LevelType;  
import com.speech.up.auth.service.servicetype.ProviderType;  
import com.speech.up.user.entity.UserEntity;  
  
public class GithubProvider implements ProviderOAuth {  
    final String socialId;  
    final String email;  
    final String name;  
    final String providerType;  
    final String authorization;  
    final String level;  
    final boolean isUse;  
  
    public GithubProvider(OAuth2User user) {  
       this.socialId = user.getAttributes().get("id") + "";  
       this.name = user.getAttributes().get("name") + "";  
       this.email = "none";  
       this.providerType = ProviderType.GITHUB.name();  
       this.authorization = UserAuthorizationType.ROLE_GENERAL_USER.name();  
       this.level = LevelType.BRONZE.name();  
       this.isUse = true;  
    }  
  
    @Override  
    public UserEntity getUser() {  
       return UserEntity.providerOf(socialId, email, level, name, authorization, providerType);  
    }  
}

package com.speech.up.auth.provider;  
  
import org.springframework.security.oauth2.core.user.OAuth2User;  
  
import com.speech.up.auth.service.implement.UserAuthorizationType;  
import com.speech.up.auth.service.servicetype.LevelType;  
import com.speech.up.auth.service.servicetype.ProviderType;  
import com.speech.up.user.entity.UserEntity;  
  
public class GoogleProvider implements ProviderOAuth {  
    final String socialId;  
    final String email;  
    final String name;  
    final String providerType;  
    final String authorization;  
    final String level;  
    final boolean isUse;  
  
    public GoogleProvider(OAuth2User user) {  
       this.socialId = user.getAttributes().get("sub") + "";  
       this.providerType = ProviderType.GOOGLE.name();  
       this.email = user.getAttributes().get("email") + "";  
       this.name = user.getAttributes().get("name") + "";  
       this.authorization = UserAuthorizationType.ROLE_GENERAL_USER.name();  
       this.level = LevelType.BRONZE.name();  
       this.isUse = true;  
    }  
  
    @Override  
    public UserEntity getUser() {  
       return UserEntity.providerOf(socialId, email, level, name, authorization, providerType);  
    }  
}

package com.speech.up.auth.provider;  
  
import java.util.HashMap;  
import java.util.Map;  
  
import org.springframework.security.oauth2.core.user.OAuth2User;  
  
import com.speech.up.auth.service.implement.UserAuthorizationType;  
import com.speech.up.auth.service.servicetype.LevelType;  
import com.speech.up.auth.service.servicetype.ProviderType;  
import com.speech.up.user.entity.UserEntity;  
  
public class KakaoProvider implements ProviderOAuth {  
    final String socialId;  
    final String email;  
    final String name;  
    final String providerType;  
    final String authorization;  
    final String level;  
    final boolean isUse;  
  
    public KakaoProvider(OAuth2User user) {  
       Object properties = user.getAttributes().get("properties");  
       Map<String, String> responseMap = new HashMap<>();  
       if (properties instanceof Map<?, ?> tempMap) {  
          response(responseMap, tempMap);  
       }  
  
       this.socialId = user.getAttributes().get("id") + "";  
       this.providerType = ProviderType.KAKAO.name();  
       this.email = "none";  
       this.name = responseMap.get("nickname");  
       this.authorization = UserAuthorizationType.ROLE_GENERAL_USER.name();  
       this.level = LevelType.BRONZE.name();  
       this.isUse = true;  
    }  
  
    private void response(Map<String, String> response, Map<?, ?> tempMap) {  
       for (Map.Entry<?, ?> entry : tempMap.entrySet()) {  
          if (entry.getKey() instanceof String && entry.getValue() instanceof String) {  
             response.put((String)entry.getKey(), (String)entry.getValue());  
          }  
       }  
    }  
  
    @Override  
    public UserEntity getUser() {  
       return UserEntity.providerOf(socialId, email, level, name, authorization, providerType);  
    }  
  
}

package com.speech.up.auth.service.implement;  
  
import java.util.NoSuchElementException;  
  
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;  
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;  
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;  
import org.springframework.security.oauth2.core.user.OAuth2User;  
import org.springframework.stereotype.Service;  
  
import com.speech.up.auth.entity.CustomOAuth2User;  
import com.speech.up.auth.provider.Provider;  
import com.speech.up.auth.service.servicetype.ProviderType;  
import com.speech.up.user.entity.UserEntity;  
import com.speech.up.user.repository.UserRepository;  
  
import lombok.RequiredArgsConstructor;  
import lombok.extern.slf4j.Slf4j;  
  
@Slf4j  
@Service  
@RequiredArgsConstructor  
public class OAuth2UserServiceImplement extends DefaultOAuth2UserService {  
    private final UserRepository userRepository;  
  
    @Override  
    public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {  
       OAuth2User oAuth2User = super.loadUser(request);  
       String oauthClientName = request.getClientRegistration().getClientName();  
  
       Provider provider = new Provider(oAuth2User);  
       UserEntity userEntity = provider.getUser(ProviderType.valueOf(oauthClientName.toUpperCase()));  
  
       assert userEntity != null;  
  
       if (!userRepository.existsBySocialId(userEntity.getSocialId())) {  
          userRepository.save(userEntity);  
       } else {  
          UserEntity user = userRepository.findBySocialId(userEntity.getSocialId())  
             .orElseThrow(  
                () -> new NoSuchElementException("not found UserEntity by socialId: " + userEntity.getSocialId()));  
          UserEntity updateUserAccess = UserEntity.updateUserAccess(user);  
          userRepository.save(updateUserAccess);  
       }  
       return new CustomOAuth2User(userEntity.getSocialId());  
    }  
  
}

package com.speech.up.user.entity;  
  
import java.time.LocalDateTime;  
import java.util.List;  
  
import com.fasterxml.jackson.annotation.JsonManagedReference;  
import com.fasterxml.jackson.databind.PropertyNamingStrategies;  
import com.fasterxml.jackson.databind.annotation.JsonNaming;  
import com.speech.up.script.entity.ScriptEntity;  
  
import jakarta.persistence.CascadeType;  
import jakarta.persistence.Column;  
import jakarta.persistence.Entity;  
import jakarta.persistence.GeneratedValue;  
import jakarta.persistence.GenerationType;  
import jakarta.persistence.Id;  
import jakarta.persistence.OneToMany;  
import jakarta.persistence.Table;  
import jakarta.validation.constraints.Null;  
import lombok.AccessLevel;  
import lombok.Getter;  
import lombok.NoArgsConstructor;  
import lombok.ToString;  
  
@ToString  
@Getter  
@NoArgsConstructor(access = AccessLevel.PROTECTED)  
@Entity  
@Table(name = "user")  
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)  
public class UserEntity {  
    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    @Column(name = "user_id")  
    private Long userId;  
  
    private String name;  
  
    @Column(name = "social_id")  
    private String socialId;  
  
    private String email;  
  
    private String level;  
  
    private String authorization;  
  
    private String providerType;  
  
    private LocalDateTime lastAccessedAt;  
  
    private boolean isUse;  
  
    @Null  
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)  
    @JsonManagedReference  
    private List<ScriptEntity> scriptEntity;  
  
    private UserEntity(String socialId, String email, String level,  
       String name, String authorization, String providerType) {  
       this.socialId = socialId;  
       this.email = email;  
       this.level = level;  
       this.name = name;  
       this.authorization = authorization;  
       this.providerType = providerType;  
       this.lastAccessedAt = LocalDateTime.now();  
       this.isUse = true;  
    }  
  
    private UserEntity(UserEntity user) {  
       this.userId = user.getUserId();  
       this.name = user.getName();  
       this.socialId = user.getSocialId();  
       this.email = user.getEmail();  
       this.level = user.getLevel();  
       this.providerType = user.getProviderType();  
       this.authorization = user.getAuthorization();  
       this.lastAccessedAt = LocalDateTime.now();  
       this.isUse = true;  
    }  
  
    public static UserEntity providerOf(String socialId, String email, String level,  
       String name, String authorization, String providerType) {  
       return new UserEntity(socialId, email, level, name, authorization, providerType);  
    }  
  
    public static UserEntity updateUserAccess(UserEntity user) {  
       return new UserEntity(user);  
    }  
}

board-style.css

.justify-content-center ul{
 
display: flex;
 
flex-wrap: wrap;
 
padding: 0;
 
margin: 0;
 
list-style: none;
 
}

boardWrite

document.addEventListener('DOMContentLoaded', function () {  
    const jwtToken = getItemWithExpiry("jwtToken");  
  
    fetch('/users/me', {  
        method: 'GET',  
        headers: {  
            'Content-Type': 'application/json',  
            'Authorization': `${jwtToken}`  
        }  
    })  
        .then(response => response.json())  
        .then(userData => {  
            document.getElementById('board-form').addEventListener('submit', function (event) {  
                event.preventDefault();  
  
                const title = document.getElementById('card-title').value;  
                const content = document.getElementById('card-text').value;  
  
                const requestBody = {  
                    title: title,  
                    content: content,  
                    user: {  
                        user_id: userData.userId,  
                    }  
                };  
  
                fetch(`/api/boards`, {  
                    method: 'POST',  
                    headers: {  
                        'Content-Type': 'application/json',  
                        'Authorization': `${jwtToken}`  
                    },  
                    body: JSON.stringify(requestBody)  
                })  
                    .then(response => response.json())  
                    .then(data => {  
                        if (data) {  
                            console.log(data)  
                            alert("게시글이 성공적으로 작성되었습니다.");  
                            window.location.href = "/boards";  
                        } else {  
                            alert("게시글 작성에 실패했습니다.");  
                        }  
                    })  
                    .catch(error => console.error('Error:', error));  
            });  
        })  
        .catch(error => {  
            console.error('사용자 정보를 가져오는 데 실패했습니다.', error);  
        });  
});

board-write

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Board Edit</title>  
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">  
    <link rel="stylesheet" type="text/css" href="/css/header-style.css">  
    <link rel="stylesheet" type="text/css" href="/css/board-write.css">  
  
</head>  
<body>  
  
  
<header>  
    <div id="nav-buttons" class="text-right p-3">  
        <!-- 네비게이션 버튼이 필요하다면 여기서 동적으로 추가 -->  
    </div>  
</header>  
  
<div class="container">  
    <h1 class="my-4 text-center">Edit Board Post</h1>  
  
    <!-- 게시글 수정 폼 -->  
    <div class="card">  
        <h3 class="mb-4">게시물 작성</h3>  
        <form id="board-form" method="post">  
            <div class="form-group">  
                <label for="card-title">제목:</label>  
                <input type="text" class="form-control" id="card-title" name="title" required/>  
            </div>  
            <div class="form-group">  
                <label for="card-text">내용:</label>  
                <textarea class="form-control" id="card-text" name="content" rows="10" required></textarea>  
            </div>  
            <div class="text-right">  
                <button type="submit" class="btn btn-custom">저장</button>  
            </div>  
        </form>  
    </div>  
  
    <!-- 링크를 통해 목록으로 돌아가기 -->  
    <div class="text-center mt-4">  
        <a class="btn btn-outline-secondary" href="/boards">Back to List</a>  
    </div>  
</div>  
<script src="/scriptPage/js/userMe.js"></script>  
<script src="/scriptPage/js/boardWrite.js"></script>  
</body>  
</html>

<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Board List</title>  
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">  
    <link rel="stylesheet" type="text/css" href="/css/header-style.css">  
    <link rel="stylesheet" type="text/css" href="/css/board-style.css">  
    <script src="/scriptPage/js/userMe.js"></script>  
  
</head>  
  
<body>  
<header>  
  
    <div id="nav-buttons">  
        <!-- 이 부분은 자바스크립트로 동적으로 업데이트됩니다. -->  
    </div>  
</header>  
<div class="container">  
    <h1 class="my-4">Board List</h1>  
  
    <!-- 게시판 테이블 -->  
    <table class="table table-striped">  
        <thead>  
        <tr>  
            <th>ID</th>  
            <th>Title</th>  
            <th>Content</th>  
            <th>Created At</th>  
            <th>Modified At</th>  
        </tr>  
        </thead>  
        <tbody>        <!-- 게시글 목록을 반복하여 표시 -->  
        <tr th:each="post : ${boardList}">  
            <td><a th:href="@{/boards/{id}(id=${post.boardId})}" th:text="${post.boardId}">1</a></td>  
            <td><a th:href="@{/boards/{id}(id=${post.boardId})}" th:text="${post.title}">Sample Title</a></td>  
            <td><a th:href="@{/boards/{id}(id=${post.boardId})}" th:text="${post.content}">Sample Content</a></td>  
            <td><a th:href="@{/boards/{id}(id=${post.boardId})}" th:text="${post.createdAt}">2024-08-14</a></td>  
            <td><a th:href="@{/boards/{id}(id=${post.boardId})}" th:text="${post.modifiedAt}">2024-08-14</a></td>  
        </tr>  
        </tbody>  
    </table>  
  
    <!--     페이지네이션 -->  
    <nav aria-label="Page navigation">  
        <ul class="pagination justify-content-center">  
            <li class="page-item" th:classappend="${pageNumber == 1} ? 'disabled'">  
                <a class="page-link" th:href="@{/boards(page=${pageNumber - 10 < 1 ? 1 : pageNumber - 10}, size=${pageSize})}" aria-label="Previous">  
                    <span aria-hidden="true">&laquo;</span>  
                </a>  
            </li>  
            <li th:with="startPage=${((pageNumber - 1) / 10) * 10 + 1}, endPage=${startPage + 9}, totalPages=${totalPages}">  
                <ul>  
                    <li class="page-item" th:each="i : ${#numbers.sequence(startPage, endPage)}"  
                        th:if="${i <= totalPages}"  
                        th:classappend="${i == pageNumber} ? 'active'">  
                        <a class="page-link" th:href="@{/boards(page=${i}, size=${pageSize})}" th:text="${i}">1</a>  
                    </li>  
                </ul>  
            </li>  
  
            <li class="page-item" th:classappend="${pageNumber == totalPages} ? 'disabled'">  
                <a class="page-link" th:href="@{/boards(page=${pageNumber + 10 > totalPages ? totalPages : pageNumber + 10}, size=${pageSize})}" aria-label="Next">  
                    <span aria-hidden="true">&raquo;</span>  
                </a>  
            </li>  
        </ul>  
    </nav>  
  
    <!--    작성하기 -->  
    <div id="board-buttons">  
    </div></div>  
<script src="/scriptPage/js/userMe.js"></script>  
<script src="/scriptPage/js/checkLogined.js"></script>  
</body>  
</html>