diff --git a/backend/api/views.py b/backend/api/views.py index 7100e7d..89faec3 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -282,6 +282,7 @@ class FileFilterView(APIView): queryset = queryset.filter(course__id=course_id) if semester_id: queryset = queryset.filter(semester__id=semester_id) + queryset = queryset.annotate( upvote_count=Count("upvotes"), downvote_count=Count("downvotes") diff --git a/file_peer/peer_service.py b/file_peer/peer_service.py index 4e1fb15..ff7e469 100644 --- a/file_peer/peer_service.py +++ b/file_peer/peer_service.py @@ -15,14 +15,24 @@ CORS(app) @app.route("/copy-file", methods=["POST"]) def copy_file(): try: - data = request.json - source_file = data["file_path"] - id = data["file_id"] - if not os.path.exists(source_file): - return f"Source file does not exist at {source_file}", 400 - filename = os.path.basename(source_file) + files = request.files + id = request.form["file_id"] + print(id) + if "file" not in files: + return "File must be sent in request", 400 + file = request.files['file'] + filename = file.filename.replace(" ", "_") + # id = data["file_id"] + # if not os.path.exists(source_file): + # return f"Source file does not exist at {source_file}", 400 + # filename = os.path.basename(source_file) destination_path = os.path.join("./uploads/", str(id) + "_" + filename) - shutil.copyfile(source_file, destination_path) + # shutil.copyfile(source_file, destination_path) + if os.path.exists(destination_path): + return "File with name already exists", 200 + + with open(destination_path, "wb") as f: + f.write(file.read()) return "File Moved successfully", 200 except Exception as e: print(f"An error occured: {e}") @@ -54,7 +64,7 @@ def send(): ) -@app.route("/request", methods=["GET"]) +@app.route("/request", methods=["POST"]) def request_file(): data = request.json file_id = data["id"] @@ -64,7 +74,7 @@ def request_file(): url = f"http://{ip}:8080/send" params = { "id": file_id, - "filename": filename, + "filename": filename.replace(" ", "_"), } response = requests.get(url, params=params) @@ -83,6 +93,13 @@ def request_file(): print("Error:", error) return error, response.status_code +@app.route("/files", methods=["GET"]) +def list_files(): + files = os.listdir("./uploads") + newFiles = [] + for file in files: + newFiles.append("_".join(file.split("_", 1)[1:])) + return {"files": newFiles}, 200 @app.route("/request-tests", methods=["GET"]) def request_test_(): diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 27f8456..446f514 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,11 +3,12 @@ import { Link, useNavigate } from 'react-router-dom'; import { Outlet } from "react-router-dom"; import { SessionContext, destroySessionCookie, getSessionCookie } from "./contexts/session"; import { useState, useEffect, useContext } from "react"; +import { usePoll } from "./hooks/usePoll"; export default function App() { const [session, setSession] = useState(getSessionCookie()); const navigate = useNavigate(); - + usePoll(); // Redirect to login if session is undefined useEffect( () => { diff --git a/frontend/src/hooks/usePoll.ts b/frontend/src/hooks/usePoll.ts new file mode 100644 index 0000000..ff3bc1d --- /dev/null +++ b/frontend/src/hooks/usePoll.ts @@ -0,0 +1,33 @@ +import { useEffect } from "react" +import { getAuthHeaders } from "../utils/getAuthHeaders" +export const usePoll = () => { + useEffect(() => { + const interval = setInterval(() => { + pollServer() + .catch((error) => { + console.error('Error:', error); + }); + }, 30 * 60000) // poll every 30 minutes + return () => { + // clean up + clearInterval(interval); + }; + }, []) + + const pollServer = async () => { + // request private ip from local api + return fetch('http://localhost:8080/ip') + .then(response => response.text()) + .then((data) => { + // send private ip to central server + return fetch('http://localhost:8000/api/poll/', { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ip: data}), + }) + }).then(response => response.json()) + .then(data => { + console.log("Poll response data:", data); + }) + } +} \ No newline at end of file diff --git a/frontend/src/screens/MainSearch.tsx b/frontend/src/screens/MainSearch.tsx index 98b98bd..52dab6c 100644 --- a/frontend/src/screens/MainSearch.tsx +++ b/frontend/src/screens/MainSearch.tsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import '../styles/MainScreenWrapper.css'; import { getAuthHeaders } from '../utils/getAuthHeaders'; import { Professor, Course, Topic, Semester } from "../types/types"; +import { getSessionCookie } from '../contexts/session'; interface Filters extends Record { professor: string; @@ -23,6 +24,9 @@ const MainSearch: React.FC = () => { const navigate = useNavigate(); useEffect(() => { + if (!getSessionCookie()) { + return; + } // Fetch professors fetchProfessors(); // Fetch courses diff --git a/frontend/src/screens/RegisterFile.tsx b/frontend/src/screens/RegisterFile.tsx index 78fab0a..be04836 100644 --- a/frontend/src/screens/RegisterFile.tsx +++ b/frontend/src/screens/RegisterFile.tsx @@ -1,29 +1,84 @@ import "../styles/RegisterFile.css"; -import { RegisteredFile } from "../types/types"; +import { RegisteredFile, Status } from "../types/types"; import { useState, useEffect } from "react"; +import { getAuthHeaders } from "../utils/getAuthHeaders"; export default function RegisterFile() { + const [serverFiles, setServerFiles] = useState([]); const [registeredFiles, setRegisteredFiles] = useState([]); useEffect(() => { - fetchRegisteredFiles(); + fetchServerFiles(); }, []) - const fetchRegisteredFiles = () => { - return - fetch("http://localhost:5000/files") // replace with local api endpoint + + useEffect(() => { + fetch(`http://localhost:8080/files`, { + method: 'GET', + }) .then(response => response.json()) .then(data => { - setRegisteredFiles(data); + const files: string[] = data.files.map((file: string) => { + return file.replace(/\s/g, "_"); + }) + setRegisteredFiles( + serverFiles.map((file) => { + if (files.includes(file.filename)) { + file.status = Status.HOSTED; + return file; + } + file.status = Status.PRIVATE; + return file; + }) + ) }) - .catch(error => console.error(error)); + }, [serverFiles]) + + const fetchServerFiles = () => { + fetch(`http://localhost:8000/api/get-peer-files/`, { + method: 'GET', + headers: getAuthHeaders() + }) + .then(response => response.json()) + .then(data => { + setServerFiles(data) + }) + .catch(error => console.error(error)); } + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); // handle file upload + const fileInput = document.getElementById('file'); + // @ts-expect-error - fileInput is an HTMLInputElement + const filename: string = fileInput.files[0].name.replace(/\s/g, "_"); const formData = new FormData(event.target as HTMLFormElement); - const file = formData.get('file'); - console.log(file, typeof file) - + // register file with central server + fetch(`http://localhost:8000/api/register/`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ + filename: filename, + topic: 1, + semester: 1, + professor: 1, + course: 1, + }) + }) + .then(response => response.json()) + .then(data => { + formData.append("file_id", data.id) + // send file to local + return fetch("http://localhost:8080/copy-file", { + method: 'POST', + body: formData + }) + }) + .then(response => response.text()) + .then(data => { + console.log(data); + window.location.reload(); + }) + .catch(error => console.error(error)); } return (
diff --git a/frontend/src/screens/Results.tsx b/frontend/src/screens/Results.tsx index f889044..2bc6a72 100644 --- a/frontend/src/screens/Results.tsx +++ b/frontend/src/screens/Results.tsx @@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import styles from '../styles/Results.module.css'; // Make sure this path is correct import { getAuthHeaders } from '../utils/getAuthHeaders'; import { File } from '../types/types'; +import { Link } from 'react-router-dom'; const Results: React.FC = () => { const [files, setFiles] = useState([]); @@ -72,7 +73,7 @@ const Results: React.FC = () => { {file.upvotes.length} {file.downvotes.length} {file.original_author.username} - Download + ))} @@ -86,3 +87,36 @@ const Results: React.FC = () => { }; export default Results; + + +function DownloadButton({ file }: { file: File }) { + const [success, setSuccess] = useState(false); + const handleDownload = () => { + // Implement download functionality + fetch("http://localhost:8080/request", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: file.id, filename: file.filename, ip: file.original_author.ip_address }), + }) + .then((response) => response.text()) + .then((data) => { + console.log("Success download response data:", data); + setSuccess(true); + }) + .catch((error) => { + console.error("Error:", error); + setSuccess(false); + }); + }; + return ( + <> + { + success === true ? + Downloaded! : + + } + + ); +} \ No newline at end of file diff --git a/frontend/src/styles/App.css b/frontend/src/styles/App.css index f7ca5de..80060a9 100644 --- a/frontend/src/styles/App.css +++ b/frontend/src/styles/App.css @@ -28,7 +28,7 @@ nav div { align-items: center; justify-content: center; /* If you're centering the content */ height: 100vh; /* Full height */ - margin-top: -100px; /* Move up */ + margin-top: 100px; /* Move up */ width: 100%; } diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 2d7d0ee..da7f1f9 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -38,7 +38,7 @@ export interface File { downvotes: number[]; } -enum Status { +export enum Status { HOSTED = "HOSTED", PRIVATE = 'PRIVATE', }