Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/llm-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Claude Auto PR Review
on:
pull_request:
types: [opened, edited, synchronize]

permissions:
contents: read
pull-requests: write
checks: write
id-token: write

jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Simple LLM Code Review
uses: codingbaraGo/simple-llm-code-review@latest
with:
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
language: korean
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'org.assertj:assertj-core:3.16.1'


// h2 database
implementation 'com.h2database:h2:2.2.224'
}

test {
Expand Down
52 changes: 52 additions & 0 deletions src/main/java/db/ArticleDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package db;

import model.Article;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class ArticleDao {

// 게시글 저장
public void insert(Article article) {
String sql = "INSERT INTO ARTICLE (writer, title, contents) VALUES (?, ?, ?)";

try (Connection connection = ConnectionManager.getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql)) {

pstmt.setString(1, article.writer());
pstmt.setString(2, article.title());
pstmt.setString(3, article.contents());

pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException("Failed to save article: " + e.getMessage());
}
}

// 게시글 조회
public List<Article> selectAll() {
String sql = "SELECT * FROM ARTICLE ORDER BY createdAt DESC";
List<Article> articles = new ArrayList<>();

try (Connection connection = ConnectionManager.getConnection();
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {

while (rs.next()) {
Article article = new Article(
rs.getLong("id"),
rs.getString("writer"),
rs.getString("title"),
rs.getString("contents"),
rs.getTimestamp("createdAt").toLocalDateTime()
);
articles.add(article);
}
} catch (SQLException e) {
throw new RuntimeException("Failed to retrieve article list: {}", e);
}
return articles;
}
}
18 changes: 18 additions & 0 deletions src/main/java/db/ConnectionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package db;

import webserver.config.Config;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {
public static Connection getConnection() {
try {
Class.forName("org.h2.Driver");
return DriverManager.getConnection(Config.DB_URL, Config.DB_USER, Config.DB_PW);
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException("Failed to Connect SQL: " + e.getMessage());
}
}
}
53 changes: 53 additions & 0 deletions src/main/java/db/UserDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package db;

import model.User;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {

// 회원가입
public void insert(User user) {
String sql = "INSERT INTO USERS (userId, name, password, email) VALUES (?, ?, ?, ?)";

try (Connection connection = ConnectionManager.getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql)) {

pstmt.setString(1, user.userId());
pstmt.setString(2, user.name());
pstmt.setString(3, user.password());
pstmt.setString(4, user.email());

pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException("Failed to save user", e);
}
}

// ID로 유저 정보 찾기
public User findUserById(String userId) {
String sql = "SELECT * FROM USERS WHERE userId = ?";

try (Connection connection = ConnectionManager.getConnection();
PreparedStatement pstmt = connection.prepareStatement(sql)) {

pstmt.setString(1, userId);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getString("userId"),
rs.getString("password"),
rs.getString("name"),
rs.getString("email")
);
}
}
} catch (SQLException e) {
throw new RuntimeException("Failed to find user", e);
}
return null;
}
}
11 changes: 11 additions & 0 deletions src/main/java/model/Article.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package model;

import java.time.LocalDateTime;

public record Article(Long id, String writer, String title, String contents, LocalDateTime createdAt) {
public Article(String writer, String title, String contents) {
this(null, writer, title, contents, null);
}
}

// TODO writer -> writerId로 변경
18 changes: 17 additions & 1 deletion src/main/java/webserver/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void sendRedirect(String redirectUrl) {
processWrite(new byte[0]); // 바디 없음
}

public void fileResponse(String url, User loginUser) {
public void fileResponse(String url, User loginUser, Map<String, String> additionalModel) {
File file = new File(Config.STATIC_RESOURCE_PATH + url);
if (!file.exists()) {
sendError(HttpStatus.NOT_FOUND);
Expand All @@ -66,6 +66,10 @@ public void fileResponse(String url, User loginUser) {
Map<String, String> model = new HashMap<>();
model.put("header_items", PageRender.renderHeader(loginUser));

if (additionalModel != null) {
model.putAll(additionalModel);
}

content = TemplateEngine.render(content, model);

body = content.getBytes(Config.UTF_8);
Expand Down Expand Up @@ -122,4 +126,16 @@ private void setHttpHeader(String contentType, int contentLength) {
addHeader("Content-Type", contentType + ";charset=" + Config.UTF_8);
addHeader("Content-Length", String.valueOf(contentLength));
}

public void sendHtmlContent(String content) {
try {
byte[] body = content.getBytes(Config.UTF_8);
this.status = HttpStatus.OK;
setHttpHeader("text/html", body.length);
processWrite(body);
} catch (Exception e) {
logger.error("Error while encoding HTML content: {}", e.getMessage());
sendError(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/webserver/PageRender.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package webserver;

import model.Article;
import model.User;

import java.util.List;

public class PageRender {
public static String renderHeader(User loginUser) {
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -35,4 +38,31 @@ public static String renderHeader(User loginUser) {
}
return sb.toString();
}

public static String renderArticleList(List<Article> articles) {
if (articles.isEmpty()) {
return "<div class='post'><p class='post__article'>등록된 게시글이 없습니다.</p></div>";
}

StringBuilder sb = new StringBuilder();
for (Article article : articles) {
sb.append("<div class=\"post\">")
.append(" <div class=\"post__account\">")
.append(" <img class=\"post__account__img\" src=\"./img/default-avatar.svg\" />")
.append(" <p class=\"post__account__nickname\">").append(article.writer()).append("</p>")
.append(" </div>")
.append(" <div class=\"post__title\" style=\"font-weight:bold; margin: 10px 0;\">")
.append(article.title())
.append(" </div>")
.append(" <p class=\"post__article\">").append(article.contents()).append("</p>")
.append(" <div class=\"post__menu\">")
.append(" <ul class=\"post__menu__personal\">")
.append(" <li><button class=\"post__menu__btn\"><img src=\"./img/like.svg\" /></button></li>")
.append(" </ul>")
.append(" </div>")
.append("</div>")
.append("<hr style=\"border: 0.5px solid #eee; margin: 40px 0;\">"); // 게시글 구분선
}
return sb.toString();
}
}
11 changes: 6 additions & 5 deletions src/main/java/webserver/RequestHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.net.Socket;

import db.Database;
import db.UserDao;
import model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -15,12 +16,12 @@ public class RequestHandler implements Runnable {

private Socket connection;
private final RouteGuide routeGuide;
private final Database database;
private final UserDao userDao;

public RequestHandler(Socket connectionSocket, RouteGuide routeGuide, Database database) {
public RequestHandler(Socket connectionSocket, RouteGuide routeGuide, UserDao userDao) {
this.connection = connectionSocket;
this.routeGuide = routeGuide;
this.database = database;
this.userDao = userDao;
}

public void run() {
Expand All @@ -36,7 +37,7 @@ public void run() {

// 유저 정보 추출
String sessionId = request.getCookie("sid");
User loginUser = SessionManager.getLoginUser(sessionId, database);
User loginUser = SessionManager.getLoginUser(sessionId, userDao);

String path = request.getPath();
if (path == null) return;
Expand All @@ -55,7 +56,7 @@ public void run() {
handler.process(request, response);
} else {
// 없으면 정적 파일 서빙
response.fileResponse(path, loginUser);
response.fileResponse(path, loginUser, null);
}

} catch (IOException e) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/webserver/SecurityInterceptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

public class SecurityInterceptor {
// 권한을 제한할 경로
private static final List<String> restrictedPaths = List.of("/mypage", "/user/logout");
private static final List<String> restrictedPaths = List.of("/mypage", "/user/logout", "article/write");

public static boolean preHandler(String path, User loginUser) {
if (restrictedPaths.contains(path)) {
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/webserver/SessionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import db.Database;
import db.SessionDatabase;
import db.SessionEntry;
import db.UserDao;
import model.User;

import java.time.Duration;
Expand All @@ -18,7 +19,7 @@ public static String createSession(User user) {
return sessionId;
}

public static User getSessionUser(String sessionId, Database database) {
public static User getSessionUser(String sessionId, UserDao userDao) {
SessionEntry entry = SessionDatabase.find(sessionId);
if (entry == null) return null;

Expand All @@ -28,10 +29,10 @@ public static User getSessionUser(String sessionId, Database database) {
}

entry.updateLastAccessedTime();
return database.findUserById(entry.getUserId());
return userDao.findUserById(entry.getUserId());
}

public static User getLoginUser(String sessionId, Database database) {
public static User getLoginUser(String sessionId, UserDao userDao) {
if (sessionId == null) {
return null;
}
Expand All @@ -42,7 +43,7 @@ public static User getLoginUser(String sessionId, Database database) {
}

String userId = entry.getUserId();
return database.findUserById(userId);
return userDao.findUserById(userId);
}

public static boolean isExpired(SessionEntry entry) {
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/webserver/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import db.Database;
import db.SessionDatabase;
import db.UserDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webserver.config.AppConfig;
Expand All @@ -32,7 +33,7 @@ public static void main(String args[]) throws Exception {
port = Integer.parseInt(args[0]);
}

Database database = AppConfig.getDatabase();
UserDao userDao = AppConfig.getUserDao();
RouteGuide routeGuide = new RouteGuide(AppConfig.getRouteMappings());

// [백그라운드 작업] 만료된 세션 청소
Expand All @@ -45,7 +46,7 @@ public static void main(String args[]) throws Exception {
// 클라이언트가 연결될때까지 대기한다.
Socket connection;
while ((connection = listenSocket.accept()) != null) {
executorService.execute(new RequestHandler(connection, routeGuide, database));
executorService.execute(new RequestHandler(connection, routeGuide, userDao));
}
} finally {
executorService.shutdown();
Expand Down
Loading
Loading