diff --git a/src/main/java/common/Config.java b/src/main/java/common/Config.java index 8f72cc433..c49f789bf 100644 --- a/src/main/java/common/Config.java +++ b/src/main/java/common/Config.java @@ -4,6 +4,8 @@ public class Config { public static final String CRLF = "\r\n"; public static final String REPLACE_PLACEHOLDER = "page"; + public static final int MIN_USER_DATA_LENGTH = 4; + public static final String DEFAULT_PAGE_PATH = "/index.html"; public static final String MAIN_PAGE_PATH = "/main/index.html"; public static final String REGISTRATION_PAGE_PATH = "/registration/index.html"; @@ -12,6 +14,8 @@ public class Config { public static final String COMMENT_PAGE_PATH = "/comment/index.html"; public static final String ARTICLE_PAGE_PATH = "/article/index.html"; public static final String NOPOST_PAGE_PATH = "/exception/nopost.html"; + public static final String COMMENT_PAGE_PATH_PREFIX = "/comment"; + public static final String POST_PAGE_PATH = "/post"; public static final String HEADER_CONTENT_LENGTH = "content-length"; public static final String HEADER_LOCATION = "location"; @@ -23,14 +27,27 @@ public class Config { public static final String IMAGE_BASE_PATH = "./src/main/resources/uploads"; public static final String IMAGE_PROFILE_BASE_PATH = "./src/main/resources/uploads/profiles"; public static final String IMAGE_DEFAULT_PROFILE_NAME = "default.png"; + public static final String IMAGE_DEFAULT_PROFILE_API = "/image/profile/default.png"; + + public static final String POST_ID_QUERY_NAME = "postId"; + + public static final String REPEAT_FORMAT_PLACEHOLDER = "{{REPEAT}}"; + + public static final String NO_COMMENT = "댓글이 없습니다. 먼저 작성을 시작해주세요!"; + + public static final String IMAGE_TYPE_UNKNOWN = "unknown"; + public static final String IMAGE_TYPE_PNG = "png"; + public static final String IMAGE_TYPE_JPEG = "JPEG"; + + public static final int DEFAULT_COMMENT_COUNT = 3; public static final String PAGE_HEADER_LOGIN = "
" + ""; + + public static final String COMMENT_REPEAT_FORMAT = "
  • \n" + + "
    \n" + + " \n" + + "

    {{{{REPEAT}}.authorName}}

    \n" + + "
    \n" + + "

    \n" + + " {{{{REPEAT}}.content}}\n" + + "

    \n" + + "
  • "; + + public static final String COMMENT_WANT_TO_SEE_MORE = "" + + "모든 댓글 보기({{post.commentNum}}개)" + + ""; } diff --git a/src/main/java/common/Utils.java b/src/main/java/common/Utils.java index ba753de2d..bb3a505f2 100644 --- a/src/main/java/common/Utils.java +++ b/src/main/java/common/Utils.java @@ -2,10 +2,7 @@ import webserver.http.RequestBody; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; public class Utils { public static String getRestStr(String wholeStr, String splitParam, int idx) { @@ -23,31 +20,26 @@ public static String getRestStr(String wholeStr, String splitParam, int idx) { return sb.toString(); } - public static String parseMapToQueryString_RequestBody(Map queryParam) { + public static String parseGeneralMapToQueryString(Map queryParam) { StringBuilder sb = new StringBuilder(); boolean first = true; - for (Map.Entry entry : queryParam.entrySet()) { + for (Map.Entry entry : queryParam.entrySet()) { if (!first) sb.append("&"); sb.append(entry.getKey()) .append("=") - .append(entry.getValue().toString()); + .append(String.valueOf(entry.getValue())); first = false; } return sb.toString(); } - public static String parseMapToQueryString(Map queryParam) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (Map.Entry entry : queryParam.entrySet()) { - if (!first) sb.append("&"); - sb.append(entry.getKey()) - .append("=") - .append(entry.getValue()); - first = false; - } - return sb.toString(); + public static String parseMapToQueryString_RequestBody(Map queryParam) { + return parseGeneralMapToQueryString(queryParam); + } + + public static String parseMapToQueryString(Map queryParam) { + return parseGeneralMapToQueryString(queryParam); } public static void replaceAll(StringBuilder sb, String target, String replacement) { int index; @@ -120,4 +112,23 @@ public static List getBothSplit (byte[] origin, byte[] splitter){ } return null; } + + public static String detectImageType(byte[] data) { + if (data.length < 4) return Config.IMAGE_TYPE_UNKNOWN; + + if ((data[0] & 0xFF) == 0xFF && + (data[1] & 0xFF) == 0xD8 && + (data[2] & 0xFF) == 0xFF) { + return Config.IMAGE_TYPE_JPEG; + } + + if ((data[0] & 0xFF) == 0x89 && + (data[1] & 0xFF) == 0x50 && + (data[2] & 0xFF) == 0x4E && + (data[3] & 0x47) == 0x47) { + return Config.IMAGE_TYPE_PNG; + } + return Config.IMAGE_TYPE_UNKNOWN; + } + } diff --git a/src/main/java/customException/CommentExceptionConverter.java b/src/main/java/customException/CommentExceptionConverter.java new file mode 100644 index 000000000..36eaa46b3 --- /dev/null +++ b/src/main/java/customException/CommentExceptionConverter.java @@ -0,0 +1,20 @@ +package customException; + +import common.Config; + +public class CommentExceptionConverter { + public static WebException noPostId() { + return new WebException( + WebException.HTTPStatus.MOVED_TEMPORALLY, + "댓글 작성 페이지에 대한 잘못된 접근입니다.", + Config.MAIN_PAGE_PATH + ); + } + + public static WebException badContentComment(){ + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "댓글 내용이 비어있습니다." + ); + } +} diff --git a/src/main/java/customException/DBExceptionConverter.java b/src/main/java/customException/DBExceptionConverter.java index 0c136063c..4e73a6254 100644 --- a/src/main/java/customException/DBExceptionConverter.java +++ b/src/main/java/customException/DBExceptionConverter.java @@ -22,6 +22,13 @@ public static WebException failToFindUser() { ); } + public static WebException failToUpdatePost(){ + return new WebException( + WebException.HTTPStatus.INTERNAL_SERVER_ERROR, + "포스트의 좋아요를 업데이트 하는 것에 실패했습니다." + ); + } + public static WebException failToAddPost() { return new WebException( @@ -57,4 +64,11 @@ public static WebException failToFindComment() { "댓글 데이터를 DB에서 탐색하는 과정에서 에러가 발생했습니다." ); } + + public static WebException notAppliedImageType() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "png 파일과 jpeg 파일만 지원합니다." + ); + } } diff --git a/src/main/java/customException/PostExceptionConverter.java b/src/main/java/customException/PostExceptionConverter.java index e5b02ba41..35246997e 100644 --- a/src/main/java/customException/PostExceptionConverter.java +++ b/src/main/java/customException/PostExceptionConverter.java @@ -14,4 +14,18 @@ public static WebException badContentPost() { "포스트의 내용이 존재하지 않습니다." ); } + + public static WebException badFileContentPost() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "포스트의 이미지가 존재하지 않습니다." + ); + } + + public static WebException badPostId() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "포스트 id가 형식에 맞지 않습니다." + ); + } } diff --git a/src/main/java/customException/UserExceptionConverter.java b/src/main/java/customException/UserExceptionConverter.java index 2a74197a0..08c451e12 100644 --- a/src/main/java/customException/UserExceptionConverter.java +++ b/src/main/java/customException/UserExceptionConverter.java @@ -1,13 +1,20 @@ package customException; public class UserExceptionConverter { - public static WebException conflictUser() { + public static WebException conflictUserID() { return new WebException( WebException.HTTPStatus.CONFLICT, "해당 아이디를 사용하는 회원이 존재합니다." ); } + public static WebException conflictUserName() { + return new WebException( + WebException.HTTPStatus.CONFLICT, + "해당 이름을 사용하는 회원이 존재합니다." + ); + } + public static WebException needUserId() { return new WebException( WebException.HTTPStatus.BAD_REQUEST, @@ -15,6 +22,34 @@ public static WebException needUserId() { ); } + public static WebException tooShortUserId() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "사용자 아이디는 4자 이상이어야 합니다." + ); + } + + public static WebException tooShortUserName() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "사용자명은 4자 이상이어야 합니다." + ); + } + + public static WebException tooShortUserPassword() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "비밀번호는 4자 이상이어야 합니다." + ); + } + + public static WebException needUserName() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "사용자 이름을 요청에 포함해야 합니다." + ); + } + public static WebException needUserData() { return new WebException( WebException.HTTPStatus.BAD_REQUEST, @@ -32,7 +67,7 @@ public static WebException notFoundUser() { public static WebException unAuthorized() { return new WebException( WebException.HTTPStatus.UNAUTHORIZED, - "로그인에 실패했습니다." + "비밀번호가 틀렸습니다." ); } @@ -43,4 +78,19 @@ public static WebException needToLogin() { "/login" ); } + + + public static WebException passwordNotMatch() { + return new WebException( + WebException.HTTPStatus.BAD_REQUEST, + "비밀번호가 일치하지 않습니다." + ); + } + + public static WebException failedUserUpdate() { + return new WebException( + WebException.HTTPStatus.INTERNAL_SERVER_ERROR, + "DB 내 사용자 데이터를 업데이트하는 것을 실패했습니다." + ); + } } diff --git a/src/main/java/db/Database.java b/src/main/java/db/Database.java index 4687c7867..80350f46a 100644 --- a/src/main/java/db/Database.java +++ b/src/main/java/db/Database.java @@ -25,8 +25,8 @@ public static void init(){ stmt.execute("CREATE TABLE users (\n" + " user_id VARCHAR(50) PRIMARY KEY,\n" + " password VARCHAR(255) NOT NULL,\n" + - " name VARCHAR(50) NOT NULL,\n" + - " email VARCHAR(100) NOT NULL\n" + + " name VARCHAR(50) NOT NULL UNIQUE,\n" + + " image_path VARCHAR(255) NOT NULL\n" + ");\n"); stmt.execute("CREATE TABLE posts (\n" + @@ -55,13 +55,13 @@ public static void init(){ public static void addUser(User user) { try { - String sql = "INSERT INTO users(user_id, password, name, email) VALUES (?, ?, ?, ?)"; + String sql = "INSERT INTO users(user_id, password, name, image_path) VALUES (?, ?, ?, ?)"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, user.getUserId()); pstmt.setString(2, user.getPassword()); pstmt.setString(3, user.getName()); - pstmt.setString(4, user.getEmail()); + pstmt.setString(4, user.getImagePath()); pstmt.executeUpdate(); } @@ -87,7 +87,7 @@ public static User findUserById(String userId) { result.getString("user_id"), result.getString("password"), result.getString("name"), - result.getString("email") + result.getString("image_path") ); } catch (SQLException e){ @@ -96,6 +96,31 @@ public static User findUserById(String userId) { } } + public static User findUserByName(String name) { + try { + if (name == null || name.isBlank()) throw UserExceptionConverter.needUserName(); + String sql = "SELECT * FROM users WHERE name = (?)"; + PreparedStatement pstmt = conn.prepareStatement(sql); + pstmt.setString(1, name); + + ResultSet result = pstmt.executeQuery(); + if (!result.next()) { + return null; + } + + return new User( + result.getString("user_id"), + result.getString("password"), + result.getString("name"), + result.getString("image_path") + ); + } + catch (SQLException e){ + logger.error(e.getMessage()); + throw DBExceptionConverter.failToFindUser(); + } + } + public static Collection findAllUser() { try { Collection users = new ArrayList<>(); @@ -107,7 +132,7 @@ public static Collection findAllUser() { users.add(new User(result.getString("user_id"), result.getString("password"), result.getString("name"), - result.getString("email"))); + result.getString("image_path"))); } return users; } @@ -121,7 +146,7 @@ public static Collection findAllUser() { public static void addPost(Post post) { try { - String imagePath = ImageManager.saveImage(post.image()); + String imagePath = ImageManager.saveImagePost(post.image()); String sql = "INSERT INTO posts(user_id, image_path, content, likes) VALUES (?, ?, ?, 0)"; @@ -138,6 +163,36 @@ public static void addPost(Post post) { } } + public static Post getPostByPostId(long postId){ + String sql = """ + SELECT post_id, user_id, image_path, content, likes + FROM posts + WHERE post_id = ? + """; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, postId); + ResultSet rs = pstmt.executeQuery(); + + if (!rs.next()) { + return null; + } + String imagePath = rs.getString("image_path"); + return new Post( + rs.getLong("post_id"), + ImageManager.readImagePost(imagePath), + rs.getString("user_id"), + rs.getString("content"), + rs.getInt("likes"), + imagePath + ); + + } catch (SQLException e) { + logger.error(e.getMessage()); + throw DBExceptionConverter.failToFindPost(); + } + } + public static Post getRecentPost(){ String sql = """ SELECT post_id, user_id, image_path, content, likes @@ -154,7 +209,7 @@ public static Post getRecentPost(){ String imagePath = rs.getString("image_path"); return new Post( rs.getLong("post_id"), - ImageManager.readImage(imagePath), + ImageManager.readImagePost(imagePath), rs.getString("user_id"), rs.getString("content"), rs.getInt("likes"), @@ -187,7 +242,7 @@ public static void addComment(Comment comment) { public static Collection findCommentsByPost(long postId) { try { Collection comments = new ArrayList<>(); - String sql = "SELECT * FROM comment WHERE post_id = (?)"; + String sql = "SELECT * FROM comments WHERE post_id = (?)"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setLong(1, postId); @@ -201,7 +256,108 @@ public static Collection findCommentsByPost(long postId) { return comments; } catch (SQLException e){ - throw DBExceptionConverter.failToAddUser(); + logger.error(e.getMessage()); + throw DBExceptionConverter.failToFindComment(); + } + } + + public static int updatePostLikes(long postId) { + String sql = """ + UPDATE posts + SET likes = likes + 1 + WHERE post_id = ? + """; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, postId); + + int updated = pstmt.executeUpdate(); + if (updated == 0) { + throw PostExceptionConverter.notFoundPost(); + } + + return getPostLikes(postId); + } + catch (SQLException e){ + throw DBExceptionConverter.failToUpdatePost(); + } + } + + + public static int getPostLikes(long postId) { + String sql = "SELECT likes FROM posts WHERE post_id = ?"; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, postId); + ResultSet rs = pstmt.executeQuery(); + rs.next(); + return rs.getInt("likes"); + } + catch (SQLException e){ + throw PostExceptionConverter.notFoundPost(); + } + } + + public static void updateUser(User user){ + String sql = "UPDATE users SET name = ?, password = ?, image_path = ? WHERE user_id = ?"; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setString(1, user.getName()); + pstmt.setString(2, user.getPassword()); + pstmt.setString(3, user.getImagePath()); + pstmt.setString(4, user.getUserId()); + pstmt.execute(); + } + catch (SQLException e){ + logger.error(e.getMessage()); + throw UserExceptionConverter.failedUserUpdate(); + } + } + + public static Long getPrevPostId(long postId) { + String sql = """ + SELECT post_id + FROM posts + WHERE post_id < ? + ORDER BY post_id DESC + LIMIT 1 + """; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, postId); + ResultSet rs = pstmt.executeQuery(); + + if (!rs.next()) { + return null; + } + + return rs.getLong("post_id"); + } catch (SQLException e) { + logger.error(e.getMessage()); + throw DBExceptionConverter.failToFindPost(); + } + } + public static Long getNextPostId(long postId) { + String sql = """ + SELECT post_id + FROM posts + WHERE post_id > ? + ORDER BY post_id ASC + LIMIT 1 + """; + + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, postId); + ResultSet rs = pstmt.executeQuery(); + + if (!rs.next()) { + return null; + } + + return rs.getLong("post_id"); + } catch (SQLException e) { + logger.error(e.getMessage()); + throw DBExceptionConverter.failToFindPost(); } } } diff --git a/src/main/java/db/ImageManager.java b/src/main/java/db/ImageManager.java index aa296cf3b..7d1abb434 100644 --- a/src/main/java/db/ImageManager.java +++ b/src/main/java/db/ImageManager.java @@ -17,17 +17,24 @@ public class ImageManager { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS"); - public static String saveImage(byte[] imageBytes) { + public static String saveImagePost(byte[] imageBytes) { + return saveImage(basePath, imageBytes); + } + + public static String saveImageProfile(byte[] imageBytes) { + return saveImage(baseProfilePath, imageBytes); + } + + private static String saveImage(String inputBasePath, byte[] imageBytes) { if (imageBytes == null || imageBytes.length == 0) { throw DBExceptionConverter.failToLoadImage(); } - File dir = new File(basePath); + File dir = new File(inputBasePath); if (!dir.exists()) { dir.mkdirs(); } - // 파일명 생성 String timestamp = LocalDateTime.now().format(FORMATTER); String fileName = timestamp + ".png"; @@ -42,8 +49,16 @@ public static String saveImage(byte[] imageBytes) { return fileName; } - public static byte[] readImage(String path){ - File file = new File(basePath+"/"+path); + public static byte[] readImagePost(String path){ + return readImage(basePath, path); + } + + public static byte[] readImageProfile(String path){ + return readImage(baseProfilePath, path); + } + + private static byte[] readImage(String inputBasePath, String path){ + File file = new File(inputBasePath+"/"+path); if (!file.exists() || !file.isFile()) { throw DBExceptionConverter.failToLoadImage(); } @@ -59,6 +74,17 @@ public static byte[] readImage(String path){ } catch (IOException e) { throw DBExceptionConverter.failToLoadImage(); } + return image; } + + public static boolean deleteProfileFile(String path) { + File file = new File(baseProfilePath, path); + + if (!file.exists() || !file.isFile()) { + return false; + } + + return file.delete(); + } } diff --git a/src/main/java/model/Comment.java b/src/main/java/model/Comment.java index 9d502954f..316c710d6 100644 --- a/src/main/java/model/Comment.java +++ b/src/main/java/model/Comment.java @@ -1,7 +1,7 @@ package model; public record Comment(long commentId, long postId, String authorId, String content) { - Comment(long postId, String userId, String content) { + public Comment(long postId, String userId, String content) { this(0, postId, userId, content); } } diff --git a/src/main/java/model/User.java b/src/main/java/model/User.java index ea04a0c2c..c579bbe54 100644 --- a/src/main/java/model/User.java +++ b/src/main/java/model/User.java @@ -7,22 +7,28 @@ public class User { private String userId; private String password; private String name; - private String email; - private byte[] image; private String imagePath; - public User(String userId, String password, String name, String email) { - if (userId == null || password == null || name == null || email == null - || userId.isBlank() || password.isBlank() || name.isBlank() || email.isBlank()) + public User(String userId, String password, String name) { + if (userId == null || password == null || name == null + || userId.isBlank() || password.isBlank() || name.isBlank()) throw UserExceptionConverter.needUserData(); - this.userId = userId; - this.password = password; - this.name = name; - this.email = email; - this.image = null; + this.userId = userId.trim(); + this.password = password.trim(); + this.name = name.trim(); this.imagePath = Config.IMAGE_DEFAULT_PROFILE_NAME; } + public User(String userId, String password, String name, String imagePath) { + if (userId == null || password == null || name == null + || userId.isBlank() || password.isBlank() || name.isBlank()) + throw UserExceptionConverter.needUserData(); + this.userId = userId.trim(); + this.password = password.trim(); + this.name = name.trim(); + this.imagePath = imagePath.trim(); + } + public String getUserId() { return userId; } @@ -35,10 +41,6 @@ public String getName() { return name; } - public String getEmail() { - return email; - } - public String getImagePath() { return this.imagePath; } @@ -47,8 +49,16 @@ public void setImagePath(String path) { this.imagePath = path; } + public void setName(String name) { + this.name = name; + } + + public void setPassword(String password){ + this.password = password; + } + @Override public String toString() { - return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]"; + return "User [userId=" + userId + ", password=" + password + ", name=" + name + "]"; } } diff --git a/src/main/java/webserver/RequestHandler.java b/src/main/java/webserver/RequestHandler.java index edf4ba1d1..0ff339543 100644 --- a/src/main/java/webserver/RequestHandler.java +++ b/src/main/java/webserver/RequestHandler.java @@ -25,7 +25,7 @@ public RequestHandler(Socket connectionSocket) { } public static void addTestUser(){ - Database.addUser(new User("tttt", "1234", "현지", "t@n.c")); + Database.addUser(new User("tttt", "1234", "백엔드 현지")); } public void run() { diff --git a/src/main/java/webserver/http/Response.java b/src/main/java/webserver/http/Response.java index 4ac434ae4..5afd767ff 100644 --- a/src/main/java/webserver/http/Response.java +++ b/src/main/java/webserver/http/Response.java @@ -13,6 +13,7 @@ public enum ContentType { HTML("text/html;charset=utf-8"), CSS("text/css"), JS("application/javascript"), + JSON("application/json"), PNG("image/png"), JPG("image/png"), JPEG("image/jpeg"), diff --git a/src/main/java/webserver/parse/DTO/CommentViewer.java b/src/main/java/webserver/parse/DTO/CommentViewer.java new file mode 100644 index 000000000..e5372262f --- /dev/null +++ b/src/main/java/webserver/parse/DTO/CommentViewer.java @@ -0,0 +1,4 @@ +package webserver.parse.DTO; + +public record CommentViewer(String content, String authorName, String authorImagePath) { +} diff --git a/src/main/java/webserver/parse/DTO/CursorViewer.java b/src/main/java/webserver/parse/DTO/CursorViewer.java new file mode 100644 index 000000000..7b876be47 --- /dev/null +++ b/src/main/java/webserver/parse/DTO/CursorViewer.java @@ -0,0 +1,4 @@ +package webserver.parse.DTO; + +public record CursorViewer (String ablePrev, String ableNext, String prev, String next){ +} diff --git a/src/main/java/webserver/parse/DTO/PostViewer.java b/src/main/java/webserver/parse/DTO/PostViewer.java index 2b8934c8d..86456a6d1 100644 --- a/src/main/java/webserver/parse/DTO/PostViewer.java +++ b/src/main/java/webserver/parse/DTO/PostViewer.java @@ -3,23 +3,17 @@ import model.Post; import model.User; -public class PostViewer { - String authorName; - String authorImagePath; - String imagePath; - int likes; - int commentNum; - String content; - long postId; +public record PostViewer(String authorName, String authorImagePath, String imagePath, int likes, int commentNum, String content, long postId) { public PostViewer(Post post, User user, int commentNum){ - this.authorImagePath = user.getImagePath(); - this.authorName = user.getName(); - this.imagePath = post.postImagePath(); - this.likes = post.likes(); - this.commentNum = commentNum; - this.content = post.content(); - this.postId = post.postId(); + this(user.getName(), + user.getImagePath(), + post.postImagePath(), + post.likes(), + commentNum, + post.content(), + post.postId() + ); } @Override diff --git a/src/main/java/webserver/parse/PageStruct.java b/src/main/java/webserver/parse/PageStruct.java index 473638343..2c1dd0143 100644 --- a/src/main/java/webserver/parse/PageStruct.java +++ b/src/main/java/webserver/parse/PageStruct.java @@ -26,7 +26,11 @@ public void setState(String path, boolean isLogin){ private void init(){ headerContent.setPage(false, PageReplacer.PageType.DEFAULT, Config.PAGE_HEADER_NOT_LOGIN); headerContent.setPage(true, PageReplacer.PageType.DEFAULT, Config.PAGE_HEADER_LOGIN); - headerContent.setPage(false, PageReplacer.PageType.ARTICLE, Config.PAGE_HEADER_NOT_LOGIN); + headerContent.setPage(false, PageReplacer.PageType.DEFAULT, Config.PAGE_HEADER_NOT_LOGIN); + headerContent.setPage(true, PageReplacer.PageType.MAIN, Config.PAGE_HEADER_LOGIN); + headerContent.setPage(false, PageReplacer.PageType.MAIN, Config.PAGE_HEADER_NOT_LOGIN); headerContent.setPage(true,PageReplacer.PageType.ARTICLE, Config.PAGE_HEADER_LOGIN); + headerContent.setPage(true,PageReplacer.PageType.COMMENT, Config.PAGE_HEADER_LOGIN); + headerContent.setPage(true,PageReplacer.PageType.MY_PAGE, Config.PAGE_HEADER_LOGIN); } } diff --git a/src/main/java/webserver/parse/RepeatDataReplacer.java b/src/main/java/webserver/parse/RepeatDataReplacer.java new file mode 100644 index 000000000..d53eec7e4 --- /dev/null +++ b/src/main/java/webserver/parse/RepeatDataReplacer.java @@ -0,0 +1,49 @@ +package webserver.parse; + +import common.Config; +import common.Utils; + +import java.util.Collection; + +public class RepeatDataReplacer { + String replacerName; + String format; + String noDataFormat; + + public RepeatDataReplacer(String replacerName, String format, String noDataFormat) { + this.replacerName = replacerName; + this.format = format; + this.noDataFormat = noDataFormat; + } + + public String repeatReplace(Collection objects, String template){ + StringBuilder dataInputs = new StringBuilder(); + StringBuilder tns; + StringBuilder result = new StringBuilder(template); + DataReplacer dataReplacer = new DataReplacer(replacerName); + for(Object object: objects){ + tns = new StringBuilder(format); + Utils.replaceAll(tns, Config.REPEAT_FORMAT_PLACEHOLDER, replacerName); + dataInputs.append(dataReplacer.replace(object, tns.toString())); + } + Utils.replaceAll(result, "{{" + this.replacerName + "}}", !objects.isEmpty() ?dataInputs.toString():noDataFormat); + return result.toString(); + } + + public String repeatReplace(Collection objects, String template, int count){ + StringBuilder dataInputs = new StringBuilder(); + StringBuilder tns; + StringBuilder result = new StringBuilder(template); + DataReplacer dataReplacer = new DataReplacer(replacerName); + int i = 0; + for(Object object: objects){ + tns = new StringBuilder(format); + Utils.replaceAll(tns, Config.REPEAT_FORMAT_PLACEHOLDER, replacerName); + dataInputs.append(dataReplacer.replace(object, tns.toString())); + i++; + if(i >= count){ break; } + } + Utils.replaceAll(result, "{{" + this.replacerName + "}}", !objects.isEmpty() ?dataInputs.toString():noDataFormat); + return result.toString(); + } +} diff --git a/src/main/java/webserver/process/CommentProcessor.java b/src/main/java/webserver/process/CommentProcessor.java new file mode 100644 index 000000000..58606f796 --- /dev/null +++ b/src/main/java/webserver/process/CommentProcessor.java @@ -0,0 +1,36 @@ +package webserver.process; + +import common.Config; +import common.Utils; +import customException.CommentExceptionConverter; +import customException.UserExceptionConverter; +import customException.WebException; +import db.Database; +import model.Comment; +import model.User; +import webserver.http.Request; +import webserver.http.RequestBody; +import webserver.http.Response; + +import java.util.Optional; + +public class CommentProcessor { + public Response createComment(Request request, User user) { + + String[] pathSplit = request.path.split("/"); + if (request.bodyParam.getOrDefault("content", null) == null) { + throw CommentExceptionConverter.badContentComment(); + } + try { + long postId = Long.parseLong(pathSplit[pathSplit.length - 1]); + Database.addComment(new Comment(postId, user.getUserId(), request.bodyParam.get("content").toString())); + return new Response( + WebException.HTTPStatus.CREATED, + null, + Response.ContentType.PLAIN_TEXT + ); + } catch (NumberFormatException e) { + throw CommentExceptionConverter.noPostId(); + } + } +} diff --git a/src/main/java/webserver/process/PostProcessor.java b/src/main/java/webserver/process/PostProcessor.java new file mode 100644 index 000000000..3bff001c4 --- /dev/null +++ b/src/main/java/webserver/process/PostProcessor.java @@ -0,0 +1,25 @@ +package webserver.process; + +import customException.PostExceptionConverter; +import customException.WebException; +import db.Database; +import model.Post; +import webserver.http.Request; +import webserver.http.Response; + +public class PostProcessor { + public Response addLikeToPost(Request request) { + String[] pathSplit = request.path.split("/"); + try { + long postId = Long.parseLong(pathSplit[pathSplit.length - 1]); + int likes = Database.updatePostLikes(postId); + return new Response( + WebException.HTTPStatus.OK, + ("{\"likes\":" + likes + "}").getBytes(), + Response.ContentType.JSON + ); + } catch (NumberFormatException e) { + throw PostExceptionConverter.badPostId(); + } + } +} diff --git a/src/main/java/webserver/process/Processor.java b/src/main/java/webserver/process/Processor.java index 5f16caf83..e4990b067 100644 --- a/src/main/java/webserver/process/Processor.java +++ b/src/main/java/webserver/process/Processor.java @@ -1,31 +1,44 @@ package webserver.process; import common.Config; -import customException.PostExceptionConverter; -import customException.WebException; -import customException.WebStatusConverter; +import common.Utils; +import customException.*; import db.Database; +import db.ImageManager; +import model.Comment; import model.Post; import model.User; import webserver.http.Request; import webserver.http.RequestBody; import webserver.http.Response; +import webserver.parse.DTO.CommentViewer; +import webserver.parse.DTO.CursorViewer; import webserver.parse.DTO.PostViewer; import webserver.parse.PageReplacer; import webserver.parse.DataReplacer; import webserver.parse.PageStruct; +import webserver.parse.RepeatDataReplacer; import webserver.route.Router; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collection; + +import static webserver.process.StaticFileProcessor.checkImageType; public class Processor { private static final Router router = new Router(); private static final UserProcessor userProcessor = new UserProcessor(); + private static final CommentProcessor commentProcessor = new CommentProcessor(); + private static final PostProcessor postProcessor = new PostProcessor(); private static final DataReplacer pageReplacer = new DataReplacer("page"); private static final DataReplacer userReplacer = new DataReplacer("user"); private static final DataReplacer postReplacer = new DataReplacer("post"); + private static final DataReplacer cursorReplacer = new DataReplacer("cursor"); private static final PageStruct pageStruct = new PageStruct(); + private static final RepeatDataReplacer commentRepeatReplacer = new RepeatDataReplacer("comment", Config.COMMENT_REPEAT_FORMAT, Config.NO_COMMENT); + //private static final PageReplacer pageReplacer = new PageReplacer(); public Processor() { @@ -42,8 +55,8 @@ public void init() { router.register(new Request(Request.Method.GET, "/login"), (request) -> { request.path = Config.LOGIN_PAGE_PATH; - if(userProcessor.getUser(request) != null){ - request.path = Config.DEFAULT_PAGE_PATH; + if (userProcessor.getUser(request) != null) { + request.path = Config.MAIN_PAGE_PATH; } return process(request); } @@ -60,7 +73,7 @@ public void init() { }); router.register(new Request(Request.Method.GET, "/"), request -> { - request.path = Config.DEFAULT_PAGE_PATH; + request.path = Config.MAIN_PAGE_PATH; return process(request); }); @@ -87,17 +100,69 @@ public void init() { return response; }); + + router.register(new Request(Request.Method.POST, "/user/update"), request -> { + userProcessor.updateUser(request); + return new Response(WebException.HTTPStatus.OK, null, Response.ContentType.HTML); + }); + router.register(new Request(Request.Method.POST, "/post/create"), request -> { User user = userProcessor.getUserOrException(request); - if(request.bodyParam.getOrDefault("content", null)==null){ - throw PostExceptionConverter.badContentPost(); + if (request.bodyParam.get("image") == null) { + throw PostExceptionConverter.badFileContentPost(); } - Post post = new Post(request.bodyParam.getOrDefault("image", new RequestBody("")).getContent(), + checkImageType(request.bodyParam.get("image").getContent()); + Post post = new Post(request.bodyParam.get("image").getContent(), user.getUserId(), - request.bodyParam.get("content").toString()); + request.bodyParam.getOrDefault("content", new RequestBody("")).toString()); Database.addPost(post); return new Response(WebException.HTTPStatus.OK, null, Response.ContentType.PLAIN_TEXT); }); + + router.register(new Request(Request.Method.GET, "/image/profile"), request -> { + String[] pathSplit = request.path.split("/"); + byte[] image = ImageManager.readImageProfile(pathSplit[pathSplit.length - 1]); + return new Response(WebException.HTTPStatus.OK, image, Response.contentType(request.path)); + }); + + router.register(new Request(Request.Method.GET, "/image"), request -> { + String[] pathSplit = request.path.split("/"); + byte[] image = ImageManager.readImagePost(pathSplit[pathSplit.length - 1]); + return new Response(WebException.HTTPStatus.OK, image, Response.contentType(request.path)); + }); + + + router.register(new Request(Request.Method.POST, "/post/like"), postProcessor::addLikeToPost); + + router.register(new Request(Request.Method.GET, "/comment"), request -> { + String[] pathSplit = request.path.split("/"); + try { + request.path = Config.COMMENT_PAGE_PATH; + request.queryParam.put(Config.POST_ID_QUERY_NAME, pathSplit[pathSplit.length - 1]); + return process(request); + } catch (NumberFormatException e) { + throw CommentExceptionConverter.noPostId(); + } + }); + + router.register(new Request(Request.Method.POST, "/comment"), request -> { + User user = userProcessor.getUserOrException(request); + return commentProcessor.createComment(request, user); + }); + + router.register(new Request(Request.Method.GET, "/post"), request -> { + String[] pathSplit = request.path.split("/"); + try { + request.path = Config.MAIN_PAGE_PATH; + if(Database.getPostByPostId(Long.parseLong(pathSplit[pathSplit.length - 1])) != null) + request.queryParam.put(Config.POST_ID_QUERY_NAME, pathSplit[pathSplit.length - 1]); + + return process(request); + } + catch (NumberFormatException e) { + throw PostExceptionConverter.badPostId(); + } + }); } public Response process(Request simpleReq) { @@ -111,12 +176,31 @@ public Response process(Request simpleReq) { if (body != null) { pageStruct.setState(simpleReq.path, user != null); String template = pageReplacer.replace(pageStruct, new String(body)); - template = userReplacer.replace(user, template); - PostViewer postViewer = getPostViewer(simpleReq); - body = postReplacer.replace(postViewer, template).getBytes(); - if(postViewer == null && Router.needPostData(simpleReq.path)){ - body = getNoPostExceptionPage(simpleReq, user); + if (Router.needPostData(simpleReq.path)) { + PostViewer postViewer = getPostViewer(simpleReq); + if(postViewer == null){ + template = new String(getNoPostExceptionPage(simpleReq, user)); + } + else if(Router.needCommentData(simpleReq.path)){ + Collection comments = getCommentViewers(postViewer); + String queryP = simpleReq.queryParam.get("expanded"); + boolean expanded = queryP != null && queryP.equals("true"); + + StringBuilder sb = new StringBuilder(template); + Utils.replaceAll(sb, "{{expand_comment_btn}}", + (!expanded && postViewer.commentNum()>Config.DEFAULT_COMMENT_COUNT)?Config.COMMENT_WANT_TO_SEE_MORE:""); + template = sb.toString(); + if(!expanded) + template = commentRepeatReplacer.repeatReplace(comments, template, Config.DEFAULT_COMMENT_COUNT); + else + template = commentRepeatReplacer.repeatReplace(comments, template); + + template = cursorReplacer.replace(getCursorViewer(postViewer.postId()), template); + } + template = postReplacer.replace(postViewer, template); } + template = userReplacer.replace(user, template); + body = template.getBytes(StandardCharsets.UTF_8); response = new Response(WebException.HTTPStatus.OK, body, Response.contentType(simpleReq.path)); } @@ -127,20 +211,41 @@ public Response process(Request simpleReq) { return response; } - private byte[] getNoPostExceptionPage(Request request, User user){ + private byte[] getNoPostExceptionPage(Request request, User user) { request.path = Config.NOPOST_PAGE_PATH; byte[] body = StaticFileProcessor.processReq(request); - if(body == null) throw WebStatusConverter.cannotFindNoPostPage(); + if (body == null) throw WebStatusConverter.cannotFindNoPostPage(); String template = pageReplacer.replace(pageStruct, new String(body)); return userReplacer.replace(user, template).getBytes(StandardCharsets.UTF_8); } - private PostViewer getPostViewer(Request request){ - Post post = Database.getRecentPost(); - if(post == null) return null; + private PostViewer getPostViewer(Request request) { + Post post = null; + if (request.queryParam.get(Config.POST_ID_QUERY_NAME) != null) { + long postId = Long.parseLong(request.queryParam.get(Config.POST_ID_QUERY_NAME)); + post = Database.getPostByPostId(postId); + } else { + post = Database.getRecentPost(); + } + if (post == null) return null; User author = Database.findUserById(post.userId()); - if(author == null) return null; - return new PostViewer(post, author, 0); + if (author == null) return null; + return new PostViewer(post, author, Database.findCommentsByPost(post.postId()).size()); + } + + private Collection getCommentViewers(PostViewer postViewer){ + Collection comments = Database.findCommentsByPost(postViewer.postId()); + return comments.stream().map(comment -> { + User user = Database.findUserById(comment.authorId()); + if(user == null) throw UserExceptionConverter.notFoundUser(); + return new CommentViewer(comment.content(), user.getName(), user.getImagePath()); + }).toList(); + } + + private CursorViewer getCursorViewer(long postId){ + Long prevId = Database.getPrevPostId(postId); + Long nextId = Database.getNextPostId(postId); + return new CursorViewer(prevId==null?"disabled":"", nextId==null?"disabled":"", prevId==null?"":String.valueOf(prevId), nextId==null?"":String.valueOf(nextId)); } } diff --git a/src/main/java/webserver/process/StaticFileProcessor.java b/src/main/java/webserver/process/StaticFileProcessor.java index 5a0f93a21..693471a78 100644 --- a/src/main/java/webserver/process/StaticFileProcessor.java +++ b/src/main/java/webserver/process/StaticFileProcessor.java @@ -1,5 +1,8 @@ package webserver.process; +import common.Config; +import common.Utils; +import customException.DBExceptionConverter; import customException.WebStatusConverter; import webserver.http.Request; @@ -12,12 +15,17 @@ public class StaticFileProcessor { public static byte[] processReq(Request simpleReq) { byte[] result = "".getBytes(); - if (simpleReq != null && simpleReq.method== Request.Method.GET) { + if (simpleReq != null && simpleReq.method == Request.Method.GET) { result = getStaticSources(simpleReq.path); } return result; } + static public void checkImageType(byte[] data) { + String type = Utils.detectImageType(data); + if(type.compareTo(Config.IMAGE_TYPE_UNKNOWN) == 0) throw DBExceptionConverter.notAppliedImageType(); + } + static public void setBasePath(String basePath) { StaticFileProcessor.basePath = basePath; } diff --git a/src/main/java/webserver/process/UserProcessor.java b/src/main/java/webserver/process/UserProcessor.java index 9074597a0..767167150 100644 --- a/src/main/java/webserver/process/UserProcessor.java +++ b/src/main/java/webserver/process/UserProcessor.java @@ -5,12 +5,15 @@ import common.Utils; import customException.UserExceptionConverter; import db.Database; +import db.ImageManager; import model.User; import webserver.http.Request; import webserver.http.RequestBody; import java.util.Optional; +import static webserver.process.StaticFileProcessor.checkImageType; + public class UserProcessor { public byte[] createUser(Request request) { User user = new User(Optional.ofNullable(request.bodyParam.get("userId")) @@ -20,13 +23,16 @@ public byte[] createUser(Request request) { .map(RequestBody::getContentString) .orElseThrow(UserExceptionConverter::needUserData), Optional.ofNullable(request.bodyParam.get("name")) - .map(RequestBody::getContentString) - .orElseThrow(UserExceptionConverter::needUserData), - Optional.ofNullable(request.bodyParam.get("email")) .map(RequestBody::getContentString) .orElseThrow(UserExceptionConverter::needUserData)); - if (Database.findUserById(user.getUserId()) != null) throw UserExceptionConverter.conflictUser(); + if (Database.findUserById(user.getUserId()) != null) throw UserExceptionConverter.conflictUserID(); + if (Database.findUserByName(user.getName()) != null) throw UserExceptionConverter.conflictUserName(); + + if (user.getUserId().length() < Config.MIN_USER_DATA_LENGTH) throw UserExceptionConverter.tooShortUserId(); + if (user.getName().length() < Config.MIN_USER_DATA_LENGTH) throw UserExceptionConverter.tooShortUserName(); + if (user.getPassword().length() < Config.MIN_USER_DATA_LENGTH) + throw UserExceptionConverter.tooShortUserPassword(); Database.addUser(user); return user.toString().getBytes(); @@ -67,5 +73,54 @@ public void deleteUserSession(Request request) { Auth.deleteSession(SID); } + + public void updateUser(Request request) { + User user = getUserOrException(request); + if (request.bodyParam.get("userName") != null && !request.bodyParam.get("userName").toString().isBlank()) { + String userName = request.bodyParam.get("userName").getContentString().trim(); + if (userName.length() < Config.MIN_USER_DATA_LENGTH) throw UserExceptionConverter.tooShortUserName(); + user.setName(userName); + } + + if (request.bodyParam.get("password") != null && request.bodyParam.get("checkPassword") != null + && !request.bodyParam.get("password").toString().isBlank()) { + String password = request.bodyParam.get("password").getContentString().trim(); + String checkPassword = request.bodyParam.get("checkPassword").getContentString().trim(); + if(password.compareTo(checkPassword) != 0) throw UserExceptionConverter.passwordNotMatch(); + if (password.length() < Config.MIN_USER_DATA_LENGTH) + throw UserExceptionConverter.tooShortUserPassword(); + user.setPassword(password); + } + + RequestBody image = request.bodyParam.get("profileImage"); + RequestBody imagePath = request.bodyParam.get("previewImg"); + if(image != null) { + checkImageType(image.getContent()); + switchProfileImage(image.getContent(), user); + } + else if(imagePath != null){ + try { + String raw = imagePath.getContentString(); + String path = new java.net.URL(raw).getPath(); + + if (Config.IMAGE_DEFAULT_PROFILE_API.equals(path)) { + switchProfileImage( + ImageManager.readImageProfile(Config.IMAGE_DEFAULT_PROFILE_NAME), + user + ); + } + } catch (Exception e) { + } + } + Database.updateUser(user); + } + + private void switchProfileImage(byte[] image, User user) { + if(!user.getImagePath().endsWith(Config.IMAGE_DEFAULT_PROFILE_NAME)){ + ImageManager.deleteProfileFile(user.getImagePath()); + } + String path = ImageManager.saveImageProfile(image); + user.setImagePath(path); + } } diff --git a/src/main/java/webserver/route/Router.java b/src/main/java/webserver/route/Router.java index 8b76a9772..4cd330e08 100644 --- a/src/main/java/webserver/route/Router.java +++ b/src/main/java/webserver/route/Router.java @@ -12,14 +12,19 @@ public class Router { private final RouterNode root = new RouterNode(); - public static boolean needLogin(String path){ + public static boolean needLogin(String path) { return (path.compareTo(Config.MY_PAGE_PAGE_PATH) == 0) || (path.compareTo("/mypage") == 0) - || (path.compareTo("/write") ==0)|| (path.compareTo(Config.ARTICLE_PAGE_PATH)==0); + || (path.compareTo("/write") == 0) || (path.compareTo(Config.ARTICLE_PAGE_PATH) == 0) + || (path.startsWith(Config.COMMENT_PAGE_PATH_PREFIX)); } - public static boolean needPostData(String path){ + public static boolean needPostData(String path) { return (path.compareTo(Config.DEFAULT_PAGE_PATH) == 0) || (path.compareTo("/") == 0) - || (path.compareTo(Config.MAIN_PAGE_PATH)==0); + || (path.compareTo(Config.MAIN_PAGE_PATH) == 0) || (path.startsWith(Config.COMMENT_PAGE_PATH_PREFIX)); + } + + public static boolean needCommentData(String path){ + return path.compareTo(Config.DEFAULT_PAGE_PATH) == 0 || path.compareTo(Config.MAIN_PAGE_PATH) == 0; } public void register(Request req, Function func) { @@ -41,8 +46,9 @@ public Response route(Request req) { for (String part : parts) { if (part.isEmpty()) continue; - curNode = curNode.children.get(part); - if (curNode == null) throw WebStatusConverter.notAllowedPath(); + RouterNode tnsNode = curNode.children.get(part); + if (tnsNode == null) break; + curNode = tnsNode; } Function func = curNode.funcs.get(req.method); diff --git a/src/main/resources/static/article/index.html b/src/main/resources/static/article/index.html index cff452d1b..aeff3cc8c 100644 --- a/src/main/resources/static/article/index.html +++ b/src/main/resources/static/article/index.html @@ -3,13 +3,15 @@ - - + + + +
    - + {{page.header}}
    @@ -37,6 +39,6 @@

    게시글 작성

    - + diff --git a/src/main/resources/static/comment/index.html b/src/main/resources/static/comment/index.html index 35df2b252..a4da6bb6c 100644 --- a/src/main/resources/static/comment/index.html +++ b/src/main/resources/static/comment/index.html @@ -3,40 +3,37 @@ - - + + + +
    - - + + {{page.header}}

    댓글 작성

    -
    +

    내용

    @@ -44,4 +41,6 @@

    댓글 작성

    + + diff --git a/src/main/resources/static/exception/nopost.html b/src/main/resources/static/exception/nopost.html index 7ad0ac37e..5e1c7c9dc 100644 --- a/src/main/resources/static/exception/nopost.html +++ b/src/main/resources/static/exception/nopost.html @@ -3,6 +3,7 @@ + diff --git a/src/main/resources/static/global.css b/src/main/resources/static/global.css index 233d795ba..4640e2fee 100644 --- a/src/main/resources/static/global.css +++ b/src/main/resources/static/global.css @@ -151,6 +151,14 @@ height: 200px; border-radius: 9999px; background: #d9d9d9; + object-fit: cover; + overflow: hidden; +} + +img { + width: 100%; + height: 100%; + object-fit: cover; } .profile_container { diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 3a04d4d70..b4204dc19 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -1,150 +1,89 @@ - - - - - - - - -
    -
    - + + + + + + + + + +
    +
    + {{page.header}} -
    -
    +
    +
    - - -
    - - -
    -

    - {{post.content}} - 우리는 시스템 아키텍처에 대한 일관성 있는 접근이 필요하며, 필요한 - 모든 측면은 이미 개별적으로 인식되고 있다고 생각합니다. 즉, 응답이 - 잘 되고, 탄력적이며 유연하고 메시지 기반으로 동작하는 시스템 입니다. - 우리는 이것을 리액티브 시스템(Reactive Systems)라고 부릅니다. - 리액티브 시스템으로 구축된 시스템은 보다 유연하고, 느슨한 결합을 - 갖고, 확장성 이 있습니다. 이로 인해 개발이 더 쉬워지고 변경 사항을 - 적용하기 쉬워집니다. 이 시스템은 장애 에 대해 더 강한 내성을 지니며, - 비록 장애가 발생 하더라도, 재난이 일어나기 보다는 간결한 방식으로 - 해결합니다. 리액티브 시스템은 높은 응답성을 가지며 사용자 에게 - 효과적인 상호적 피드백을 제공합니다. -

    -
    -
      -
    • -
      - -

      account

      -
      -

      - 군인 또는 군무원이 아닌 국민은 대한민국의 영역안에서는 중대한 - 군사상 기밀·초병·초소·유독음식물공급·포로·군용물에 관한 죄중 - 법률이 정한 경우와 비상계엄이 선포된 경우를 제외하고는 군사법원의 - 재판을 받지 아니한다. -

      -
    • -
    • -
      - -

      account

      -

      - 대통령의 임기연장 또는 중임변경을 위한 헌법개정은 그 헌법개정 제안 - 당시의 대통령에 대하여는 효력이 없다. 민주평화통일자문회의의 - 조직·직무범위 기타 필요한 사항은 법률로 정한다. +

      + {{post.content}}

      -
    • -
    • -
      - -

      account

      -
      -

      - 민주평화통일자문회의의 조직·직무범위 기타 필요한 사항은 법률로 - 정한다. -

      -
    • - - - - +
    +
      + {{comment}} + {{expand_comment_btn}}
    -
    - 글쓰기
    + 글쓰기 + - - + + + diff --git a/src/main/resources/static/login/index.html b/src/main/resources/static/login/index.html index d84d89f5d..afc777240 100644 --- a/src/main/resources/static/login/index.html +++ b/src/main/resources/static/login/index.html @@ -3,13 +3,14 @@ - - + + +
    - +
    - + diff --git a/src/main/resources/static/main.css b/src/main/resources/static/main.css index b6282394a..45d55ccae 100644 --- a/src/main/resources/static/main.css +++ b/src/main/resources/static/main.css @@ -148,4 +148,14 @@ .post__menu__personal{ vertical-align: center; align-items: center; +} +.nav__menu__item__btn.disabled { + color: #aaa; + cursor: not-allowed; + pointer-events: none; + opacity: 0.6; +} + +.nav__menu__item__btn.disabled img { + opacity: 0.4; } \ No newline at end of file diff --git a/src/main/resources/static/main/index.html b/src/main/resources/static/main/index.html index b5b91be02..b4204dc19 100644 --- a/src/main/resources/static/main/index.html +++ b/src/main/resources/static/main/index.html @@ -1,149 +1,89 @@ - - - - - - - - -
    -
    - - -
    -
    + + + + + + + + + +
    +
    + + {{page.header}} +
    +
    - - -
    -
      -
    • - -
    • -
    • + + +
      + -
    • -
    - -
    -

    - 우리는 시스템 아키텍처에 대한 일관성 있는 접근이 필요하며, 필요한 - 모든 측면은 이미 개별적으로 인식되고 있다고 생각합니다. 즉, 응답이 - 잘 되고, 탄력적이며 유연하고 메시지 기반으로 동작하는 시스템 입니다. - 우리는 이것을 리액티브 시스템(Reactive Systems)라고 부릅니다. - 리액티브 시스템으로 구축된 시스템은 보다 유연하고, 느슨한 결합을 - 갖고, 확장성 이 있습니다. 이로 인해 개발이 더 쉬워지고 변경 사항을 - 적용하기 쉬워집니다. 이 시스템은 장애 에 대해 더 강한 내성을 지니며, - 비록 장애가 발생 하더라도, 재난이 일어나기 보다는 간결한 방식으로 - 해결합니다. 리액티브 시스템은 높은 응답성을 가지며 사용자 에게 - 효과적인 상호적 피드백을 제공합니다. -

    -
    -
      -
    • -
      - -

      account

      -
      -

      - 군인 또는 군무원이 아닌 국민은 대한민국의 영역안에서는 중대한 - 군사상 기밀·초병·초소·유독음식물공급·포로·군용물에 관한 죄중 - 법률이 정한 경우와 비상계엄이 선포된 경우를 제외하고는 군사법원의 - 재판을 받지 아니한다. -

      -
    • -
    • -
      - -

      account

      -

      - 대통령의 임기연장 또는 중임변경을 위한 헌법개정은 그 헌법개정 제안 - 당시의 대통령에 대하여는 효력이 없다. 민주평화통일자문회의의 - 조직·직무범위 기타 필요한 사항은 법률로 정한다. +

      + {{post.content}}

      -
    • -
    • -
      - -

      account

      -
      -

      - 민주평화통일자문회의의 조직·직무범위 기타 필요한 사항은 법률로 - 정한다. -

      -
    • - - - - +
    +
      + {{comment}} + {{expand_comment_btn}}
    -
    - + 글쓰기 +
    + + + + diff --git a/src/main/resources/static/mypage/index.html b/src/main/resources/static/mypage/index.html index 46db19b33..d2c64d8d0 100644 --- a/src/main/resources/static/mypage/index.html +++ b/src/main/resources/static/mypage/index.html @@ -1,82 +1,85 @@ - - - - - - - -
    -
    - - -
    -
    + + + + + + + + + +
    +
    + + {{page.header}} +
    +

    마이페이지

    -
    -
    - -
    -
    -
    -
    수정
    -
    -
    -
    -
    삭제
    -
    -
    -
    -
    -
    -

    닉네임

    - -
    -
    -

    비밀번호

    - -
    -
    -

    비밀번호 확인

    - +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    삭제
    +
    +
    - - + type="submit" + > + 변경사항 저장 + +
    -
    - - +
    + + + diff --git a/src/main/resources/static/registration/index.html b/src/main/resources/static/registration/index.html index 73452adfc..6d1824247 100644 --- a/src/main/resources/static/registration/index.html +++ b/src/main/resources/static/registration/index.html @@ -3,13 +3,14 @@ - - + + +
    - +
    • 로그인 @@ -34,17 +35,6 @@

      회원가입

      id="userId" />
    -
    -

    이메일

    - -

    닉네임

    회원가입
    - + diff --git a/src/main/resources/static/script/alert.js b/src/main/resources/static/script/alert.js index f9b10c189..16e6e517d 100644 --- a/src/main/resources/static/script/alert.js +++ b/src/main/resources/static/script/alert.js @@ -2,8 +2,16 @@ async function formResponseProcessToMain(response) { await formResponseProcess(response, "./index.html") } +async function formResponseProcessToLogin(response) { + await formResponseProcess(response, "./login/index.html") +} + +async function formResponseProcessToRegistration(response) { + await formResponseProcess(response, "./index.html") +} + async function formResponseProcess(response, nextPath) { - if(await alertCall(response)) return; + if (await alertCall(response)) return; // 성공 if (response.status >= 200 && response.status < 300 && nextPath != null && nextPath.length > 0) { @@ -12,7 +20,7 @@ async function formResponseProcess(response, nextPath) { } async function getView(response) { - if(await alertCall(response)) return; + if (await alertCall(response)) return; const html = await response.text(); @@ -21,7 +29,7 @@ async function getView(response) { document.close(); } -async function alertCall(response){ +async function alertCall(response) { if (response.status >= 400 && response.status < 500) { const msg = await response.text(); alert("에러 발생: " + msg); @@ -30,6 +38,29 @@ async function alertCall(response){ return false; } +function addListenerAddComment() { + const btn = document.getElementById('create_comment_btn'); + + btn?.addEventListener('submit', async (e) => { + e.preventDefault(); + const postId = btn.dataset.postId; + if (!postId) return; + const content = document.querySelector("#content").value; + + const response = await fetch(`/comment/${postId}`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: new URLSearchParams({ + content + }) + }); + + await formResponseProcess(response, `/post/${postId}`); + }); +} + document.getElementById("login")?.addEventListener("submit", async (e) => { e.preventDefault(); // 기본 submit 막기 const userId = document.querySelector("#userId").value; @@ -45,6 +76,21 @@ document.getElementById("login")?.addEventListener("submit", async (e) => { password }) }); + if (response.status >= 400 && response.status < 500) { + const text = await response.text(); + if (text === '사용자 데이터가 존재하지 않습니다.') { + const goSignup = confirm( + '존재하지 않는 아이디입니다.\n회원 가입 하시겠습니까?' + ); + + if (goSignup) { + location.href = '/registration/index.html'; + } + return; + } + alert("에러 발생: " + text); + return; + } await formResponseProcessToMain(response); }); @@ -53,7 +99,6 @@ document.getElementById("registration")?.addEventListener("submit", async (e) => e.preventDefault(); // 기본 submit 막기 const userId = document.querySelector("#userId").value; const password = document.querySelector("#password").value; - const email = document.querySelector("#email").value; const name = document.querySelector("#name").value; const response = await fetch("/user/create", { @@ -64,22 +109,50 @@ document.getElementById("registration")?.addEventListener("submit", async (e) => body: new URLSearchParams({ userId, password, - email, name }) }); - await formResponseProcessToMain(response); + await formResponseProcessToLogin(response); }); -document.getElementById("link_to_mypage")?.addEventListener("click", async (e) => { +document.getElementById("user_update")?.addEventListener("submit", async (e) => { e.preventDefault(); // 기본 submit 막기 + const userName = document.querySelector("#update-name").value; + const password = document.querySelector("#update-password").value; + const checkPassword = document.querySelector("#update-check-password").value; + const imageInput = document.querySelector("#update-image"); + const previewImg = document.getElementById('user-profile-preview'); + const file = imageInput.files[0]; + + const formData = new FormData(); + formData.append("userName", userName); + formData.append("password", password); + formData.append("checkPassword", checkPassword); + formData.append("previewImg", previewImg.src); + + if (file) { + formData.append("profileImage", file); // 이미지 추가 + } - const response = await fetch("/mypage", { - method: "GET" + const response = await fetch("/user/update", { + method: "POST", + body: formData }); - await getView(response); + await formResponseProcessToMain(response); +}); + +document.querySelectorAll(".link_to_mypage").forEach(el => { + el.addEventListener("click", async (e) => { + e.preventDefault(); // 기본 submit 막기 + + const response = await fetch("/mypage", { + method: "GET" + }); + + await getView(response); + }) }); document.getElementById("logout-btn")?.addEventListener("click", async (e) => { @@ -93,10 +166,15 @@ document.getElementById("logout-btn")?.addEventListener("click", async (e) => { }); document.getElementById("post")?.addEventListener("submit", async (e) => { - e.preventDefault(); // 기본 submit 막기 - + e.preventDefault(); const form = e.target; - const formData = new FormData(form); + const formData = new FormData(); + formData.append("content", form.querySelector("#content").value); + + const fileInput = form.querySelector('input[name="image"]'); + if (fileInput.files.length > 0) { + formData.append("image", fileInput.files[0]); + } const response = await fetch("/post/create", { method: "POST", @@ -105,3 +183,5 @@ document.getElementById("post")?.addEventListener("submit", async (e) => { await formResponseProcessToMain(response); }); + +addListenerAddComment(); \ No newline at end of file diff --git a/src/main/resources/static/script/extraEvent.js b/src/main/resources/static/script/extraEvent.js new file mode 100644 index 000000000..caad2451d --- /dev/null +++ b/src/main/resources/static/script/extraEvent.js @@ -0,0 +1,52 @@ +function addListenerLikeAction(){ + const btn = document.getElementById('post__like__btn'); + if(btn == null) return; + btn.addEventListener('click', async () => { + const postId = btn.dataset.postId; + if (!postId) return; + + const response = await fetch(`/post/like/${postId}`, { + method: "POST" + }); + + if (!response.ok) return; + + const data = await response.json(); + + document.querySelector('.post__like__count').textContent = data.likes; + }); + +} + +function addListenerSaveTempImage(){ + const imageInput = document.getElementById('update-image'); + if(imageInput == null) return; + + imageInput.addEventListener('change', async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = () => { + document.getElementById('user-profile-preview').src = reader.result; + }; + reader.readAsDataURL(file); + }); +} + +function addListenerRemoveTempImage(){ + const removeBtn = document.getElementById('remove_profile_image'); + const imageInput = document.getElementById('update-image'); + const previewImg = document.getElementById('user-profile-preview'); + if(removeBtn == null || imageInput == null || previewImg == null) return; + + removeBtn.addEventListener('click', async (e) => { + imageInput.value = ""; + previewImg.src = "/image/profile/default.png"; + }); +} + + +addListenerLikeAction(); +addListenerSaveTempImage(); +addListenerRemoveTempImage(); \ No newline at end of file