diff --git a/api/backend/app.py b/api/backend/app.py index 95ac85b..8cfae0e 100644 --- a/api/backend/app.py +++ b/api/backend/app.py @@ -3,9 +3,6 @@ import uuid import logging from io import BytesIO from openpyxl import Workbook -from typing import Any -from datetime import datetime -from bson import ObjectId # PDM from fastapi import BackgroundTasks, FastAPI, HTTPException @@ -56,18 +53,18 @@ app.add_middleware( allow_headers=["*"], ) -app.mount("/_next/static", StaticFiles(directory="./dist/_next/static"), name="static") -app.mount("/images", StaticFiles(directory="./dist/images"), name="images") +# app.mount("/_next/static", StaticFiles(directory="./dist/_next/static"), name="static") +# app.mount("/images", StaticFiles(directory="./dist/images"), name="images") -@app.get("/") -def read_root(): - return FileResponse("./dist/index.html") +# @app.get("/") +# def read_root(): +# return FileResponse("./dist/index.html") -@app.get("/favicon.ico") -def read_favicon(): - return FileResponse("dist/favicon.ico") +# @app.get("/favicon.ico") +# def read_favicon(): +# return FileResponse("dist/favicon.ico") @app.post("/api/update") diff --git a/next.config.mjs b/next.config.mjs index b21f9d2..6670f4d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -3,13 +3,8 @@ const nextConfig = { output: "export", distDir: "./dist", images: { unoptimized: true }, - async rewrites() { - return [ - { - source: "/auth/:path*", - destination: "/api/auth/:path*", - }, - ]; + env: { + DOMAIN: "http://localhost:8000", }, }; diff --git a/package-lock.json b/package-lock.json index d979427..8c8fce9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "axios": "^1.7.2", "bootstrap": "^5.3.0", "chart.js": "^4.4.3", + "cookie": "^0.6.0", "framer-motion": "^4.1.17", "next": "^14.2.4", "next-auth": "^4.24.7", @@ -115,14 +116,6 @@ "next": ">=10" } }, - "node_modules/@auth0/nextjs-auth0/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -8022,6 +8015,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -8340,9 +8334,10 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10274,6 +10269,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -15360,6 +15364,15 @@ } } }, + "node_modules/next-auth/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", diff --git a/package.json b/package.json index aaf87bc..5405d2c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "axios": "^1.7.2", "bootstrap": "^5.3.0", "chart.js": "^4.4.3", + "cookie": "^0.6.0", "framer-motion": "^4.1.17", "next": "^14.2.4", "next-auth": "^4.24.7", diff --git a/src/components/JobTable.tsx b/src/components/JobTable.tsx index cbf4bfc..c2e7b0a 100644 --- a/src/components/JobTable.tsx +++ b/src/components/JobTable.tsx @@ -18,6 +18,7 @@ import StarIcon from "@mui/icons-material/Star"; import { useRouter } from "next/router"; import { Favorites, JobQueue } from "./jobs"; import { Job } from "../types"; +import { Constants } from "../lib"; interface JobTableProps { jobs: Job[]; @@ -45,7 +46,7 @@ const JobTable: React.FC = ({ jobs, fetchJobs }) => { const router = useRouter(); const handleDownload = async (ids: string[]) => { - const response = await fetch("/api/download", { + const response = await fetch(`${Constants.DOMAIN}/api/download`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: ids }), @@ -100,7 +101,7 @@ const JobTable: React.FC = ({ jobs, fetchJobs }) => { }; const handleDeleteSelected = async () => { - const response = await fetch("/api/delete-scrape-jobs", { + const response = await fetch(`${Constants.DOMAIN}/api/delete-scrape-jobs`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: Array.from(selectedJobs) }), @@ -130,7 +131,7 @@ const JobTable: React.FC = ({ jobs, fetchJobs }) => { value: value, }; - await fetch("/api/update", { + await fetch(`${Constants.DOMAIN}/api/update`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(postBody), diff --git a/src/components/NavDrawer.tsx b/src/components/NavDrawer.tsx index 6d8b0b7..54cff5f 100644 --- a/src/components/NavDrawer.tsx +++ b/src/components/NavDrawer.tsx @@ -1,3 +1,5 @@ +"use client"; + import React from "react"; import { useAuth } from "../contexts/AuthContext"; import { @@ -10,7 +12,6 @@ import { Typography, Button, Switch, - Tooltip, Drawer, Divider, Accordion, @@ -23,8 +24,6 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import TerminalIcon from "@mui/icons-material/Terminal"; import BarChart from "@mui/icons-material/BarChart"; import { useRouter } from "next/router"; -import { useTheme } from "@mui/material/styles"; -import Image from "next/image"; interface NavDrawerProps { toggleTheme: () => void; @@ -35,7 +34,7 @@ const drawerWidth = 240; const NavDrawer: React.FC = ({ toggleTheme, isDarkMode }) => { const router = useRouter(); - const theme = useTheme(); + const { logout, user, isAuthenticated } = useAuth(); return ( @@ -143,7 +142,7 @@ const NavDrawer: React.FC = ({ toggleTheme, isDarkMode }) => {
- +

Dark Theme Toggle

diff --git a/src/components/submit/ElementTable.tsx b/src/components/submit/ElementTable.tsx index 9146cc2..5a7f858 100644 --- a/src/components/submit/ElementTable.tsx +++ b/src/components/submit/ElementTable.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useState, Dispatch, SetStateAction } from "react"; import { Typography, diff --git a/src/components/submit/JobSubmitter.tsx b/src/components/submit/JobSubmitter.tsx index 0284544..3fb836a 100644 --- a/src/components/submit/JobSubmitter.tsx +++ b/src/components/submit/JobSubmitter.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useEffect, useState, Dispatch } from "react"; import { TextField, @@ -7,9 +9,10 @@ import { FormControlLabel, CircularProgress, } from "@mui/material"; -import { Element, Result } from "../../types"; +import { Element } from "../../types"; import { useAuth } from "../../contexts/AuthContext"; import { useRouter } from "next/router"; +import { Constants } from "../../lib"; interface StateProps { submittedURL: string; @@ -89,7 +92,7 @@ export const JobSubmitter = ({ stateProps }: Props) => { return; } - fetch("/api/submit-scrape-job", { + fetch(`${Constants.DOMAIN}/api/submit-scrape-job`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ @@ -112,12 +115,12 @@ export const JobSubmitter = ({ stateProps }: Props) => { return response.json(); }) .then((data) => { - setSnackbarMessage(data); + setSnackbarMessage(data || "Job submitted successfully."); setSnackbarSeverity("info"); setSnackbarOpen(true); }) .catch((error) => { - setSnackbarMessage(error.message || "An error occurred."); + setSnackbarMessage(error || "An error occurred."); setSnackbarSeverity("error"); setSnackbarOpen(true); }) diff --git a/src/components/submit/ResultsTable.tsx b/src/components/submit/ResultsTable.tsx deleted file mode 100644 index 53dafaf..0000000 --- a/src/components/submit/ResultsTable.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import { - Typography, - Table, - TableBody, - TableContainer, - TableCell, - TableHead, - TableRow, - Box, -} from "@mui/material"; -import { Result } from "../../types"; - -interface stateProps { - results: Result; -} - -interface Props { - stateProps: stateProps; - resultsRef: React.MutableRefObject; -} - -export const ResultsTable = ({ stateProps, resultsRef }: Props) => { - const { results } = stateProps; - - return ( - <> - {Object.keys(results).length ? ( - <> - Results - - - - - - Name - - - XPath - - - Text - - - - - {Object.keys(results).map((key, index) => ( - - {results[key].map((result, resultIndex) => ( - - {result.name} - {result.xpath} - {result.text} - - ))} - - ))} - -
-
- - ) : null} - - ); -}; diff --git a/src/components/submit/index.ts b/src/components/submit/index.ts index f0dc927..7f96dc7 100644 --- a/src/components/submit/index.ts +++ b/src/components/submit/index.ts @@ -1,3 +1,2 @@ export * from "./ElementTable"; export * from "./JobSubmitter"; -export * from "./ResultsTable"; diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index d106659..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -const Constants = {}; - -export default Constants; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index f1d6503..46b5b87 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,11 +1,13 @@ import React, { createContext, useContext, useState, useEffect } from "react"; import axios from "axios"; +import { Constants } from "../lib"; interface AuthContextProps { user: any; isAuthenticated: boolean; login: (email: string, password: string) => Promise; logout: () => void; + setUser: (user: any) => void; } const AuthContext = createContext(undefined); @@ -22,7 +24,7 @@ export const AuthProvider: React.FC = ({ children }) => { const token = localStorage.getItem("token"); if (token) { axios - .get("/api/auth/users/me", { + .get(`${Constants.DOMAIN}/api/auth/users/me`, { headers: { Authorization: `Bearer ${token}` }, }) .then((response) => { @@ -39,11 +41,17 @@ export const AuthProvider: React.FC = ({ children }) => { const params = new URLSearchParams(); params.append("username", email); params.append("password", password); - const response = await axios.post("/api/auth/token", params); + const response = await axios.post( + `${Constants.DOMAIN}/api/auth/token`, + params + ); localStorage.setItem("token", response.data.access_token); - const userResponse = await axios.get("/api/auth/users/me", { - headers: { Authorization: `Bearer ${response.data.access_token}` }, - }); + const userResponse = await axios.get( + `${Constants.DOMAIN}/api/auth/users/me`, + { + headers: { Authorization: `Bearer ${response.data.access_token}` }, + } + ); setUser(userResponse.data); setIsAuthenticated(true); }; @@ -55,7 +63,9 @@ export const AuthProvider: React.FC = ({ children }) => { }; return ( - + {children} ); diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..e13ca6b --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1,3 @@ +export const Constants = { + DOMAIN: process.env.DOMAIN, +}; diff --git a/src/lib/index.ts b/src/lib/index.ts new file mode 100644 index 0000000..b04bfcf --- /dev/null +++ b/src/lib/index.ts @@ -0,0 +1 @@ +export * from "./constants"; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2c20a1f..6a4abdc 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -9,8 +9,6 @@ import NavDrawer from "../components/NavDrawer"; import { darkTheme, lightTheme } from "../styles/themes"; import { AuthProvider } from "../contexts/AuthContext"; -const drawerWidth = 240; - const App: React.FC = ({ Component, pageProps }) => { const [isDarkMode, setIsDarkMode] = useState(false); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a90a44d..0207165 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,8 +1,10 @@ +"use client"; + import React, { useState, useEffect, useRef } from "react"; -import { Typography, Container, Box, Snackbar, Alert } from "@mui/material"; +import { Container, Box, Snackbar, Alert } from "@mui/material"; import { useRouter } from "next/router"; import { Element, Result } from "../types"; -import { ElementTable, JobSubmitter, ResultsTable } from "../components/submit"; +import { ElementTable, JobSubmitter } from "../components/submit"; const Home = () => { const router = useRouter(); diff --git a/src/pages/jobs.tsx b/src/pages/jobs.tsx index ef37850..789d322 100644 --- a/src/pages/jobs.tsx +++ b/src/pages/jobs.tsx @@ -2,13 +2,29 @@ import React, { useEffect, useState } from "react"; import JobTable from "../components/JobTable"; import { useAuth } from "../contexts/AuthContext"; import { Box } from "@mui/system"; +import { Constants } from "../lib"; +import { Job } from "../types"; +import { GetServerSideProps } from "next"; +import axios from "axios"; +import { cookies } from "next/headers"; -const Jobs = () => { - const { user } = useAuth(); - const [jobs, setJobs] = useState([]); +interface JobsProps { + initialJobs: Job[]; + initialUser: any; +} + +const Jobs: React.FC = ({ initialJobs, initialUser }) => { + const { user, setUser } = useAuth(); + const [jobs, setJobs] = useState(initialJobs || []); + + useEffect(() => { + if (!user && initialUser) { + setUser(initialUser); + } + }, [user, initialUser, setUser]); const fetchJobs = async () => { - await fetch("/api/retrieve-scrape-jobs", { + await fetch(`${Constants.DOMAIN}/api/retrieve-scrape-jobs`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ user: user?.email }), @@ -62,4 +78,49 @@ const Jobs = () => { ); }; +export const getServerSideProps: GetServerSideProps = async (context) => { + const { req, res } = context; + + const cookies = cookies.parse(req.headers.cookie || ""); + const token = cookies.token; + let user = null; + let initialJobs: Job[] = []; + + if (token) { + try { + const userResponse = await axios.get( + `${Constants.DOMAIN}/api/auth/users/me`, + { + headers: { Authorization: `Bearer ${token}` }, + } + ); + user = userResponse.data; + + const jobsResponse = await axios.post( + `${Constants.DOMAIN}/api/retrieve-scrape-jobs`, + { user: user.email }, + { headers: { "content-type": "application/json" } } + ); + + initialJobs = jobsResponse.data; + } catch (error) { + console.error("Error fetching user or jobs:", error); + res.setHeader( + "Set-Cookie", + cookies.serialize("token", "", { + maxAge: -1, + path: "/", + }) + ); + } + } + + return { + props: { + initialJobs, + initialUser: user, + }, + }; +}; + export default Jobs; diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 2047d39..e27df3a 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -1,9 +1,12 @@ +"use client"; + import React, { useState } from "react"; import axios from "axios"; import { Button, TextField, Typography, Box } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useRouter } from "next/router"; import { useAuth } from "../contexts/AuthContext"; +import { Constants } from "../lib"; type Mode = "login" | "signup"; @@ -24,7 +27,7 @@ const AuthForm: React.FC = () => { alert("Login successful"); router.push("/"); } else { - await axios.post("/api/auth/signup", { + await axios.post(`${Constants.DOMAIN}/api/auth/signup`, { email: email, password: password, full_name: fullName, diff --git a/src/pages/statistics.tsx b/src/pages/statistics.tsx index ca83009..1b83b8f 100644 --- a/src/pages/statistics.tsx +++ b/src/pages/statistics.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from "react"; import { Chart, registerables } from "chart.js"; import { Box, Typography, useTheme } from "@mui/material"; import { useAuth } from "../contexts/AuthContext"; +import { Constants } from "../lib"; Chart.register(...registerables); @@ -17,7 +18,7 @@ const Statistics: React.FC = () => { const fetchElementsData = async () => { try { const response = await fetch( - "/api/statistics/get-average-element-per-link", + `${Constants.DOMAIN}/api/statistics/get-average-element-per-link`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -91,7 +92,7 @@ const Statistics: React.FC = () => { const fetchJobsData = async () => { try { const response = await fetch( - "/api/statistics/get-average-jobs-per-day", + `${Constants.DOMAIN}/api/statistics/get-average-jobs-per-day`, { method: "POST", headers: { "Content-Type": "application/json" },