From 6270f3a795fe3e888c7397439f4009beac4c876c Mon Sep 17 00:00:00 2001 From: ahamedvifaaq Date: Sun, 26 Oct 2025 03:51:06 +0530 Subject: [PATCH 1/5] study plan page ready --- app/page.tsx | 73 +++++++-- components/pages/ai-chat.tsx | 12 +- components/pages/auth.tsx | 107 +++++++++++++ components/pages/dashboard.tsx | 6 +- components/pages/goal-creation.tsx | 35 ++-- components/pages/learning-screen.tsx | 85 +++++++--- components/pages/study-plan.tsx | 149 +++++++++++++----- spring-backend/build.log | Bin 0 -> 11740 bytes spring-backend/pom.xml | 39 +++++ .../aichat/controller/AuthController.java | 86 ++++++++++ .../aichat/controller/ChatController.java | 2 +- .../aichat/controller/DbHealthController.java | 36 +++++ .../aichat/controller/GoalController.java | 101 ++++++++++++ .../controller/StudyPlanController.java | 141 +++++++++++++++++ .../com/example/aichat/dto/AuthRequest.java | 18 +++ .../com/example/aichat/dto/AuthResponse.java | 29 ++++ .../example/aichat/dto/CreateGoalRequest.java | 14 ++ .../java/com/example/aichat/dto/GoalDto.java | 26 +++ .../example/aichat/dto/RegisterRequest.java | 25 +++ .../example/aichat/dto/StudyPlanRequest.java | 16 ++ .../java/com/example/aichat/model/Goal.java | 55 +++++++ .../com/example/aichat/model/StudyPlan.java | 56 +++++++ .../java/com/example/aichat/model/User.java | 48 ++++++ .../example/aichat/repo/GoalRepository.java | 13 ++ .../aichat/repo/StudyPlanRepository.java | 13 ++ .../example/aichat/repo/UserRepository.java | 11 ++ .../aichat/security/JwtAuthFilter.java | 54 +++++++ .../example/aichat/security/JwtService.java | 67 ++++++++ .../aichat/security/SecurityConfig.java | 66 ++++++++ .../ApplicationUserDetailsService.java | 32 ++++ .../example/aichat/service/OpenAIService.java | 2 +- .../src/main/resources/application.properties | 20 +++ .../target/aichat-0.0.1-SNAPSHOT.jar | Bin 0 -> 26763 bytes .../target/classes/application.properties | 20 +++ .../example/aichat/AiChatApplication.class | Bin 713 -> 713 bytes .../example/aichat/config/CorsConfig.class | Bin 1660 -> 1660 bytes .../aichat/controller/AuthController.class | Bin 0 -> 5814 bytes .../aichat/controller/ChatController.class | Bin 3881 -> 4098 bytes .../controller/DbHealthController.class | Bin 0 -> 2171 bytes .../aichat/controller/GoalController.class | Bin 0 -> 8348 bytes .../controller/StudyPlanController.class | Bin 0 -> 8133 bytes .../com/example/aichat/dto/AuthRequest.class | Bin 0 -> 950 bytes .../com/example/aichat/dto/AuthResponse.class | Bin 0 -> 1313 bytes .../com/example/aichat/dto/ChatReplyDto.class | Bin 644 -> 644 bytes .../example/aichat/dto/ChatRequestDto.class | Bin 1095 -> 1095 bytes .../aichat/dto/CreateGoalRequest.class | Bin 0 -> 966 bytes .../com/example/aichat/dto/GoalDto.class | Bin 0 -> 1356 bytes .../com/example/aichat/dto/MessageDto.class | Bin 878 -> 878 bytes .../example/aichat/dto/RegisterRequest.class | Bin 0 -> 1237 bytes .../example/aichat/dto/StudyPlanRequest.class | Bin 0 -> 1010 bytes .../com/example/aichat/model/Goal.class | Bin 0 -> 2763 bytes .../com/example/aichat/model/StudyPlan.class | Bin 0 -> 2814 bytes .../com/example/aichat/model/User.class | Bin 0 -> 2192 bytes .../example/aichat/repo/GoalRepository.class | Bin 0 -> 743 bytes .../aichat/repo/StudyPlanRepository.class | Bin 0 -> 877 bytes .../example/aichat/repo/UserRepository.class | Bin 0 -> 536 bytes .../aichat/security/JwtAuthFilter.class | Bin 0 -> 3530 bytes .../example/aichat/security/JwtService.class | Bin 0 -> 3204 bytes .../aichat/security/SecurityConfig.class | Bin 0 -> 7181 bytes .../ApplicationUserDetailsService.class | Bin 0 -> 2876 bytes .../aichat/service/OpenAIService.class | Bin 7290 -> 7619 bytes .../target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 28 +++- .../compile/default-compile/inputFiles.lst | 26 ++- 64 files changed, 1417 insertions(+), 97 deletions(-) create mode 100644 components/pages/auth.tsx create mode 100644 spring-backend/build.log create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/AuthController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/DbHealthController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/GoalController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/StudyPlanController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/AuthRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/AuthResponse.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/CreateGoalRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/GoalDto.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/RegisterRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/StudyPlanRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/model/Goal.java create mode 100644 spring-backend/src/main/java/com/example/aichat/model/StudyPlan.java create mode 100644 spring-backend/src/main/java/com/example/aichat/model/User.java create mode 100644 spring-backend/src/main/java/com/example/aichat/repo/GoalRepository.java create mode 100644 spring-backend/src/main/java/com/example/aichat/repo/StudyPlanRepository.java create mode 100644 spring-backend/src/main/java/com/example/aichat/repo/UserRepository.java create mode 100644 spring-backend/src/main/java/com/example/aichat/security/JwtAuthFilter.java create mode 100644 spring-backend/src/main/java/com/example/aichat/security/JwtService.java create mode 100644 spring-backend/src/main/java/com/example/aichat/security/SecurityConfig.java create mode 100644 spring-backend/src/main/java/com/example/aichat/service/ApplicationUserDetailsService.java create mode 100644 spring-backend/target/aichat-0.0.1-SNAPSHOT.jar create mode 100644 spring-backend/target/classes/com/example/aichat/controller/AuthController.class create mode 100644 spring-backend/target/classes/com/example/aichat/controller/DbHealthController.class create mode 100644 spring-backend/target/classes/com/example/aichat/controller/GoalController.class create mode 100644 spring-backend/target/classes/com/example/aichat/controller/StudyPlanController.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/AuthRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/AuthResponse.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/CreateGoalRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/GoalDto.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/RegisterRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/StudyPlanRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/model/Goal.class create mode 100644 spring-backend/target/classes/com/example/aichat/model/StudyPlan.class create mode 100644 spring-backend/target/classes/com/example/aichat/model/User.class create mode 100644 spring-backend/target/classes/com/example/aichat/repo/GoalRepository.class create mode 100644 spring-backend/target/classes/com/example/aichat/repo/StudyPlanRepository.class create mode 100644 spring-backend/target/classes/com/example/aichat/repo/UserRepository.class create mode 100644 spring-backend/target/classes/com/example/aichat/security/JwtAuthFilter.class create mode 100644 spring-backend/target/classes/com/example/aichat/security/JwtService.class create mode 100644 spring-backend/target/classes/com/example/aichat/security/SecurityConfig.class create mode 100644 spring-backend/target/classes/com/example/aichat/service/ApplicationUserDetailsService.class create mode 100644 spring-backend/target/maven-archiver/pom.properties diff --git a/app/page.tsx b/app/page.tsx index ed2f3fc..432e0f4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect } from 'react' import Navigation from '@/components/navigation' import Dashboard from '@/components/pages/dashboard' import GoalCreation from '@/components/pages/goal-creation' @@ -10,14 +10,13 @@ import AIChat from '@/components/pages/ai-chat' import ResourceLibrary from '@/components/pages/resource-library' import Analytics from '@/components/pages/analytics' import Settings from '@/components/pages/settings' +import Auth from '@/components/pages/auth' + +type Goal = { id: number; title: string; progress: number; daysLeft: number } export default function Home() { const [currentPage, setCurrentPage] = useState('dashboard') - const [goals, setGoals] = useState([ - { id: 1, title: "Master Calculus", progress: 65, daysLeft: 12 }, - { id: 2, title: "Biology Fundamentals", progress: 42, daysLeft: 18 }, - { id: 3, title: "Physics Concepts", progress: 78, daysLeft: 8 }, - ]) + const [goals, setGoals] = useState([]) const [learningScreenState, setLearningScreenState] = useState({ isCompleted: false, @@ -25,21 +24,61 @@ export default function Home() { messages: [ { id: 1, role: "tutor", text: "Hi! I'm your AI tutor. What would you like to learn about derivatives?" }, ], - inputMessage: "" + inputMessage: "", + chatLoading: false, }); - const handleDeleteGoal = (id: number) => { - setGoals(goals.filter(goal => goal.id !== id)); + const [auth, setAuth] = useState<{ token: string | null; name?: string; email?: string; role?: string }>({ token: null }) + const [selectedGoal, setSelectedGoal] = useState(null) + + useEffect(() => { + const t = typeof window !== 'undefined' ? localStorage.getItem('token') : null + if (!t) return + const base = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8080' + fetch(`${base}/api/auth/me`, { headers: { Authorization: `Bearer ${t}` } }) + .then(res => res.ok ? res.json() : null) + .then(data => { + if (data) setAuth({ token: t, name: data.name, email: data.email, role: data.role }) + else setAuth({ token: null }) + }) + .catch(() => setAuth({ token: null })) + }, []) + + useEffect(() => { + const t = auth.token + if (!t) return + const base = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8080' + fetch(`${base}/api/goals`, { headers: { Authorization: `Bearer ${t}` } }) + .then(res => res.ok ? res.json() : []) + .then((list) => setGoals(Array.isArray(list) ? list : [])) + .catch(() => setGoals([])) + }, [auth.token]) + + const handleDeleteGoal = async (id: number) => { + const t = typeof window !== 'undefined' ? localStorage.getItem('token') : null + const base = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8080' + try { + await fetch(`${base}/api/goals/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${t}` } }) + setGoals(goals.filter(goal => goal.id !== id)) + if (selectedGoal && selectedGoal.id === id) setSelectedGoal(null) + } catch {} }; const renderPage = () => { switch (currentPage) { case 'dashboard': - return + return ( + { setSelectedGoal(goal); setCurrentPage('study-plan'); }} + /> + ) case 'goals': return case 'study-plan': - return + return { setSelectedGoal(g); setCurrentPage('study-plan'); }} /> case 'learning': return - -
{renderPage()}
- + auth.token ? ( +
+ { if (page === 'study-plan') setSelectedGoal(null); setCurrentPage(page); }} /> +
{renderPage()}
+
+ ) : ( + setAuth(u)} /> + ) ) } diff --git a/components/pages/ai-chat.tsx b/components/pages/ai-chat.tsx index e098631..a3cb2ad 100644 --- a/components/pages/ai-chat.tsx +++ b/components/pages/ai-chat.tsx @@ -35,17 +35,23 @@ export default function AIChat() { const endpoint = process.env.NEXT_PUBLIC_BACKEND_URL ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/ai-chat` : "http://localhost:8080/api/ai-chat"; + const payloadMessages = [...messages, userMsg].map((m: any) => ({ role: m.role, text: m.text })) + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null const res = await fetch(endpoint, { method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ messages: [...messages, userMsg] }), + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ messages: payloadMessages }), }) if (!res.ok) throw new Error("Failed to get AI response") const data = await res.json() const reply = (data?.reply ?? "I couldn't generate a response.").toString() + const clean = reply.replace(/\$/g, "").replace(/\\[()\\[\\]]/g, "") setMessages((prev) => [ ...prev, - { id: prev.length + 1, role: "tutor", text: reply }, + { id: prev.length + 1, role: "tutor", text: clean }, ]) } catch (err) { setMessages((prev) => [ diff --git a/components/pages/auth.tsx b/components/pages/auth.tsx new file mode 100644 index 0000000..7f0a791 --- /dev/null +++ b/components/pages/auth.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; + +interface AuthProps { + onAuthenticated: (auth: { token: string; name: string; email: string; role: string }) => void; +} + +export default function Auth({ onAuthenticated }: AuthProps) { + const [mode, setMode] = useState<"login" | "register">("login"); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + setError(null); + }, [mode]); + + const submit = async () => { + if (!email || !password || (mode === "register" && !name)) return; + setLoading(true); + setError(null); + try { + const endpointBase = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"; + const url = mode === "login" ? `${endpointBase}/api/auth/login` : `${endpointBase}/api/auth/register`; + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify( + mode === "login" ? { email, password } : { name, email, password } + ), + }); + const data = await res.json(); + if (!res.ok) { + setError(data?.error || "Authentication failed"); + return; + } + const token = data?.token as string; + if (token) { + localStorage.setItem("token", token); + onAuthenticated({ token, name: data?.name, email: data?.email, role: data?.role }); + } else { + setError("No token returned by server"); + } + } catch (e: any) { + setError(e?.message || "Request failed"); + } finally { + setLoading(false); + } + }; + + return ( +
+ +

+ {mode === "login" ? "Login" : "Create an account"} +

+ {mode === "register" && ( +
+ + setName(e.target.value)} + placeholder="Your name" + /> +
+ )} +
+ + setEmail(e.target.value)} + placeholder="you@example.com" + /> +
+
+ + setPassword(e.target.value)} + placeholder="••••••••" + /> +
+ {error ? ( +

{error}

+ ) : null} + + +
+
+ ); +} diff --git a/components/pages/dashboard.tsx b/components/pages/dashboard.tsx index fb5a03a..259f28b 100644 --- a/components/pages/dashboard.tsx +++ b/components/pages/dashboard.tsx @@ -15,11 +15,13 @@ interface Goal { export default function Dashboard({ onNavigate, goals, - onDeleteGoal + onDeleteGoal, + onSelectGoal, }: { onNavigate: (page: string) => void; goals: Goal[]; onDeleteGoal: (id: number) => void; + onSelectGoal?: (goal: Goal) => void; }) { const [dailyTasks, setDailyTasks] = useState([ { id: 1, title: "Complete Calculus Chapter 3", duration: "45 min", completed: true }, @@ -148,7 +150,7 @@ export default function Dashboard({ {goals.map((goal) => (
-

onNavigate("learning")}> +

onSelectGoal ? onSelectGoal(goal) : onNavigate("study-plan")}> {goal.title}

diff --git a/components/pages/goal-creation.tsx b/components/pages/goal-creation.tsx index 44d04c9..642fec3 100644 --- a/components/pages/goal-creation.tsx +++ b/components/pages/goal-creation.tsx @@ -46,15 +46,32 @@ export default function GoalCreation({ setGoals, onNavigate }: GoalCreationProps } } - const handleCreateGoal = () => { - const newGoal: Goal = { - id: Date.now(), - title: goalTitle, - progress: 0, - daysLeft: deadline ? Math.ceil((new Date(deadline).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : 0, - }; - setGoals((prevGoals) => [...prevGoals, newGoal]); - onNavigate("dashboard"); + const handleCreateGoal = async () => { + const base = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"; + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; + try { + const res = await fetch(`${base}/api/goals`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + title: goalTitle, + description, + targetDate: deadline || null, + }) + }) + const data = await res.json(); + if (!res.ok) { + alert(data?.error || 'Failed to create goal'); + return; + } + setGoals((prev) => [...prev, data]); + onNavigate("dashboard"); + } catch (e) { + alert('Failed to create goal'); + } } return ( diff --git a/components/pages/learning-screen.tsx b/components/pages/learning-screen.tsx index 7c88cbf..ba3d686 100644 --- a/components/pages/learning-screen.tsx +++ b/components/pages/learning-screen.tsx @@ -4,7 +4,9 @@ import { useState, useEffect } from "react" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { CheckCircle2, MessageSquare, FileText, BookMarked, Save } from "lucide-react" +import { CheckCircle2, MessageSquare, FileText, BookMarked, Save, Loader2 } from "lucide-react" +import ReactMarkdown from "react-markdown" +import remarkGfm from "remark-gfm" interface LearningScreenProps { onNavigate: (page: string) => void; @@ -13,28 +15,65 @@ interface LearningScreenProps { } export default function LearningScreen({ onNavigate, learningState, setLearningState }: LearningScreenProps) { - const { isCompleted, notes, messages, inputMessage } = learningState; + const { isCompleted, notes, messages, inputMessage, chatLoading } = learningState; const [saveStatus, setSaveStatus] = useState("idle"); // idle, saving, saved const setState = (newState: any) => { setLearningState({ ...learningState, ...newState }); }; - const sendMessage = () => { + const topic = "Power Rule & Chain Rule"; + + const sendMessage = async () => { if (inputMessage.trim()) { const newMessages = [...messages, { id: messages.length + 1, role: "user", text: inputMessage }]; - setState({ messages: newMessages, inputMessage: "" }); - - setTimeout(() => { - setState({ messages: [ - ...newMessages, - { - id: newMessages.length + 1, - role: "tutor", - text: "Great question! The derivative measures how a function changes at a specific point...", + setState({ messages: newMessages, inputMessage: "", chatLoading: true }); + + try { + const endpoint = process.env.NEXT_PUBLIC_BACKEND_URL + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/ai-chat` + : "http://localhost:8080/api/ai-chat"; + const payloadMessages = newMessages.map((m: any) => ({ role: m.role, text: m.text })); + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; + const res = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), }, - ]}) - }, 500) + body: JSON.stringify({ + messages: payloadMessages, + systemPrompt: `You are a friendly, student-focused AI tutor. Write answers in clean Markdown (headings, lists, bold) with step-by-step clarity. Do NOT use LaTeX or $...$ or \\(...\\) or \\[...\\]. Use plain-text math: exponents with ^ (x^2), fractions as a/b, and inline code for short expressions (like x^2 + 1). Stay strictly on the topic: ${topic}.`, + }), + }); + if (!res.ok) throw new Error("Failed to get AI response"); + const data = await res.json(); + const reply = (data?.reply ?? "I couldn't generate a response.").toString(); + const clean = reply.replace(/\$/g, "").replace(/\\[()\\[\\]]/g, ""); + setState({ + messages: [ + ...newMessages, + { + id: newMessages.length + 1, + role: "tutor", + text: clean, + }, + ], + chatLoading: false, + }); + } catch (e) { + setState({ + messages: [ + ...newMessages, + { + id: newMessages.length + 1, + role: "tutor", + text: "Sorry, I had trouble answering that. Please try again.", + }, + ], + chatLoading: false, + }); + } } } @@ -59,7 +98,7 @@ export default function LearningScreen({ onNavigate, learningState, setLearningS
-

Power Rule & Chain Rule

+

{topic}

Calculus Fundamentals

))} @@ -161,11 +208,13 @@ export default function LearningScreen({ onNavigate, learningState, setLearningS type="text" value={inputMessage} onChange={(e) => setState({ inputMessage: e.target.value })} - onKeyPress={(e) => e.key === "Enter" && sendMessage()} + onKeyPress={(e) => e.key === "Enter" && !chatLoading && sendMessage()} placeholder="Ask your tutor..." className="flex-1 px-4 py-2 rounded-lg border border-input bg-background text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary" + disabled={!!chatLoading} /> -
diff --git a/components/pages/study-plan.tsx b/components/pages/study-plan.tsx index 13157a5..b555366 100644 --- a/components/pages/study-plan.tsx +++ b/components/pages/study-plan.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from "react" +import { useEffect, useState } from "react" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { CheckCircle2, Circle, ChevronRight } from "lucide-react" @@ -15,44 +15,75 @@ interface Module { description: string; } -export default function StudyPlan({ onNavigate }: { onNavigate: (page: string) => void }) { +type Goal = { id: number; title: string; progress: number; daysLeft: number } + +export default function StudyPlan({ onNavigate, goal, onSelectGoal }: { onNavigate: (page: string) => void; goal?: Goal; onSelectGoal?: (goal: Goal) => void }) { const [selectedModule, setSelectedModule] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const [studyPlan, setStudyPlan] = useState([]) + const [goalsList, setGoalsList] = useState([]) + const [loadingGoals, setLoadingGoals] = useState(false) + + useEffect(() => { + if (!goal?.title) return + const fetchPlan = async () => { + setLoading(true) + setError(null) + try { + const base = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080" + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null + const res = await fetch(`${base}/api/study-plan/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + goalTitle: goal?.title || 'General Study', + days: goal?.daysLeft ? Math.max(1, Math.min(7, goal.daysLeft)) : 4, + level: 'beginner', + }) + }) + const data = await res.json() + if (!res.ok) throw new Error(data?.error || 'Failed to generate plan') + const planNode = data?.plan + if (planNode?.days && Array.isArray(planNode.days)) { + setStudyPlan(planNode.days) + } else { + const parsed = typeof data?.planText === 'string' ? JSON.parse(data.planText) : null + if (parsed?.days && Array.isArray(parsed.days)) setStudyPlan(parsed.days) + } + } catch (e: any) { + setError(e?.message || 'Failed to load study plan') + } finally { + setLoading(false) + } + } + fetchPlan() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [goal?.title]) - const studyPlan = [ - { - day: "Monday", - date: "Jan 15", - modules: [ - { id: 1, title: "Limits & Continuity", duration: "45 min", completed: true, type: "video", description: "An introduction to the fundamental concepts of limits and continuity, including definitions and examples." }, - { id: 2, title: "Derivatives Basics", duration: "60 min", completed: true, type: "article", description: "Learn the basics of derivatives, including the power rule and how to find the derivative of simple functions." }, - ], - }, - { - day: "Tuesday", - date: "Jan 16", - modules: [ - { id: 3, title: "Power Rule & Chain Rule", duration: "50 min", completed: false, type: "video", description: "A deep dive into the power rule and chain rule, with examples of how to apply them to more complex functions." }, - { id: 4, title: "Practice Problems Set 1", duration: "40 min", completed: false, type: "quiz", description: "A set of practice problems to test your understanding of derivatives and the power and chain rules." }, - ], - }, - { - day: "Wednesday", - date: "Jan 17", - modules: [ - { id: 5, title: "Integration Fundamentals", duration: "55 min", completed: false, type: "video", description: "An introduction to integration, including the concept of the antiderivative and the basic rules of integration." }, - { id: 6, title: "Integration Techniques", duration: "45 min", completed: false, type: "article", description: "Learn various integration techniques, including substitution and integration by parts." }, - ], - }, - { - day: "Thursday", - date: "Jan 18", - modules: [ - { id: 7, title: "Applications of Calculus", duration: "60 min", completed: false, type: "video", description: "Explore the real-world applications of calculus, including optimization problems and related rates." }, - { id: 8, title: "Real-world Problems", duration: "50 min", completed: false, type: "quiz", description: "A set of real-world problems to test your ability to apply calculus concepts to practical scenarios." }, - ], - }, - ] + useEffect(() => { + if (goal?.title) return + const fetchGoals = async () => { + setLoadingGoals(true) + try { + const base = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080" + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null + const res = await fetch(`${base}/api/goals`, { headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}) } }) + const list = await res.json() + setGoalsList(Array.isArray(list) ? list : []) + } catch { + setGoalsList([]) + } finally { + setLoadingGoals(false) + } + } + fetchGoals() + }, [goal?.title]) const handleModuleClick = (module: Module) => { setSelectedModule(module) @@ -83,28 +114,64 @@ export default function StudyPlan({ onNavigate }: { onNavigate: (page: string) = return type.charAt(0).toUpperCase() + type.slice(1) } + if (!goal?.title) { + return ( +
+
+

Select a Goal

+

Choose a goal to view its study plan

+
+ {loadingGoals ? ( +

Loading your goals...

+ ) : goalsList.length === 0 ? ( +

No goals yet. Create one from Goals page.

+ ) : ( +
+ {goalsList.map((g) => ( + onSelectGoal && onSelectGoal(g)}> +
+
+

{g.title}

+

{g.daysLeft} days

+
+ +
+
+ ))} +
+ )} +
+ ) + } + return (

Your Study Plan

-

Master Calculus - 4 weeks program

+

{goal?.title || 'Custom Plan'} - {goal?.daysLeft ? `${goal.daysLeft} days` : 'Flexible'}

+ {loading ? ( +

Generating your study plan...

+ ) : error ? ( +

{error}

+ ) : null} +
{studyPlan.map((day, dayIndex) => (
-

{day.day}

-

{day.date}

+

{day.day || `Day ${dayIndex + 1}`}

+

{day.date || ''}

- {day.modules.filter((m) => m.completed).length}/{day.modules.length} completed + {day.modules.filter((m: any) => m.completed).length}/{day.modules.length} completed
- {day.modules.map((module) => ( + {day.modules.map((module: any) => ( !Gmd|{`u*&~ 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 literal 0 HcmV?d00001 diff --git a/spring-backend/pom.xml b/spring-backend/pom.xml index 1054223..178741b 100644 --- a/spring-backend/pom.xml +++ b/spring-backend/pom.xml @@ -36,6 +36,36 @@ com.fasterxml.jackson.core jackson-databind + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + com.mysql + mysql-connector-j + runtime + + + io.jsonwebtoken + jjwt-api + 0.12.5 + + + io.jsonwebtoken + jjwt-impl + 0.12.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.5 + runtime + @@ -43,6 +73,15 @@ org.springframework.boot spring-boot-maven-plugin + ${spring-boot.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + diff --git a/spring-backend/src/main/java/com/example/aichat/controller/AuthController.java b/spring-backend/src/main/java/com/example/aichat/controller/AuthController.java new file mode 100644 index 0000000..05b890c --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/controller/AuthController.java @@ -0,0 +1,86 @@ +package com.example.aichat.controller; + +import com.example.aichat.dto.AuthRequest; +import com.example.aichat.dto.AuthResponse; +import com.example.aichat.dto.RegisterRequest; +import com.example.aichat.model.User; +import com.example.aichat.repo.UserRepository; +import com.example.aichat.security.JwtService; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = {"http://localhost:3000"}) +public class AuthController { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final JwtService jwtService; + private final AuthenticationManager authenticationManager; + + public AuthController(UserRepository userRepository, + PasswordEncoder passwordEncoder, + JwtService jwtService, + AuthenticationManager authenticationManager) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.jwtService = jwtService; + this.authenticationManager = authenticationManager; + } + + @PostMapping("/register") + public ResponseEntity register(@Valid @RequestBody RegisterRequest req) { + if (userRepository.existsByEmail(req.getEmail())) { + return ResponseEntity.status(HttpStatus.CONFLICT) + .body(Map.of("error", "Email already registered")); + } + User u = new User(); + u.setName(req.getName()); + u.setEmail(req.getEmail()); + u.setPassword(passwordEncoder.encode(req.getPassword())); + u = userRepository.save(u); + String token = jwtService.generateToken(u.getEmail()); + return ResponseEntity.ok(new AuthResponse(token, u.getName(), u.getEmail(), u.getRole())); + } + + @PostMapping("/login") + public ResponseEntity login(@Valid @RequestBody AuthRequest req) { + Authentication auth = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(req.getEmail(), req.getPassword()) + ); + if (!auth.isAuthenticated()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Map.of("error", "Invalid credentials")); + } + User user = userRepository.findByEmail(req.getEmail()).orElseThrow(); + String token = jwtService.generateToken(user.getEmail()); + return ResponseEntity.ok(new AuthResponse(token, user.getName(), user.getEmail(), user.getRole())); + } + + @GetMapping("/me") + public ResponseEntity me(@AuthenticationPrincipal UserDetails principal) { + if (principal == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "Unauthorized")); + } + User user = userRepository.findByEmail(principal.getUsername()).orElse(null); + if (user == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "Unauthorized")); + } + return ResponseEntity.ok(Map.of( + "name", user.getName(), + "email", user.getEmail(), + "role", user.getRole() + )); + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/controller/ChatController.java b/spring-backend/src/main/java/com/example/aichat/controller/ChatController.java index c530bf0..8ff61ad 100644 --- a/spring-backend/src/main/java/com/example/aichat/controller/ChatController.java +++ b/spring-backend/src/main/java/com/example/aichat/controller/ChatController.java @@ -26,7 +26,7 @@ public ResponseEntity chat(@RequestBody ChatRequestDto req) { try { List> msgs = new ArrayList<>(); String system = (req.getSystemPrompt() == null || req.getSystemPrompt().isBlank()) - ? "You are a friendly, student-focused AI tutor. Explain step-by-step, use simple language, add small examples, and keep responses concise unless asked for more detail." + ? "You are a friendly, student-focused AI tutor. Write answers in clean Markdown (headings, lists, bold) with step-by-step clarity. Do NOT use LaTeX or $...$ or \\(...\\) or \\[...\\]. Use plain-text math: exponents with ^ (x^2), fractions as a/b, inline code for short expressions (like `x^2 + 1`). Keep responses concise unless asked for more detail and include small, relevant examples." : req.getSystemPrompt(); msgs.add(Map.of("role", "system", "content", system)); diff --git a/spring-backend/src/main/java/com/example/aichat/controller/DbHealthController.java b/spring-backend/src/main/java/com/example/aichat/controller/DbHealthController.java new file mode 100644 index 0000000..54dc30c --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/controller/DbHealthController.java @@ -0,0 +1,36 @@ +package com.example.aichat.controller; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/db") +public class DbHealthController { + + @PersistenceContext + private EntityManager entityManager; + + @GetMapping("/health") + @CrossOrigin(origins = {"http://localhost:3000"}) + public ResponseEntity health() { + Map result = new HashMap<>(); + try { + Object one = entityManager.createNativeQuery("SELECT 1").getSingleResult(); + result.put("status", "up"); + result.put("db", one); + return ResponseEntity.ok(result); + } catch (Exception ex) { + result.put("status", "down"); + result.put("error", ex.getMessage()); + return ResponseEntity.status(500).body(result); + } + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/controller/GoalController.java b/spring-backend/src/main/java/com/example/aichat/controller/GoalController.java new file mode 100644 index 0000000..5a539b1 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/controller/GoalController.java @@ -0,0 +1,101 @@ +package com.example.aichat.controller; + +import com.example.aichat.dto.CreateGoalRequest; +import com.example.aichat.dto.GoalDto; +import com.example.aichat.model.Goal; +import com.example.aichat.model.User; +import com.example.aichat.repo.GoalRepository; +import com.example.aichat.repo.StudyPlanRepository; +import com.example.aichat.repo.UserRepository; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/goals") +@CrossOrigin(origins = {"http://localhost:3000"}) +public class GoalController { + + private final GoalRepository goalRepository; + private final UserRepository userRepository; + private final StudyPlanRepository studyPlanRepository; + + public GoalController(GoalRepository goalRepository, UserRepository userRepository, StudyPlanRepository studyPlanRepository) { + this.goalRepository = goalRepository; + this.userRepository = userRepository; + this.studyPlanRepository = studyPlanRepository; + } + + @GetMapping + public ResponseEntity list(@AuthenticationPrincipal UserDetails principal) { + if (principal == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + User user = userRepository.findByEmail(principal.getUsername()).orElse(null); + if (user == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + List goals = goalRepository.findByUserOrderByCreatedAtDesc(user); + LocalDate today = LocalDate.now(); + List dtos = goals.stream().map(g -> new GoalDto( + g.getId(), + g.getTitle(), + g.getProgress(), + g.getTargetDate() != null ? (int) Math.max(0, ChronoUnit.DAYS.between(today, g.getTargetDate())) : 0 + )).collect(Collectors.toList()); + return ResponseEntity.ok(dtos); + } + + @PostMapping + public ResponseEntity create(@AuthenticationPrincipal UserDetails principal, @RequestBody CreateGoalRequest req) { + try { + if (principal == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + User user = userRepository.findByEmail(principal.getUsername()).orElse(null); + if (user == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + String title = Optional.ofNullable(req.getTitle()).orElse("").trim(); + if (title.isEmpty()) return ResponseEntity.badRequest().body(Map.of("error", "Title is required")); + + Goal g = new Goal(); + g.setUser(user); + g.setTitle(title); + g.setDescription(req.getDescription()); + if (req.getTargetDate() != null && !req.getTargetDate().isBlank()) { + g.setTargetDate(LocalDate.parse(req.getTargetDate())); + } + g.setProgress(0); + Goal saved = goalRepository.save(g); + + LocalDate today = LocalDate.now(); + int daysLeft = saved.getTargetDate() != null ? (int) Math.max(0, ChronoUnit.DAYS.between(today, saved.getTargetDate())) : 0; + return ResponseEntity.ok(new GoalDto(saved.getId(), saved.getTitle(), saved.getProgress(), daysLeft)); + } catch (DataIntegrityViolationException dup) { + return ResponseEntity.status(409).body(Map.of("error", "Goal with this title already exists")); + } catch (Exception ex) { + return ResponseEntity.status(500).body(Map.of("error", "Failed to create goal", "details", ex.getMessage())); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@AuthenticationPrincipal UserDetails principal, @PathVariable Long id) { + try { + if (principal == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + User user = userRepository.findByEmail(principal.getUsername()).orElse(null); + if (user == null) return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + Goal g = goalRepository.findById(id).orElse(null); + if (g == null || !g.getUser().getId().equals(user.getId())) { + return ResponseEntity.status(404).body(Map.of("error", "Goal not found")); + } + // delete related study plan for this goal if any + studyPlanRepository.deleteByUserAndGoalTitle(user, g.getTitle()); + goalRepository.delete(g); + return ResponseEntity.ok(Map.of("ok", true)); + } catch (Exception ex) { + return ResponseEntity.status(500).body(Map.of("error", "Failed to delete goal", "details", ex.getMessage())); + } + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/controller/StudyPlanController.java b/spring-backend/src/main/java/com/example/aichat/controller/StudyPlanController.java new file mode 100644 index 0000000..297e21f --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/controller/StudyPlanController.java @@ -0,0 +1,141 @@ +package com.example.aichat.controller; + +import com.example.aichat.dto.StudyPlanRequest; +import com.example.aichat.service.OpenAIService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.example.aichat.model.StudyPlan; +import com.example.aichat.model.User; +import com.example.aichat.repo.StudyPlanRepository; +import com.example.aichat.repo.UserRepository; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; +import org.springframework.dao.DataIntegrityViolationException; + +import java.util.*; + +@RestController +@RequestMapping("/api/study-plan") +@CrossOrigin(origins = {"http://localhost:3000"}) +public class StudyPlanController { + + private final OpenAIService aiService; + private final ObjectMapper mapper = new ObjectMapper(); + private final StudyPlanRepository studyPlanRepository; + private final UserRepository userRepository; + + public StudyPlanController(OpenAIService aiService, StudyPlanRepository studyPlanRepository, UserRepository userRepository) { + this.aiService = aiService; + this.studyPlanRepository = studyPlanRepository; + this.userRepository = userRepository; + } + + @PostMapping(value = "/generate", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity generate(@AuthenticationPrincipal UserDetails principal, @RequestBody StudyPlanRequest req) { + try { + if (principal == null) { + return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + } + String email = principal.getUsername(); + User user = userRepository.findByEmail(email).orElse(null); + if (user == null) { + return ResponseEntity.status(401).body(Map.of("error", "Unauthorized")); + } + String goal = Optional.ofNullable(req.getGoalTitle()).orElse("General Study"); + int days = Optional.ofNullable(req.getDays()).orElse(4); + String level = Optional.ofNullable(req.getLevel()).orElse("beginner"); + String goalKey = goal.trim(); + + // 1) Return stored plan if exists + Optional existing = studyPlanRepository.findByUserAndGoalTitle(user, goalKey); + if (existing.isPresent()) { + String planJson = existing.get().getPlanJson(); + try { + JsonNode node = mapper.readTree(planJson); + return ResponseEntity.ok(Map.of("plan", node)); + } catch (Exception parseEx) { + return ResponseEntity.ok(Map.of("planText", planJson)); + } + } + + List> messages = new ArrayList<>(); + String system = "You are a study plan generator. Return STRICT JSON only (no markdown, no prose). Schema: {\n" + + " \"days\": [ {\n" + + " \"day\": string, \"date\": string,\n" + + " \"modules\": [ { \"id\": number, \"title\": string, \"duration\": string, \"completed\": boolean, \"type\": one of [\\\"video\\\",\\\"article\\\",\\\"quiz\\\"], \"description\": string } ]\n" + + " } ]\n" + + "}. Keep durations short like '45 min'. No extra text."; + messages.add(Map.of("role", "system", "content", system)); + + String prompt = String.format("Create a %d-day study plan for the goal: '%s'. Difficulty level: %s. Ensure a balanced mix of video, article, and quiz modules with concise descriptions.", days, goal, level); + messages.add(Map.of("role", "user", "content", prompt)); + + String content = aiService.getChatCompletion(messages); + // sanitize fenced code blocks if present + String cleaned = content.trim(); + if (cleaned.startsWith("```") ) { + cleaned = cleaned.replaceFirst("^```[a-zA-Z]*\\n", ""); + if (cleaned.endsWith("```")) cleaned = cleaned.substring(0, cleaned.length() - 3); + } + + Map body = new HashMap<>(); + try { + JsonNode node = mapper.readTree(cleaned); + body.put("plan", node); + // Save normalized JSON for consistency + try { + StudyPlan sp = new StudyPlan(); + sp.setUser(user); + sp.setGoalTitle(goalKey); + sp.setDays(days); + sp.setLevel(level); + sp.setPlanJson(mapper.writeValueAsString(node)); + studyPlanRepository.save(sp); + } catch (DataIntegrityViolationException dup) { + Optional exist2 = studyPlanRepository.findByUserAndGoalTitle(user, goalKey); + if (exist2.isPresent()) { + String pj = exist2.get().getPlanJson(); + try { + JsonNode n2 = mapper.readTree(pj); + return ResponseEntity.ok(Map.of("plan", n2)); + } catch (Exception ignore) { + return ResponseEntity.ok(Map.of("planText", pj)); + } + } + } + } catch (Exception parseEx) { + body.put("planText", content); + // store raw text as fallback + try { + StudyPlan sp = new StudyPlan(); + sp.setUser(user); + sp.setGoalTitle(goalKey); + sp.setDays(days); + sp.setLevel(level); + sp.setPlanJson(cleaned); + studyPlanRepository.save(sp); + } catch (DataIntegrityViolationException dup) { + Optional exist2 = studyPlanRepository.findByUserAndGoalTitle(user, goalKey); + if (exist2.isPresent()) { + String pj = exist2.get().getPlanJson(); + try { + JsonNode n2 = mapper.readTree(pj); + return ResponseEntity.ok(Map.of("plan", n2)); + } catch (Exception ignore) { + return ResponseEntity.ok(Map.of("planText", pj)); + } + } + } + } + return ResponseEntity.ok(body); + } catch (Exception ex) { + Map err = new HashMap<>(); + err.put("error", "Failed to generate study plan"); + err.put("details", ex.getMessage()); + return ResponseEntity.status(500).body(err); + } + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/AuthRequest.java b/spring-backend/src/main/java/com/example/aichat/dto/AuthRequest.java new file mode 100644 index 0000000..53bb5a9 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/AuthRequest.java @@ -0,0 +1,18 @@ +package com.example.aichat.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public class AuthRequest { + @Email + @NotBlank + private String email; + + @NotBlank + private String password; + + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/AuthResponse.java b/spring-backend/src/main/java/com/example/aichat/dto/AuthResponse.java new file mode 100644 index 0000000..32c0151 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/AuthResponse.java @@ -0,0 +1,29 @@ +package com.example.aichat.dto; + +public class AuthResponse { + private String token; + private String name; + private String email; + private String role; + + public AuthResponse() {} + + public AuthResponse(String token, String name, String email, String role) { + this.token = token; + this.name = name; + this.email = email; + this.role = role; + } + + public String getToken() { return token; } + public void setToken(String token) { this.token = token; } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/CreateGoalRequest.java b/spring-backend/src/main/java/com/example/aichat/dto/CreateGoalRequest.java new file mode 100644 index 0000000..9cddd1b --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/CreateGoalRequest.java @@ -0,0 +1,14 @@ +package com.example.aichat.dto; + +public class CreateGoalRequest { + private String title; + private String description; + private String targetDate; // ISO yyyy-MM-dd + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public String getTargetDate() { return targetDate; } + public void setTargetDate(String targetDate) { this.targetDate = targetDate; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/GoalDto.java b/spring-backend/src/main/java/com/example/aichat/dto/GoalDto.java new file mode 100644 index 0000000..40f1f0f --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/GoalDto.java @@ -0,0 +1,26 @@ +package com.example.aichat.dto; + +public class GoalDto { + private Long id; + private String title; + private int progress; + private int daysLeft; + + public GoalDto() {} + + public GoalDto(Long id, String title, int progress, int daysLeft) { + this.id = id; + this.title = title; + this.progress = progress; + this.daysLeft = daysLeft; + } + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public int getProgress() { return progress; } + public void setProgress(int progress) { this.progress = progress; } + public int getDaysLeft() { return daysLeft; } + public void setDaysLeft(int daysLeft) { this.daysLeft = daysLeft; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/RegisterRequest.java b/spring-backend/src/main/java/com/example/aichat/dto/RegisterRequest.java new file mode 100644 index 0000000..220d95c --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/RegisterRequest.java @@ -0,0 +1,25 @@ +package com.example.aichat.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class RegisterRequest { + @NotBlank + private String name; + + @Email + @NotBlank + private String email; + + @NotBlank + @Size(min = 6) + private String password; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/dto/StudyPlanRequest.java b/spring-backend/src/main/java/com/example/aichat/dto/StudyPlanRequest.java new file mode 100644 index 0000000..b11082d --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/dto/StudyPlanRequest.java @@ -0,0 +1,16 @@ +package com.example.aichat.dto; + +public class StudyPlanRequest { + private String goalTitle; + private Integer days; + private String level; // optional: beginner/intermediate/advanced + + public String getGoalTitle() { return goalTitle; } + public void setGoalTitle(String goalTitle) { this.goalTitle = goalTitle; } + + public Integer getDays() { return days; } + public void setDays(Integer days) { this.days = days; } + + public String getLevel() { return level; } + public void setLevel(String level) { this.level = level; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/model/Goal.java b/spring-backend/src/main/java/com/example/aichat/model/Goal.java new file mode 100644 index 0000000..5f13ff2 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/model/Goal.java @@ -0,0 +1,55 @@ +package com.example.aichat.model; + +import jakarta.persistence.*; +import java.time.Instant; +import java.time.LocalDate; + +@Entity +@Table(name = "goals", uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "title"}) +}) +public class Goal { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") + private User user; + + @Column(nullable = false) + private String title; + + @Lob + private String description; + + private LocalDate targetDate; + + @Column(nullable = false) + private int progress = 0; + + @Column(nullable = false, updatable = false) + private Instant createdAt = Instant.now(); + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public User getUser() { return user; } + public void setUser(User user) { this.user = user; } + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public LocalDate getTargetDate() { return targetDate; } + public void setTargetDate(LocalDate targetDate) { this.targetDate = targetDate; } + + public int getProgress() { return progress; } + public void setProgress(int progress) { this.progress = progress; } + + public Instant getCreatedAt() { return createdAt; } + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/model/StudyPlan.java b/spring-backend/src/main/java/com/example/aichat/model/StudyPlan.java new file mode 100644 index 0000000..be188a2 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/model/StudyPlan.java @@ -0,0 +1,56 @@ +package com.example.aichat.model; + +import jakarta.persistence.*; +import java.time.Instant; + +@Entity +@Table(name = "study_plans", uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "goal_title"}) +}) +public class StudyPlan { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id") + private User user; + + @Column(name = "goal_title", nullable = false) + private String goalTitle; + + @Lob + @Column(name = "plan_json", nullable = false, columnDefinition = "LONGTEXT") + private String planJson; + + @Column + private Integer days; + + @Column + private String level; + + @Column(nullable = false, updatable = false) + private Instant createdAt = Instant.now(); + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public User getUser() { return user; } + public void setUser(User user) { this.user = user; } + + public String getGoalTitle() { return goalTitle; } + public void setGoalTitle(String goalTitle) { this.goalTitle = goalTitle; } + + public String getPlanJson() { return planJson; } + public void setPlanJson(String planJson) { this.planJson = planJson; } + + public Integer getDays() { return days; } + public void setDays(Integer days) { this.days = days; } + + public String getLevel() { return level; } + public void setLevel(String level) { this.level = level; } + + public Instant getCreatedAt() { return createdAt; } + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/model/User.java b/spring-backend/src/main/java/com/example/aichat/model/User.java new file mode 100644 index 0000000..9b191b2 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/model/User.java @@ -0,0 +1,48 @@ +package com.example.aichat.model; + +import jakarta.persistence.*; +import java.time.Instant; + +@Entity +@Table(name = "users", uniqueConstraints = { + @UniqueConstraint(columnNames = {"email"}) +}) +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false, unique = true) + private String email; + + @Column(nullable = false) + private String password; // BCrypt hashed + + @Column(nullable = false) + private String role = "ROLE_USER"; + + @Column(nullable = false, updatable = false) + private Instant createdAt = Instant.now(); + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + + public Instant getCreatedAt() { return createdAt; } + public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } +} diff --git a/spring-backend/src/main/java/com/example/aichat/repo/GoalRepository.java b/spring-backend/src/main/java/com/example/aichat/repo/GoalRepository.java new file mode 100644 index 0000000..4b613ee --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/repo/GoalRepository.java @@ -0,0 +1,13 @@ +package com.example.aichat.repo; + +import com.example.aichat.model.Goal; +import com.example.aichat.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface GoalRepository extends JpaRepository { + List findByUserOrderByCreatedAtDesc(User user); + Optional findByUserAndTitle(User user, String title); +} diff --git a/spring-backend/src/main/java/com/example/aichat/repo/StudyPlanRepository.java b/spring-backend/src/main/java/com/example/aichat/repo/StudyPlanRepository.java new file mode 100644 index 0000000..ebfbd27 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/repo/StudyPlanRepository.java @@ -0,0 +1,13 @@ +package com.example.aichat.repo; + +import com.example.aichat.model.StudyPlan; +import com.example.aichat.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface StudyPlanRepository extends JpaRepository { + Optional findByUserAndGoalTitle(User user, String goalTitle); + Optional findByUserEmailAndGoalTitle(String email, String goalTitle); + void deleteByUserAndGoalTitle(User user, String goalTitle); +} diff --git a/spring-backend/src/main/java/com/example/aichat/repo/UserRepository.java b/spring-backend/src/main/java/com/example/aichat/repo/UserRepository.java new file mode 100644 index 0000000..63bbbc9 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/repo/UserRepository.java @@ -0,0 +1,11 @@ +package com.example.aichat.repo; + +import com.example.aichat.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + boolean existsByEmail(String email); +} diff --git a/spring-backend/src/main/java/com/example/aichat/security/JwtAuthFilter.java b/spring-backend/src/main/java/com/example/aichat/security/JwtAuthFilter.java new file mode 100644 index 0000000..7924f76 --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/security/JwtAuthFilter.java @@ -0,0 +1,54 @@ +package com.example.aichat.security; + +import com.example.aichat.service.ApplicationUserDetailsService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class JwtAuthFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final ApplicationUserDetailsService userDetailsService; + + public JwtAuthFilter(JwtService jwtService, ApplicationUserDetailsService userDetailsService) { + this.jwtService = jwtService; + this.userDetailsService = userDetailsService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + String token = authHeader.substring(7); + try { + String username = jwtService.extractUsername(token); + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + if (jwtService.isTokenValid(token, userDetails.getUsername())) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + } catch (Exception ignored) { + // invalid token -> proceed without authentication + } + filterChain.doFilter(request, response); + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/security/JwtService.java b/spring-backend/src/main/java/com/example/aichat/security/JwtService.java new file mode 100644 index 0000000..86e835c --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/security/JwtService.java @@ -0,0 +1,67 @@ +package com.example.aichat.security; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; + +@Service +public class JwtService { + + private final SecretKey key; + private final long expirationMs; + + public JwtService( + @Value("${jwt.secret:CHANGEME_SUPER_SECRET_KEY_32_BYTES_MIN_LENGTH_1234567890}") String secret, + @Value("${jwt.expiration:86400000}") long expirationMs + ) { + // ensure at least 32 bytes for HS256 + if (secret == null || secret.getBytes(StandardCharsets.UTF_8).length < 32) { + secret = "CHANGEME_SUPER_SECRET_KEY_32_BYTES_MIN_LENGTH_123456"; + } + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + this.expirationMs = expirationMs; + } + + public String generateToken(String subject) { + long now = System.currentTimeMillis(); + return Jwts.builder() + .subject(subject) + .issuedAt(new Date(now)) + .expiration(new Date(now + expirationMs)) + .signWith(key) + .compact(); + } + + public String extractUsername(String token) { + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload() + .getSubject(); + } + + public boolean isTokenValid(String token, String username) { + try { + String sub = extractUsername(token); + return username.equals(sub) && !isExpired(token); + } catch (Exception ex) { + return false; + } + } + + public boolean isExpired(String token) { + Date exp = Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload() + .getExpiration(); + return exp.before(new Date()); + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/security/SecurityConfig.java b/spring-backend/src/main/java/com/example/aichat/security/SecurityConfig.java new file mode 100644 index 0000000..62ccded --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/security/SecurityConfig.java @@ -0,0 +1,66 @@ +package com.example.aichat.security; + +import com.example.aichat.repo.UserRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableMethodSecurity +public class SecurityConfig { + + private final JwtAuthFilter jwtAuthFilter; + private final UserDetailsService userDetailsService; + + public SecurityConfig(JwtAuthFilter jwtAuthFilter, UserDetailsService userDetailsService) { + this.jwtAuthFilter = jwtAuthFilter; + this.userDetailsService = userDetailsService; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .cors(c -> {}) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.POST, "/api/auth/login", "/api/auth/register").permitAll() + .requestMatchers(HttpMethod.GET, "/api/db/health").permitAll() + .requestMatchers(HttpMethod.POST, "/api/ai-chat").permitAll() + .anyRequest().authenticated() + ) + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + return provider; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { + return configuration.getAuthenticationManager(); + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/service/ApplicationUserDetailsService.java b/spring-backend/src/main/java/com/example/aichat/service/ApplicationUserDetailsService.java new file mode 100644 index 0000000..0602a9f --- /dev/null +++ b/spring-backend/src/main/java/com/example/aichat/service/ApplicationUserDetailsService.java @@ -0,0 +1,32 @@ +package com.example.aichat.service; + +import com.example.aichat.model.User; +import com.example.aichat.repo.UserRepository; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ApplicationUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + public ApplicationUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email)); + return new org.springframework.security.core.userdetails.User( + user.getEmail(), + user.getPassword(), + List.of(new SimpleGrantedAuthority(user.getRole())) + ); + } +} diff --git a/spring-backend/src/main/java/com/example/aichat/service/OpenAIService.java b/spring-backend/src/main/java/com/example/aichat/service/OpenAIService.java index 2d66680..e6d5809 100644 --- a/spring-backend/src/main/java/com/example/aichat/service/OpenAIService.java +++ b/spring-backend/src/main/java/com/example/aichat/service/OpenAIService.java @@ -137,4 +137,4 @@ private String callGemini(List> messages, String geminiKey) } return contentText.trim(); } -} +} \ No newline at end of file diff --git a/spring-backend/src/main/resources/application.properties b/spring-backend/src/main/resources/application.properties index dfe828e..f8c611d 100644 --- a/spring-backend/src/main/resources/application.properties +++ b/spring-backend/src/main/resources/application.properties @@ -5,3 +5,23 @@ openai.model=gpt-4o-mini gemini.model=gemini-2.5-pro gemini.apiKey=AIzaSyAjZiHnVoah5ZNpGKk2n6rlFPN10rceboI gemini.baseUrl=https://generativelanguage.googleapis.com/v1 + +# ============================ +# MySQL datasource (local) +# ============================ +spring.datasource.url=jdbc:mysql://localhost:3306/studyhub?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Kolkata&createDatabaseIfNotExist=true +spring.datasource.username=root +spring.datasource.password=vifaaq8808 + +# JPA/Hibernate +spring.jpa.hibernate.ddl-auto=update +spring.jpa.open-in-view=false +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect + +# Connection pool +spring.datasource.hikari.maximum-pool-size=10 + +# JWT +jwt.secret=CHANGE_ME_SUPER_SECRET_32_BYTES_MIN +jwt.expiration=86400000 diff --git a/spring-backend/target/aichat-0.0.1-SNAPSHOT.jar b/spring-backend/target/aichat-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..9b5146e62268df437237c99c745c3d8844dd8260 GIT binary patch literal 26763 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 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/application.properties b/spring-backend/target/classes/application.properties index dfe828e..f8c611d 100644 --- a/spring-backend/target/classes/application.properties +++ b/spring-backend/target/classes/application.properties @@ -5,3 +5,23 @@ openai.model=gpt-4o-mini gemini.model=gemini-2.5-pro gemini.apiKey=AIzaSyAjZiHnVoah5ZNpGKk2n6rlFPN10rceboI gemini.baseUrl=https://generativelanguage.googleapis.com/v1 + +# ============================ +# MySQL datasource (local) +# ============================ +spring.datasource.url=jdbc:mysql://localhost:3306/studyhub?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf8&useUnicode=true&serverTimezone=Asia/Kolkata&createDatabaseIfNotExist=true +spring.datasource.username=root +spring.datasource.password=vifaaq8808 + +# JPA/Hibernate +spring.jpa.hibernate.ddl-auto=update +spring.jpa.open-in-view=false +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect + +# Connection pool +spring.datasource.hikari.maximum-pool-size=10 + +# JWT +jwt.secret=CHANGE_ME_SUPER_SECRET_32_BYTES_MIN +jwt.expiration=86400000 diff --git a/spring-backend/target/classes/com/example/aichat/AiChatApplication.class b/spring-backend/target/classes/com/example/aichat/AiChatApplication.class index de4e1085eb0ab118aa34b72112b7098f05ed1d6a..332585e0d38a9797bc7ac78e13d028ae5661a946 100644 GIT binary patch delta 17 ZcmX@fdXkmn)W2Q(7#J9AH*y?g0sul32A%)_ delta 17 ZcmX@fdXkmn)W2Q(7#J8#HgX(f0sukh29*E+ diff --git a/spring-backend/target/classes/com/example/aichat/config/CorsConfig.class b/spring-backend/target/classes/com/example/aichat/config/CorsConfig.class index 4506d85e5cc4bf86b6842518b481fa9f5ed74fe3..bf0e12cf936f16aaeb4ebd067d5cad95155707fe 100644 GIT binary patch delta 17 Zcmeyv^M{Ay)W2Q(7#J9AH*%D)0RTv_2FU;b delta 17 Zcmeyv^M{Ay)W2Q(7#J8#Hgc4(0RTvY2EYIS diff --git a/spring-backend/target/classes/com/example/aichat/controller/AuthController.class b/spring-backend/target/classes/com/example/aichat/controller/AuthController.class new file mode 100644 index 0000000000000000000000000000000000000000..3185e48b9a998e613fd317eb60b436ed042fdc93 GIT binary patch literal 5814 zcmb_g=YJH}6+LgYXjamIu`t-e!Ilw44GpFwHY>oA5Jp%9OF|AfAs($p(xBDOGBXRv ziLujruXcLx33iM?CdH{voZ=Lx{~7s^FaG71b7#9WD@5WCyEAX*zW44u_uMk?`Tsog zLje2n&ss!Kr6Ho-D#(6|A0bObIVu_VyMwjtD_FO zLVLoQHLUaIY&vNfrk$8JeIwzdeAh`PE!XJE_|rXwp~9MsXSu^x+VO1Papx48`$HCR zDPSBglZ?y0YjoUzwF)<+P0u^$xD&mpgfn5e3J3Zfcgpb6uAQ2ibj?|dZ)Xh8N@QHy zpEDBfT-tYx{50cO`TStZ_;Wj z<=Y9Mqc+^Zt?ZaOV@5W zS%YoZuAx~+3tAPL!kjhj`)T6{|3-Y%&v;b1XK3(ne_zk2!q!ma7R?|w?bxBALx+K# z3fn90GHiKiC*@ha^pQ^zR(rWEA8KC7Mm^+Qyrr^a`SM%3or$^bjGZJ=<1y?~SY^4c zW?CWQOe*Xx#X1|T z@e=fNPjb%A&!wk3v0uXh9S3ob!tNEKRBDDobeu*h><`gk#lXZhbSrEMVel*gP6i2} z7G0ZfBp5~ESL-+- z3fEELqHc|NP{(WVT6(}U&oV)6D_ytT<0Uzqq0xm~Yw-|F4dXfzmdCy(l+JZAy`adB=z~#atc0R104cbw*+HvJeFM zS%imbaR%qbS_ankQfn)R_E;TWk2h#|M8^d@s?c>kMwBaEs8V`~tt8lFcuFh~CE>W% zqFPlUH!eG+4sXPpG`v~ITkuweoy!^$vL~AY+bgliBEi|N1eNFQI^H2=M?XH;b$s;5 z&~V?$-a|3GOJQAK>a3ZxCmIv3H6ac(lb%$<_vm;p-pAmZv{Ms>+SXj^lycq&*>xx_ z<=#v(+CG2}YWR?j591>W^~L0!7va*5+ne;P(P`H?x40(f93I2RMCp_`#BuG1r7C;^ zk85~B$0sFD?!Gok(r-;zJ`2AWB-BIM`)tn3(^&NVw2se6Ua8LN6~pJ4V}#=|d_hW< z^bRq6i4WP@SyG<9BDLwO3Xhb+Qjx1Ak@ zzWgpF(M7T>ikw^!##>lP0K6>!;A zezq3Xm+&eVk}OzkArmuflyczIjM!5tHkB^X8+-Y!>})+ijT(kCOdzw?nC;p0OIIr8 zu&bnx>6X2roH%EV8{ekc5o+U7nlq{9_$lg^aQ{$Qvg#FC(MbFPa-Tf+EI zY$JjiN(G`4Tnf8F(Jq8)NcE7pk|`C2UtZm_)TlOc$V6u8%oSDC3VHjwP^*#8;e?$w z`O$ZctQ25UX-@@vB~3u3=_~#q$bz*e)O%sEa<5eqDpiJHdpLA*x$8@!P54;s+m7Yo zA{}Z+oQ#{W4%?D(*NI&57^6e#xx!tcdxa^=r05r?bM@<)8j!n~5^4-HZ5vsluRN{v zxQ^!yxi;gI+iUD5c*n%FUXu1cgZyMknP%?Uw|nPKemm=ZDYpDQNuHBf6ss%whQ%E` ziHY#+D2jSH1L9r19Loe(o}dI*o`VDuObYlK6u!>UW1O{$*Nv^0p<3H6L2GTlgw?G( zF5$-3hUg`%f0Az__=d=!n*UAQV+(4q6>E5aunybNfM$G?Z}wzY!ng2kjznHGQNf+) zBKmEV^BhzKccM>}@4tiZa;9kWZ|)G~^+M~@(AwtFuz=emcpMv^!qzKjtG}~;_Z942 zKsD^Ur0*<1DoDwM1-x63dq43S6ufq1@!A&^z#5_)PRS$wmLS%OT5cbw z!kzSVoZL7-)D9BEdx+CP;?&LafgX(EPzh(80$L8y`YBCnpv2GcbDG&lmi>abRq;tf zfLk=cEe7=x_G2zb9-0eHW{^Yoao?*)P<%skchTnRiMEr(|TA6MLo0I7L= zN*L81E?xgwVOIU;g)EhZ2_3yQ8>%07Xlh-Z0zuR&Z)v5 o_#eR^1JT2wd>9g}Pfc!iw8rMPq9 z0X&0e5o;n8)Pt(`y{fm}S9HFAKfhY7*6DlaK4~W}n=(=BTdyO7Y*?@l&=!SZvbZEa zE#1N*t(Zz}pF$3!LzFglrp&O=u|kMk_r|MU)5S=_S}B}4TwV7u4~z@y^BtT=S1HwP z#2^(l$hPu9skItBD%{9wZAjZ*BF)Ukao!a{+`I8I#M% S@H5#4L$lg&_~^9vcK!f|O>NQu delta 102 zcmZotSSiPG>ff$?3=9k=8#$sFnHZK%PGQt@ajhuGNzBYsC@x7Y&`qk;1<^VRrNyZV z#hJMUIjIUciFxUziRr023W+Hx3dOmJIXMcc6^S6ZVxU-FibD3}E+(VR51ABs0V*{k AoB#j- diff --git a/spring-backend/target/classes/com/example/aichat/controller/DbHealthController.class b/spring-backend/target/classes/com/example/aichat/controller/DbHealthController.class new file mode 100644 index 0000000000000000000000000000000000000000..3d46a5d25d9c7a50c884f97da618e18cbd7fa250 GIT binary patch literal 2171 zcmb7FYgZdp6x}xgCWJvCEqzeif~74l8OlQ|0cuHUK?4+mRIIO?S3DyYfI9 zmb6R8dVW_G0)e(U(>8+zf%wqywuXd2Poz`{Ov_l7etA{8Noa_-qXW8zP95)|OJJhl z>>KJx?z@&Uq**A-z$iF&;5nA1JmX1zSxGA>XU;?doyrc(;CNNqvZOqLJK0^iC%r%# zuJU}-50qU{#!{nFI)NU6L~bd&l-cTk(1A<1tf5!O6bJI&{1}qZ|aTLH}LH_Ej|>yzA@<%wJ%BINKyf zTEhc@OLa1qjta^ROvg@ON}#3a9C9@y(5gJo@sgOp$2uP36M-aYT~)r%!ucqC~ifI*<$3!pSk%muoJPyg3YBI1!491ZsBCBMx!b;v(+oo^k zE%n&89j2SX`T`+~=PUnty$iy&QAa_VXc?!-n7gv3t=200dF5?M!U?3ZPC;7R(lf(- zP1q8YP2x{x-?ckwsjVD!tiUKMw#mqOE$-NnH}$mE%916cjs$ue%JVGYp*gSCAYE03 z=)qy7^XSUkp&h%Y;my3QI@bVQD}x3}&5Z>DcIE_d!&%edvVY1jKO*_EHAgDj(^x<8u{#fDd zIuV*7UDGJ$1@sl$R$j)EzONWfM?))OfZt7wpBSqs{BSrga@WQ=!#T#8KN`m;+!>q& z=hl(e5I;r7OB};vpc_v)>Qx0i!!maQEBFlY=<=VOUL25IUxzL*o}CH2Secc?@4foc3|GP2CqSA$)v^9j?EM i)-OrS*OBHok!FgT-*W%mnfCXP+$XtT;QO0zYyTJYqf~tW literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/controller/GoalController.class b/spring-backend/target/classes/com/example/aichat/controller/GoalController.class new file mode 100644 index 0000000000000000000000000000000000000000..e55bd8e64dbb95b2fb7773ad1185cc238b28481d GIT binary patch literal 8348 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 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/controller/StudyPlanController.class b/spring-backend/target/classes/com/example/aichat/controller/StudyPlanController.class new file mode 100644 index 0000000000000000000000000000000000000000..172302e57489bdb988297ef853baa69e80e3141c GIT binary patch literal 8133 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 O9}+xbCb6Bj{NOiXs literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/dto/AuthResponse.class b/spring-backend/target/classes/com/example/aichat/dto/AuthResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..672c4f2f13b31500cd56ed12ae90e1e5386f33cb GIT binary patch literal 1313 zcmbV~O>fgc5Qg7PY^MoMnO5L}3g)K$q&1Nc#hnYGna=|)1x($4I9=G|vz_V=HkzW}^MtArf#1z0X@I0BV(`APbrjE4S) z{<#_?0?vyd3X)d>`BuAE#2ShPlw90^E3iL^$G-Y3#}}dUWiS}Y#6L@7|Me^x9joa@ z98Hygox~qiBv9=x)K8L05DgDmZ{5WPi&>EzD?ZqCQRM?$jb#w>$(D<{4w=Lu30iOB zGcp_9AW}!OabHbNWuJdkyYWDVJvj+@pB-AsD3}TybpHYNP~d&*I_CeKw0i<;LzSGS zygkVnC($HH&9KdD0Ia^$h@5l5z8=_H;_2I^hF9ooD) znx{Mu>`{Yr^cpN$X{zMqcp%!+9JHnGlP!UU0*BTH?R>I=tljwm@lA)_q?LaIMQqdR zrU7Weqa7#j(8|eQ=n|W($H|>Xa}+v{=O}lc%u(qytvPB7MUON=Xcp-eQN}ubH9~Bn zqHlSYDrL8jrY?giJY5JE=kb$5UVB`OMI5`hmC-Cp`HsR@3d&(oO4V4(ZQRL{pHnnn z;pj3J*l8nbI=hiZTp~IdqQG4vM0pvaZiLt~LzI^x8Y>`p%sj+abSkH@`}=N?5=>|H^C4O^za}41wJmhL;wH) literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/dto/ChatReplyDto.class b/spring-backend/target/classes/com/example/aichat/dto/ChatReplyDto.class index 036e3a60547be94342d33dc3c309727c2d923416..4596147df43b83a4b73f0b2d9a4e584256da10ec 100644 GIT binary patch delta 17 YcmZo+ZDHj&^>5cc1_lP(jT|*h066Le&j0`b delta 17 YcmZo+ZDHj&^>5cc1_lO`jT|*h064t`#sB~S diff --git a/spring-backend/target/classes/com/example/aichat/dto/ChatRequestDto.class b/spring-backend/target/classes/com/example/aichat/dto/ChatRequestDto.class index 9e7784430adbeaab95775aa916cfc0b4184d3b21..f0472a4fa6ac233db3594f12d2e0e6d64e5a7e07 100644 GIT binary patch delta 17 ZcmX@kah!wW)W2Q(7#J9AH*(ms002QO1|R?c delta 17 ZcmX@kah!wW)W2Q(7#J8#Hgedr002P$1{VMT diff --git a/spring-backend/target/classes/com/example/aichat/dto/CreateGoalRequest.class b/spring-backend/target/classes/com/example/aichat/dto/CreateGoalRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..783f73bb585b222f09e999ef855e41dd499c123c GIT binary patch literal 966 zcma)(%Wm3G5QhK5&0y?62%+~2T>+8eeQB~u)ND{IH4*!RIU*-AHZ_N+kJU)2DrM0J z$U{Y)0TjSEQZ_T5@m#+7&;7&QKLAJgWFv#Dg`9&t3If$Dc`dy_h9mF$;7a*MpzuY9 z+8hgHTkU=cMU*Vq4$5!@T7ES4)Gs-n1j>`zzm&!sn#enel{D%slEDx4bEc*=CU3L} zlt8VwT5)b-9gey@bjLx3Czgk5>c@IwbQE%a*FlZ*wvq8jnNu1okUNQnI;6!sRt5l^0 zJ&HW?f_%R77vg4KRwv89dB{FlXHkF#UQi_P60gW^Hs8YfO+PbOH5XHxU*pZP`7>2B zX`yrQ2Uc=*Y2MvfR4i9B7T?6gZ&M-MB!ry`@h%<0O+u8PfM7ux;(aPaH3{LQLVQSv Ss3sxYCm{GW8RFwy_vU}4{hF)* literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/dto/GoalDto.class b/spring-backend/target/classes/com/example/aichat/dto/GoalDto.class new file mode 100644 index 0000000000000000000000000000000000000000..0bc245bd58c4d92d0616a003d566669c2a00967a GIT binary patch literal 1356 zcmaKq%Wl&^6o&svoNJw4a%+>)QfQ%xTj~uf3T&uAks?59mHi|!sjDQe92dck2Vwyd z5fTd?fQLeyGd8ht9b{?7Gjq;A-+#{h`u*c4fM+-MIyz;QRUK=gTM;&8 zAhHfvJDqlq%#19rLypU>b|gfl(BaHKtlX00)I6nL_@$`+lO0?@5tJW9)S?nW0E?`U z$lFDss9_!2qqJ~OYbj6;JX;iwL&8<@+_`ZF)=Qg9rw6S(;TGZ^6+T4|N28G*0R5%7 zi)yAeshPS$a}i`1SsJ%!WzsBY&KTbzzN)YWjeHCg(4 z$;rmi96957j*_u6$A(eU=BR~=P0|FRX{1*`39Ix~2(dwse9L2BDY%6+l?|q7DkE&J z$uACR^~Yv3;@HLAfF`u^4Y@BAlp^uJR?6y^wO1VbDv%a8s51Mk3j literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/dto/MessageDto.class b/spring-backend/target/classes/com/example/aichat/dto/MessageDto.class index 336affc46414f23bd297b09cc58a2d6488d60b80..9819395fa9c059880f939ed09fd21ad07f0b7036 100644 GIT binary patch delta 17 ZcmaFI_KuC?)W2Q(7#J9AH*%yg0{})H29N*% delta 17 ZcmaFI_KuC?)W2Q(7#J8#Hgcpf0{}(v28RFu diff --git a/spring-backend/target/classes/com/example/aichat/dto/RegisterRequest.class b/spring-backend/target/classes/com/example/aichat/dto/RegisterRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..e770b395c108e8d9d2c14876c8ad51ca09aba064 GIT binary patch literal 1237 zcma)4+iuf95FO{{G)bG#T-pMq6lh3_gx@J30Rjmr3Mj4ezHwIRw!Q>=Q!ZZx5(tS0 zK7fxxoN=9$7+2-Nv%52Aj?bRiU%!9+B%&wOEm4NDCgm!Wr-DJX6LBUiM|czK&FDnh z+MvQ?pD^twQUZDNaQgzW0N%L0f~Frp!ggobNX1}a-H7) zxr|?#6RG==(~S;$GM{DaH2~op(CqwM?+)QOD54u;3vj$GBl~tSiZTipr_!N63vBsH z@kJVm(LZDnw2P5uU_llU4+pW%h7j;roA`=kAjPK)zl9jVud{{R9JNLnppaQ$@k(NB z6)WR5z`FUGwj$bk)POV^76lUsymrLvz*|@itoi-#WPHIULpKn)Ok~nc#L78qgYwI!DC6W?AeXE54g5Vl^qE unJVI5x`@@Jh}I<{ctk8>FI7Y>DPkj4L_1wXEh%F25)nLm7I8oFz4Hf`-PfQ1 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/dto/StudyPlanRequest.class b/spring-backend/target/classes/com/example/aichat/dto/StudyPlanRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..720e2c2b34aa3cd7cabf536e634c05d8167b4f61 GIT binary patch literal 1010 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 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/repo/GoalRepository.class b/spring-backend/target/classes/com/example/aichat/repo/GoalRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..94a0e36a17ac87063b8ff1aa185a9b388340c228 GIT binary patch literal 743 zcmb_a%TB{E5L}l>Xn7U5b3|~#;=n0TsRdC9MXJ=m2Q0}pbxGpLae(k?9QXh}3SlP_ zZ6#0vaj@j^V`g@C-`-zd0pJ{V9B42&2xP+P36m@)jv^SN=87`O`x4`!+2le?RXVWD zpgq9{qP@zT4J&|jdLDLsein>t5VEEhr zXW#4DG~A96*qyrWrs0jyu~F(TQmTzs$@$tQHRpYhX(3Z1z+n78Oh2jQdB#-TPS^WW zD;J5)=y=kUZewiuvQPnC2^-(u{Hz_@uA0GOuw>SX8PgV$l>6K|Tc L`eqBZs~7DLhk)<3 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/repo/StudyPlanRepository.class b/spring-backend/target/classes/com/example/aichat/repo/StudyPlanRepository.class new file mode 100644 index 0000000000000000000000000000000000000000..2912b777f5006752451f303ce00753d18c260ec8 GIT binary patch literal 877 zcmb_bO-sW-5S?w+*!oo!q~IT@2<}BMMg;{eNT6cXvtzQ2TQ}LT-KahJzdZN@{88eh zfu_CaF z!A?iU!A1YRC(L;qG&QO_X_a8mYcO0879l~}1`R4#)` zTZS>()DUa*KddLydmE0;J)_#Yg}loMrTVM!xG^qbJTgVrb~A}@E3>&@kynKXS6ov29pV-!FL3{! z!7!&+LP=$Ylc$^~E`VJI=gMSgwGm}DHB|67WnM5PN0@7BSSOjw&Z4PP7xMBpEX3#v&`pRfaRT{o}KXXv3a`O1W$n4X-?Rz-Pib z`)%oX6Z?_DV$ErrLm2%lgWpW8{SzslM{TkftI8yNETrH5=x0f1b%MST23!8@_?A1q YwE*_K>edQ+UiV?Yxwn%695m{13F_^p{Qv*} literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/security/JwtAuthFilter.class b/spring-backend/target/classes/com/example/aichat/security/JwtAuthFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..c613f6b258b0f4737ddef8e99a31e624514b56e1 GIT binary patch literal 3530 zcmb_eS#uLd5dOxNwX(d%ARs{yh`BAvup~eN5|?ctHZeXh$T5&(wHn)NuXn}n$Oq)U zU#TLgN*=;vo~a5Z4pn(1RryU+Dm|-}j4atvQF$3@=jiGF`kSx&?|=UI6Tn$~A4L5_r z;v7SBS0dYr2wD|HRkT56NEptX#uxRug2gr6G^TZ-dE6+vrdZOhEQp?>m@eCi&Mb&A z?#`PAXXqOIkCVa9jJ$A@iVkdMh!s8VUgSbIEw5rJUEh*_kJeKtSf-&1)3I;VoXseR zF&wA|%Cry#t)GT|uIIVyMX-&bT}I=$=3U=j8(zVy3U;W7V<*Gm8GTlFg-+qS^A;Cc z#f0>fCIVC$TBo=unPoWCH8`Ws>zbw8Q`(r2naLz-ry_WbLFwhX%iYc>_TY5|ohtTX zAH(Lg1{k8AAXmI`Q%o~#uXn}GHXOh~1&34|Mi;}bwceMK);F!2HbW%ul#_T-TB{DU zI=;;M>t4aJJ)TKqWdunTDRdJWUNPtSflF5h>I5IfQD_Q|sdz&IeW)?wFm&)m;p&Ev ziMNUEQ5?q!1t(R!g|`{b*UNi_yN2U(&2Vf%Mzpbj(S4=we#atC5X_XwV2a^vS7Pv` zEoRzq8fgU?6=!gk;q*&eV%S1nNuqLF1k7hR(U5c1r!rBT#{~sFDtgh!aH=lu4Lmb? zx0a*lWg2>y%E@3DuE)y;AS{xdd`SZ|X)VwLg76Y9D|kmmKL#3ta2-nwZ3N+}quU6) zwQr2TIf{2x4B{$-YI@h5S#D=_%gmD!)CsKCK;ou?VM1ZUOzTy7^uv`*9??CIv^!s& zFy9A>!l;UC7-QHJFo7gXnA~I7QI4c2OiSx?EQ=db8Y!9~+>k{gD~bGqv%|R=+9Ka{ zS){6F*4jyA6}-nVv|&)P;H(L3oad@zDT};QbPamA<7k${Efu%%J^`L9npQs8!%(c% zq;aHoj zgUOJXHi?O;`ghiA95Phm%JpY}^q92VV(=QzIgz)zVAYZ%s7y6GU#NlvHWe$$TmPLg zjBcDSxpP&58+p1EXq%Lz3Xw7!-{3Q19StK}5xFGQLS|x>AwiQt?Sxxu^QeidiMtjd zzax}ZU0wo5iYv8WV?sA(uj++>O3@10 zJq2Gdbgs?wSgG-KxCSo=J4> zv292AeIT(pSwB(4RqlwT0w<}Rquz(;kilN+XPc-yZ$eDA(DW}Rn_GW|kpfSH!KAMV z*}BtjNAd|+GW8frvimXGANd<+XpA95;|?0*XvI!!!Y<6xOlRpBEX>gt8St-R{y%w9 z6P>W(&@WxX?$MR4wDvF++q#17O-P60p=W51hhn>yvHNGJM^fD@*x!st@z7>v8HsdD z@;4~)mSr4ChvVUAnD`xUuHY2IUl^jL_IP+1=h8}PS6o@aMTTWu`3c9KVrU|+FgZ8E z@C4V>k$B`O#wVU&BDIX0%lLq7#Um@oHQ`sZq{EfV36&J?;{hfqJQ@CFY@x9eQ9?A1 zeKhW;@gVIwO#NLKttT*wBnfVcfat~oj^H~g+V`mod_bM#k2rx}aMFkDHbJmJ5EgKU zvNMVcaN$8AJGUTEL|6h;0rVl}X}1LQAr`SjK+1o2agRKU;s<<8Cqi_qz$f&t1)tLT pHncoPT*02mbJ+CLt6-AhGg=AL`OonsjbGDO6TZQ>{wyPJ@h{ijImiG2 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/security/JwtService.class b/spring-backend/target/classes/com/example/aichat/security/JwtService.class new file mode 100644 index 0000000000000000000000000000000000000000..62ffd1043404da79ed9863b9ed8ee85e9a8264d3 GIT binary patch literal 3204 zcmbVOTUQiU7XD5*P}o*P@fy6L5v4&XP`t!eBalX;4Iws?QIqr(%>fF!tJ za2lT_5JOx;gN{Zt39O$p7fhpI+B3$ZlXEif3pAawY|B3*5bsP*tU)u@Xh`T-3teDO zAZ}ZZk)JhPPx?mIH|;6Yof-=Fozne^zn@$gwrbd><9@UW?4(z7o?|b{N#B{5w&BTq*|q$N zaZy%?dyqgUJBel96{SCEqc!GVe;C0;x6Fft?z5>DY~Sf&F=> zXvihASSm=vv=}_UrvAdBpOx-{m6we9ysQXpyO-L%Iv&720bMSYEZ6ic#~vX(aUp^I zIG~|ZM-pADqgwHNSrphnhg@m<<5p3QScQVcdt{k658{x99vudH1vdRU5=2NzZL(|? zrX*e8)tSuHkr@o%r`F;yj%euDaTLdx2zM8yQm-84U2nv5kc?p-r0!`(1G8*hkYr`w zDi{x&zD(daPO98a2{d$GNKPm$r*)jcLrf~m^U88+fJxpH8LM8rw<)D%K*ylclBmU9 z9Y3rijbSSHtQq@p%bykKi9bKBJH*t};4CFv?Ob zsUjpH`|1-i7QC*}GKR-AT+)%nxIlXyT2@`4s|(V#rYoUWr$VplBzs+!R;gl2jA25@ zY*ed3A6u<&)kxRZy?k|&7+bpsn>|g0ewJPdl#X2}TO1RFfpr>Yma5t~K8v#sS zRrWj`KFR`XEHACPh@?NOq<^t=Rl_2qQL~p`%FB}KIm(-gjz8dOwnlmr=n1-!K*FGM*hryZ9bZj4aYWRq7J!o<*xnda~wAcfhtCKZpjc_h%e;#_&pNv`s64MaP{tCZ%b6#x&V>+=^jVFBlVM zp)84{aX~EvhW9-^x9InTxK92*lkWJ52Ya7kBMueS`q#-5$NGEKKfWY~w&XaG<7)VE zF4zv4Zwy8uhmGY0=fq|)R}R!+=gx2`9^>rq0x`M71Y03dO(Xi*T&`w&oY*+ILcv-> zuwJrG+0DygOEKM2o0vU{BX)UAG{*|AY12-$1*G`@Ub_{yCajH)gt`gWK5r9kzG1 ze}_F^Q$CL8`D{ZIpPQ*+E1%o=*T%uU9o@A40^hV#j=J$8UgED>y^L3AU#%3H7+&Ri z17bh%IzQ!hXjn%yF}%j(tuacz&i`+Cc(b;Ai_5oB{2Ou^ z{8+4k*#(W4l6-bSXGB{Wv#mHpcs(?c41jD26<2|D;N1X-zGC? z=)prbVSk7gwR1m$*6;-y;x|U)Hz*ELw*w0ixvjyqBc!;Wr$@09#{#4M>T{dln;+0r zoTo7+(cc21cUOVzj)16W{+)6m(K`|U5pCr7toVt>BEq?c&}deuMjoMlnF>|z^!;Yg zSdAkG72&t=$D4TO1}}wdh5<}5fTtM1(+uDl2Jj&wI!i7GRxsID#oZU-?%<7o;6rMP R;iEv~$M}T5O@#L;@H0~JFv|b{ literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/security/SecurityConfig.class b/spring-backend/target/classes/com/example/aichat/security/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..498f28dc7fa82816afdfe976dedcd6930534b6f2 GIT binary patch literal 7181 zcmcgxi+dbZ76092lS!6I8@5o}S`gY4@@PAyEhtH;O}1OovY|1XrlufuvU{_cWOinm znN1r+QG5b|_oBw*(hSWx%+T7~H`R#_bV`WJ zxCSyqf7UE0{JdHy>ReH^?40T-HqVwU%~?`1{zK97rO9G_8UN}8VImaU9BPBEa8A(pkRS%!yu*9xgQADRI{L{Sigi#N!) zQTP`ldhG;dlG}T;j2)u)TASN8xih93YMvLk;Uv)N4cR5*7BS>{mGZ%~h-TsC1b?u^ zZO4vNuzO|np`T&17(t?1)}?0LG8Z+Xh2dDEs8kCoBJ)=3aU-=`*xJMJbOTbG(|8q$ zmY^_9)=eIfDiK^FwGEm9IH$Xe97qj&>`s6VI=NuBf$T}5vNrrTxQfwwZWx#`bv9Ywif(kn?r67FO;*pQZB z(s`Bn;eOJXBcke zMFRtUQxZ-v9I4B{k8;Lsm64>-89@@qaY6eS?v-#-#yH-=kgu=QU4oQQb&G^K>kcSc zUj40(wzq!y-x#*!xx0*2iiua?s3xTvg&<Yk4+D<p`sL>6+Yz32C6b6+_lEt zUPT>xrgpNg!Hd_mriW*99o$6GhwxzuACd7S!VGpq9%1CxrXVXqdnATOo6Dd`;?_K{G}<4Ej1N$SFc7qum}V5kMr1a9 zb|Rhe)cGtgiX)aS;j`p^rMRzrVc0rRqVq<9PieMB1P`yA%ZT4SHQ`Y1JNUf@Q! zYLLZ_;>v;Lo|>XT0HT}ty8BL2?4F!zY(ZXJbL>LZRh&J8dE9U()F`wOy#9Ar(Kfn5?@Y9d%^u?r0Mua}d0R_Z%>8^gr9olMdVN|2u1a-+>? zUwv}DZbkIg_hjEy@uX@+c0F0Y7u7ncl4s14mE|;HX^GqvtRNnNdqq`exT7X@R34(G zI-uRHcvKb6)D#SAgkK=T6{z5z!6KD2Wr)?;JDvsjNO|{&X*#xJsYUOT-Ho46H5V5y z!NrPy%h5Nru^ku1nZGRKdw8K6FHpzNM8~p>Z{zuHJWm}ji;h=hd;`yQ<2ma1rReyT zjA!s{H=d=A-^TGf{9eKzWc(4YGVG|B(u_rOfh%dZ3=$x9R?Rx5I7D4TPF3Ds3*+;J zJp7}ok<+=|lQzwTQZal#OIIkotFDMAwh0s|8Qc zZvrv;eKY-TC(#?Qfv$x=PCYkyVt5iy(Hkjn%`o>#7-}H{pTN^JN|Vl} zWn8~ZI;q{hjJJsPre*9DZBKt0x0W%`{}QCc-ZJ)`9w_7B6};_7B<&&EL9T(Wq1#Df z1A6FYaTj5_g?e^lFZR&I=Ux}#X3ydZ!a;lzpCVMBCg~WppTXzoshyraPc0L7(j+0! zo}pHt-F6v+)0c24E#P#Oan}H$8!jU?P(kDIbAT`%B(HDB7P{mV{CL*4`PMs#$1mWE zWTo0B-S_!6_fsp_>$r^LWb>Yi1%VJ4a<$6d7+-6(t}o%sKHGQFD1pC?xJySB9SOl} zTOp?nU%^*NtR^|#aZz#Ts~1WS(0Km^Otig>cTTq^&Sa)ziTg8UJa7fMGG;GeKIVNX zggYnvPP5Wk#)GbibGnjpn<+m*!ARg%`s>9Z3QdaM1+5+ds%S{4Jq^h?+PjC#5eIRR{<4UU%>#W7fiDX5>K!vB*^d`eAgAEvh54*qgDvEQ+9lC zmD!yk>2~^`(f4U&3_qaX7QE=DgZMw)59wV*O4#nF@4ul?UcgJZ{+~qztGgmQTtay^Ednh|H9V)0yH@zvH$=8 literal 0 HcmV?d00001 diff --git a/spring-backend/target/classes/com/example/aichat/service/ApplicationUserDetailsService.class b/spring-backend/target/classes/com/example/aichat/service/ApplicationUserDetailsService.class new file mode 100644 index 0000000000000000000000000000000000000000..3f6eb05fbe12ce219902259462ea776cda37c9ff GIT binary patch literal 2876 zcmcImeN!7n82@c}xe`uDBQ4SvNGq)YD%aMcKw2$OXlp~Mgr;Jxx?DD}aJh|l7g|1p zU%-!{GukT5IR5SUnH-Y5VR311^1$+i zwJg&lnrmxet5DMz2rrk!I~Xu<#>82iV@QXV511kMl(*pcVs+C~&l)yDrdEY@ zPn!~zGcd$37z0K{m4p+5XduIIO?exZU)2GZJzf#dl($VuvumCVb}d_Z!qTZJMfnQl zk{HAV6YpY#;RHcYhp;c(>_#0!&3nnjdz!bKcpq}#Cle(DqYQIL+vc@kQ-!V+q%NyF z9(My#ntesq`o~RN*7^<7zoHzGL>3def-Qzqamgum<0`Hh_`t+QYl^+m@KJ`kB!?8J>#)N@chFkyK zy*C?u{Md~>T4ZRSr)p*B%}ZB2s8!a5x60QYqNMZ6=1!4&Qt#`;_Fz*|STk{>gKGHb zIg}&Cj^ZUfV&-=BTcgXc91GOH27}CWh-mJlTMLAu);VSvj*FwwzN97jI^h)dS%+8F zOMIj?fRQT76=fa`WenPh!!@x?-4P-?Wr7tOmv#zUx@O+?H zviVqW1K|l3>{f+Uh|)=hQ*%lMe&F%ylAu{p@>6)saBvvaySKL!Ye_n?zA&+jhber? zaG!=&tf@mMU_1yX@D;;JT{RgBB~#WNIg?>z^_wI()(zMuO3?T;&Kl|NsBK~8!`x8> zyv*(B1njOf@BVDgUtK5)_%3%#j_^nFN^RGwxz>Veq8t!hB_NyGkN-0=dAMxzAgODf z=Z;g5fyfag8%buVR?N7`uJfjN55({7Re zC+M3TdkHo+{xggpX_vrldiM}7NUj}kqYHglpxx<+o8<1$j^skEjI`awJ^H70eg`9z zoE-ZJ#`v#zD}nJn(t#J~{~d!%`xsuk`~v6cxwwz4UX%6rf5DZP`0xi(5Sbq$HK5B+ z3IpiJ8JwdFP=?-FI>oQkUb~Zt483+|5})EeIn$3J49o zP>B1OUK`s(4tR-A8n!!$4`f%{FvE3Es9Ed!jA#k0pg>>Ew@Tkf^rVUUHR&zlb38$r R{yc?4^dzk+Q1})De*a)8uzj^Qd z-tT?i`@T2(_T(p;w1cPiJPu%rOnyk~P@)>DkvDJ^ZWNriL9LzGWKz4@z~@jU7~Gy% zX3xl^TN1ei$=njIoLnNEb2O~NBn?@7-oPzb<9=AJExXmgZCLBBQk!G93zU{6nPiJi z%k}t@f(-^-Dvzlbj9QO7415JOv`ISCve`tZXS313rlRbU_KsYqim$p;B9qni*lb`6 z?sl(_Ov-VJU817eA5fOEaA+zKb7;)eapbNFE!f;V}B%@@-#aPJmhKgK+~LQwNoYpJH2!~A|YJn4hUDc zH}==v?tV(|ef@xn(FzKJilvDaiN@u*WUA2zHYe#Yghvt9(SK+_AGdX5twGKf>h#qs`9E%U*0R zPApH+_*vq0I-Q)|?&1B3f#>j3cf8(UzTlyzKX32P4g3PXbUXB^gANNyI}+KP$oRBliQF1%XB%YsP4aR`YF@OExg@rpn#*1%Em z2SNWfJ8fqZxnw3yvMf%vnFwLEiq`~MOCsHxY)#|>QvBJ#U-0_QmBzIpQFk8hw=bOM z_UV~S&dFsH9nE%bNv73N<$OW;ybe1(ZFaSt&1SOkYEfmBpjsN+z4|zf5-%R=G1a5P z)Ma+(6k3hu)|q~dd$*RCNrNV1rBRV_EUJ(fHGa`jiS*32#$10JN6jc3pQ&J@e6C+6Y5a8Zz4$Z&8 zp4UnAH~D+Z!C$F3A9Z|qkK%7sutUyN{GE!1vnl>T1-tFc9sfibg{A&gPPp-k(c|zh zhS3jUUZW^MIY0gJHlGYy0z>g{oa8-(cQBrk6Yd=qb+h_xI+xByq5?Jkra?3WV#Q5m zFmcL{<20ozTCVVvL*YkBvOjHoYQZ1b1{ z(8d%nd=sL3F=3H8aThM^t{+X&`*+ZQ@~a{-KHRvL+X9EMWctFSEzphRT)s z*j}t!WGMw)w;MO?!DoS8xT(AT5mb1>oA;x-fG@;VOEuRO@Wpt2OWvK=3!!C2u-&?SuMAOt$ zu#(-3z8?#wuqfZ*@4+#9@GvlUFQzWqg=z7Uy?B(1UGdV;#P z5bVdyxMC^h0rTlD91Qchnk7}td(5qTcHbBr%m3I?3plh1Q_SbB$Zq^BuJ!oWEj`$H zX&4K*e$mo)*p4~o({auGZPAZnPUln6m)_&cunhBnr4=_D^rZ*B zsyyGNmQi$ey1^>#Vm)ye%B>Rf_wIeM8aeJBj9pwLEZG+j_SHUwrLc#vWS{v*kFbos zNLb!`2&?;q^||_@XJnXL<5A1-4TpL6KW#y!W%yRl_cA(aMGqjaThVtZ&1D}-Ka4S? zzDz3&nT!T;GqsZW6=)}jdnoqakleiNJY{RwKffnpUE4gp;pTS~0 zi#EQfm*8b2@fMbH5~fJ5G}}2N`AS?TRmjR%I11TKakIRMTjUhhgao(x&2@yFU(I23m`$TGv|9e-Drdv( z!Xyuek2c=yRLZdN?T_H-*(!4#t`m|`fG6UAa{StbsLlU8do}z zUPL5=VG!?R8P2exelL3?BtRMKMH!FgY4Q~ju;f;iKh@R%_MLA<6a6ELo$A*r`_ni}-5G+cl@-$XJJKLtSPm^~r$)79L zM(B(;z)+OPxeO&FBmLJrU&m4dNG)XpF)}P;C^*l**7?_ZnLw3B=>_sWnFQ-UUf#bu delta 2867 zcmb7Gdz=+z8Ghb5yE}7c=D_YO3k)~+0=WyUi`p;XfFY!=6revcknDEa9hQT;XV-Ji z3Twt8l_1j$r^2PQ%nD0U5Ie9E7h5Ax1GSrJ5P8coMWQST+@5dd>_)&p{mz+h-ud3k z_df4!rgz%$Ddv;!J+>3T`(@H*vqPVwBPSTPG;w{Rp*4|eY4~V1olLdN>P)sadubCZ z5HWC*V9*sEUTWs-N-v#mPsb}2;}#pM@I^sMqJz9kiz-^N(VBr`M9=|q>3x!1;b3wxKgb!58?e9wQ{ zm|}F}2R1g~e&3Ex&i#lwZC)mmXz>g@s19jQWxZ6^z(WEkGn;H|#u$Y+bCtI|YoZcc zY&@!*FJjQzqOg#+u@$v~Sc}(|OeJR}GTz71tp<7su%WHJ*=x--D7o!6exl^8P;PE} z6Vap49P_ zji>!Cvu6H2?6+|MYw1;rZW?%&RJy&DuzpFZGvlQ#G~(AbeuIw)>=rM3MfwtNaiX)8 z!p{?5rYn>6+7!MQY`loy`)`@`&LM@8_LS~n8-K)0e!VrL{1riQMb!C*mXs6QJ+$Y24SsiC2S zo5gAsiTJCAOqe7^bYl=ATw^H0VE#&_m`^q(fss-oI`5hoIGQ=m|hyXrCrD~Ei#V!%ZDz@Rd2_Qi?(CM zqODlzd?AmUT|JNM@>re6ty^*XuJMneTy1@2H!AbEGj6zs^Nl?2ibvgO9(Q-cQupt4 zqr|yylN*iX@qOoq$4%GVLoqXta|&W49|=PtMZPy|a&8?v=zd(zVKN(! zU)j5{KW?YPQQjI7N9*PHP#tcF|K1h^0jG-Bp#0!Pw2r<1@RKM zqz8M2gZqG@ej(V6#<=e0bmtl8xgPu~!i`GiSa%+9HV!;9)qDE(sywR%Zs^_5|TtT0(9S&Q z&MZC_j9LB0Y}a<4am|9ZO=H%n#_WQzzIwN~hq=dk40em0BmRDOtQ_@^xtIKJc^_kZ z6pGW!n_K zK?B6xm}-F7e;a^gp#jtscmxxsmFEl0i@+JWMn=% zSEK!k7@kz}%~Yp%HqjhDPY=!~suh=xONQ*a7#(AsP&l+Y#%-Oz39}l3pGA?i^jU xocDp@qV|B}&?@?{66V=-&OU-pVsbEyDsAnI7YC;h%SOFI&I>*R)Qh?S_zwxtWK;kE diff --git a/spring-backend/target/maven-archiver/pom.properties b/spring-backend/target/maven-archiver/pom.properties new file mode 100644 index 0000000..4f22c25 --- /dev/null +++ b/spring-backend/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=aichat +groupId=com.example +version=0.0.1-SNAPSHOT diff --git a/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index ef65012..385f3c3 100644 --- a/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -1,7 +1,27 @@ -com\example\aichat\config\CorsConfig.class -com\example\aichat\dto\MessageDto.class +com\example\aichat\dto\GoalDto.class +com\example\aichat\security\JwtAuthFilter.class +com\example\aichat\repo\StudyPlanRepository.class +com\example\aichat\security\JwtService.class +com\example\aichat\repo\UserRepository.class +com\example\aichat\controller\ChatController.class +com\example\aichat\controller\DbHealthController.class com\example\aichat\dto\ChatRequestDto.class +com\example\aichat\dto\CreateGoalRequest.class +com\example\aichat\dto\StudyPlanRequest.class +com\example\aichat\controller\AuthController.class +com\example\aichat\dto\RegisterRequest.class +com\example\aichat\AiChatApplication.class +com\example\aichat\controller\StudyPlanController.class +com\example\aichat\model\StudyPlan.class +com\example\aichat\dto\MessageDto.class +com\example\aichat\model\User.class +com\example\aichat\controller\GoalController.class com\example\aichat\dto\ChatReplyDto.class +com\example\aichat\dto\AuthResponse.class +com\example\aichat\model\Goal.class +com\example\aichat\dto\AuthRequest.class +com\example\aichat\service\ApplicationUserDetailsService.class +com\example\aichat\config\CorsConfig.class +com\example\aichat\repo\GoalRepository.class +com\example\aichat\security\SecurityConfig.class com\example\aichat\service\OpenAIService.class -com\example\aichat\controller\ChatController.class -com\example\aichat\AiChatApplication.class diff --git a/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index a7f3748..4abe0b1 100644 --- a/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/spring-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,7 +1,27 @@ -C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\AiChatApplication.java -C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\config\CorsConfig.java C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\controller\ChatController.java -C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\ChatReplyDto.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\AuthResponse.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\repo\UserRepository.java C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\ChatRequestDto.java C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\MessageDto.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\security\SecurityConfig.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\GoalDto.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\controller\GoalController.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\controller\AuthController.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\AuthRequest.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\ChatReplyDto.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\RegisterRequest.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\security\JwtService.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\controller\StudyPlanController.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\StudyPlanRequest.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\model\Goal.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\repo\GoalRepository.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\controller\DbHealthController.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\dto\CreateGoalRequest.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\AiChatApplication.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\security\JwtAuthFilter.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\service\ApplicationUserDetailsService.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\model\User.java C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\service\OpenAIService.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\config\CorsConfig.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\repo\StudyPlanRepository.java +C:\Users\Faster\Desktop\javaproject\Java_Pkrris\spring-backend\src\main\java\com\example\aichat\model\StudyPlan.java From 0dc0617b005da97b1b89f4d19fe622dc75232907 Mon Sep 17 00:00:00 2001 From: dummytemp87-code Date: Sun, 26 Oct 2025 14:00:32 +0530 Subject: [PATCH 2/5] Update sample.txt --- sample.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample.txt b/sample.txt index b6fc4c6..343b4af 100644 --- a/sample.txt +++ b/sample.txt @@ -1 +1 @@ -hello \ No newline at end of file +hello 80% From 0e4cf9c24801024c3609119bfb6535c1c8eb89b6 Mon Sep 17 00:00:00 2001 From: ahamedvifaaq Date: Fri, 31 Oct 2025 15:51:07 +0530 Subject: [PATCH 3/5] with youtube feature --- app/page.tsx | 4 +- components/pages/learning-screen.tsx | 135 +++++++++++++++--- components/pages/study-plan.tsx | 5 +- .../aichat/controller/ArticleController.java | 82 +++++++++++ .../aichat/controller/GoalController.java | 12 +- .../aichat/controller/VideoController.java | 97 +++++++++++++ .../example/aichat/dto/ArticleRequest.java | 17 +++ .../com/example/aichat/dto/VideoRequest.java | 14 ++ .../example/aichat/model/ArticleContent.java | 50 +++++++ .../example/aichat/model/VideoContent.java | 64 +++++++++ .../aichat/repo/ArticleContentRepository.java | 12 ++ .../aichat/repo/VideoContentRepository.java | 12 ++ .../aichat/service/YouTubeService.java | 42 ++++++ .../src/main/resources/application.properties | 2 + .../target/classes/application.properties | 2 + .../aichat/controller/ArticleController.class | Bin 0 -> 6324 bytes .../aichat/controller/GoalController.class | Bin 8348 -> 8834 bytes .../aichat/controller/VideoController.class | Bin 0 -> 6597 bytes .../example/aichat/dto/ArticleRequest.class | Bin 0 -> 1230 bytes .../com/example/aichat/dto/VideoRequest.class | Bin 0 -> 1028 bytes .../example/aichat/model/ArticleContent.class | Bin 0 -> 2930 bytes .../example/aichat/model/VideoContent.class | Bin 0 -> 3488 bytes .../repo/ArticleContentRepository.class | Bin 0 -> 741 bytes .../aichat/repo/VideoContentRepository.class | Bin 0 -> 733 bytes .../aichat/service/YouTubeService.class | Bin 0 -> 2728 bytes .../compile/default-compile/createdFiles.lst | 9 ++ .../compile/default-compile/inputFiles.lst | 9 ++ 27 files changed, 543 insertions(+), 25 deletions(-) create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/ArticleController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/controller/VideoController.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/ArticleRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/dto/VideoRequest.java create mode 100644 spring-backend/src/main/java/com/example/aichat/model/ArticleContent.java create mode 100644 spring-backend/src/main/java/com/example/aichat/model/VideoContent.java create mode 100644 spring-backend/src/main/java/com/example/aichat/repo/ArticleContentRepository.java create mode 100644 spring-backend/src/main/java/com/example/aichat/repo/VideoContentRepository.java create mode 100644 spring-backend/src/main/java/com/example/aichat/service/YouTubeService.java create mode 100644 spring-backend/target/classes/com/example/aichat/controller/ArticleController.class create mode 100644 spring-backend/target/classes/com/example/aichat/controller/VideoController.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/ArticleRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/dto/VideoRequest.class create mode 100644 spring-backend/target/classes/com/example/aichat/model/ArticleContent.class create mode 100644 spring-backend/target/classes/com/example/aichat/model/VideoContent.class create mode 100644 spring-backend/target/classes/com/example/aichat/repo/ArticleContentRepository.class create mode 100644 spring-backend/target/classes/com/example/aichat/repo/VideoContentRepository.class create mode 100644 spring-backend/target/classes/com/example/aichat/service/YouTubeService.class diff --git a/app/page.tsx b/app/page.tsx index 432e0f4..7978512 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -26,6 +26,8 @@ export default function Home() { ], inputMessage: "", chatLoading: false, + selectedGoalTitle: null as string | null, + selectedModule: null as any, }); const [auth, setAuth] = useState<{ token: string | null; name?: string; email?: string; role?: string }>({ token: null }) @@ -78,7 +80,7 @@ export default function Home() { case 'goals': return case 'study-plan': - return { setSelectedGoal(g); setCurrentPage('study-plan'); }} /> + return { setSelectedGoal(g); setCurrentPage('study-plan'); }} onStartLearning={(goalTitle, module) => { setLearningScreenState({ ...learningScreenState, selectedGoalTitle: goalTitle, selectedModule: module }); setCurrentPage('learning'); }} /> case 'learning': return (null) + const [articleContent, setArticleContent] = useState("") + const [videoLoading, setVideoLoading] = useState(false) + const [videoError, setVideoError] = useState(null) + const [video, setVideo] = useState<{ videoId: string; url: string; videoTitle?: string; channelTitle?: string } | null>(null) const setState = (newState: any) => { setLearningState({ ...learningState, ...newState }); }; - const topic = "Power Rule & Chain Rule"; + const topic = selectedModule?.title || "Learning Module"; + + useEffect(() => { + if (!selectedGoalTitle || !selectedModule?.title) return; + let cancelled = false; + const run = async () => { + setArticleLoading(true); + setArticleError(null); + try { + const base = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"; + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; + const res = await fetch(`${base}/api/articles/content`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + goalTitle: selectedGoalTitle, + moduleTitle: selectedModule.title, + moduleType: selectedModule.type, + moduleId: selectedModule.id, + }) + }) + const data = await res.json(); + if (!res.ok) throw new Error(data?.error || 'Failed to load article'); + if (!cancelled) setArticleContent((data?.content ?? '').toString()); + } catch (e: any) { + if (!cancelled) setArticleError(e?.message || 'Failed to load article'); + } finally { + if (!cancelled) setArticleLoading(false); + } + }; + run(); + return () => { cancelled = true; }; + }, [selectedGoalTitle, selectedModule?.title]) + + useEffect(() => { + if (!selectedGoalTitle || !selectedModule?.title) return; + let cancelled = false; + const run = async () => { + setVideoLoading(true); + setVideoError(null); + try { + const base = process.env.NEXT_PUBLIC_BACKEND_URL || "http://localhost:8080"; + const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null; + const res = await fetch(`${base}/api/videos/content`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + goalTitle: selectedGoalTitle, + moduleTitle: selectedModule.title, + moduleId: selectedModule.id, + }) + }) + const data = await res.json(); + if (!res.ok) throw new Error(data?.error || 'Failed to load video'); + if (!cancelled) setVideo({ videoId: data.videoId, url: data.url, videoTitle: data.videoTitle, channelTitle: data.channelTitle }); + } catch (e: any) { + if (!cancelled) setVideoError(e?.message || 'Failed to load video'); + } finally { + if (!cancelled) setVideoLoading(false); + } + }; + run(); + return () => { cancelled = true; }; + }, [selectedGoalTitle, selectedModule?.title]) const sendMessage = async () => { if (inputMessage.trim()) { @@ -99,7 +174,7 @@ export default function LearningScreen({ onNavigate, learningState, setLearningS

{topic}

-

Calculus Fundamentals

+

{selectedGoalTitle || 'Personalized Learning'}