From 7a64bceecd211a9685530386e50a4b0e69cb1124 Mon Sep 17 00:00:00 2001 From: ShadowArcher289 Date: Sat, 2 May 2026 19:52:17 -0400 Subject: [PATCH 1/4] updated visuals for auth --- frontend/app/Components/Navbar.tsx | 6 ++++-- frontend/app/Components/NavbarAuthControls.tsx | 11 +++++------ frontend/app/login/page.tsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) 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/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 From 595bcc02ac103e576429d684eafdd1a394ff7b9d Mon Sep 17 00:00:00 2001 From: ShadowArcher289 Date: Sun, 3 May 2026 16:46:23 -0400 Subject: [PATCH 2/4] fixed sql bug --- .../alembic/versions/001_initial_schema.py | 2 +- .../stp_scheduler/db/models/association.py | 4 ++-- backend/stp_scheduler/db/models/section.py | 2 +- frontend/app/Components/InputPage.tsx | 2 +- frontend/app/Cruds/deleteStudent.tsx | 2 +- frontend/app/Cruds/deleteTeacher.tsx | 21 +++++++++++-------- frontend/app/GetFromApi.ts | 17 +++++++++++---- frontend/app/SendToApi.ts | 17 +++++++++++++++ 8 files changed, 48 insertions(+), 19 deletions(-) diff --git a/backend/alembic/versions/001_initial_schema.py b/backend/alembic/versions/001_initial_schema.py index a7e059b..ed843a9 100644 --- a/backend/alembic/versions/001_initial_schema.py +++ b/backend/alembic/versions/001_initial_schema.py @@ -51,7 +51,7 @@ def upgrade() -> None: sa.Column("days", sa.String(), nullable=True), sa.Column("instructor_id", sa.String(), nullable=True), sa.ForeignKeyConstraint(["time_block_id"], ["time_blocks.id"]), - sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"]), + sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_table( 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/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; From 4f96f1ba65775537f25e8524c24083c389ad627e Mon Sep 17 00:00:00 2001 From: ShadowArcher289 Date: Thu, 7 May 2026 11:14:55 -0400 Subject: [PATCH 3/4] restored old schema and put new schema as the header --- .../alembic/versions/001_initial_schema.py | 4 +- .../alembic/versions/002_initial_schema.py | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 backend/alembic/versions/002_initial_schema.py diff --git a/backend/alembic/versions/001_initial_schema.py b/backend/alembic/versions/001_initial_schema.py index ed843a9..bd03ce0 100644 --- a/backend/alembic/versions/001_initial_schema.py +++ b/backend/alembic/versions/001_initial_schema.py @@ -51,7 +51,7 @@ def upgrade() -> None: sa.Column("days", sa.String(), nullable=True), sa.Column("instructor_id", sa.String(), nullable=True), sa.ForeignKeyConstraint(["time_block_id"], ["time_blocks.id"]), - sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"]), sa.PrimaryKeyConstraint("id"), ) op.create_table( @@ -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..759ee1f --- /dev/null +++ b/backend/alembic/versions/002_initial_schema.py @@ -0,0 +1,90 @@ +"""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: + op.create_table( + "time_blocks", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("start_time", sa.Integer(), nullable=False), + sa.Column("end_time", sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "instructors", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("max_sections", sa.Integer(), nullable=False), + sa.Column("is_mentor", sa.Boolean(), nullable=False), + sa.Column("subject_weights", postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "students", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("subject_abilities", postgresql.JSONB(astext_type=sa.Text()), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "sections", + sa.Column("id", sa.String(), nullable=False), + sa.Column("subject", sa.String(), nullable=False), + sa.Column("level", sa.Integer(), nullable=False), + sa.Column("time_block_id", sa.Integer(), nullable=True), + sa.Column("days", sa.String(), nullable=True), + sa.Column("instructor_id", sa.String(), nullable=True), + sa.ForeignKeyConstraint(["time_block_id"], ["time_blocks.id"]), + sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "student_sections", + sa.Column("student_id", sa.String(), nullable=False), + sa.Column("section_id", sa.String(), nullable=False), + sa.ForeignKeyConstraint(["student_id"], ["students.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["section_id"], ["sections.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("student_id", "section_id"), + ) + + tb = sa.table( + "time_blocks", + sa.column("id", sa.Integer()), + sa.column("start_time", sa.Integer()), + sa.column("end_time", sa.Integer()), + ) + op.bulk_insert( + tb, + [ + {"id": 0, "start_time": 800, "end_time": 900}, + {"id": 1, "start_time": 915, "end_time": 1015}, + {"id": 2, "start_time": 1045, "end_time": 1145}, + {"id": 3, "start_time": 1245, "end_time": 1345}, + {"id": 4, "start_time": 1400, "end_time": 1500}, + {"id": 5, "start_time": 1530, "end_time": 1630}, + ], + ) + + +def downgrade() -> None: + op.drop_table("student_sections") + op.drop_table("sections") + op.drop_table("students") + op.drop_table("instructors") + op.drop_table("time_blocks") From b6a4aa5bb1c84e15971b77ded1c0b0179d72df5f Mon Sep 17 00:00:00 2001 From: ShadowArcher289 Date: Mon, 11 May 2026 11:29:57 -0400 Subject: [PATCH 4/4] updated schema --- .../alembic/versions/002_initial_schema.py | 89 ++++++------------- 1 file changed, 27 insertions(+), 62 deletions(-) diff --git a/backend/alembic/versions/002_initial_schema.py b/backend/alembic/versions/002_initial_schema.py index 759ee1f..61f637c 100644 --- a/backend/alembic/versions/002_initial_schema.py +++ b/backend/alembic/versions/002_initial_schema.py @@ -19,72 +19,37 @@ def upgrade() -> None: - op.create_table( - "time_blocks", - sa.Column("id", sa.Integer(), nullable=False), - sa.Column("start_time", sa.Integer(), nullable=False), - sa.Column("end_time", sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "instructors", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("max_sections", sa.Integer(), nullable=False), - sa.Column("is_mentor", sa.Boolean(), nullable=False), - sa.Column("subject_weights", postgresql.JSONB(astext_type=sa.Text()), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "students", - sa.Column("id", sa.String(), nullable=False), - sa.Column("name", sa.String(), nullable=False), - sa.Column("subject_abilities", postgresql.JSONB(astext_type=sa.Text()), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( + # Drop the old FK constraint + op.drop_constraint( + "sections_instructor_id_fkey", "sections", - sa.Column("id", sa.String(), nullable=False), - sa.Column("subject", sa.String(), nullable=False), - sa.Column("level", sa.Integer(), nullable=False), - sa.Column("time_block_id", sa.Integer(), nullable=True), - sa.Column("days", sa.String(), nullable=True), - sa.Column("instructor_id", sa.String(), nullable=True), - sa.ForeignKeyConstraint(["time_block_id"], ["time_blocks.id"]), - sa.ForeignKeyConstraint(["instructor_id"], ["instructors.id"], ondelete="CASCADE"), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "student_sections", - sa.Column("student_id", sa.String(), nullable=False), - sa.Column("section_id", sa.String(), nullable=False), - sa.ForeignKeyConstraint(["student_id"], ["students.id"], ondelete="CASCADE"), - sa.ForeignKeyConstraint(["section_id"], ["sections.id"], ondelete="CASCADE"), - sa.PrimaryKeyConstraint("student_id", "section_id"), + type_="foreignkey" ) - tb = sa.table( - "time_blocks", - sa.column("id", sa.Integer()), - sa.column("start_time", sa.Integer()), - sa.column("end_time", sa.Integer()), - ) - op.bulk_insert( - tb, - [ - {"id": 0, "start_time": 800, "end_time": 900}, - {"id": 1, "start_time": 915, "end_time": 1015}, - {"id": 2, "start_time": 1045, "end_time": 1145}, - {"id": 3, "start_time": 1245, "end_time": 1345}, - {"id": 4, "start_time": 1400, "end_time": 1500}, - {"id": 5, "start_time": 1530, "end_time": 1630}, - ], + # 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: - op.drop_table("student_sections") - op.drop_table("sections") - op.drop_table("students") - op.drop_table("instructors") - op.drop_table("time_blocks") + # 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