Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fe4250d
Merge pull request #13 from kimhji/feat_extra1
kimhji Jan 15, 2026
11de429
feat[7단계]: post의 image, author image 불러오기 기능 구현
kimhji Jan 15, 2026
33fc8bb
feat[7단계]: post의 본문 바인딩, header의 프로필 사진 또한 이미지로 바인딩
kimhji Jan 15, 2026
cb35909
feat[7단계]: 회원가입 시 name의 중복 처리
kimhji Jan 15, 2026
46da4d5
feat[7단계]: 이메일 삭제
kimhji Jan 15, 2026
ccb8945
feat[7단계]: main 으로 가는 링크를 모두 /으로 가도록 병합. css 누락 수정
kimhji Jan 15, 2026
3715fb4
feat[7단계]: 좋아요 기능 연동
kimhji Jan 15, 2026
b7dd071
feat[7단계]: 필요없는 데이터 셋 연동 삭제
kimhji Jan 15, 2026
5f4a826
feat[7단계]: 댓글 작성 페이지 이동 구현
kimhji Jan 15, 2026
7ea36e8
feat[7단계]: 댓글 작성 페이지 헤더 추가 및 css 누락 해결
kimhji Jan 15, 2026
f9e2d31
feat[7단계]: 댓글 작성 기능 추가
kimhji Jan 15, 2026
b6955b5
feat[7단계]: comment 기능 분리
kimhji Jan 15, 2026
4688dbb
feat[7단계]: 디버깅용 콘솔 출력 삭제
kimhji Jan 15, 2026
9c42b04
feat[7단계]: 탐색창을 통한 url 변경 시에도 동일한 basepath를 가지도록 base 통일
kimhji Jan 15, 2026
07f4dfd
feat[7단계]: 탐색창을 통한 post 접근 시 해당하는 post가 없다면 기본 페이지 표시
kimhji Jan 15, 2026
8da6a56
feat[7단계]: 댓글 생성 시, 기존 포스트로 이동
kimhji Jan 15, 2026
6097f90
feat[7단계]: 댓글 표시 기능 추가
kimhji Jan 15, 2026
6b43f72
feat[7단계]: 회원가입 시 사용자 데이터의 최소 길이 체크
kimhji Jan 15, 2026
0e10e52
feat[7단계]: 로그인 시 없는 아이디의 경우 회원가입 유도 창 표시
kimhji Jan 15, 2026
e0760a2
feat[7단계]: 로그인 시 패스워드만 틀린 경우의 메세지 수정
kimhji Jan 15, 2026
5c898fe
feat[7단계]: 사용자 정보 업데이트 구현[이름, 패스워드]
kimhji Jan 15, 2026
a89bfcc
feat[7단계]: 사용자 정보 페이지 헤더 누락 수정
kimhji Jan 15, 2026
f6baab9
feat[7단계]: 사용자 정보로 이동할 수 있는 헤더 범위 증가
kimhji Jan 15, 2026
1630a9a
feat[7단계]: 사용자 이미지 임시 수정 추가
kimhji Jan 15, 2026
d33b8c7
feat[7단계]: 사용자 이미지 임시 삭제 추가
kimhji Jan 15, 2026
5b855c7
feat[7단계]: 사용자 이미지 업데이트 추가
kimhji Jan 15, 2026
fa7130a
feat[7단계]: 사용자 이미지 업데이트 DB 반영
kimhji Jan 15, 2026
eb36a18
feat[7단계]: 사용자 이미지 수정 시 기존에 보관하던 이미지 삭제
kimhji Jan 15, 2026
d5cb5bf
feat[7단계]: 사용자 이미지 삭제 에러 수정
kimhji Jan 15, 2026
b2d17e8
feat[7단계]: 사용자 이름 미리 인풋에 넣어놓기
kimhji Jan 15, 2026
de2def4
feat[7단계]: 포스트 생성 시 파일 여부만 체크
kimhji Jan 15, 2026
272a92e
feat[7단계]: 파일 입력 시 png와 JPEG 만 지원
kimhji Jan 15, 2026
ddf3704
feat[7단계]: 댓글 펼쳐보기 버튼 동적 처리 추가
kimhji Jan 15, 2026
0c6cd30
feat[7단계]: 댓글 펼쳐보기 동적 처리 추가
kimhji Jan 15, 2026
78aadaf
feat[7단계]: 페이지 이동 기능 추가
kimhji Jan 15, 2026
9a3b52d
feat[7단계]: 댓글 쓴 사람 연동 오류 수정
kimhji Jan 15, 2026
8813d88
refactor[7단계]: utils 중복 코드 분리
kimhji Jan 16, 2026
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
37 changes: 35 additions & 2 deletions src/main/java/common/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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 = "<div>" +
"<ul class=\"header__menu\">" +
" <li class=\"header__menu__item\">" +
" <img id=link_to_mypage class=\"post__account__img\" />" +
" <img class=\"post__account__img link_to_mypage\" src=\"/image/profile/{{user.imagePath}}\" />" +
" </li>" +
" <li class=\"header__menu__item\">" +
" <p id=link_to_mypage class=\"post__account__nickname\">안녕하세요, {{user.name}}님!</p>" +
" <p class=\"post__account__nickname link_to_mypage\">안녕하세요, {{user.name}}님!</p>" +
" </li>" +
"<li class=\"header__menu__item\">\n" +
"<a class=\"btn btn_contained btn_size_s\" href=\"/write\">글쓰기</a>" +
Expand All @@ -50,4 +67,20 @@ public class Config {
" <a class=\"btn btn_ghost btn_size_s\" href=\"/registration\">회원 가입</a>" +
" </li>" +
"</ul>";

public static final String COMMENT_REPEAT_FORMAT = "<li class=\"comment__item\">\n" +
" <div class=\"comment__item__user\">\n" +
" <img class=\"comment__item__user__img\" src=\"/image/profile/{{{{REPEAT}}.authorImagePath}}\"/>\n" +
" <p class=\"comment__item__user__nickname\">{{{{REPEAT}}.authorName}}</p>\n" +
" </div>\n" +
" <p class=\"comment__item__article\">\n" +
" {{{{REPEAT}}.content}}\n" +
" </p>\n" +
" </li>";

public static final String COMMENT_WANT_TO_SEE_MORE = "<a id=\"show-all-btn\" " +
"class=\"btn btn_ghost btn_size_m\" " +
"href=\"/post/{{post.postId}}?expanded=true\">" +
"모든 댓글 보기({{post.commentNum}}개)" +
"</a>";
}
47 changes: 29 additions & 18 deletions src/main/java/common/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -23,31 +20,26 @@ public static String getRestStr(String wholeStr, String splitParam, int idx) {
return sb.toString();
}

public static String parseMapToQueryString_RequestBody(Map<String, RequestBody> queryParam) {
public static <T> String parseGeneralMapToQueryString(Map<String, T> queryParam) {
StringBuilder sb = new StringBuilder();

boolean first = true;
for (Map.Entry<String, RequestBody> entry : queryParam.entrySet()) {
for (Map.Entry<String, T> 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<String, String> queryParam) {
StringBuilder sb = new StringBuilder();

boolean first = true;
for (Map.Entry<String, String> 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<String, RequestBody> queryParam) {
return parseGeneralMapToQueryString(queryParam);
}

public static String parseMapToQueryString(Map<String, String> queryParam) {
return parseGeneralMapToQueryString(queryParam);
}
public static void replaceAll(StringBuilder sb, String target, String replacement) {
int index;
Expand Down Expand Up @@ -120,4 +112,23 @@ public static List<byte[]> 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;
}

}
20 changes: 20 additions & 0 deletions src/main/java/customException/CommentExceptionConverter.java
Original file line number Diff line number Diff line change
@@ -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,
"댓글 내용이 비어있습니다."
);
}
}
14 changes: 14 additions & 0 deletions src/main/java/customException/DBExceptionConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -57,4 +64,11 @@ public static WebException failToFindComment() {
"댓글 데이터를 DB에서 탐색하는 과정에서 에러가 발생했습니다."
);
}

public static WebException notAppliedImageType() {
return new WebException(
WebException.HTTPStatus.BAD_REQUEST,
"png 파일과 jpeg 파일만 지원합니다."
);
}
}
14 changes: 14 additions & 0 deletions src/main/java/customException/PostExceptionConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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가 형식에 맞지 않습니다."
);
}
}
54 changes: 52 additions & 2 deletions src/main/java/customException/UserExceptionConverter.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
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,
"사용자 아이디를 요청에 포함해야 합니다."
);
}

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,
Expand All @@ -32,7 +67,7 @@ public static WebException notFoundUser() {
public static WebException unAuthorized() {
return new WebException(
WebException.HTTPStatus.UNAUTHORIZED,
"로그인에 실패했습니다."
"비밀번호가 틀렸습니다."
);
}

Expand All @@ -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 내 사용자 데이터를 업데이트하는 것을 실패했습니다."
);
}
}
Loading