diff --git a/.gitignore b/.gitignore index 6fbb1a503..94da6c9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -197,3 +197,5 @@ gradle-app.setting *.hprof # End of https://www.toptal.com/developers/gitignore/api/gradle,intellij,java,macos + +**/uploads \ No newline at end of file diff --git a/src/main/java/db/ArticleRepositoryImpl.java b/src/main/java/db/ArticleRepositoryImpl.java index 9c4247632..050f93d3e 100644 --- a/src/main/java/db/ArticleRepositoryImpl.java +++ b/src/main/java/db/ArticleRepositoryImpl.java @@ -15,7 +15,7 @@ public class ArticleRepositoryImpl implements ArticleRepository { private static final Logger logger = LoggerFactory.getLogger(ArticleRepositoryImpl.class); public Article save(Article article) { - String sql = "insert into article_tbl(creatorId, title, content) values(?, ?, ?)"; + String sql = "insert into article_tbl(creatorId, title, content, image_url) values(?, ?, ?, ?)"; try ( Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); PreparedStatement pstmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); @@ -23,6 +23,7 @@ public Article save(Article article) { pstmt.setLong(1, article.getCreatorId()); pstmt.setString(2, article.getTitle()); pstmt.setString(3, article.getContent()); + pstmt.setString(4, article.getImageUrl()); pstmt.executeUpdate(); @@ -41,7 +42,7 @@ public Article save(Article article) { @Override public List
findTopNLessThanByIdDecreasingOrder(int limit, long id) { - String sql = "select id, creatorId, title, content from article_tbl where id < ? order by id desc limit ?"; + String sql = "select id, creatorId, title, content, image_url from article_tbl where id < ? order by id desc limit ?"; try ( Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); @@ -58,7 +59,8 @@ public List
findTopNLessThanByIdDecreasingOrder(int limit, long id) { long creatorId = rs.getLong("creatorId"); String title = rs.getString("title"); String content = rs.getString("content"); - articles.add(new Article(articleId, creatorId, title, content)); + String imageUrl = rs.getString("image_url"); + articles.add(new Article(articleId, creatorId, title, content, imageUrl)); } } return articles; diff --git a/src/main/java/db/UserRepository.java b/src/main/java/db/UserRepository.java index ca9d999bc..c31d4365c 100644 --- a/src/main/java/db/UserRepository.java +++ b/src/main/java/db/UserRepository.java @@ -9,4 +9,5 @@ public interface UserRepository { User save(User user); Optional findByEmail(String email); Optional findById(Long id); + User update(User user); } diff --git a/src/main/java/db/UserRepositoryImpl.java b/src/main/java/db/UserRepositoryImpl.java index f215c6b16..90bd0ad39 100644 --- a/src/main/java/db/UserRepositoryImpl.java +++ b/src/main/java/db/UserRepositoryImpl.java @@ -14,7 +14,7 @@ public class UserRepositoryImpl implements UserRepository { private static final Logger logger = LoggerFactory.getLogger(UserRepository.class); public User save(User user) { - String sql = "insert into user_tbl(password, nickname, email) values(?, ?, ?)"; + String sql = "insert into user_tbl(password, nickname, email, image_url) values(?, ?, ?)"; try ( Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); @@ -25,6 +25,7 @@ public User save(User user) { pstmt.setString(1, user.getPassword()); pstmt.setString(2, user.getName()); pstmt.setString(3, user.getEmail()); + pstmt.setString(4, user.getImageUrl()); pstmt.executeUpdate(); @@ -43,7 +44,7 @@ public User save(User user) { } public Optional findByEmail(String email) { - String sql = "select id, password, nickname from user_tbl where email = ?"; + String sql = "select id, password, nickname, image_url from user_tbl where email = ?"; try ( Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); @@ -61,7 +62,8 @@ public Optional findByEmail(String email) { long id = rs.getLong("id"); String password = rs.getString("password"); String nickname = rs.getString("nickname"); - user = new User(id, password, nickname, email); + String imageUrl = rs.getString("image_url"); + user = new User(id, password, nickname, email, imageUrl); } return Optional.of(user); @@ -73,7 +75,7 @@ public Optional findByEmail(String email) { @Override public Optional findById(Long id) { - String sql = "select password, nickname, email from user_tbl where id = ?"; + String sql = "select password, nickname, email, image_url from user_tbl where id = ?"; try ( Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); @@ -91,7 +93,8 @@ public Optional findById(Long id) { String password = rs.getString("password"); String nickname = rs.getString("nickname"); String email = rs.getString("email"); - user = new User(id, password, nickname, email); + String imageUrl = rs.getString("image_url"); + user = new User(id, password, nickname, email, imageUrl); } return Optional.of(user); @@ -100,4 +103,34 @@ public Optional findById(Long id) { throw new RuntimeException(e); } } + + @Override + public User update(User user) { + String sql = "update user_tbl set password = ?, nickname = ?, email = ?, image_url = ? where id = ?"; + + try ( + Connection con = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); + PreparedStatement pstmt = con.prepareStatement(sql); + ) { + logger.debug("DB 연결 성공"); + + pstmt.setString(1, user.getPassword()); + pstmt.setString(2, user.getName()); + pstmt.setString(3, user.getEmail()); + pstmt.setString(4, user.getImageUrl()); + pstmt.setLong(5, user.getId()); + + int updatedRows = pstmt.executeUpdate(); + + if (updatedRows == 0) { + throw new SQLException("업데이트 실패: 해당 id 없음"); + } + + return user; + } catch (SQLException e) { + logger.error("DB 업데이트 실패", e); + throw new RuntimeException(e); + } + } + } \ No newline at end of file diff --git a/src/main/java/model/Article.java b/src/main/java/model/Article.java index 7d02b6d9e..abdf5b7ad 100644 --- a/src/main/java/model/Article.java +++ b/src/main/java/model/Article.java @@ -6,18 +6,21 @@ public class Article { private String writerName; private String title; private String content; + private String imageUrl; - public Article(Long id, Long creatorId, String title, String content) { + public Article(Long id, Long creatorId, String title, String content, String imageUrl) { this.id = id; this.creatorId = creatorId; this.title = title; this.content = content; + this.imageUrl = imageUrl; } - public Article(Long creatorId, String title, String content) { + public Article(Long creatorId, String title, String content, String imageUrl) { this.creatorId = creatorId; this.title = title; this.content = content; + this.imageUrl = imageUrl; } public Long getId() { @@ -40,6 +43,10 @@ public String getContent() { return content; } + public String getImageUrl() { + return imageUrl; + } + public void setId(Long id) { this.id = id; } diff --git a/src/main/java/model/User.java b/src/main/java/model/User.java index c4593aa3c..0fdcce95e 100644 --- a/src/main/java/model/User.java +++ b/src/main/java/model/User.java @@ -7,12 +7,14 @@ public class User { private String password; private String name; private String email; + private String imageUrl; - public User(Long id, String password, String name, String email) { + public User(Long id, String password, String name, String email, String imageUrl) { this.id = id; this.password = password; this.name = name; this.email = email; + this.imageUrl = imageUrl; } public User(String password, String name, String email) { @@ -51,4 +53,15 @@ public void setId(Long id) { public String toString() { return "User [userId=" + id + ", password=" + password + ", name=" + name + ", email=" + email + "]"; } + + public void changeProfileImage(String imageUrl) { + if(imageUrl == null){ + throw new BusinessException(); + } + this.imageUrl = imageUrl; + } + + public String getImageUrl() { + return imageUrl; + } } diff --git a/src/main/java/webserver/handler/CreateArticleFormHandler.java b/src/main/java/webserver/handler/CreateArticleFormHandler.java index 4fe7c00ed..f6661ad1c 100644 --- a/src/main/java/webserver/handler/CreateArticleFormHandler.java +++ b/src/main/java/webserver/handler/CreateArticleFormHandler.java @@ -4,8 +4,8 @@ import webserver.http.Response; import webserver.mvc.Handler; import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; -import webserver.mvc.StaticResourceView; +import webserver.view.RedirectView; +import webserver.view.StaticResourceView; import webserver.util.AuthUtil; public class CreateArticleFormHandler implements Handler { diff --git a/src/main/java/webserver/handler/CreateArticleHandler.java b/src/main/java/webserver/handler/CreateArticleHandler.java index a51cf9f96..bffc8c2ef 100644 --- a/src/main/java/webserver/handler/CreateArticleHandler.java +++ b/src/main/java/webserver/handler/CreateArticleHandler.java @@ -5,10 +5,14 @@ import model.User; import webserver.http.Request; import webserver.http.Response; -import webserver.mvc.Handler; -import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; +import webserver.mvc.*; import webserver.util.AuthUtil; +import webserver.mvc.ModelAndView; +import webserver.util.MultipartFileUtil; +import webserver.view.RedirectView; +import webserver.view.StaticResourceView; + +import java.io.IOException; public class CreateArticleHandler implements Handler { private final ArticleRepository articleRepository; @@ -26,7 +30,20 @@ public ModelAndView handle(Request request, Response response) { String title = request.getParameter("title"); String content = request.getParameter("content"); - articleRepository.save(new Article(user.getId(), title, content)); + if(request.getMultipartFiles().size() != 1){ + return new StaticResourceView("/article/error.html"); + } + + MultipartFile multipartFile = request.getMultipartFiles().get(0); + + String imageUrl = null; + try { + imageUrl = MultipartFileUtil.saveFile("uploads/images", multipartFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + + articleRepository.save(new Article(user.getId(), title, content, imageUrl)); return new RedirectView("/"); } diff --git a/src/main/java/webserver/handler/CreateUserHandler.java b/src/main/java/webserver/handler/CreateUserHandler.java index 7278c57cb..cb5c5375a 100644 --- a/src/main/java/webserver/handler/CreateUserHandler.java +++ b/src/main/java/webserver/handler/CreateUserHandler.java @@ -9,7 +9,7 @@ import webserver.http.Response; import webserver.mvc.Handler; import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; +import webserver.view.RedirectView; public class CreateUserHandler implements Handler { private static final Logger logger = LoggerFactory.getLogger(CreateUserHandler.class); diff --git a/src/main/java/webserver/handler/GetImageHandler.java b/src/main/java/webserver/handler/GetImageHandler.java new file mode 100644 index 000000000..34754914d --- /dev/null +++ b/src/main/java/webserver/handler/GetImageHandler.java @@ -0,0 +1,19 @@ +package webserver.handler; + +import webserver.exception.StaticResourceNotFoundException; +import webserver.http.Request; +import webserver.http.Response; +import webserver.mvc.Handler; +import webserver.view.UploadedFileView; +import webserver.mvc.ModelAndView; + +public class GetImageHandler implements Handler { + @Override + public ModelAndView handle(Request request, Response response) { + String imageFileUrl = request.getParameter("imageUrl"); + if(imageFileUrl == null){ + throw new StaticResourceNotFoundException(); + } + return new UploadedFileView("/uploads/images/" + imageFileUrl); + } +} diff --git a/src/main/java/webserver/handler/LoginFormHandler.java b/src/main/java/webserver/handler/LoginFormHandler.java index caa11a7cb..86a2c2fd8 100644 --- a/src/main/java/webserver/handler/LoginFormHandler.java +++ b/src/main/java/webserver/handler/LoginFormHandler.java @@ -6,7 +6,7 @@ import webserver.http.Response; import webserver.mvc.Handler; import webserver.mvc.ModelAndView; -import webserver.mvc.StaticResourceView; +import webserver.view.StaticResourceView; public class LoginFormHandler implements Handler { private static final Logger logger = LoggerFactory.getLogger(LoginFormHandler.class); diff --git a/src/main/java/webserver/handler/LoginHandler.java b/src/main/java/webserver/handler/LoginHandler.java index bc09b911e..05e83819e 100644 --- a/src/main/java/webserver/handler/LoginHandler.java +++ b/src/main/java/webserver/handler/LoginHandler.java @@ -1,6 +1,5 @@ package webserver.handler; -import db.Database; import db.UserRepository; import model.User; import org.slf4j.Logger; @@ -8,8 +7,8 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.Handler; -import webserver.mvc.RedirectView; -import webserver.mvc.StaticResourceView; +import webserver.view.RedirectView; +import webserver.view.StaticResourceView; import webserver.session.SessionStore; import webserver.mvc.ModelAndView; diff --git a/src/main/java/webserver/handler/LogoutHandler.java b/src/main/java/webserver/handler/LogoutHandler.java index b1bb19a96..8673ecc79 100644 --- a/src/main/java/webserver/handler/LogoutHandler.java +++ b/src/main/java/webserver/handler/LogoutHandler.java @@ -5,7 +5,7 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.Handler; -import webserver.mvc.RedirectView; +import webserver.view.RedirectView; import webserver.util.AuthUtil; import webserver.session.SessionStore; import webserver.mvc.ModelAndView; diff --git a/src/main/java/webserver/handler/MainHandler.java b/src/main/java/webserver/handler/MainHandler.java index 8c3bf361d..8309d1962 100644 --- a/src/main/java/webserver/handler/MainHandler.java +++ b/src/main/java/webserver/handler/MainHandler.java @@ -10,6 +10,8 @@ import webserver.http.Response; import webserver.mvc.*; import webserver.util.AuthUtil; +import webserver.view.MainPageDynamicView; +import webserver.mvc.ModelAndView; import java.util.HashMap; import java.util.List; diff --git a/src/main/java/webserver/handler/MyPageHandler.java b/src/main/java/webserver/handler/MyPageHandler.java index 2ebd88024..a33ee8bf7 100644 --- a/src/main/java/webserver/handler/MyPageHandler.java +++ b/src/main/java/webserver/handler/MyPageHandler.java @@ -1,19 +1,42 @@ package webserver.handler; +import db.UserRepository; +import model.User; +import webserver.exception.BusinessException; import webserver.http.Request; import webserver.http.Response; -import webserver.mvc.Handler; -import webserver.mvc.RedirectView; -import webserver.mvc.StaticResourceView; -import webserver.util.AuthUtil; +import webserver.mvc.*; import webserver.mvc.ModelAndView; +import webserver.view.MyPageDynamicView; +import webserver.util.AuthUtil; +import webserver.view.RedirectView; + +import java.util.HashMap; public class MyPageHandler implements Handler { + private final UserRepository userRepository; + + public MyPageHandler(UserRepository userRepository) { + this.userRepository = userRepository; + } + @Override public ModelAndView handle(Request request, Response response) { if(!AuthUtil.isAuthenticatedUser(request)){ return new RedirectView("/login"); } - return new StaticResourceView("/mypage/index.html"); + + User cachedUser = AuthUtil.getAuthenticatedUser(request); + + User user = userRepository.findByEmail(cachedUser.getEmail()) + .orElseThrow(() -> new BusinessException()); + + HashMap model = new HashMap<>(); + model.put("name", user.getName()); + String imageUrl = user.getImageUrl(); + if(imageUrl != null){ + model.put("profile_image", imageUrl); + } + return new MyPageDynamicView(model, "/mypage/index.html"); } } diff --git a/src/main/java/webserver/handler/PatchMyPageHandler.java b/src/main/java/webserver/handler/PatchMyPageHandler.java new file mode 100644 index 000000000..4947b7eba --- /dev/null +++ b/src/main/java/webserver/handler/PatchMyPageHandler.java @@ -0,0 +1,52 @@ +package webserver.handler; + +import db.UserRepository; +import model.User; +import webserver.exception.BusinessException; +import webserver.http.Request; +import webserver.http.Response; +import webserver.mvc.Handler; +import webserver.mvc.ModelAndView; +import webserver.mvc.MultipartFile; +import webserver.util.AuthUtil; +import webserver.util.MultipartFileUtil; +import webserver.view.RedirectView; + +import java.io.IOException; + +public class PatchMyPageHandler implements Handler { + private final UserRepository userRepository; + + public PatchMyPageHandler(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public ModelAndView handle(Request request, Response response) { + if (!AuthUtil.isAuthenticatedUser(request)) { + return new RedirectView("/login"); + } + + User cachedUser = AuthUtil.getAuthenticatedUser(request); + User userByEmail = userRepository.findByEmail(cachedUser.getEmail()) + .orElseThrow(() -> new BusinessException()); + + if (request.getMultipartFiles().size() != 1) { + return new RedirectView("/mypage"); + } + MultipartFile multipartFile = request.getMultipartFiles().get(0); + + String imageUrl = null; + try { + imageUrl = MultipartFileUtil.saveFile("uploads/images", multipartFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + + userByEmail.changeProfileImage(imageUrl); + + userRepository.update(userByEmail); + + return new RedirectView("/mypage"); + } +} diff --git a/src/main/java/webserver/handler/RegisterFormHandler.java b/src/main/java/webserver/handler/RegisterFormHandler.java index 985ea3032..b00b07d63 100644 --- a/src/main/java/webserver/handler/RegisterFormHandler.java +++ b/src/main/java/webserver/handler/RegisterFormHandler.java @@ -6,7 +6,7 @@ import webserver.http.Response; import webserver.mvc.Handler; import webserver.mvc.ModelAndView; -import webserver.mvc.StaticResourceView; +import webserver.view.StaticResourceView; public class RegisterFormHandler implements Handler { private static final Logger logger = LoggerFactory.getLogger(RegisterFormHandler.class); diff --git a/src/main/java/webserver/handler/StaticResourceHandler.java b/src/main/java/webserver/handler/StaticResourceHandler.java index 98405b20d..11640a10d 100644 --- a/src/main/java/webserver/handler/StaticResourceHandler.java +++ b/src/main/java/webserver/handler/StaticResourceHandler.java @@ -5,7 +5,7 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.Handler; -import webserver.mvc.StaticResourceView; +import webserver.view.StaticResourceView; import webserver.mvc.ModelAndView; public class StaticResourceHandler implements Handler { diff --git a/src/main/java/webserver/http/Request.java b/src/main/java/webserver/http/Request.java index d5ee80a2f..419df10f4 100644 --- a/src/main/java/webserver/http/Request.java +++ b/src/main/java/webserver/http/Request.java @@ -73,6 +73,10 @@ public List getMultipartFiles() { return multipartFiles; } + public void setParameter(String name, String value) { + params.put(name, value); + } + @Override public String toString() { diff --git a/src/main/java/webserver/mvc/PathVariableRouting.java b/src/main/java/webserver/mvc/PathVariableRouting.java new file mode 100644 index 000000000..db79f4e70 --- /dev/null +++ b/src/main/java/webserver/mvc/PathVariableRouting.java @@ -0,0 +1,58 @@ +package webserver.mvc; + +import webserver.http.Request; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class PathVariableRouting implements Routing { + private Map handlerMap; + private List pathVariableNames = new ArrayList<>(); + private Pattern pattern; + + public PathVariableRouting(String uri, Map handlerMap) { + this.handlerMap = handlerMap; + String regex = Arrays.stream(uri.split("/")) + .map(token -> { + if (token.startsWith("{") && token.endsWith("}")) { + String varName = token.substring(1, token.length() - 1); + pathVariableNames.add(varName); + return "([^/]+)"; + } + return token; + }) + .collect(Collectors.joining("/")); + + this.pattern = Pattern.compile(regex); + } + + @Override + public boolean supportsUri(String uri) { + return pattern.matcher(uri).matches(); + } + + @Override + public boolean supportsMethod(String method) { + return handlerMap.containsKey(method); + } + + @Override + public Handler resolveHandler(Request request) { + Matcher matcher = pattern.matcher(request.getPath()); + + if (matcher.matches()) { + for (int i = 0; i < pathVariableNames.size(); i++) { + String name = pathVariableNames.get(i); + String value = matcher.group(i + 1); // group(1)부터 시작 + request.setParameter(name, value); + } + } + + return handlerMap.get(request.getMethod()); + } +} diff --git a/src/main/java/webserver/mvc/RequestHandler.java b/src/main/java/webserver/mvc/RequestHandler.java index f78917444..6b0180d58 100644 --- a/src/main/java/webserver/mvc/RequestHandler.java +++ b/src/main/java/webserver/mvc/RequestHandler.java @@ -2,7 +2,8 @@ import java.io.*; import java.net.Socket; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import db.ArticleRepository; @@ -11,17 +12,18 @@ import db.UserRepositoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import webserver.exception.BadRequestException; import webserver.exception.MethodNotAllowedException; -import webserver.exception.RequestParsingException; import webserver.exception.StaticResourceNotFoundException; import webserver.handler.*; import webserver.http.Request; import webserver.http.Response; import webserver.session.SessionStore; +import webserver.view.StaticResourceView; public class RequestHandler implements Runnable { private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class); - private static Map> routingTable; + private static List routingTable; private static Handler staticResourceHandler; private static SessionStore sessionStore; private Socket connection; @@ -29,15 +31,18 @@ public class RequestHandler implements Runnable { static{ UserRepository userRepository = new UserRepositoryImpl(); ArticleRepository articleRepository = new ArticleRepositoryImpl(); - routingTable = new HashMap<>(); - routingTable.put("/", Map.of("GET", new MainHandler(articleRepository, userRepository))); - routingTable.put("/registration", Map.of("GET", new RegisterFormHandler())); - routingTable.put("/create", Map.of("POST", new CreateUserHandler(userRepository))); - routingTable.put("/login", Map.of("GET", new LoginFormHandler(), "POST", new LoginHandler(userRepository))); - routingTable.put("/logout", Map.of("POST", new LogoutHandler())); - routingTable.put("/mypage", Map.of("GET", new MyPageHandler())); - routingTable.put("/article/create-form", Map.of("GET", new CreateArticleFormHandler())); - routingTable.put("/article", Map.of("POST", new CreateArticleHandler(articleRepository))); + + routingTable = new ArrayList<>(); + routingTable.add(new SimpleRouting("/", Map.of("GET", new MainHandler(articleRepository, userRepository)))); + routingTable.add(new SimpleRouting("/registration", Map.of("GET", new RegisterFormHandler()))); + routingTable.add(new SimpleRouting("/create", Map.of("POST", new CreateUserHandler(userRepository)))); + routingTable.add(new SimpleRouting("/login", Map.of("GET", new LoginFormHandler(), "POST", new LoginHandler(userRepository)))); + routingTable.add(new SimpleRouting("/logout", Map.of("POST", new LogoutHandler()))); + routingTable.add(new SimpleRouting("/mypage", Map.of("GET", new MyPageHandler(userRepository), "PATCH", new PatchMyPageHandler(userRepository)))); + routingTable.add(new SimpleRouting("/article/create-form", Map.of("GET", new CreateArticleFormHandler()))); + routingTable.add(new SimpleRouting("/article", Map.of("POST", new CreateArticleHandler(articleRepository)))); + routingTable.add(new PathVariableRouting("/uploads/images/{imageUrl}", Map.of("GET", new GetImageHandler()))); + staticResourceHandler = new StaticResourceHandler(); sessionStore = new SessionStore(); } @@ -71,7 +76,7 @@ private void processRequest(OutputStream out, InputStream in) throws IOException logger.debug("request parsing complete"); logger.debug("request={}", request); - Handler handler = resovleHandler(request.getPath(), request.getMethod()); + Handler handler = resovleHandler(request); ModelAndView mav = handler.handle(request, response); mav.render(response); @@ -88,9 +93,10 @@ private void processRequest(OutputStream out, InputStream in) throws IOException logger.debug("method not allowed error", e); sendErrorPage(responseWriter, Response.methodNotAllowed(), new StaticResourceView("/error/405_error.html")); } - catch (RequestParsingException e){ + catch (BadRequestException e){ logger.debug("request parsing error", e); - responseWriter.write(Response.badRequest()); + sendErrorPage(responseWriter, Response.badRequest(), new StaticResourceView("/error/400_error.html")); + } catch (RuntimeException e){ logger.debug("internal server error", e); @@ -103,14 +109,14 @@ private static void sendErrorPage(ResponseWriter responseWriter, Response respon responseWriter.write(response); } - private Handler resovleHandler(String uri, String method) { - Map methodHandlers = routingTable.get(uri); - if(methodHandlers != null){ - Handler handler = methodHandlers.get(method); - if(handler == null){ - throw new MethodNotAllowedException(); + private Handler resovleHandler(Request request) { + for(Routing routing : routingTable){ + if(routing.supportsUri(request.getPath())){ + if(!routing.supportsMethod(request.getMethod())){ + throw new MethodNotAllowedException(); + } + return routing.resolveHandler(request); } - return handler; } return staticResourceHandler; } diff --git a/src/main/java/webserver/mvc/RequestParser.java b/src/main/java/webserver/mvc/RequestParser.java index 7d2a6fa25..be3b8e0f7 100644 --- a/src/main/java/webserver/mvc/RequestParser.java +++ b/src/main/java/webserver/mvc/RequestParser.java @@ -5,6 +5,7 @@ import webserver.exception.RequestParsingException; import java.io.*; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -86,7 +87,7 @@ private void parseRequestLineAndHeaders(byte[] headerBytes) { private void parsePathAndQuery(String url) { String[] tokens = url.split("\\?", 2); - path = tokens[0]; + path = URLDecoder.decode(tokens[0], StandardCharsets.UTF_8);; if (tokens.length == 2) { parseQueryString(tokens[1]); diff --git a/src/main/java/webserver/mvc/Routing.java b/src/main/java/webserver/mvc/Routing.java new file mode 100644 index 000000000..cc8bff211 --- /dev/null +++ b/src/main/java/webserver/mvc/Routing.java @@ -0,0 +1,11 @@ +package webserver.mvc; + +import webserver.http.Request; + +public interface Routing { + boolean supportsUri(String uri); + + boolean supportsMethod(String method); + + Handler resolveHandler(Request method); +} diff --git a/src/main/java/webserver/mvc/SimpleRouting.java b/src/main/java/webserver/mvc/SimpleRouting.java new file mode 100644 index 000000000..00ecdec61 --- /dev/null +++ b/src/main/java/webserver/mvc/SimpleRouting.java @@ -0,0 +1,30 @@ +package webserver.mvc; + +import webserver.http.Request; + +import java.util.Map; + +public class SimpleRouting implements Routing { + private String uri; + private Map handlerMap; + + public SimpleRouting(String uri, Map handlerMap) { + this.uri = uri; + this.handlerMap = handlerMap; + } + + @Override + public boolean supportsUri(String uri) { + return this.uri.equals(uri); + } + + @Override + public boolean supportsMethod(String method) { + return this.handlerMap.containsKey(method); + } + + @Override + public Handler resolveHandler(Request request) { + return handlerMap.get(request.getMethod()); + } +} diff --git a/src/main/java/webserver/util/MultipartFileUtil.java b/src/main/java/webserver/util/MultipartFileUtil.java new file mode 100644 index 000000000..2ff9e736d --- /dev/null +++ b/src/main/java/webserver/util/MultipartFileUtil.java @@ -0,0 +1,39 @@ +package webserver.util; + +import webserver.mvc.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.UUID; + +public class MultipartFileUtil { + public static String saveFile(String directoryPath, MultipartFile multipartFile) throws IOException { + if (directoryPath == null || directoryPath.isBlank()) { + throw new IllegalArgumentException(); + } + + String filename = UUID.randomUUID() + multipartFile.getFilename(); + + Path uploadDir = Paths.get(System.getProperty("user.dir")) + .resolve(directoryPath); + + Files.createDirectories(uploadDir); + + Path target = uploadDir.resolve(filename); + + try (InputStream in = multipartFile.getInputStream(); + OutputStream out = Files.newOutputStream(target, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)) { + + in.transferTo(out); + } + + return directoryPath + "/" + filename; + } +} diff --git a/src/main/java/webserver/mvc/MainPageDynamicView.java b/src/main/java/webserver/view/MainPageDynamicView.java similarity index 84% rename from src/main/java/webserver/mvc/MainPageDynamicView.java rename to src/main/java/webserver/view/MainPageDynamicView.java index f67929baa..71a96d5ca 100644 --- a/src/main/java/webserver/mvc/MainPageDynamicView.java +++ b/src/main/java/webserver/view/MainPageDynamicView.java @@ -1,4 +1,4 @@ -package webserver.mvc; +package webserver.view; import model.Article; import org.slf4j.Logger; @@ -6,6 +6,7 @@ import webserver.constant.FileMimeType; import webserver.exception.StaticResourceNotFoundException; import webserver.http.Response; +import webserver.mvc.ModelAndView; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -14,7 +15,7 @@ import java.nio.file.Paths; import java.util.Map; -public class MainPageDynamicView implements ModelAndView{ +public class MainPageDynamicView implements ModelAndView { private static final Logger logger = LoggerFactory.getLogger(MainPageDynamicView.class); private Map model; private String viewName; @@ -31,27 +32,26 @@ public String getViewName() { @Override public void render(Response response) { - try{ + try { Path filePath = Paths.get("./src/main/resources/static" + getViewName()); String baseHtml = Files.readString(filePath, StandardCharsets.UTF_8); - logger.debug("before baseHtml = {} " , baseHtml); + logger.debug("before baseHtml = {} ", baseHtml); - if(model.containsKey("name")){ + if (model.containsKey("name")) { Path templatePath = Paths.get("./src/main/resources/static" + "/template/header_menu_logined.html"); String headerMenuTemplate = Files.readString(templatePath, StandardCharsets.UTF_8); logger.debug("headerMenuTemplate= {}", headerMenuTemplate); headerMenuTemplate = headerMenuTemplate.replace("${{name}}", (String) model.get("name")); baseHtml = baseHtml.replace("${{header_menu}}", headerMenuTemplate); - } - else{ + } else { Path templatePath = Paths.get("./src/main/resources/static" + "/template/header_menu_public.html"); String headerMenuTemplate = Files.readString(templatePath, StandardCharsets.UTF_8); logger.debug("headerMenuTemplate= {}", headerMenuTemplate); baseHtml = baseHtml.replace("${{header_menu}}", headerMenuTemplate); } - if(model.containsKey("article")){ + if (model.containsKey("article")) { Article article = (Article) model.get("article"); Path templatePath = Paths.get("./src/main/resources/static" + "/template/article.html"); String articleTemplate = Files.readString(templatePath, StandardCharsets.UTF_8); @@ -59,26 +59,25 @@ public void render(Response response) { articleTemplate = articleTemplate.replace("${{writer}}", article.getWriterName()); articleTemplate = articleTemplate.replace("${{content}}", article.getContent()); + articleTemplate = articleTemplate.replace("${{image_url}}", article.getImageUrl()); - if(!model.containsKey("comments")){ + if (!model.containsKey("comments")) { articleTemplate = articleTemplate.replace("${{comments}}", "댓글이 없습니다"); articleTemplate = articleTemplate.replace("${{all-comments-button}}", ""); } baseHtml = baseHtml.replace("${{article}}", articleTemplate); - } - else{ + } else { baseHtml = baseHtml.replace("${{article}}", "첫 게시글을 써주세요"); } - logger.debug("after baseHtml = {} " , baseHtml); + logger.debug("after baseHtml = {} ", baseHtml); byte[] body = baseHtml.getBytes(StandardCharsets.UTF_8); String contentType = FileMimeType.resolveMimeType(getViewName()); response.setOk(body, contentType); - } - catch (IOException e) { + } catch (IOException e) { logger.error("error occurred while reading static resource"); throw new StaticResourceNotFoundException(); } diff --git a/src/main/java/webserver/view/MyPageDynamicView.java b/src/main/java/webserver/view/MyPageDynamicView.java new file mode 100644 index 000000000..b97c58071 --- /dev/null +++ b/src/main/java/webserver/view/MyPageDynamicView.java @@ -0,0 +1,61 @@ +package webserver.view; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import webserver.constant.FileMimeType; +import webserver.exception.StaticResourceNotFoundException; +import webserver.http.Response; +import webserver.mvc.ModelAndView; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +public class MyPageDynamicView implements ModelAndView { + private static final Logger logger = LoggerFactory.getLogger(MyPageDynamicView.class); + private Map model; + private String viewName; + + public MyPageDynamicView(Map model, String viewName) { + this.model = model; + this.viewName = viewName; + } + + @Override + public String getViewName() { + return viewName; + } + + @Override + public void render(Response response) { + try { + Path filePath = Paths.get("./src/main/resources/static" + viewName); + String baseHtml = Files.readString(filePath, StandardCharsets.UTF_8); + + logger.debug("before baseHtml = {} ", baseHtml); + + if (model.containsKey("name")) { + baseHtml = baseHtml.replace("${{name}}", (String) model.get("name")); + } + + if (model.containsKey("profile_image")) { + baseHtml = baseHtml.replace("${{profile_image}}", (String) model.get("profile_image")); + } + else{ + baseHtml = baseHtml.replace("${{profile_image}}", ""); + } + + logger.debug("after baseHtml = {} ", baseHtml); + + byte[] body = baseHtml.getBytes(StandardCharsets.UTF_8); + String contentType = FileMimeType.resolveMimeType(getViewName()); + response.setOk(body, contentType); + } catch (IOException e) { + logger.error("error occurred while reading static resource"); + throw new StaticResourceNotFoundException(); + } + } +} diff --git a/src/main/java/webserver/mvc/RedirectView.java b/src/main/java/webserver/view/RedirectView.java similarity index 86% rename from src/main/java/webserver/mvc/RedirectView.java rename to src/main/java/webserver/view/RedirectView.java index 1fd70316e..53b9711e2 100644 --- a/src/main/java/webserver/mvc/RedirectView.java +++ b/src/main/java/webserver/view/RedirectView.java @@ -1,6 +1,7 @@ -package webserver.mvc; +package webserver.view; import webserver.http.Response; +import webserver.mvc.ModelAndView; public class RedirectView implements ModelAndView { private String viewName; diff --git a/src/main/java/webserver/mvc/StaticResourceView.java b/src/main/java/webserver/view/StaticResourceView.java similarity index 90% rename from src/main/java/webserver/mvc/StaticResourceView.java rename to src/main/java/webserver/view/StaticResourceView.java index a51b56727..43b57505c 100644 --- a/src/main/java/webserver/mvc/StaticResourceView.java +++ b/src/main/java/webserver/view/StaticResourceView.java @@ -1,16 +1,17 @@ -package webserver.mvc; +package webserver.view; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import webserver.constant.FileMimeType; import webserver.exception.StaticResourceNotFoundException; import webserver.http.Response; +import webserver.mvc.ModelAndView; import java.io.File; import java.io.IOException; import java.nio.file.Files; -public class StaticResourceView implements ModelAndView{ +public class StaticResourceView implements ModelAndView { private static final Logger logger = LoggerFactory.getLogger(StaticResourceView.class); private String viewName; diff --git a/src/main/java/webserver/view/UploadedFileView.java b/src/main/java/webserver/view/UploadedFileView.java new file mode 100644 index 000000000..a204fd635 --- /dev/null +++ b/src/main/java/webserver/view/UploadedFileView.java @@ -0,0 +1,44 @@ +package webserver.view; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import webserver.constant.FileMimeType; +import webserver.exception.StaticResourceNotFoundException; +import webserver.http.Response; +import webserver.mvc.ModelAndView; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class UploadedFileView implements ModelAndView { + private static final Logger logger = LoggerFactory.getLogger(UploadedFileView.class); + private String viewName; + + public UploadedFileView(String viewName) { + this.viewName = viewName; + } + + @Override + public String getViewName() { + return viewName; + } + + @Override + public void render(Response response) { + try{ + String projectRoot = System.getProperty("user.dir"); + String pathname = projectRoot + getViewName(); + + logger.debug("pathname : {}", pathname); + + byte[] body = Files.readAllBytes(new File(pathname).toPath()); + String contentType = FileMimeType.resolveMimeType(getViewName()); + response.setOk(body, contentType); + } + catch (IOException e) { + logger.error("error occurred while reading static resource"); + throw new StaticResourceNotFoundException(); + } + } +} diff --git a/src/main/resources/static/article/error.html b/src/main/resources/static/article/error.html new file mode 100644 index 000000000..0c9a62e2e --- /dev/null +++ b/src/main/resources/static/article/error.html @@ -0,0 +1,61 @@ + + + + + + + + + +
+
+ +
    +
  • + 글쓰기 +
  • +
  • +
    + +
    +
  • +
+
+
+

게시글 작성

+
+
+

제목

+ +
+
+

내용

+ +
+ +

이미지는 필수 항목입니다

+ +
+
+
+ + diff --git a/src/main/resources/static/article/index.html b/src/main/resources/static/article/index.html index f53ef1087..1de055f1d 100644 --- a/src/main/resources/static/article/index.html +++ b/src/main/resources/static/article/index.html @@ -25,7 +25,7 @@

게시글 작성

-
+

제목

+ -
- +
  • + 글쓰기 +
  • +
  • +
    + +
    +
  • - -
    + +

    마이페이지

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

    닉네임

    - -
    -
    -

    비밀번호

    - -
    -
    -

    비밀번호 확인

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

    닉네임

    + +
    +
    +

    비밀번호

    + +
    +
    +

    비밀번호 확인

    + +
    +
    + type="button" + > + 변경사항 저장 + +
    +
    - +
    + + diff --git a/src/main/resources/static/template/article.html b/src/main/resources/static/template/article.html index e84fe38af..116f78795 100644 --- a/src/main/resources/static/template/article.html +++ b/src/main/resources/static/template/article.html @@ -3,7 +3,7 @@
    - +
    • diff --git a/src/test/java/webserver/handler/LoginHandlerTest.java b/src/test/java/webserver/handler/LoginHandlerTest.java index f4ce6f76f..fa7c2dc1e 100644 --- a/src/test/java/webserver/handler/LoginHandlerTest.java +++ b/src/test/java/webserver/handler/LoginHandlerTest.java @@ -7,8 +7,8 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; -import webserver.mvc.StaticResourceView; +import webserver.view.RedirectView; +import webserver.view.StaticResourceView; import webserver.session.SessionStore; import java.util.HashMap; diff --git a/src/test/java/webserver/handler/LogoutHandlerTest.java b/src/test/java/webserver/handler/LogoutHandlerTest.java index f2d28c4af..9fc1f632e 100644 --- a/src/test/java/webserver/handler/LogoutHandlerTest.java +++ b/src/test/java/webserver/handler/LogoutHandlerTest.java @@ -6,7 +6,7 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; +import webserver.view.RedirectView; import webserver.session.SessionStore; import java.util.HashMap; diff --git a/src/test/java/webserver/handler/MyPageHandlerTest.java b/src/test/java/webserver/handler/MyPageHandlerTest.java index d954ac908..fba49f244 100644 --- a/src/test/java/webserver/handler/MyPageHandlerTest.java +++ b/src/test/java/webserver/handler/MyPageHandlerTest.java @@ -6,8 +6,8 @@ import webserver.http.Request; import webserver.http.Response; import webserver.mvc.ModelAndView; -import webserver.mvc.RedirectView; -import webserver.mvc.StaticResourceView; +import webserver.view.RedirectView; +import webserver.view.StaticResourceView; import webserver.session.SessionStore; import java.util.HashMap;