From ddbc977c01f63f44caeb9759978395c8866c6e6d Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 00:44:01 -0400 Subject: [PATCH 1/6] fix fetching on undefined session --- frontend/src/screens/MainSearch.tsx | 4 ++++ 1 file changed, 4 insertions(+) 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 From 7467092e0f318309944986db611fe44c286c4e4f Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 01:39:25 -0400 Subject: [PATCH 2/6] add polling --- frontend/src/App.tsx | 3 ++- frontend/src/hooks/usePoll.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 frontend/src/hooks/usePoll.ts 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..598ef2b --- /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://127.0.0.1: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 From 9fe99e0d9678796274a274179a5fd8322bc786be Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 02:27:34 -0400 Subject: [PATCH 3/6] add download functionality --- file_peer/peer_service.py | 4 ++-- frontend/src/hooks/usePoll.ts | 4 ++-- frontend/src/screens/Results.tsx | 35 +++++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/file_peer/peer_service.py b/file_peer/peer_service.py index 4e1fb15..8014ed4 100644 --- a/file_peer/peer_service.py +++ b/file_peer/peer_service.py @@ -54,7 +54,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 +64,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) diff --git a/frontend/src/hooks/usePoll.ts b/frontend/src/hooks/usePoll.ts index 598ef2b..ff3bc1d 100644 --- a/frontend/src/hooks/usePoll.ts +++ b/frontend/src/hooks/usePoll.ts @@ -7,7 +7,7 @@ export const usePoll = () => { .catch((error) => { console.error('Error:', error); }); - }, 30 * 60000) // poll every 30 minutes + }, 30 * 60000) // poll every 30 minutes return () => { // clean up clearInterval(interval); @@ -16,7 +16,7 @@ export const usePoll = () => { const pollServer = async () => { // request private ip from local api - return fetch('http://127.0.0.1:8080/ip') + return fetch('http://localhost:8080/ip') .then(response => response.text()) .then((data) => { // send private ip to central server diff --git a/frontend/src/screens/Results.tsx b/frontend/src/screens/Results.tsx index f889044..29415cb 100644 --- a/frontend/src/screens/Results.tsx +++ b/frontend/src/screens/Results.tsx @@ -72,7 +72,7 @@ const Results: React.FC = () => { {file.upvotes.length} {file.downvotes.length} {file.original_author.username} - Download + ))} @@ -86,3 +86,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 From 89749d30caf25962ddcdf24f83d54541a9265118 Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 03:56:13 -0400 Subject: [PATCH 4/6] get peer's registered files --- backend/api/views.py | 18 +++++++++--- file_peer/peer_service.py | 7 +++++ frontend/src/screens/RegisterFile.tsx | 40 ++++++++++++++++++++++----- frontend/src/screens/Results.tsx | 3 +- frontend/src/types/types.ts | 2 +- 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index 1f5ceae..94d9f9d 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -247,6 +247,7 @@ class FileFilterView(APIView): professor_id = request.query_params.get("professor") course_id = request.query_params.get("course") semester_id = request.query_params.get("semester") + peer_id = request.query_params.get("peer_id") # Check if provided filter IDs exist if topic_id and not Topic.objects.filter(id=topic_id).exists(): @@ -269,6 +270,11 @@ class FileFilterView(APIView): {"error": f"Semester with id {semester_id} does not exist"}, status=status.HTTP_400_BAD_REQUEST, ) + if peer_id and not PeerUser.objects.filter(id=peer_id).exists(): + return Response( + {"error": f"Peer with id {peer_id} does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) # Start with the base queryset queryset = File.objects.all() @@ -282,16 +288,20 @@ 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") ).order_by(F("downvote_count") - F("upvote_count")) # Filter files based on active peers in the past hour - active_peer_ids = PeerUser.objects.filter( - last_poll__gte=timezone.now() - timedelta(hours=1) - ).values_list("id", flat=True) - queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() + if peer_id: + queryset = queryset.filter(peer_users__in=peer_id) + else: + active_peer_ids = PeerUser.objects.filter( + last_poll__gte=timezone.now() - timedelta(hours=1) + ).values_list("id", flat=True) + queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() serializer = FileSerializer(queryset, many=True) # Loop through each serialized file to sort its peer_users diff --git a/file_peer/peer_service.py b/file_peer/peer_service.py index 8014ed4..ef66322 100644 --- a/file_peer/peer_service.py +++ b/file_peer/peer_service.py @@ -83,6 +83,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(file.split("_")[1]) + return {"files": newFiles}, 200 @app.route("/request-tests", methods=["GET"]) def request_test_(): diff --git a/frontend/src/screens/RegisterFile.tsx b/frontend/src/screens/RegisterFile.tsx index 78fab0a..f2f763d 100644 --- a/frontend/src/screens/RegisterFile.tsx +++ b/frontend/src/screens/RegisterFile.tsx @@ -1,22 +1,48 @@ 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 + 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/files/filter?peer_id=1`, { + method: 'GET', + headers: getAuthHeaders() + }) // replace with local api endpoint + .then(response => response.json()) + .then(data => { + setServerFiles(data) + }) + .catch(error => console.error(error)); } + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); // handle file upload diff --git a/frontend/src/screens/Results.tsx b/frontend/src/screens/Results.tsx index 29415cb..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([]); @@ -113,7 +114,7 @@ function DownloadButton({ file }: { file: File }) { <> { success === true ? -

Downloaded!

: + Downloaded! : } 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', } From a3010201332ba49c199212647d609b0e062f59a5 Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 05:03:36 -0400 Subject: [PATCH 5/6] add register file integration --- backend/api/views.py | 9 ++++--- file_peer/peer_service.py | 26 ++++++++++++++------ frontend/src/screens/RegisterFile.tsx | 35 ++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index 94d9f9d..f6be02d 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -298,10 +298,11 @@ class FileFilterView(APIView): if peer_id: queryset = queryset.filter(peer_users__in=peer_id) else: - active_peer_ids = PeerUser.objects.filter( - last_poll__gte=timezone.now() - timedelta(hours=1) - ).values_list("id", flat=True) - queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() + # active_peer_ids = PeerUser.objects.filter( + # last_poll__gte=timezone.now() - timedelta(hours=1) + # ).values_list("id", flat=True) + # queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() + pass serializer = FileSerializer(queryset, many=True) # Loop through each serialized file to sort its peer_users diff --git a/file_peer/peer_service.py b/file_peer/peer_service.py index ef66322..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}") @@ -88,7 +98,7 @@ def list_files(): files = os.listdir("./uploads") newFiles = [] for file in files: - newFiles.append(file.split("_")[1]) + newFiles.append("_".join(file.split("_", 1)[1:])) return {"files": newFiles}, 200 @app.route("/request-tests", methods=["GET"]) diff --git a/frontend/src/screens/RegisterFile.tsx b/frontend/src/screens/RegisterFile.tsx index f2f763d..f3b95b1 100644 --- a/frontend/src/screens/RegisterFile.tsx +++ b/frontend/src/screens/RegisterFile.tsx @@ -35,7 +35,7 @@ export default function RegisterFile() { fetch(`http://localhost:8000/api/files/filter?peer_id=1`, { method: 'GET', headers: getAuthHeaders() - }) // replace with local api endpoint + }) .then(response => response.json()) .then(data => { setServerFiles(data) @@ -46,10 +46,37 @@ export default function RegisterFile() { 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 (
From 2bcbf73ea3df8a83abbe84fc560551fec90276c8 Mon Sep 17 00:00:00 2001 From: afazio1 Date: Mon, 22 Apr 2024 10:32:38 -0400 Subject: [PATCH 6/6] change get peers endpoint --- backend/api/views.py | 18 ++++-------------- frontend/src/screens/RegisterFile.tsx | 8 +++++--- frontend/src/styles/App.css | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index d449c5b..89faec3 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -247,7 +247,6 @@ class FileFilterView(APIView): professor_id = request.query_params.get("professor") course_id = request.query_params.get("course") semester_id = request.query_params.get("semester") - peer_id = request.query_params.get("peer_id") # Check if provided filter IDs exist if topic_id and not Topic.objects.filter(id=topic_id).exists(): @@ -270,11 +269,6 @@ class FileFilterView(APIView): {"error": f"Semester with id {semester_id} does not exist"}, status=status.HTTP_400_BAD_REQUEST, ) - if peer_id and not PeerUser.objects.filter(id=peer_id).exists(): - return Response( - {"error": f"Peer with id {peer_id} does not exist"}, - status=status.HTTP_400_BAD_REQUEST, - ) # Start with the base queryset queryset = File.objects.all() @@ -295,14 +289,10 @@ class FileFilterView(APIView): ).order_by(F("downvote_count") - F("upvote_count")) # Filter files based on active peers in the past hour - if peer_id: - queryset = queryset.filter(peer_users__in=peer_id) - else: - # active_peer_ids = PeerUser.objects.filter( - # last_poll__gte=timezone.now() - timedelta(hours=1) - # ).values_list("id", flat=True) - # queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() - pass + active_peer_ids = PeerUser.objects.filter( + last_poll__gte=timezone.now() - timedelta(hours=1) + ).values_list("id", flat=True) + queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() serializer = FileSerializer(queryset, many=True) # Loop through each serialized file to sort its peer_users diff --git a/frontend/src/screens/RegisterFile.tsx b/frontend/src/screens/RegisterFile.tsx index f3b95b1..be04836 100644 --- a/frontend/src/screens/RegisterFile.tsx +++ b/frontend/src/screens/RegisterFile.tsx @@ -17,7 +17,9 @@ export default function RegisterFile() { }) .then(response => response.json()) .then(data => { - const files: string[] = data.files + const files: string[] = data.files.map((file: string) => { + return file.replace(/\s/g, "_"); + }) setRegisteredFiles( serverFiles.map((file) => { if (files.includes(file.filename)) { @@ -32,12 +34,12 @@ export default function RegisterFile() { }, [serverFiles]) const fetchServerFiles = () => { - fetch(`http://localhost:8000/api/files/filter?peer_id=1`, { + fetch(`http://localhost:8000/api/get-peer-files/`, { method: 'GET', headers: getAuthHeaders() }) .then(response => response.json()) - .then(data => { + .then(data => { setServerFiles(data) }) .catch(error => console.error(error)); 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%; }