diff --git a/.dockerignore b/.dockerignore index cfa6356..7deb0f1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,11 @@ # Git and OS files .git .DS_Store -*.json +!server/*.json + +# Secrets .env +server/.env # VSCode .vscode/ @@ -15,9 +18,10 @@ __pycache__/ .venv/ server/.venv/ -# Tests +# Tests & Infrastructure server/tests/ pytest.ini +.github/ # Client folder client/ \ No newline at end of file diff --git a/client/components/Chat.tsx b/client/components/Chat.tsx index d85558e..3d17db9 100644 --- a/client/components/Chat.tsx +++ b/client/components/Chat.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import ReactMarkdown from "react-markdown"; import { useAuth } from "@/context/AuthContext"; @@ -8,11 +8,40 @@ interface Message { content: string; } +const API_URL = process.env.NEXT_PUBLIC_API_URL; + export default function Chat() { const { user } = useAuth(); const [input, setInput] = useState(""); const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + const fetchChatHistory = async () => { + if (!user) return; + setIsLoading(true); + try{ + const res = await fetch(`${API_URL}/chats/${user.uid}`) + if (!res.ok) {throw new Error("Failed to load history")} + const history = await res.json(); + + const historicalMessages = history.flatMap((session: any) => + session.messages.map((m: any) => ({ + role: m.role, + content: m.content + })) + ).reverse(); // if the api orders newest-first but ui wants oldest-first + + setMessages(historicalMessages); + } catch (error) { + console.error("History fetch error:", error) + } finally { + setIsLoading(false) + } + } + + fetchChatHistory() + }, [user]) const handleSend = async () => { if (!input.trim()) return; @@ -24,21 +53,21 @@ export default function Chat() { let res; try { if (!user) { - res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chat`, { + res = await fetch(`${API_URL}/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: userMessage.content, - userId: "None", + user_id: "None", }), }); } else { - res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/chat`, { + res = await fetch(`${API_URL}/chat`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: userMessage.content, - userId: user?.uid, + user_id: user?.uid, }), }); } diff --git a/client/components/ResumeBuilder.tsx b/client/components/ResumeBuilder.tsx index 675e605..1c19ae5 100644 --- a/client/components/ResumeBuilder.tsx +++ b/client/components/ResumeBuilder.tsx @@ -1,10 +1,9 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import type { MouseEvent } from "react"; import { useAuth } from "@/context/AuthContext"; import ResumeDisplay from "./ResumeDisplay"; -// The URL for the deployed backend. const API_URL = process.env.NEXT_PUBLIC_API_URL; export default function ResumeBuilder() { @@ -17,6 +16,7 @@ export default function ResumeBuilder() { const [tailoredResume, setTailoredResume] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [history, setHistory] = useState([]) const handleSubmit = async (e: MouseEvent) => { if (!user) return; @@ -29,7 +29,7 @@ export default function ResumeBuilder() { const formData = new FormData(); formData.append("base_resume", resume); formData.append("job_description", jobDescription); - formData.append("userId", user.uid); + formData.append("user_id", user.uid); try { // Make the API call to /resumes endpoint @@ -60,6 +60,47 @@ export default function ResumeBuilder() { } }; + useEffect(() => { + if (!user) { + setHistory([]); // Clear history on logout + return + }; + + const fetchResumeHistory = async () => { + setIsLoading(true); + + try { + const res = await fetch(`${API_URL}/resumes/${user.uid}`) + + if (!res.ok) throw new Error("Filed to fetch resume history") + + const history = await res.json() + + const historicalMessages = history.map((session: any) => ({ + jobDescription: session.jobDescription, + originalResume: session.originalResume, + tailoredResume: session.tailoredResume, + }) + ).reverse() + + setHistory(historicalMessages) + + // Show latest resume on load + if (historicalMessages.length > 0) { + setTailoredResume(historicalMessages[0].tailored_resume) + } + + } catch (error) { + console.error("History fetch error:", error) + } finally { + setIsLoading(false) + } + }; + + fetchResumeHistory() + }, [user]) + + return (
diff --git a/server/Dockerfile b/server/Dockerfile index 41e9d39..a22a44a 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -8,12 +8,12 @@ WORKDIR /app # This fixes "ModuleNotFoundError" by allowing main.py to import utils.py ENV PYTHONPATH "${PYTHONPATH}:/app" -# Copy the requirements file *from the server subdirectory* -COPY server/requirements.txt . +# Copy the requirements file from the server subdirectory* +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy the entire server directory *into* the /app directory -COPY server/ . +COPY . . # Tell Docker that the container will listen on port 8000 EXPOSE 8000 diff --git a/server/main.py b/server/main.py index a44cc2a..f616a15 100644 --- a/server/main.py +++ b/server/main.py @@ -86,6 +86,11 @@ async def lifespan(app: FastAPI): # --- API Endpoints --- # --- Post Chat Endpoint --- +@app.get("/") +async def root(): + return {"status": "ApplyAI API is online", "version": "1.0.0"} + + @app.post( "/chat", response_model=ChatResponse, summary="Handles Chat with Gemini AI Agent" ) diff --git a/server/tests/test_main.py b/server/tests/test_main.py index 612f69f..e66b3c4 100644 --- a/server/tests/test_main.py +++ b/server/tests/test_main.py @@ -65,10 +65,10 @@ def sample_job_data(): def test_client_is_working(client): """ A simple "sanity check" test to make sure the client is working. - We don't have a "/" route, so we expect a 404 "Not Found" error. + We now have a "/" route, so we expect a 200 "OK" success code. """ response = client.get("/") - assert response.status_code == 404 + assert response.status_code == 200 def test_chat_endpoint(client, mocker):