From 100c089c1f93fa26a6094aa1ccb0a6155c866b36 Mon Sep 17 00:00:00 2001 From: Stanley Huang Date: Tue, 29 Apr 2025 22:05:33 -0400 Subject: [PATCH] Added Bookmark button to job postings and made dashboard properly pull from database --- backend-api/app/database.py | 4 +-- backend-api/app/routes.py | 30 ++++++++++------ backend-api/instance/database.db | Bin 36864 -> 77824 bytes backend-api/run.py | 23 ++++++++++--- frontend/app/pages/Dashboard.tsx | 8 +++-- frontend/app/pages/Postings.tsx | 57 +++++++++++++++++++++++++++---- 6 files changed, 95 insertions(+), 27 deletions(-) diff --git a/backend-api/app/database.py b/backend-api/app/database.py index 9f19987..731af1a 100644 --- a/backend-api/app/database.py +++ b/backend-api/app/database.py @@ -45,12 +45,12 @@ class ProfileAutofillAnswer(db.Model): class Job(db.Model): __tablename__ = 'Job' JobID = db.Column(db.Integer, primary_key=True, autoincrement=True) - Salary = db.Column(db.Numeric(10,2)) + Salary = db.Column(db.String(50)) Type = db.Column(db.String(50)) Keywords = db.Column(db.Text) Description = db.Column(db.Text) Date = db.Column(db.Date, server_default=db.func.current_date()) - CompanyName = db.Column(db.String(50)) + CompanyName = db.Column(db.String(100)) UserID = db.Column(db.Integer, ForeignKey('Users.UserID', ondelete='SET NULL')) applications = relationship('AppliedTo', backref='job', cascade="all, delete") bookmarks = relationship('Bookmark', backref='job', cascade="all, delete") diff --git a/backend-api/app/routes.py b/backend-api/app/routes.py index 2437b11..2b19367 100644 --- a/backend-api/app/routes.py +++ b/backend-api/app/routes.py @@ -132,6 +132,7 @@ def delete_profile(profile_id): @bp.route('/jobs', methods=['POST']) def create_job(): + print("Job creation route hit") data = request.get_json() user_id = data.get('UserID') user = User.query.get_or_404(user_id) @@ -217,15 +218,18 @@ def create_application(): 'FollowUpDeadline': application.FollowUpDeadline }), 201 -@bp.route('/applications/', methods=['GET']) -def get_application(application_id): - application = AppliedTo.query.get_or_404(application_id) - return jsonify({ - 'ApplicationID': application.ApplicationID, - 'Status': application.Status, - 'FollowUpDeadline': application.FollowUpDeadline, - 'Note': application.Note - }) +@bp.route('/applications/user/', methods=['GET']) +def get_applications_for_user(user_id): + applications = db.session.query(AppliedTo).filter(AppliedTo.UserID == user_id).all() + return jsonify([ + { + 'ApplicationID': application.ApplicationID, + 'Status': application.Status, + 'FollowUpDeadline': application.FollowUpDeadline, + 'Note': application.Note + } + for application in applications + ]) @bp.route('/applications/', methods=['PUT']) def update_application(application_id): @@ -261,6 +265,11 @@ def create_bookmark(): user = User.query.get_or_404(user_id) job = Job.query.get_or_404(job_id) + # Check if the bookmark already exists + existing_bookmark = Bookmark.query.filter_by(UserID=user.UserID, JobID=job.JobID).first() + if existing_bookmark: + return jsonify({"message": "Bookmark already exists"}), 200 + bookmark = Bookmark( UserID=user.UserID, JobID=job.JobID, @@ -282,9 +291,8 @@ def get_bookmarks(user_id): bookmark_list = [] for bookmark, job in bookmarks: bookmark_list.append({ - 'UserID': bookmark.UserID, 'JobID': bookmark.JobID, - 'Note': bookmark.Note if bookmark.Note else "No note", + 'Note': bookmark.Note or "No note", 'CompanyName': job.CompanyName, 'Type': job.Type }) diff --git a/backend-api/instance/database.db b/backend-api/instance/database.db index f40f221f33d93bebaf6d5b0718f4fa022f4405ac..090fe84053c33761426d110df20893507b198fb0 100644 GIT binary patch literal 77824 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 delta 51 zcmZp8z|ydQX@WE>8v_Fa=R^g2Mz)O!OZYdl82sU9_OTZs_5Z~ZG FKLBm_4qE^K diff --git a/backend-api/run.py b/backend-api/run.py index 35fd1b1..71fd8ad 100644 --- a/backend-api/run.py +++ b/backend-api/run.py @@ -1,20 +1,33 @@ # Runs instance of app from app import create_app -from app.database import db +from app.database import db, User from sqlalchemy import inspect import os app = create_app() +def create_test_user(): + existing = User.query.filter_by(Email="testuser@example.com").first() + if not existing: + user = User( + Name="Test User", + Email="testuser@example.com", + Password="password123" + ) + db.session.add(user) + db.session.commit() + print(f"Test user created with UserID {user.UserID}") + else: + print("Test user already exists.") + if __name__ == '__main__': - # Use the PORT environment variable if defined, otherwise default to 5000. port = int(os.environ.get("PORT", 5000)) - # Creates database tables with app.app_context(): db.create_all() inspector = inspect(db.engine) - print(inspector.get_table_names()) - # Bind to all interfaces to allow external access. + print("Database tables:", inspector.get_table_names()) + create_test_user() + app.run(host='0.0.0.0', port=port, debug=True) \ No newline at end of file diff --git a/frontend/app/pages/Dashboard.tsx b/frontend/app/pages/Dashboard.tsx index c2c54ce..dfa81ad 100644 --- a/frontend/app/pages/Dashboard.tsx +++ b/frontend/app/pages/Dashboard.tsx @@ -38,11 +38,13 @@ export default function Dashboard() { const [error, setError] = useState(null); useEffect(() => { - const fetchNotifications = axios.get(`/api/applied_to/${userId}`); - const fetchBookmarks = axios.get(`/api/bookmarks/${userId}`); - + const fetchNotifications = axios.get(`http://localhost:5000/api/applications/user/${userId}`); + const fetchBookmarks = axios.get(`http://localhost:5000/api/bookmarks/${userId}`); + Promise.all([fetchNotifications, fetchBookmarks]) .then(([notificationsRes, bookmarksRes]) => { + console.log("Fetched applications from API:", notificationsRes.data); // Debugging log + console.log("Fetched bookmarks from API:", bookmarksRes.data); // Debugging log setNotifications(Array.isArray(notificationsRes.data) ? notificationsRes.data : []); setBookmarks(Array.isArray(bookmarksRes.data) ? bookmarksRes.data : []); }) diff --git a/frontend/app/pages/Postings.tsx b/frontend/app/pages/Postings.tsx index d0aa7b5..d351a80 100644 --- a/frontend/app/pages/Postings.tsx +++ b/frontend/app/pages/Postings.tsx @@ -1,6 +1,7 @@ import { Box, Typography, Card, CardContent, Button, TextField, Grid, List, ListItem, ListItemText } from "@mui/material"; import { useState } from "react"; -import jobData from "../../dataset_indeed-scraper_2025-04-29_18-38-42-368.json"; +import axios from "axios"; +import jobData from "../../dataset_indeed-scraper_2025-04-29_18-38-42-368.json"; // Mock job data export default function Postings() { const [filters, setFilters] = useState({ @@ -79,6 +80,44 @@ function JobDetails({ job }: { job: any }) { setShowFullDescription(!showFullDescription); }; + const handleBookmark = () => { + // Step 1: Create the job + axios + .post("http://localhost:5000/api/jobs", { + Salary: job.salary || "Not specified", + Type: job.positionName, + Keywords: job.keywords || "", + Description: job.description, + CompanyName: job.company, + UserID: 1, // Replace with actual user ID + }) + .then((createdJobResponse) => { + console.log("Job created successfully:", createdJobResponse.data); + createBookmark(createdJobResponse.data); + }) + .catch((err) => { + console.error("Failed to create job:", err); + alert("Failed to create job and bookmark."); + }); + + function createBookmark(jobData: any) { + console.log("Creating bookmark for job:", jobData); + axios + .post("http://localhost:5000/api/bookmarks", { + UserID: 1, // Replace with real user ID + JobID: jobData.JobID, // This is the actual JobID from the DB + Note: "Bookmarked from UI", + }) + .then(() => { + alert("Job bookmarked!"); + }) + .catch((err) => { + console.error("Failed to bookmark:", err); + alert("Failed to bookmark job."); + }); + } + }; + return ( @@ -114,11 +153,7 @@ function JobDetails({ job }: { job: any }) { {job.description} - @@ -131,6 +166,16 @@ function JobDetails({ job }: { job: any }) { Apply Here + + + );