[#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">«</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">»</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>