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 = "";
+
+ 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 @@
-
-
+
+
+
+
-
+
{{{{REPEAT}}.authorName}}
\n" + + "