Skip to content
Merged
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
37 changes: 0 additions & 37 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,40 +1,3 @@
네, default.conf 파일 잘 받았습니다.

파일을 보니... expires -1; 설정이 있어서, Nginx가 JS/CSS 파일을 캐시하는 문제는 아니었습니다.

하지만 드디어 진짜 원인을 찾은 것 같습니다. 님이 겪는 문제는 두 가지의 심각한 오류가 동시에 발생하고 있었기 때문입니다.

화면이 안 바뀌는 문제: web 컨테이너의 .dockerignore 파일에 Vite 캐시(.vite)가 누락되어, 님이 수정한 Review.jsx가 아닌 옛날 파일로 계속 빌드되었습니다.

"Failed to fetch" 문제: Nginx와 FastAPI의 API 주소 끝에 슬래시(/)가 일치하지 않아 API 요청이 404 오류로 실패하고 있었습니다.

🛠️ 최종 해결 (1+2번 문제 동시 해결)
아래 4단계를 순서대로 진행하시면, 디자인과 API 오류가 모두 해결됩니다.

1단계: review-service의 API 경로 수정
FastAPI(main.py)가 Nginx(default.conf)와 동일하게 슬래시가 붙은 주소를 받도록 수정합니다.

apps/review-service/main.py 파일을 열어서 @app.post 부분을 수정하세요.

수정 전:

Python

@app.post("/api/review")
async def handle_code_review(code: str = Form(...)):
수정 후: (끝에 / 추가)

Python

@app.post("/api/review/")
async def handle_code_review(code: str = Form(...)):
2단계: web의 .dockerignore 파일 수정
Vite 캐시 폴더(.vite)가 Docker 빌드 시 복사되지 않도록 .dockerignore 파일에 추가합니다.

apps/web/.dockerignore 파일을 열어서 맨 아래에 .vite를 추가하세요.

수정 후:

# 기본 무시 항목
node_modules
dist
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Environment variables (🔒 절대 Git에 올리면 안 되는 민감 정보)
.env
1 change: 0 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// apps/web/src/App.jsx
import { Routes, Route } from "react-router-dom";

// 맨 위 import 부분
Expand Down
34 changes: 11 additions & 23 deletions src/api/codingService.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// src/api/codingService.js

/**
* Vite 프록시 설정을 통해 백엔드(Spring) API 서버와 통신합니다.
* vite.config.js에서 '/api' → http://localhost:8080 같은 식으로 프록시된다고 가정합니다.
*/
const BASE_URL = "/api";
const BASE_URL =
import.meta.env.VITE_API_BASE_URL
? import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "") // 끝에 / 제거
: "/api";

/**
* 난이도에 따라 랜덤 코딩 문제를 가져옵니다.
Expand All @@ -26,21 +23,20 @@ export const fetchRandomProblem = async (difficulty) => {

/**
* 코드를 백엔드로 제출하여 전체 테스트 케이스로 채점합니다.
* (Spring의 /api/coding/submissions 엔드포인트 호출)
*
* @param {object} params
* @param {number} params.problemId - 문제 ID
* @param {string} params.code - 사용자 코드
* @param {string} params.language - 언어 ("python" | "java" | "cpp" ...)
* @param {string} [params.userId] - 사용자 식별자 (없으면 "guest")
* @returns {Promise<object>} - { submissionId, status, score, passedCount, totalCount, message }
* @param {number} params.problemId
* @param {string} params.code
* @param {string} params.language
* @param {string} [params.userId]
* @returns {Promise<object>}
*/
export const submitCode = async ({ problemId, code, language, userId }) => {
const payload = {
problemId,
problemId,
sourceCode: code,
language,
userId: userId ?? "guest"
userId: userId ?? "guest",
};

const response = await fetch(`${BASE_URL}/coding/submissions`, {
Expand All @@ -58,11 +54,3 @@ export const submitCode = async ({ problemId, code, language, userId }) => {

return await response.json();
};

/**
* (선택) 코드 실행(run) 기능을 나중에 붙이고 싶다면 여기에 구현할 수 있습니다.
* 현재 Spring 백엔드에는 /coding/run 같은 엔드포인트가 없어서 기본적으로는 사용하지 않습니다.
*/
// export const runCode = async (code, language, inputData) => {
// // TODO: 나중에 Judge0/Piston 실행용 엔드포인트 만들면 여기에 연결
// };
16 changes: 8 additions & 8 deletions src/api/interviewService.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// src/api/interviewService.js

const BASE_URL = "/api";
const BASE_URL =
import.meta.env.VITE_API_BASE_URL
? import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "")
: "/api";

/**
* 새 AI 면접 세션 시작 (레포 URL 기반 질문 생성)
* -> POST /api/interview/start
* -> POST /interview/start
*/
export const startInterview = async (repoUrl) => {
const res = await fetch(`${BASE_URL}/interview/start`, {
Expand All @@ -22,9 +23,8 @@ export const startInterview = async (repoUrl) => {
};

/**
* 모든 질문에 대한 답변(STT 전환된 텍스트 포함)을 전달하고
* 최종 면접 결과 피드백을 받음
* -> POST /api/interview/feedback
* 모든 질문 답변(STT 포함) 전달하고 최종 피드백 받기
* -> POST /interview/feedback
*/
export const requestInterviewFeedback = async (sessionId, answers) => {
const res = await fetch(`${BASE_URL}/interview/feedback`, {
Expand All @@ -43,7 +43,7 @@ export const requestInterviewFeedback = async (sessionId, answers) => {

/**
* 음성 Blob → 텍스트(STT)
* -> POST /api/interview/stt
* -> POST /interview/stt
*/
export const transcribeAnswer = async (audioBlob) => {
const formData = new FormData();
Expand Down
7 changes: 5 additions & 2 deletions src/api/reviewService.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// src/api/reviewService.js
const BASE_URL = "http://localhost:8080/api";
const BASE_URL =
import.meta.env.VITE_API_BASE_URL
? import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "")
: "/api";

export const fetchCodeReview = async (code, comment, repoUrl) => {
const payload = {
Expand Down Expand Up @@ -37,6 +39,7 @@ export const fetchCodeReview = async (code, comment, repoUrl) => {
try {
return JSON.parse(raw);
} catch {
// 응답이 순수 텍스트일 때
return { review: raw, questions: [] };
}
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion src/features/auth/GithubCallback.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/features/auth/GithubCallback.jsx
import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";

Expand Down
7 changes: 4 additions & 3 deletions src/features/auth/GithubLoginButton.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// src/features/auth/GithubLoginButton.jsx
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL
? import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, "")
: "/api";

export default function GithubLoginButton() {
const handleGithubLogin = () => {
// 백엔드 OAuth 진입점 (나중에 실제 엔드포인트로만 바꿔주면 됨)
window.location.href = `${API_BASE_URL}/oauth2/authorization/github`;
};

Expand Down
4 changes: 0 additions & 4 deletions src/features/auth/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/features/auth/Login.jsx
import GithubLoginButton from "./GithubLoginButton";

export default function Login() {
Expand All @@ -7,9 +6,6 @@ export default function Login() {
<div className="gcard max-w-md w-full">
<div className="ginner glass-sheen p-6 space-y-4">
<div className="gheader text-base">Sign in</div>

{/* 나중에 이메일 로그인 넣고 싶으면 여기 */}

<GithubLoginButton />
</div>
</div>
Expand Down
67 changes: 11 additions & 56 deletions src/features/codingTest/CodingTest.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/features/coding/CodingTest.jsx
import { useState } from "react";
import { Link } from "react-router-dom";
import {
Expand All @@ -13,58 +12,14 @@ import {
ChevronRight,
Maximize2,
Home,
Code2
Code2,
} from "lucide-react";

// -----------------------------------------------------------
// [오류 수정] codingService.js 파일을 직접 통합하여 경로 오류 해결
// -----------------------------------------------------------

const BASE_URL = "/api";

const fetchRandomProblem = async (difficulty) => {
const query = difficulty ? `?difficulty=${difficulty}` : "";
// API 경로: /api/coding/problems/random
const response = await fetch(`${BASE_URL}/coding/problems/random${query}`);

if (!response.ok) {
const text = await response.text().catch(() => "");
throw new Error(
text || `랜덤 문제를 불러오지 못했습니다. (status: ${response.status})`
);
}

return await response.json();
};

const submitCode = async ({ problemId, code, language, userId }) => {
const payload = {
problemId,
sourceCode: code,
language,
userId: userId ?? 1, // userId가 null/undefined일 경우 기본값 1 사용 (Long 타입 일치)
};

// API 경로: /api/coding/submissions
const response = await fetch(`${BASE_URL}/coding/submissions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});

if (!response.ok) {
const text = await response.text().catch(() => "");
throw new Error(text || `채점 요청에 실패했습니다. (status: ${response.status})`);
}

return await response.json();
};

// -----------------------------------------------------------
// 컴포넌트 시작
// -----------------------------------------------------------
// ✅ API 호출은 분리된 서비스에서 import
import {
fetchRandomProblem,
submitCode,
} from "../../api/codingService";

// 언어 옵션
const LANGUAGE_OPTIONS = [
Expand Down Expand Up @@ -159,7 +114,7 @@ export default function CodingTest() {
if (code !== LANGUAGE_TEMPLATES[language] && code.trim() !== "") {
if (
!window.confirm(
"언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?"
"언어를 변경하면 작성 중인 코드가 초기화됩니다. 계속하시겠습니까?",
)
) {
return;
Expand All @@ -181,7 +136,7 @@ export default function CodingTest() {
setProblem(data);
} catch (err) {
setErrorMsg(
err?.message || "문제 로딩 중 오류가 발생했습니다. (백엔드 서버 확인 필요)"
err?.message || "문제 로딩 중 오류가 발생했습니다. (백엔드 서버 확인 필요)",
);
} finally {
setIsLoadingProblem(false);
Expand Down Expand Up @@ -209,7 +164,7 @@ export default function CodingTest() {
problemId: problem.id,
code,
language,
userId: 1, // Long 타입이므로 숫자 1 사용
// userId는 codingService에서 guest 처리 (또는 나중에 로그인 정보 연결)
});
setResult(res);
// showInterview는 기본 false (코드 리뷰 먼저 보여줌)
Expand Down Expand Up @@ -548,7 +503,7 @@ export default function CodingTest() {
</div>
</div>

{/* 🔥 오른쪽: 예상 면접 질문 토글 버튼 (결과 요약 버튼 삭제) */}
{/* 🔥 오른쪽: 예상 면접 질문 토글 버튼 */}
{Array.isArray(result.interviewQuestions) &&
result.interviewQuestions.length > 0 && (
<button
Expand All @@ -570,7 +525,7 @@ export default function CodingTest() {
)}
</div>

{/* 🔥 내용: 피드백 / 질문만 보여줌, 결과 요약 뷰 제거 */}
{/* 🔥 피드백 / 질문 */}
{hasFeedback ? (
<div className="p-4 rounded-xl border border-cyan-500/30 bg-cyan-500/10 shadow-inner">
<div className="flex items-center justify-between mb-2">
Expand Down
2 changes: 0 additions & 2 deletions src/features/interview/pages/Result.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// src/features/interview/pages/Result.jsx

import { useLocation, useNavigate } from "react-router-dom";

export default function Result() {
Expand Down
2 changes: 0 additions & 2 deletions src/features/interview/pages/Session.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// src/features/interview/Session.jsx

import { useEffect, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import {
Expand Down
1 change: 0 additions & 1 deletion src/features/review/CodeReview.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/features/review/CodeReview.jsx
import { useState } from "react";
import { Link } from "react-router-dom";
import {
Expand Down
1 change: 0 additions & 1 deletion src/shared/hooks/useParticlesInit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// src/hooks/useParticlesInit.js
import { useEffect, useState } from "react";
import { initParticlesEngine } from "@tsparticles/react";
import { loadAll } from "@tsparticles/all";
Expand Down
13 changes: 7 additions & 6 deletions tailwind.config.cjs → tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
/** @type {import('tailwindcss').Config} */
module.exports = {

Check failure on line 2 in tailwind.config.js

View workflow job for this annotation

GitHub Actions / build

'module' is not defined
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
keyframes: {
fadeIn: {
"0%": { opacity: 0, transform: "translateY(10px)" },
"100%": { opacity: 1, transform: "translateY(0)" }
"100%": { opacity: 1, transform: "translateY(0)" },
},
fadeInUp: {
"0%": { opacity: 0, transform: "translateY(20px)" },
"100%": { opacity: 1, transform: "translateY(0)" }
"100%": { opacity: 1, transform: "translateY(0)" },
},
fadeOut: {
"0%": { opacity: 1, transform: "translateY(0)" },
"100%": { opacity: 0, transform: "translateY(10px)" }
"100%": { opacity: 0, transform: "translateY(10px)" },
},
},
animation: {
fadeIn: "fadeIn 0.5s ease-out forwards",
fadeInUp: "fadeInUp 0.8s ease-out forwards",
fadeInOut: "fadeIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s forwards",
fadeInOut:
"fadeIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s forwards",
},
},
},
// plugins: [] // v4에서는 CSS에서 @plugin으로 불러옵니다
};
5 changes: 5 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}
Loading
Loading