프로젝트가 보이지 않는 문제 수정

This commit is contained in:
김판돌 2026-05-27 16:47:50 +09:00
parent 40c6c88e9d
commit 8f6a49cb54
4 changed files with 251 additions and 0 deletions

View File

@ -82,6 +82,7 @@ public class WebMvcController implements WebMvcConfigurer, ErrorController {
if (!isLoggedIn(session)) {
return new ModelAndView("redirect:/login");
}
mv.addObject("myGames", gamesMapper.getGamesByUserId(sessionUserId(session)));
mv.setViewName("profile");
break;
case "signup":
@ -114,6 +115,17 @@ public class WebMvcController implements WebMvcConfigurer, ErrorController {
return session != null && session.getAttribute("userId") != null;
}
private long sessionUserId(HttpSession session) {
Object userId = session.getAttribute("userId");
if (userId instanceof Number) {
return ((Number) userId).longValue();
}
if (userId instanceof String) {
return Long.parseLong((String) userId);
}
throw new IllegalStateException("로그인 세션에 사용자 ID가 없습니다.");
}
/// 접속기기 모바일 확인 함수
private boolean isMobileDevice(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");

View File

@ -90,6 +90,30 @@ public interface GamesMapper {
""")
List<GameData> searchVisibleGames(@Param("query") String query);
@Select("""
SELECT
g.id,
g.user_id AS userId,
g.name,
u.display_name AS creator,
g.creator_note AS creatorNote,
g.git_url AS gitUrl,
g.webgl_path AS webglPath,
g.thumbnail_url AS thumbnailUrl,
g.like_count AS likeCount,
g.is_visible AS visible,
g.sort_order AS sortOrder,
g.created_at AS createdAt,
g.updated_at AS updatedAt
FROM games g
JOIN users u ON u.id = g.user_id
WHERE g.user_id = #{userId}
AND g.is_delete IS NOT TRUE
AND u.is_delete IS NOT TRUE
ORDER BY g.updated_at DESC, g.created_at DESC, g.id DESC
""")
List<GameData> getGamesByUserId(@Param("userId") long userId);
@Insert("""
INSERT INTO games (
user_id,

View File

@ -0,0 +1,24 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<header class="site-header">
<div class="site-header__inner">
<a class="logo" href="${pageContext.request.contextPath}/">비빔밥</a>
<form class="search" method="get" action="${pageContext.request.contextPath}/">
<input type="search" name="q" value="<c:out value='${q}'/>" placeholder="게임 이름 또는 제작자 검색" aria-label="검색"/>
<button type="submit">검색</button>
</form>
<nav class="auth">
<sec:authorize access="isAuthenticated()">
<a href="${pageContext.request.contextPath}/games/new">게임 올리기</a>
<form action="${pageContext.request.contextPath}/logout" method="post" class="logout-form">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<button type="submit" class="link-btn">로그아웃</button>
</form>
</sec:authorize>
<sec:authorize access="!isAuthenticated()">
<a href="${pageContext.request.contextPath}/login">로그인</a>
<a class="btn-primary" href="${pageContext.request.contextPath}/register">회원가입</a>
</sec:authorize>
</nav>
</div>
</header>

View File

@ -1,5 +1,8 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" language="java" %>
<%@ page import="com.pandoli365.bibimbap.data.GameData" %>
<%@ page import="org.springframework.web.util.HtmlUtils" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Locale" %>
<!DOCTYPE html>
<%
@ -14,6 +17,8 @@
String email = HtmlUtils.htmlEscape(rawEmail.isBlank() ? "이메일 정보 없음" : rawEmail);
String avatarUrl = HtmlUtils.htmlEscape(rawAvatarUrl);
String avatarInitial = HtmlUtils.htmlEscape(initial);
Object rawMyGames = request.getAttribute("myGames");
List<GameData> myGames = rawMyGames instanceof List<?> ? (List<GameData>) rawMyGames : Collections.emptyList();
%>
<html lang="ko">
<head>
@ -224,6 +229,129 @@
.profile-actions form {
margin: 0;
}
.profile-games {
margin-top: 1rem;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--card-bg);
box-shadow: 0 2px 8px var(--card-shadow);
overflow: hidden;
}
.profile-games__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.profile-games__title {
margin: 0;
font-size: 1.05rem;
line-height: 1.25;
letter-spacing: 0;
}
.profile-games__count {
color: var(--text-muted);
font-size: 0.8125rem;
font-weight: 800;
}
.profile-games__empty {
margin: 0;
padding: 1.25rem 1.5rem;
color: var(--text-muted);
font-size: 0.9375rem;
}
.profile-game-list {
display: grid;
}
.profile-game {
display: grid;
grid-template-columns: 3.75rem minmax(0, 1fr) auto;
gap: 0.875rem;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.profile-game:last-child {
border-bottom: 0;
}
.profile-game__thumb {
width: 3.75rem;
height: 3.75rem;
border-radius: 10px;
border: 1px solid var(--border);
background: rgba(232, 165, 75, 0.16);
overflow: hidden;
display: inline-flex;
align-items: center;
justify-content: center;
}
.profile-game__thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.profile-game__fallback {
width: 1.85rem;
height: 1.85rem;
opacity: 0.75;
}
.profile-game__body {
min-width: 0;
}
.profile-game__name {
margin: 0;
color: var(--text);
font-size: 0.975rem;
font-weight: 900;
line-height: 1.3;
text-decoration: none;
word-break: break-word;
}
.profile-game__name:hover {
color: var(--accent);
}
.profile-game__meta {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.45rem;
color: var(--text-muted);
font-size: 0.8125rem;
font-weight: 700;
}
.profile-game__status {
border-radius: 999px;
padding: 0.15rem 0.5rem;
background: rgba(92, 92, 92, 0.12);
color: var(--text-muted);
}
.profile-game__status--visible {
background: rgba(232, 165, 75, 0.18);
color: var(--text);
}
.profile-game__actions {
display: flex;
gap: 0.5rem;
}
.profile-game__action {
min-height: 2.25rem;
padding: 0 0.75rem;
border: 1px solid var(--border);
border-radius: 9px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text);
font-size: 0.8125rem;
font-weight: 900;
text-decoration: none;
}
.profile-game__action:hover {
border-color: rgba(232, 165, 75, 0.45);
color: var(--accent);
}
@media (max-width: 560px) {
.profile-main {
padding-top: 1.5rem;
@ -253,6 +381,21 @@
.profile-avatar-form .profile-button {
flex: 1;
}
.profile-games__head {
padding: 1.25rem;
}
.profile-game {
grid-template-columns: 3.25rem minmax(0, 1fr);
padding: 1rem 1.25rem;
}
.profile-game__thumb {
width: 3.25rem;
height: 3.25rem;
}
.profile-game__actions {
grid-column: 1 / -1;
justify-content: flex-end;
}
}
</style>
</head>
@ -289,6 +432,54 @@
</form>
</div>
</div>
<div class="profile-games">
<div class="profile-games__head">
<h2 class="profile-games__title">내 게임</h2>
<span class="profile-games__count"><%= myGames.size() %>개</span>
</div>
<% if (myGames.isEmpty()) { %>
<p class="profile-games__empty">등록한 게임이 없습니다.</p>
<% } else { %>
<div class="profile-game-list">
<%
for (GameData game : myGames) {
if (game == null || game.getId() == null) {
continue;
}
String gameName = HtmlUtils.htmlEscape(game.getName() == null || game.getName().isBlank() ? "제목 없음" : game.getName());
String rawThumbUrl = game.getThumbnailUrl();
boolean hasImage = rawThumbUrl != null && !rawThumbUrl.isBlank();
String thumbUrl = "";
if (hasImage) {
thumbUrl = rawThumbUrl.startsWith("/") ? ctx + rawThumbUrl : rawThumbUrl;
thumbUrl = HtmlUtils.htmlEscape(thumbUrl);
}
boolean visible = game.getVisible() == null || game.getVisible();
%>
<div class="profile-game">
<a class="profile-game__thumb" href="<%= ctx %>/game/<%= game.getId() %>" aria-hidden="true" tabindex="-1">
<% if (hasImage) { %>
<img src="<%= thumbUrl %>" alt="" loading="lazy" decoding="async" />
<% } else { %>
<img class="profile-game__fallback" src="<%= ctx %>/images/logo.png" alt="" width="40" height="40" />
<% } %>
</a>
<div class="profile-game__body">
<a class="profile-game__name" href="<%= ctx %>/game/<%= game.getId() %>"><%= gameName %></a>
<div class="profile-game__meta">
<span class="profile-game__status <%= visible ? "profile-game__status--visible" : "" %>"><%= visible ? "공개" : "비공개" %></span>
<span>좋아요 <%= String.format("%,d", game.getLikeCount() == null ? 0 : game.getLikeCount()) %></span>
</div>
</div>
<div class="profile-game__actions">
<a class="profile-game__action" href="<%= ctx %>/game/<%= game.getId() %>/edit">수정</a>
</div>
</div>
<% } %>
</div>
<% } %>
</div>
</section>
</main>
<jsp:include page="/WEB-INF/views/footer.jsp"/>