diff --git a/backend/alembic/versions/001_initial_schema.py b/backend/alembic/versions/001_initial_schema.py index a7e059b..bd03ce0 100644 --- a/backend/alembic/versions/001_initial_schema.py +++ b/backend/alembic/versions/001_initial_schema.py @@ -87,4 +87,4 @@ def downgrade() -> None: op.drop_table("sections") op.drop_table("students") op.drop_table("instructors") - op.drop_table("time_blocks") + op.drop_table("time_blocks") \ No newline at end of file diff --git a/backend/alembic/versions/002_initial_schema.py b/backend/alembic/versions/002_initial_schema.py new file mode 100644 index 0000000..61f637c --- /dev/null +++ b/backend/alembic/versions/002_initial_schema.py @@ -0,0 +1,55 @@ +"""initial schema + +Revision ID: 002 +Revises: +Create Date: 2026-05-06 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +revision: str = "002" +down_revision: Union[str, None] = "001" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Drop the old FK constraint + op.drop_constraint( + "sections_instructor_id_fkey", + "sections", + type_="foreignkey" + ) + + # Create the new FK with CASCADE + op.create_foreign_key( + None, # Let Alembic generate a name + "sections", # Source table + "instructors", # Referenced table + ["instructor_id"], # Local column + ["id"], # Remote column + ondelete="CASCADE" + ) + + +def downgrade() -> None: + # Reverse the change: drop CASCADE FK + op.drop_constraint( + None, + "sections", + type_="foreignkey" + ) + + # Restore original FK without CASCADE + op.create_foreign_key( + "sections_instructor_id_fkey", + "sections", + "instructors", + ["instructor_id"], + ["id"] + ) \ No newline at end of file diff --git a/backend/stp_scheduler/db/models/association.py b/backend/stp_scheduler/db/models/association.py index 02ba134..da0e705 100644 --- a/backend/stp_scheduler/db/models/association.py +++ b/backend/stp_scheduler/db/models/association.py @@ -5,6 +5,6 @@ student_section_table = Table( "student_sections", Base.metadata, - Column("student_id", String, ForeignKey("students.id"), primary_key=True), - Column("section_id", String, ForeignKey("sections.id"), primary_key=True), + Column("student_id", String, ForeignKey("students.id", ondelete="CASCADE"), primary_key=True), + Column("section_id", String, ForeignKey("sections.id", ondelete="CASCADE"), primary_key=True), ) diff --git a/backend/stp_scheduler/db/models/section.py b/backend/stp_scheduler/db/models/section.py index 867dd7f..cbb7b0e 100644 --- a/backend/stp_scheduler/db/models/section.py +++ b/backend/stp_scheduler/db/models/section.py @@ -15,5 +15,5 @@ class SectionRow(Base): ) days: Mapped[str | None] = mapped_column(String, nullable=True) instructor_id: Mapped[str | None] = mapped_column( - String, ForeignKey("instructors.id"), nullable=True + String, ForeignKey("instructors.id", ondelete="SET NULL"), nullable=True ) diff --git a/frontend/app/Components/InputPage.tsx b/frontend/app/Components/InputPage.tsx index 082f129..7c28b0f 100644 --- a/frontend/app/Components/InputPage.tsx +++ b/frontend/app/Components/InputPage.tsx @@ -140,7 +140,7 @@ export default function InputPage({ path }: InputPageProps) { > - + ); } diff --git a/frontend/app/Components/Navbar.tsx b/frontend/app/Components/Navbar.tsx index 70ef413..a64c08f 100644 --- a/frontend/app/Components/Navbar.tsx +++ b/frontend/app/Components/Navbar.tsx @@ -30,7 +30,9 @@ export default function Navbar(){
Student Transition Program Scheduler
-
+ + +
    {navItems.map((navItem, index) => (
  • @@ -38,9 +40,9 @@ export default function Navbar(){
  • ))}
  • |
  • -
  • |
+
); } \ No newline at end of file diff --git a/frontend/app/Components/NavbarAuthControls.tsx b/frontend/app/Components/NavbarAuthControls.tsx index 74c0c22..49530b9 100644 --- a/frontend/app/Components/NavbarAuthControls.tsx +++ b/frontend/app/Components/NavbarAuthControls.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { useEffect, useState } from "react"; import { API_URL, getToken, setToken } from "../apiClient"; +import NavItem from "./Navitem"; export default function NavbarAuthControls() { const [loggedIn, setLoggedIn] = useState(false); @@ -38,20 +39,18 @@ export default function NavbarAuthControls() { } return ( -
  • +
    {loggedIn ? ( ) : ( - - Sign in - + )} -
  • +
    ); } diff --git a/frontend/app/Cruds/deleteStudent.tsx b/frontend/app/Cruds/deleteStudent.tsx index ca4b407..3a8d586 100644 --- a/frontend/app/Cruds/deleteStudent.tsx +++ b/frontend/app/Cruds/deleteStudent.tsx @@ -28,7 +28,7 @@ export default function DeleteStudent({students}: DeleteStudentProps){ // TODO: update delete student in the API to do this because the file should not be responsible for re-updating data // Reload students without the deleted student - getFromBackendApi("Students"); + // getFromBackendApi("Students"); } return ( diff --git a/frontend/app/Cruds/deleteTeacher.tsx b/frontend/app/Cruds/deleteTeacher.tsx index 7b9f7bb..d09bec7 100644 --- a/frontend/app/Cruds/deleteTeacher.tsx +++ b/frontend/app/Cruds/deleteTeacher.tsx @@ -1,12 +1,19 @@ import { FormEvent, useEffect, useState } from "react"; import * as API from "../SendToApi"; -import { getFromBackendApi, instructor_data } from "../GetFromApi"; +import { getFromBackendApi } from "../GetFromApi"; import type { InstructorProps } from "../InstructorProps"; -export default function DeleteTeacher() { +interface DeleteInstructorProps{ + instructors: InstructorProps[]; +} +export default function DeleteTeacher({instructors}: DeleteInstructorProps) { const [instructorId, setInstructorId] = useState(""); - const [instructors, setInstructors] = useState([]); + /** + * Delete an instructor + * + * @param e FormEvent + */ function deleteInstructorHandler(e: FormEvent) { e.preventDefault(); @@ -15,14 +22,10 @@ export default function DeleteTeacher() { e.currentTarget.reset(); setInstructorId(""); - getFromBackendApi("Instructors"); - setInstructors(instructor_data); + // TODO: update delete instructor in the API to do this because the file should not be responsible for re-updating data + // getFromBackendApi("Instructors"); } - useEffect(() => { - setInstructors(instructor_data); - }, []); - return (
    diff --git a/frontend/app/GetFromApi.ts b/frontend/app/GetFromApi.ts index 95a7898..c3cb7a4 100644 --- a/frontend/app/GetFromApi.ts +++ b/frontend/app/GetFromApi.ts @@ -31,6 +31,15 @@ export function setSectionIds(ids: any) { section_ids = ids; } +/** + * Calls getFromBackendApi("Instructors"), getFromBackendApi("Students"), and getFromBackendApi("Sections") + */ +export async function getAll() { + getFromBackendApi("Instructors") + getFromBackendApi("Students") + getFromBackendApi("Sections") +} + /** * Fetches data from the backend. Use type "Instructors", "Students", or "Sections". */ @@ -45,16 +54,16 @@ export async function getFromBackendApi(type: string) { const result = await response.json(); console.log(result); - switch (type) { - case "Instructors": + switch (type.toLowerCase()) { + case "instructors": instructor_data = result; return; - case "Students": + case "students": student_data = result; return; - case "Sections": + case "sections": var ids: string[] = []; result.forEach((element: Record) => { ids.push(element.id); diff --git a/frontend/app/SendToApi.ts b/frontend/app/SendToApi.ts index 1d848ee..3779704 100644 --- a/frontend/app/SendToApi.ts +++ b/frontend/app/SendToApi.ts @@ -7,11 +7,17 @@ */ import { apiFetch } from "./apiClient"; +import { getFromBackendApi } from "./GetFromApi"; export function generateId() { return "fake-id"; } +/** + * POST /csv/update + * @param csvData the data the backend will update with + * @returns + */ export function updateFromCSV(csvData: any) { try { var result: any; @@ -33,6 +39,10 @@ export function updateFromCSV(csvData: any) { } } +/** + * POST /schedule/regenerate + * @returns + */ export function regenerateSchedule() { try { var result: any; @@ -87,6 +97,7 @@ export function createInstructor(instructor: InstructorModel) { apiFetch(`/instructors/create`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Instructors")) .then((data) => (result = data)); return result; @@ -102,6 +113,7 @@ export function editInstructor(instructor: any) { apiFetch(`/instructors/update`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Instructors")) .then((data) => (result = data)); return result; @@ -119,6 +131,7 @@ export function deleteInstructor(instructor_id: string) { requestOptions, ) .then((response) => response.json()) + .then(() => getFromBackendApi("Instructors")) .then((data) => (result = data)); return result; @@ -150,6 +163,7 @@ export function createStudent(student: StudentModel) { apiFetch(`/students/create`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Students")) .then((data) => (result = data)); return result; @@ -166,6 +180,7 @@ export function editStudent(student: any) { apiFetch(`/students/update`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Students")) .then((data) => (result = data)); return result; @@ -181,6 +196,7 @@ export function deleteStudent(student_id: string) { apiFetch(`/students/delete?student_id=${encodeURIComponent(student_id)}`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Students")) .then((data) => (result = data)); return result; @@ -197,6 +213,7 @@ export function createSection(section: string) { apiFetch(`/create/section`, requestOptions) .then((response) => response.json()) + .then(() => getFromBackendApi("Sections")) .then((data) => (result = data)); return result; diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index d17ef27..ce74896 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -66,7 +66,7 @@ export default function LoginPage() { } return ( -
    +

    Sign in

    Use the username and password matching AUTH_USERNAME and AUTH_PASSWORD on