diff --git a/src/main/java/db/ArticleDatabase.java b/src/main/java/db/ArticleDatabase.java index 0804b77a8..89510dbac 100644 --- a/src/main/java/db/ArticleDatabase.java +++ b/src/main/java/db/ArticleDatabase.java @@ -1,18 +1,24 @@ package db; +import db.config.CustomJdbcTemplate; +import db.config.RowMapper; import model.Article; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Optional; public class ArticleDatabase { private final CustomJdbcTemplate jdbcTemplate; private static final String SELECT_SQL = "select * from ARTICLE where ARTICLE_ID = ?"; - private static final String INSERT_SQL = "insert into ARTICLE (ARTICLE_ID, CONTENT, USER_ID, IMAGE_ID, LIKE_COUNT) values (?, ?, ?, ?, ?)"; - private static final String UPDATE_SQL = "update ARTICLE set CONTENT = ?, USER_ID = ?, IMAGE_ID = ?, LIKE_COUNT = ? where ARTICLE_ID = ?"; + private static final String SELECT_ALL_SQL = "select * from ARTICLE"; + private static final String SELECT_RECENT_SQL = "select * from ARTICLE order by ARTICLE_ID desc limit 1"; + private static final String SELECT_PREV_ARTICLE_SQL = "SELECT * FROM ARTICLE WHERE ARTICLE_ID > ? ORDER BY ARTICLE_ID ASC LIMIT 1"; + private static final String SELECT_NEXT_ARTICLE_SQL = "SELECT * FROM ARTICLE WHERE ARTICLE_ID < ? ORDER BY ARTICLE_ID DESC LIMIT 1"; + private static final String INSERT_SQL = "insert into ARTICLE (CONTENT, USER_ID, IMAGE_ID, LIKE_COUNT) values (?, ?, ?, ?)"; private static final String UPDATE_LIKE_COUNT_SQL = "update ARTICLE set LIKE_COUNT = LIKE_COUNT + 1 where ARTICLE_ID = ?"; public ArticleDatabase(DataSource dataSource) { @@ -21,41 +27,56 @@ public ArticleDatabase(DataSource dataSource) { public void save(Article article) { jdbcTemplate.update(INSERT_SQL, - article.articleId(), - article.content(), - article.userId(), - article.imageId(), - article.likeCount() + article.getContent(), + article.getUserId(), + article.getImageId(), + article.getLikeCount() ); } - public void update(Article article) { - jdbcTemplate.update(UPDATE_SQL, - article.content(), - article.userId(), - article.imageId(), - article.articleId(), - article.likeCount() - ); + public Long saveAndGetKey(Article article) { + return jdbcTemplate.updateAndGetKey(INSERT_SQL, + rs -> rs.getLong(1), + article.getContent(), + article.getUserId(), + article.getImageId(), + article.getLikeCount() + ).orElseThrow(() -> new RuntimeException("Failed to save article")); } - public Optional
findById(String articleId) { + public Optional
findById(Long articleId) { return jdbcTemplate.queryForObject(SELECT_SQL, new ArticleRowMapper(), articleId); } - public void addLikeCount(String articleId) { + public Optional
findRecentArticle() { + return jdbcTemplate.queryForObject(SELECT_RECENT_SQL, new ArticleRowMapper()); + } + + public void addLikeCount(Long articleId) { jdbcTemplate.update(UPDATE_LIKE_COUNT_SQL, articleId); } - static class ArticleRowMapper implements RowMapper
{ + public Optional
findPreviousArticle(Long articleId) { + return jdbcTemplate.queryForObject(SELECT_PREV_ARTICLE_SQL, new ArticleRowMapper(), articleId); + } + + public Optional
findNextArticle(Long articleId) { + return jdbcTemplate.queryForObject(SELECT_NEXT_ARTICLE_SQL, new ArticleRowMapper(), articleId); + } + + public List
findAll() { + return jdbcTemplate.query(SELECT_ALL_SQL, new ArticleRowMapper()); + } + + static class ArticleRowMapper implements RowMapper
{ @Override public Article mapRow(ResultSet rs) throws SQLException { return new Article( - rs.getString("ARTICLE_ID"), + rs.getLong("ARTICLE_ID"), rs.getString("CONTENT"), rs.getString("USER_ID"), rs.getString("IMAGE_ID"), - rs.getInt("LIKE_COUNT") + rs.getLong("LIKE_COUNT") ); } } diff --git a/src/main/java/db/CommentDatabase.java b/src/main/java/db/CommentDatabase.java index 92c45c06a..caf18f3f5 100644 --- a/src/main/java/db/CommentDatabase.java +++ b/src/main/java/db/CommentDatabase.java @@ -1,18 +1,19 @@ package db; +import db.config.CustomJdbcTemplate; +import db.config.RowMapper; import model.Comment; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Optional; +import java.util.List; public class CommentDatabase { private final CustomJdbcTemplate jdbcTemplate; - private static final String INSERT_SQL = "insert into COMMENT (COMMENT_ID, CONTENT, USER_ID, ARTICLE_ID) values (?, ?, ?, ?)"; - private static final String UPDATE_SQL = "update COMMENT set CONTENT = ?, USER_ID = ?, ARTICLE_ID = ? where COMMENT_ID = ?"; - private static final String SELECT_SQL = "select * from COMMENT where COMMENT_ID = ?"; + private static final String INSERT_SQL = "insert into COMMENT (CONTENT, USER_ID, ARTICLE_ID) values (?, ?, ?)"; + private static final String SELECT_SQL_BY_ARTICLE_ID = "select * from COMMENT where ARTICLE_ID = ?"; public CommentDatabase(DataSource dataSource) { jdbcTemplate = new CustomJdbcTemplate(dataSource); @@ -20,34 +21,24 @@ public CommentDatabase(DataSource dataSource) { public void save(Comment comment) { jdbcTemplate.update(INSERT_SQL, - comment.commentId(), - comment.content(), - comment.userId(), - comment.articleId() + comment.getContent(), + comment.getUserId(), + comment.getArticleId() ); } - public void update(Comment comment) { - jdbcTemplate.update(UPDATE_SQL, - comment.content(), - comment.userId(), - comment.articleId(), - comment.commentId() - ); - } - - public Optional findById(String commentId) { - return jdbcTemplate.queryForObject(SELECT_SQL, new CommentRowMapper(), commentId); + public List findAllByArticleId(Long articleId) { + return jdbcTemplate.query(SELECT_SQL_BY_ARTICLE_ID, new CommentRowMapper(), articleId); } - static class CommentRowMapper implements RowMapper{ + static class CommentRowMapper implements RowMapper { @Override public Comment mapRow(ResultSet rs) throws SQLException { return new Comment( - rs.getString("COMMENT_ID"), + rs.getLong("COMMENT_ID"), rs.getString("CONTENT"), rs.getString("USER_ID"), - rs.getString("ARTICLE_ID") + rs.getLong("ARTICLE_ID") ); } } diff --git a/src/main/java/db/DatabaseConfig.java b/src/main/java/db/DatabaseConfig.java deleted file mode 100644 index 6da6b97d7..000000000 --- a/src/main/java/db/DatabaseConfig.java +++ /dev/null @@ -1,8 +0,0 @@ -package db; - -public class DatabaseConfig { - public static UserDatabase userDatabase = new UserDatabase(new CustomDataSource()); - public static ArticleDatabase articleDatabase = new ArticleDatabase(new CustomDataSource()); - public static ImageDatabase imageDatabase = new ImageDatabase(new CustomDataSource()); - public static CommentDatabase commentDatabase = new CommentDatabase(new CustomDataSource()); -} diff --git a/src/main/java/db/DatabaseInitializer.java b/src/main/java/db/DatabaseInitializer.java deleted file mode 100644 index de6b72833..000000000 --- a/src/main/java/db/DatabaseInitializer.java +++ /dev/null @@ -1,36 +0,0 @@ -package db; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.sql.DataSource; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; - -public class DatabaseInitializer { - private static final String ddlSQLPath = "./src/main/resources/sql/schema.sql"; - private static final String dmlSQLPath = "./src/main/resources/sql/test_data.sql"; - private static final Logger logger = LoggerFactory.getLogger(DatabaseInitializer.class); - - public static void init(){ - try { - String ddlSql = Files.readString(Paths.get(ddlSQLPath)); - String dmlSql = Files.readString(Paths.get(dmlSQLPath)); - - DataSource dataSource = new CustomDataSource(); - try (Connection con = dataSource.getConnection(); - Statement stmt = con.createStatement()) { - stmt.execute(ddlSql + dmlSql); - logger.info("Database initialize Complete"); - } catch (SQLException e) { - throw new RuntimeException("DB Initialize Error: ", e); - } - }catch (IOException e){ - throw new RuntimeException("DB Initialize File Not Found: ", e); - } - } -} diff --git a/src/main/java/db/ImageDatabase.java b/src/main/java/db/ImageDatabase.java index 5c7282e38..3b69469e4 100644 --- a/src/main/java/db/ImageDatabase.java +++ b/src/main/java/db/ImageDatabase.java @@ -1,5 +1,7 @@ package db; +import db.config.CustomJdbcTemplate; +import db.config.RowMapper; import enums.ContentTypes; import exception.NotFoundException; import model.Image; @@ -7,7 +9,6 @@ import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Optional; public class ImageDatabase { @@ -28,16 +29,12 @@ public void update(Image image){ jdbcTemplate.update(UPDATE_SQL, image.bytes(), image.fileName(), image.contentType().getExtension(), image.imageId()); } - public Optional findById(String imageId) { - return jdbcTemplate.queryForObject(SELECT_SQL, new ImageRowMapper(), imageId); - } - - public Image findByIdOrThrow(String imageId) { + public Image findByIdOrElseThrow(String imageId) { return jdbcTemplate.queryForObject(SELECT_SQL, new ImageRowMapper(), imageId) .orElseThrow(() -> new NotFoundException(imageId + " image not found")); } - static class ImageRowMapper implements RowMapper{ + static class ImageRowMapper implements RowMapper { @Override public Image mapRow(ResultSet rs) throws SQLException { return new Image( diff --git a/src/main/java/db/UserDatabase.java b/src/main/java/db/UserDatabase.java index 42194fe79..f0819ede8 100644 --- a/src/main/java/db/UserDatabase.java +++ b/src/main/java/db/UserDatabase.java @@ -1,10 +1,14 @@ package db; +import db.config.CustomJdbcTemplate; +import db.config.RowMapper; +import exception.NotFoundException; import model.User; import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Optional; public class UserDatabase { @@ -13,6 +17,7 @@ public class UserDatabase { private static final String INSERT_SQL = "insert into USERS (USER_ID, PASSWORD, NAME, EMAIL, IMAGE_ID) values (?, ?, ?, ?, ?)"; private static final String UPDATE_SQL = "update USERS set PASSWORD = ?, NAME = ?, EMAIL = ?, IMAGE_ID = ? where USER_ID = ?"; private static final String SELECT_SQL = "select * from USERS u where u.USER_ID = ?"; + private static final String SELECT_ALL_SQL = "select * from USERS"; private static final String SELECT_SQL_BY_NAME = "select * from USERS where NAME = ?"; public UserDatabase(DataSource dataSource) { @@ -45,11 +50,20 @@ public Optional findById(String userId) { return jdbcTemplate.queryForObject(SELECT_SQL, new UserRowMapper(), userId); } + public User findByIdOrElseThrow(String userId) { + return jdbcTemplate.queryForObject(SELECT_SQL, new UserRowMapper(), userId) + .orElseThrow(() -> new NotFoundException("user not found")); + } + public Optional findByName(String name) { return jdbcTemplate.queryForObject(SELECT_SQL_BY_NAME, new UserRowMapper(), name); } - static class UserRowMapper implements RowMapper{ + public List findAll() { + return jdbcTemplate.query(SELECT_ALL_SQL, new UserRowMapper()); + } + + static class UserRowMapper implements RowMapper { @Override public User mapRow(ResultSet rs) throws SQLException { return new User( diff --git a/src/main/java/db/Cache.java b/src/main/java/db/cache/Cache.java similarity index 85% rename from src/main/java/db/Cache.java rename to src/main/java/db/cache/Cache.java index 6ee609b39..3fd6fdaa5 100644 --- a/src/main/java/db/Cache.java +++ b/src/main/java/db/cache/Cache.java @@ -1,4 +1,4 @@ -package db; +package db.cache; public interface Cache { diff --git a/src/main/java/db/ConcurrentMapCache.java b/src/main/java/db/cache/ConcurrentMapCache.java similarity index 96% rename from src/main/java/db/ConcurrentMapCache.java rename to src/main/java/db/cache/ConcurrentMapCache.java index 4c53e56ab..7bd8da8a5 100644 --- a/src/main/java/db/ConcurrentMapCache.java +++ b/src/main/java/db/cache/ConcurrentMapCache.java @@ -1,4 +1,4 @@ -package db; +package db.cache; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/src/main/java/db/SessionManager.java b/src/main/java/db/cache/SessionManager.java similarity index 97% rename from src/main/java/db/SessionManager.java rename to src/main/java/db/cache/SessionManager.java index e743eecda..e3f3627a6 100644 --- a/src/main/java/db/SessionManager.java +++ b/src/main/java/db/cache/SessionManager.java @@ -1,7 +1,8 @@ -package db; +package db.cache; import java.util.Optional; import java.util.UUID; + import model.User; public class SessionManager { diff --git a/src/main/java/db/ConnectionConst.java b/src/main/java/db/config/ConnectionConst.java similarity index 78% rename from src/main/java/db/ConnectionConst.java rename to src/main/java/db/config/ConnectionConst.java index 7246d6e88..ae100f21c 100644 --- a/src/main/java/db/ConnectionConst.java +++ b/src/main/java/db/config/ConnectionConst.java @@ -1,6 +1,10 @@ -package db; +package db.config; public abstract class ConnectionConst { + + private ConnectionConst(){ + } + public static final String URL = "jdbc:h2:tcp://localhost/~/test"; public static final String USERNAME = "sa"; public static final String PASSWORD = ""; diff --git a/src/main/java/db/CustomDataSource.java b/src/main/java/db/config/CustomDataSource.java similarity index 95% rename from src/main/java/db/CustomDataSource.java rename to src/main/java/db/config/CustomDataSource.java index ca07ce869..1e31f0e32 100644 --- a/src/main/java/db/CustomDataSource.java +++ b/src/main/java/db/config/CustomDataSource.java @@ -1,4 +1,4 @@ -package db; +package db.config; import javax.sql.DataSource; import java.io.PrintWriter; @@ -8,7 +8,7 @@ import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; -import static db.ConnectionConst.*; +import static db.config.ConnectionConst.*; public class CustomDataSource implements DataSource { @Override diff --git a/src/main/java/db/CustomJdbcTemplate.java b/src/main/java/db/config/CustomJdbcTemplate.java similarity index 71% rename from src/main/java/db/CustomJdbcTemplate.java rename to src/main/java/db/config/CustomJdbcTemplate.java index 7dcd6fb51..842fa2f9a 100644 --- a/src/main/java/db/CustomJdbcTemplate.java +++ b/src/main/java/db/config/CustomJdbcTemplate.java @@ -1,4 +1,4 @@ -package db; +package db.config; import javax.sql.DataSource; import java.sql.*; @@ -47,6 +47,26 @@ public int update(String sql, Object... args) { } } + public Optional updateAndGetKey(String sql, RowMapper rowMapper, Object... args) { + try (Connection con = dataSource.getConnection(); + PreparedStatement pstmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + setParameters(pstmt, args); + pstmt.executeUpdate(); + + try (ResultSet rs = pstmt.getGeneratedKeys()) { + if (rs.next()) { + return Optional.ofNullable(rowMapper.mapRow(rs)); + } + return Optional.empty(); + } + + } catch (SQLException e) { + throw new RuntimeException("DB Update Error: " + sql, e); + } + } + + private void setParameters(PreparedStatement pstmt, Object[] args) throws SQLException { for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); diff --git a/src/main/java/db/config/DatabaseConfig.java b/src/main/java/db/config/DatabaseConfig.java new file mode 100644 index 000000000..1b6575974 --- /dev/null +++ b/src/main/java/db/config/DatabaseConfig.java @@ -0,0 +1,13 @@ +package db.config; + +import db.*; + +public class DatabaseConfig { + private DatabaseConfig(){ + } + + public static final UserDatabase userDatabase = new UserDatabase(new CustomDataSource()); + public static final ArticleDatabase articleDatabase = new ArticleDatabase(new CustomDataSource()); + public static final ImageDatabase imageDatabase = new ImageDatabase(new CustomDataSource()); + public static final CommentDatabase commentDatabase = new CommentDatabase(new CustomDataSource()); +} diff --git a/src/main/java/db/config/DatabaseInitializer.java b/src/main/java/db/config/DatabaseInitializer.java new file mode 100644 index 000000000..26cd19c0a --- /dev/null +++ b/src/main/java/db/config/DatabaseInitializer.java @@ -0,0 +1,102 @@ +package db.config; + +import db.ArticleDatabase; +import db.CommentDatabase; +import db.ImageDatabase; +import db.UserDatabase; +import model.Article; +import model.Comment; +import model.Image; +import model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +import static model.Article.newArticle; +import static model.Comment.newComment; + +public class DatabaseInitializer { + + private DatabaseInitializer() { + } + + private static final UserDatabase userDatabase = DatabaseConfig.userDatabase; + private static final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; + private static final ArticleDatabase articleDatabase = DatabaseConfig.articleDatabase; + private static final CommentDatabase commentDatabase = DatabaseConfig.commentDatabase; + + private static final String DDL_SQL_PATH = "./src/main/resources/sql/schema.sql"; + private static final Logger logger = LoggerFactory.getLogger(DatabaseInitializer.class); + + public static void init() { + try { + String ddlSql = Files.readString(Paths.get(DDL_SQL_PATH)); + + DataSource dataSource = new CustomDataSource(); + try (Connection con = dataSource.getConnection(); + Statement stmt = con.createStatement()) { + stmt.execute(ddlSql); + logger.info("Database initialize Complete"); + } catch (SQLException e) { + throw new RuntimeException("DB Initialize Error: ", e); + } + } catch (IOException e) { + throw new RuntimeException("DB Initialize File Not Found: ", e); + } + + createUser(5); + createArticle(10); + createComments(20); + } + + private static void createComments(int count) { + List users = userDatabase.findAll(); + List
articles = articleDatabase.findAll(); + for (int i = 0; i < count; i++) { + Comment comment = newComment( + "hello" + i, + users.get(i % users.size()).getUserId(), + articles.get(i % articles.size()).getArticleId() + ); + commentDatabase.save(comment); + } + } + + private static void createArticle(int count) { + List users = userDatabase.findAll(); + for (int i = 0; i < count; i++) { + Image image = Image.defaultImage(); + imageDatabase.save(image); + Article article = newArticle( + "hello" + i, + users.get(i % users.size()).getUserId(), + image.imageId() + ); + articleDatabase.save(article); + } + } + + private static void createUser(int count) { + for (int i = 0; i < count; i++) { + Image image = Image.defaultImage(); + imageDatabase.save(image); + String countStr = String.valueOf(i); + User user = new User( + "id" + countStr , + "pw" + countStr, + "name" + countStr, + null, + image.imageId() + ); + userDatabase.save(user); + } + } +} diff --git a/src/main/java/db/RowMapper.java b/src/main/java/db/config/RowMapper.java similarity index 88% rename from src/main/java/db/RowMapper.java rename to src/main/java/db/config/RowMapper.java index 4c5b294a6..d3a8d60f7 100644 --- a/src/main/java/db/RowMapper.java +++ b/src/main/java/db/config/RowMapper.java @@ -1,4 +1,4 @@ -package db; +package db.config; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/src/main/java/enums/ContentTypes.java b/src/main/java/enums/ContentTypes.java index e4346b64a..a7a06b27c 100644 --- a/src/main/java/enums/ContentTypes.java +++ b/src/main/java/enums/ContentTypes.java @@ -28,7 +28,7 @@ public enum ContentTypes { public static ContentTypes fromExtension(String extension) { for (ContentTypes contentType : ContentTypes.values()) { - if (contentType.extension.equals(extension)) { + if (contentType.extension.equalsIgnoreCase(extension)) { return contentType; } } diff --git a/src/main/java/handler/ArticleHandler.java b/src/main/java/handler/ArticleHandler.java index c9d1d68a0..11405c094 100644 --- a/src/main/java/handler/ArticleHandler.java +++ b/src/main/java/handler/ArticleHandler.java @@ -1,6 +1,11 @@ package handler; import db.*; +import db.cache.SessionManager; +import db.config.DatabaseConfig; +import enums.HttpHeader; +import enums.HttpStatus; +import exception.BadRequestException; import exception.HttpException; import http.converter.HttpMessageConverter; import http.converter.HttpMessageConverterMapper; @@ -12,21 +17,19 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.UUID; -import model.Article; + import model.Image; import model.User; import webserver.view.StaticResourceView; import webserver.view.TemplateView; import webserver.view.View; -public class ArticleHandler extends AbstractHandler { +import static model.Article.newArticle; - private final ArticleDatabase articleDatabase = DatabaseConfig.articleDatabase; - private final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; +public class ArticleHandler extends AbstractHandler { - public ArticleHandler() { - } + private static final ArticleDatabase articleDatabase = DatabaseConfig.articleDatabase; + private static final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; @Override protected void get(HttpRequest request, HttpResponse response) throws HttpException { @@ -56,22 +59,22 @@ protected void post(HttpRequest request, HttpResponse response) { String content = multipartData.getTexts("content"); ImageForm imageForm = multipartData.getFileBytes("image"); + if(imageForm == null) throw new BadRequestException("image is required"); + Image image = Image.from(imageForm); imageDatabase.save(image); - Article article = new Article(UUID.randomUUID().toString(), content, user.get().getUserId(), image.imageId(), 0); - articleDatabase.save(article); + Long articleId = articleDatabase.saveAndGetKey(newArticle(content, user.get().getUserId(), image.imageId())); Map model = new HashMap<>(); model.put("name", user.get().getName()); model.put("content", content); model.put("image", image.toImageString()); - // TODO: 새로운 게시물의 화면으로 이동할 수 있어야 한다. - View view = new TemplateView("/main/index.html"); - view.render(model, request, response); + response.setStatusCode(HttpStatus.FOUND) + .setHeader(HttpHeader.LOCATION.getValue(), "/main?articleId=" + articleId); } else { - View view = new StaticResourceView("/login/index.html"); - view.render(Collections.emptyMap(), request, response); + response.setStatusCode(HttpStatus.FOUND) + .setHeader(HttpHeader.LOCATION.getValue(), "/login"); } } } diff --git a/src/main/java/handler/ArticleLikeHandler.java b/src/main/java/handler/ArticleLikeHandler.java index c6d153354..1a22da5fe 100644 --- a/src/main/java/handler/ArticleLikeHandler.java +++ b/src/main/java/handler/ArticleLikeHandler.java @@ -1,22 +1,11 @@ package handler; import db.ArticleDatabase; -import db.DatabaseConfig; -import db.ImageDatabase; -import db.SessionManager; +import db.config.DatabaseConfig; import enums.HttpStatus; -import exception.HttpException; import http.converter.*; import http.request.HttpRequest; import http.response.HttpResponse; -import model.Article; -import model.Image; -import model.User; -import webserver.view.StaticResourceView; -import webserver.view.TemplateView; -import webserver.view.View; - -import java.util.*; public class ArticleLikeHandler extends AbstractHandler { @@ -31,8 +20,7 @@ protected void post(HttpRequest request, HttpResponse response) { HttpMessageConverterMapper.findHttpMessageConverter(Form.class, request.getContentType()); Form form = converter.read(request); - String articleId = form.get("articleId"); - articleDatabase.addLikeCount(articleId); + articleDatabase.addLikeCount(Long.parseLong(form.get("articleId"))); response.setStatusCode(HttpStatus.NO_CONTENT); } diff --git a/src/main/java/handler/CommentHandler.java b/src/main/java/handler/CommentHandler.java index 0fbc23a2d..9e040e4e7 100644 --- a/src/main/java/handler/CommentHandler.java +++ b/src/main/java/handler/CommentHandler.java @@ -1,6 +1,10 @@ package handler; import db.*; +import db.cache.SessionManager; +import db.config.DatabaseConfig; +import enums.HttpHeader; +import enums.HttpStatus; import exception.HttpException; import http.converter.*; import http.request.HttpRequest; @@ -16,7 +20,7 @@ public class CommentHandler extends AbstractHandler { - private final CommentDatabase commentDatabase = DatabaseConfig.commentDatabase; + private static final CommentDatabase commentDatabase = DatabaseConfig.commentDatabase; public CommentHandler() { } @@ -28,7 +32,7 @@ protected void get(HttpRequest request, HttpResponse response) throws HttpExcept if (user.isPresent()) { Map model = new HashMap<>(); model.put("name", user.get().getName()); - + model.put("articleId", request.getQuery().get("articleId")); View view = new TemplateView("/comment/index.html"); view.render(model, request, response); } else { @@ -46,18 +50,14 @@ protected void post(HttpRequest request, HttpResponse response) { HttpMessageConverterMapper.findHttpMessageConverter(Form.class, request.getContentType()); Form form = converter.read(request); - String comment = form.get("comment"); - // TODO: articleId를 body가 아닌 다른 방식으로 받을 수 있어야 한다. - String articleId = form.get("articleId"); + String comment = form.get("content"); + Long articleId = Long.parseLong(form.get("articleId")); String userId = user.get().getUserId(); commentDatabase.save(newComment(comment, userId, articleId)); - Map model = new HashMap<>(); - model.put("name", user.get().getName()); - // TODO: 보고 있던 게시물의 화면으로 이동할 수 있어야 한다. - View view = new StaticResourceView("/main/index.html"); - view.render(model, request, response); + response.setStatusCode(HttpStatus.FOUND) + .setHeader(HttpHeader.LOCATION.getValue(), "/?articleId=" + articleId); } else { View view = new StaticResourceView("/login/index.html"); view.render(Collections.emptyMap(), request, response); diff --git a/src/main/java/handler/CommentResponse.java b/src/main/java/handler/CommentResponse.java new file mode 100644 index 000000000..15030a812 --- /dev/null +++ b/src/main/java/handler/CommentResponse.java @@ -0,0 +1,28 @@ +package handler; + +import model.Image; + +public class CommentResponse { + + private final String writerName; + private final Image writerProfileImage; + private final String content; + + public CommentResponse(String writerName, Image writerProfileImage, String content) { + this.writerName = writerName; + this.writerProfileImage = writerProfileImage; + this.content = content; + } + + public String getWriterName() { + return writerName; + } + + public String getWriterProfileImage() { + return writerProfileImage.toImageString(); + } + + public String getContent() { + return content; + } +} diff --git a/src/main/java/handler/CreateUserHandler.java b/src/main/java/handler/CreateUserHandler.java index aed9b93c8..0f23cdc5a 100644 --- a/src/main/java/handler/CreateUserHandler.java +++ b/src/main/java/handler/CreateUserHandler.java @@ -1,6 +1,7 @@ package handler; import db.*; +import db.config.DatabaseConfig; import enums.HttpHeader; import enums.HttpStatus; import exception.BadRequestException; diff --git a/src/main/java/handler/HomeHandler.java b/src/main/java/handler/HomeHandler.java index f8bf390cc..31edf9f06 100644 --- a/src/main/java/handler/HomeHandler.java +++ b/src/main/java/handler/HomeHandler.java @@ -1,42 +1,103 @@ package handler; -import db.SessionManager; -import enums.HttpMethod; +import db.ArticleDatabase; +import db.CommentDatabase; +import db.ImageDatabase; +import db.UserDatabase; +import db.cache.SessionManager; +import db.config.DatabaseConfig; import exception.HttpException; -import exception.MethodNotAllowedException; import http.request.HttpRequest; import http.response.HttpResponse; -import java.util.Collections; + import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; + +import model.Article; +import model.Image; import model.User; -import webserver.view.StaticResourceView; import webserver.view.TemplateView; import webserver.view.View; public class HomeHandler extends AbstractHandler { - public HomeHandler() { - } + private static final UserDatabase userDatabase = DatabaseConfig.userDatabase; + private static final ArticleDatabase articleDatabase = DatabaseConfig.articleDatabase; + private static final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; + private static final CommentDatabase commentDatabase = DatabaseConfig.commentDatabase; @Override protected void get(HttpRequest request, HttpResponse response) throws HttpException { if (request.getPath().equals("/") || request.getPath().equals("/index.html") || request.getPath().equals("/main")) { String sid = request.getCookieValue("sid"); Optional user = SessionManager.getInstance().getAttribute(sid); - if (user.isPresent()) { - Map model = new HashMap<>(); - model.put("name", user.get().getName()); - View view = new TemplateView("/main/index.html"); - view.render(model, request, response); - } else { - View view = new StaticResourceView("/index.html"); - view.render(Collections.emptyMap(), request, response); - } + provideHomeView(request, response, user); + } else { + provideResource(request, response); + } + } + + private void provideResource(HttpRequest request, HttpResponse response) { + response.respondWithStaticFile(request.getPath()); + } + + private void provideHomeView(HttpRequest request, HttpResponse response, Optional user) { + Map model = new HashMap<>(); + user.ifPresent(u -> model.put("name", u.getName())); + + String articleIdStr = request.getQuery().get("articleId"); + + if (articleIdStr == null) { // 첫 페이지를 제공 + articleDatabase.findRecentArticle() + .ifPresent(article -> collectArticleModel(model, article)); } else { - View view = new StaticResourceView(request.getPath()); - view.render(Collections.emptyMap(), request, response); + articleDatabase.findById(Long.parseLong(articleIdStr)) + .ifPresent(article -> collectArticleModel(model, article)); } + + View view = new TemplateView("/main/index.html"); + view.render(model, request, response); + } + + private void collectArticleModel(Map model, Article article) { + collectArticleWriterInfo(model, article.getUserId()); + collectArticleInfo(model, article); + collectCommentsInfo(model, article); + } + + private void collectCommentsInfo(Map model, Article article) { + List comments = commentDatabase.findAllByArticleId(article.getArticleId()) + .stream().map(comment -> { + User writer = userDatabase.findByIdOrElseThrow(comment.getUserId()); + Image writerProfileImage = imageDatabase.findByIdOrElseThrow(writer.getProfileImageId()); + return new CommentResponse( + writer.getName(), + writerProfileImage, + comment.getContent() + ); + }).toList(); + model.put("comments", comments); + model.put("commentCount", comments.size()); + } + + private void collectArticleInfo(Map model, Article article) { + model.put("likeCount", article.getLikeCount()); + model.put("articleContent", article.getContent()); + Image articleImage = imageDatabase.findByIdOrElseThrow(article.getImageId()); + model.put("articleImage", articleImage.toImageString()); + model.put("articleId", article.getArticleId()); + articleDatabase.findPreviousArticle(article.getArticleId()) + .ifPresent(pa -> model.put("previousArticleId", pa.getArticleId())); + articleDatabase.findNextArticle(article.getArticleId()) + .ifPresent(na -> model.put("nextArticleId", na.getArticleId())); + } + + private void collectArticleWriterInfo(Map model, String userId) { + User author = userDatabase.findByIdOrElseThrow(userId); + Image authorProfileImage = imageDatabase.findByIdOrElseThrow(author.getProfileImageId()); + model.put("authorName", author.getName()); + model.put("authorProfileImage", authorProfileImage.toImageString()); } } diff --git a/src/main/java/handler/LoginHandler.java b/src/main/java/handler/LoginHandler.java index 844939a02..f0e285b67 100644 --- a/src/main/java/handler/LoginHandler.java +++ b/src/main/java/handler/LoginHandler.java @@ -1,7 +1,7 @@ package handler; -import db.DatabaseConfig; -import db.SessionManager; +import db.config.DatabaseConfig; +import db.cache.SessionManager; import db.UserDatabase; import enums.HttpHeader; import enums.HttpStatus; @@ -21,10 +21,7 @@ public class LoginHandler extends AbstractHandler { - private final UserDatabase userDatabase = DatabaseConfig.userDatabase; - - public LoginHandler() { - } + private static final UserDatabase userDatabase = DatabaseConfig.userDatabase; @Override protected void get(HttpRequest request, HttpResponse response) throws HttpException { diff --git a/src/main/java/handler/LogoutHandler.java b/src/main/java/handler/LogoutHandler.java index 8907a9f1c..e14da1058 100644 --- a/src/main/java/handler/LogoutHandler.java +++ b/src/main/java/handler/LogoutHandler.java @@ -1,6 +1,6 @@ package handler; -import db.SessionManager; +import db.cache.SessionManager; import enums.ContentTypes; import enums.HttpHeader; import enums.HttpStatus; @@ -10,9 +10,6 @@ public class LogoutHandler extends AbstractHandler { - public LogoutHandler() { - } - @Override protected void post(HttpRequest request, HttpResponse response) throws HttpException { String sessionId = request.getCookieValue("sid"); diff --git a/src/main/java/handler/MyPageHandler.java b/src/main/java/handler/MyPageHandler.java index 477993ad6..d9558471a 100644 --- a/src/main/java/handler/MyPageHandler.java +++ b/src/main/java/handler/MyPageHandler.java @@ -1,6 +1,8 @@ package handler; import db.*; +import db.cache.SessionManager; +import db.config.DatabaseConfig; import enums.HttpHeader; import enums.HttpStatus; import exception.BadRequestException; @@ -16,23 +18,24 @@ import java.util.Map; import model.Image; +import model.policy.NameChangePolicy; import model.User; +import model.policy.PasswordChangePolicy; import webserver.view.TemplateView; import webserver.view.View; public class MyPageHandler extends AbstractHandler{ - private final UserDatabase userDatabase = DatabaseConfig.userDatabase; - private final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; - - public MyPageHandler() {} + private static final UserDatabase userDatabase = DatabaseConfig.userDatabase; + private static final ImageDatabase imageDatabase = DatabaseConfig.imageDatabase; @Override protected void get(HttpRequest request, HttpResponse response) throws HttpException { String sessionId = request.getCookieValue("sid"); - User user = SessionManager.getInstance().getAttribute(sessionId).orElseThrow(() -> new UnauthorizedException("unauthorized")); + User user = SessionManager.getInstance().getAttribute(sessionId) + .orElseThrow(() -> new UnauthorizedException("unauthorized")); - Image userProfileImage = imageDatabase.findByIdOrThrow(user.getProfileImageId()); + Image userProfileImage = imageDatabase.findByIdOrElseThrow(user.getProfileImageId()); Map model = new HashMap<>(); model.put("name",user.getName()); @@ -52,12 +55,11 @@ protected void patch(HttpRequest request, HttpResponse response) throws HttpExce MultipartData multipartData = converter.read(request); ImageForm imageForm = multipartData.getFileBytes("image"); - String nickname = multipartData.getTexts("nickname"); + String newName = multipartData.getTexts("nickname"); String password = multipartData.getTexts("password"); String passwordConfirm = multipartData.getTexts("passwordConfirm"); - if(nickname == null) throw new BadRequestException("nickname required"); - user.changeUserName(nickname); + user.changeUserName(user.getName(), newName, new NameChangePolicy()); if(imageForm != null){ String profileImageId = user.getProfileImageId(); @@ -66,7 +68,7 @@ protected void patch(HttpRequest request, HttpResponse response) throws HttpExce if(password != null && !password.isEmpty()){ if (password.equals(passwordConfirm)) { - user.changePassword(password); + user.changePassword(password, new PasswordChangePolicy()); }else{ throw new BadRequestException("password not matched"); } diff --git a/src/main/java/model/Article.java b/src/main/java/model/Article.java index eb878c010..8b21c20d6 100644 --- a/src/main/java/model/Article.java +++ b/src/main/java/model/Article.java @@ -1,11 +1,42 @@ package model; -public record Article( - String articleId, - String content, - String userId, - String imageId, - Integer likeCount -) { +public class Article { + private final Long articleId; + private final String content; + private final String userId; + private final String imageId; + private final Long likeCount; + + public Article(Long articleId, String content, String userId, String imageId, Long likeCount){ + this.articleId = articleId; + this.content = content; + this.userId = userId; + this.imageId = imageId; + this.likeCount = likeCount; + } + + public static Article newArticle(String content, String userId, String imageId){ + return new Article(null, content, userId, imageId, 0L); + } + + public Long getArticleId() { + return articleId; + } + + public String getContent() { + return content; + } + + public String getUserId() { + return userId; + } + + public String getImageId() { + return imageId; + } + + public Long getLikeCount() { + return likeCount; + } } diff --git a/src/main/java/model/Comment.java b/src/main/java/model/Comment.java index 86690395e..fc7a3dffb 100644 --- a/src/main/java/model/Comment.java +++ b/src/main/java/model/Comment.java @@ -1,15 +1,35 @@ package model; -import java.util.UUID; +public class Comment { + private final Long commentId; + private final String content; + private final String userId; + private final Long articleId; -public record Comment( - String commentId, - String content, - String userId, - String articleId -) { + public Comment(Long commentId, String content, String userId, Long articleId){ + this.commentId = commentId; + this.content = content; + this.userId = userId; + this.articleId = articleId; + } + + public static Comment newComment(String content, String userId, Long articleId){ + return new Comment(null, content, userId, articleId); + } + + public String getUserId() { + return userId; + } + + public Long getCommentId() { + return commentId; + } + + public String getContent() { + return content; + } - public static Comment newComment(String content, String userId, String articleId){ - return new Comment(UUID.randomUUID().toString(), content, userId, articleId); + public Long getArticleId() { + return articleId; } } diff --git a/src/main/java/model/User.java b/src/main/java/model/User.java index 6901e7e55..5724c613f 100644 --- a/src/main/java/model/User.java +++ b/src/main/java/model/User.java @@ -1,5 +1,8 @@ package model; +import model.policy.NameChangePolicy; +import model.policy.PasswordChangePolicy; + public class User { private final String userId; @@ -20,12 +23,14 @@ public String getUserId() { return userId; } - public void changeUserName(String name){ - this.name = name; + public void changeUserName(String prevName, String newName, NameChangePolicy nameChangePolicy){ + nameChangePolicy.validate(prevName, newName); + this.name = newName; } - public void changePassword(String password){ + public void changePassword(String password, PasswordChangePolicy passwordChangePolicy){ this.password = password; + passwordChangePolicy.validate(password); } public String getName() { diff --git a/src/main/java/model/policy/NameChangePolicy.java b/src/main/java/model/policy/NameChangePolicy.java new file mode 100644 index 000000000..b7f9d7a00 --- /dev/null +++ b/src/main/java/model/policy/NameChangePolicy.java @@ -0,0 +1,26 @@ +package model.policy; + +import db.UserDatabase; +import db.config.DatabaseConfig; +import exception.BadRequestException; + +public class NameChangePolicy { + + public static final int MIN_LENGTH = 4; + public static final int MAX_LENGTH = 20; + private static final UserDatabase userDatabase = DatabaseConfig.userDatabase; + + public void validate(String prevName, String newName) { + if (newName == null || newName.isEmpty()) { + throw new BadRequestException("Nickname cannot be null or empty"); + } + if (newName.length() < MIN_LENGTH || newName.length() > MAX_LENGTH) { + throw new BadRequestException("Nickname must be between 4 and 20 characters"); + } + + if(prevName.equals(newName)) return; + if(userDatabase.findByName(newName).isPresent()){ + throw new BadRequestException("Nickname already in use: " + newName); + } + } +} diff --git a/src/main/java/model/policy/PasswordChangePolicy.java b/src/main/java/model/policy/PasswordChangePolicy.java new file mode 100644 index 000000000..f33f5c2a6 --- /dev/null +++ b/src/main/java/model/policy/PasswordChangePolicy.java @@ -0,0 +1,18 @@ +package model.policy; + +import exception.BadRequestException; + +public class PasswordChangePolicy { + + public static final int MIN_LENGTH = 4; + public static final int MAX_LENGTH = 20; + + public void validate(String password) { + if (password == null || password.isEmpty()) { + throw new BadRequestException("Nickname cannot be null or empty"); + } + if (password.length() < MIN_LENGTH || password.length() > MAX_LENGTH) { + throw new BadRequestException("Nickname must be between 3 and 20 characters"); + } + } +} diff --git a/src/main/java/webserver/WebServer.java b/src/main/java/webserver/WebServer.java index 2939b701e..4c0bb51f5 100644 --- a/src/main/java/webserver/WebServer.java +++ b/src/main/java/webserver/WebServer.java @@ -8,7 +8,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import db.DatabaseInitializer; +import db.config.DatabaseInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/webserver/view/TemplateView.java b/src/main/java/webserver/view/TemplateView.java index 52bb317ff..bcf2218be 100644 --- a/src/main/java/webserver/view/TemplateView.java +++ b/src/main/java/webserver/view/TemplateView.java @@ -5,29 +5,155 @@ import enums.HttpStatus; import http.request.HttpRequest; import http.response.HttpResponse; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import util.FileReader; public class TemplateView implements View { private final String filePath; + private static final Pattern LOOP_PATTERN = + Pattern.compile("<(\\w+)[^>]*\\s+data-for=\"(\\w+)\\s*:\\s*([\\w.]+)\"[^>]*>(.*?)", Pattern.DOTALL); + private static final Pattern CONDITION_PATTERN = + Pattern.compile("<(\\w+)[^>]*\\s+data-if=\"([\\w.!]+)\"[^>]*>(.*?)", Pattern.DOTALL); + private static final Pattern VARIABLE_PATTERN = + Pattern.compile("\\{\\{\\s*([\\w.]+)\\s*}}"); + public TemplateView(String filePath) { this.filePath = filePath; } - // TODO: StringBuilder를 이용한 구현으로 변경 @Override public void render(Map model, HttpRequest request, HttpResponse response) { - String content = new String(FileReader.readFile(filePath)); + String html = new String(FileReader.readFile(filePath)); - for (String key : model.keySet()) { - content = content.replace("{{" + key + "}}", model.get(key).toString()); - } + html = processLoops(html, model); + html = processConditionals(html, model); + html = processVariables(html, model); response.setHeader(HttpHeader.CONTENT_TYPE.getValue(), ContentTypes.TEXT_HTML.getMimeType()) .setStatusCode(HttpStatus.OK) - .setBody(content.getBytes(StandardCharsets.UTF_8)); + .setBody(html.getBytes(StandardCharsets.UTF_8)); + } + + private String processLoops(String html, Map model) { + Matcher matcher = LOOP_PATTERN.matcher(html); + StringBuilder sb = new StringBuilder(); + + while (matcher.find()) { + String tagName = matcher.group(1); + String itemName = matcher.group(2); + String listName = matcher.group(3); + String innerHtml = matcher.group(4); + String fullTag = matcher.group(0); + + Object listObj = model.get(listName); + + StringBuilder loopResult = new StringBuilder(); + if (listObj instanceof Collection) { + for (Object item : (Collection) listObj) { + String processedItemHtml = replaceLoopVariables(innerHtml, itemName, item); + String tagHeader = fullTag.substring(0, fullTag.indexOf(">") + 1); + String tagHeaderWithoutFor = tagHeader.replaceAll("\\s+data-for=\"[^\"]*\"", ""); + + loopResult.append(tagHeaderWithoutFor) + .append(processedItemHtml) + .append(""); + } + } + matcher.appendReplacement(sb, Matcher.quoteReplacement(loopResult.toString())); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private String replaceLoopVariables(String html, String itemName, Object itemObj) { + Pattern pattern = Pattern.compile("\\{\\{\\s*" + itemName + "\\.([\\w.]+)\\s*}}"); + Matcher matcher = pattern.matcher(html); + StringBuilder sb = new StringBuilder(); + + while(matcher.find()) { + String fieldPath = matcher.group(1); + Object value = getFieldValue(itemObj, fieldPath); + matcher.appendReplacement(sb, value != null ? String.valueOf(value) : ""); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private String processConditionals(String html, Map model) { + Matcher matcher = CONDITION_PATTERN.matcher(html); + StringBuilder sb = new StringBuilder(); + + while (matcher.find()) { + String conditionKey = matcher.group(2); + String tagContent = matcher.group(0); + + boolean negate = false; + if (conditionKey.startsWith("!")) { + negate = true; + conditionKey = conditionKey.substring(1); + } + + Object conditionVal = model.get(conditionKey); + + boolean isTrue = false; + if (conditionVal instanceof Boolean) { + isTrue = (Boolean) conditionVal; + } else if (conditionVal != null) { + isTrue = true; + } + + if (negate) { + isTrue = !isTrue; + } + + if (isTrue) { + String processedTag = tagContent.replaceFirst("\\s+data-if=\"[^\"]*\"", ""); + matcher.appendReplacement(sb, Matcher.quoteReplacement(processedTag)); + } else { + matcher.appendReplacement(sb, ""); + } + } + matcher.appendTail(sb); + return sb.toString(); + } + + // 3. 변수 치환 처리 ({{variable}}) + private String processVariables(String html, Map model) { + Matcher matcher = VARIABLE_PATTERN.matcher(html); + StringBuilder sb = new StringBuilder(); + + while (matcher.find()) { + String key = matcher.group(1); + Object value = model.get(key); + matcher.appendReplacement(sb, value != null ? String.valueOf(value) : ""); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private Object getFieldValue(Object object, String fieldName) { + try { + String getterName = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + Method method = object.getClass().getMethod(getterName); + return method.invoke(object); + } catch (Exception e) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(object); + } catch (Exception ex) { + return ""; + } + } } } diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index c5ae6aada..8bc8827d8 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -19,16 +19,18 @@ CREATE TABLE IF NOT EXISTS users ( ); CREATE TABLE IF NOT EXISTS article ( - article_id VARCHAR(255) PRIMARY KEY, + article_id BIGINT AUTO_INCREMENT PRIMARY KEY, content CLOB, user_id VARCHAR(50) NOT NULL, image_id VARCHAR(255) NOT NULL, - like_count INTEGER DEFAULT 0 + like_count INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS comment ( - comment_id VARCHAR(255) PRIMARY KEY, + comment_id BIGINT AUTO_INCREMENT PRIMARY KEY, content CLOB NOT NULL, user_id VARCHAR(50) NOT NULL, - article_id VARCHAR(255) NOT NULL + article_id VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); \ No newline at end of file diff --git a/src/main/resources/sql/test_data.sql b/src/main/resources/sql/test_data.sql index 0da074f2f..d8d9cf7a6 100644 --- a/src/main/resources/sql/test_data.sql +++ b/src/main/resources/sql/test_data.sql @@ -20,45 +20,41 @@ VALUES ('ti', 'tp', '테스트유저', 'ti@example.com', 'user-img-id-1'), ('user5', 'pwd5', '손흥민', 'heungmin@example.com', 'user-img-id-5'); -- 3. Article 데이터 생성 -INSERT INTO article (article_id, content, user_id, image_id, like_count) -VALUES ('art-001', '안녕하세요, 테스트유저입니다.', 'ti', 'article-img-id-1', 1), - ('art-002', '철수의 게시글입니다.', 'user2', 'article-img-id-2', 2), - ('art-003', '영희가 쓴 글입니다.', 'user3', 'article-img-id-3', 3), - ('art-004', '지성이 쓴 글입니다.', 'user4', 'article-img-id-4', 4), - ('art-005', '흥민이 쓴 글입니다.', 'user5', 'article-img-id-5', 5); +INSERT INTO article (content, user_id, image_id, like_count) +VALUES ( '안녕하세요, 테스트유저입니다.', 'ti', 'article-img-id-1', 1), + ( '안녕하세요, 테스트유저2입니다.', 'ti', 'article-img-id-1', 11), + ( '철수의 게시글입니다.', 'user2', 'article-img-id-2', 2), + ( '영희가 쓴 글입니다.', 'user3', 'article-img-id-3', 3), + ( '지성이 쓴 글입니다.', 'user4', 'article-img-id-4', 4), + ( '흥민이 쓴 글입니다.', 'user5', 'article-img-id-5', 5); -INSERT INTO comment (comment_id, content, user_id, article_id) VALUES --- art-001에 대한 댓글 5개 -('cmt-001-1', '첫 번째 댓글입니다!', 'user2', 'art-001'), -('cmt-001-2', '좋은 글이네요.', 'user3', 'art-001'), -('cmt-001-3', '공감하고 갑니다.', 'user4', 'art-001'), -('cmt-001-4', '반가워요 테스트유저님.', 'user5', 'art-001'), -('cmt-001-5', '저도 작성해봅니다.', 'ti', 'art-001'), +INSERT INTO comment (content, user_id, article_id) VALUES +('첫 번째 댓글입니다!', 'user2', 1), +( '좋은 글이네요.', 'user3', 1), +('공감하고 갑니다.', 'user4', 1), +( '반가워요 테스트유저님.', 'user5', 1), +( '저도 작성해봅니다.', 'ti', 1), --- art-002에 대한 댓글 5개 -('cmt-002-1', '철수님 안녕하세요.', 'ti', 'art-002'), -('cmt-002-2', '와 신기하네요.', 'user3', 'art-002'), -('cmt-002-3', '잘 읽었습니다.', 'user4', 'art-002'), -('cmt-002-4', '내용이 알차네요.', 'user5', 'art-002'), -('cmt-002-5', '댓글 남깁니다!', 'user2', 'art-002'), +('철수님 안녕하세요.', 'ti', 2), +( '와 신기하네요.', 'user3', 2), +( '잘 읽었습니다.', 'user4', 2), +( '내용이 알차네요.', 'user5', 2), +( '댓글 남깁니다!', 'user2', 2), --- art-003에 대한 댓글 5개 -('cmt-003-1', '오늘 날씨 정말 좋죠?', 'user4', 'art-003'), -('cmt-003-2', '영희님 글 솜씨가 좋으시네요.', 'user5', 'art-003'), -('cmt-003-3', '좋은 정보 감사합니다.', 'ti', 'art-003'), -('cmt-003-4', '저도 궁금했던 내용이에요.', 'user2', 'art-003'), -('cmt-003-5', '하하하 그렇군요.', 'user3', 'art-003'), +( '오늘 날씨 정말 좋죠?', 'user4', 3), +( '영희님 글 솜씨가 좋으시네요.', 'user5', 3), +( '좋은 정보 감사합니다.', 'ti', 3), +( '저도 궁금했던 내용이에요.', 'user2', 3), +( '하하하 그렇군요.', 'user3', 3), --- art-004에 대한 댓글 5개 -('cmt-004-1', '박지성 화이팅!', 'user5', 'art-004'), -('cmt-004-2', '역시 전설이네요.', 'ti', 'art-004'), -('cmt-004-3', '대단합니다.', 'user2', 'art-004'), -('cmt-004-4', '멋진 글입니다.', 'user3', 'art-004'), -('cmt-004-5', '축구 소식 더 알려주세요.', 'user4', 'art-004'), +( '박지성 화이팅!', 'user5', 4), +( '역시 전설이네요.', 'ti', 4), +( '대단합니다.', 'user2', 4), +( '멋진 글입니다.', 'user3', 4), +( '축구 소식 더 알려주세요.', 'user4', 4), --- art-005에 대한 댓글 5개 -('cmt-005-1', '손흥민 골!', 'ti', 'art-005'), -('cmt-005-2', '주말 경기 기대되네요.', 'user2', 'art-005'), -('cmt-005-3', '최고입니다.', 'user3', 'art-005'), -('cmt-005-4', '항상 응원합니다.', 'user4', 'art-005'), -('cmt-005-5', 'EPL 소식 감사합니다.', 'user5', 'art-005'); \ No newline at end of file +('손흥민 골!', 'ti', 5), +( '주말 경기 기대되네요.', 'user2', 5), +( '최고입니다.', 'user3', 5), +( '항상 응원합니다.', 'user4', 5), +( 'EPL 소식 감사합니다.', 'user5', 5); \ No newline at end of file diff --git a/src/main/resources/static/article/index.html b/src/main/resources/static/article/index.html index 33ddc2c7e..959af5356 100644 --- a/src/main/resources/static/article/index.html +++ b/src/main/resources/static/article/index.html @@ -12,7 +12,7 @@
  • - 안녕하세요, {{name}}님!    + 안녕하세요, {{name}}님!    글쓰기
  • @@ -88,6 +88,9 @@

    게시글 작성

    } } // 2. 실패 응답 처리 (400대 또는 500대 에러인 경우만) + else if(response.status === 401) { + window.location.href = '/login'; + } else if (response.status >= 400 && response.status < 600) { alert("작성에 실패했습니다. 다시 시도해주세요. (에러 코드: " + response.status + ")"); } diff --git a/src/main/resources/static/comment/index.html b/src/main/resources/static/comment/index.html index fcca57cf4..e67abcaba 100644 --- a/src/main/resources/static/comment/index.html +++ b/src/main/resources/static/comment/index.html @@ -12,7 +12,7 @@
    • - 안녕하세요, {{name}}님!    + 안녕하세요, {{name}}님!    글쓰기
    • @@ -24,20 +24,23 @@

      댓글 작성

      -
      + + +

      내용

      diff --git a/src/main/resources/static/main/index.html b/src/main/resources/static/main/index.html index d48d89a4e..2d8aab39d 100644 --- a/src/main/resources/static/main/index.html +++ b/src/main/resources/static/main/index.html @@ -6,13 +6,27 @@ +
      -
      + +
      -
      -
      - -
      - -
      -
        -
      • - -
      • -
      • - -
      • -
      - -
      -

      - {{content}} -

      -
      -
        -
      • -
        - -

        account

        -
        -

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

        -
      • -
      • -
        - -

        account

        -
        -

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

        -
      • -
      • -
        - -

        account

        -
        -

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

        + +
        + + +
        + +
        +
        +
        + -

        Comment 2

        -
      • - - -
      -
      +
        +
      • +
        + user +

        {{comment.writerName}}

        +
        +

        + {{comment.content}} +

      • + +
      - + + +
      + +
      +

      게시글이 존재하지 않습니다.

      +
      +
      + + - + \ No newline at end of file diff --git a/src/main/resources/static/mypage/index.html b/src/main/resources/static/mypage/index.html index 9c7ba0241..50c7254e9 100644 --- a/src/main/resources/static/mypage/index.html +++ b/src/main/resources/static/mypage/index.html @@ -6,27 +6,19 @@ @@ -37,7 +29,7 @@
      • - 안녕하세요, {{name}}님!    + 안녕하세요, {{name}}님!    글쓰기
      • @@ -76,12 +68,14 @@

        마이페이지

        닉네임

        - +

        비밀번호

        - +
        @@ -99,11 +93,10 @@

        마이페이지

        - + \ No newline at end of file diff --git a/src/test/java/handler/LoginHandlerTest.java b/src/test/java/handler/LoginHandlerTest.java index cd3768160..69959e48c 100644 --- a/src/test/java/handler/LoginHandlerTest.java +++ b/src/test/java/handler/LoginHandlerTest.java @@ -10,7 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import db.DatabaseConfig; +import db.config.DatabaseConfig; import db.UserDatabase; import enums.ContentTypes; import enums.HttpHeader;