From 2b36178171ac6045e47e8b3ed27f835be1f4be69 Mon Sep 17 00:00:00 2001 From: angelsanddevslol Date: Thu, 11 Jun 2026 23:18:17 -0400 Subject: [PATCH 1/4] refactor Courses table and datatype to Course --- CHANGELOG.md | 1 + app/Controllers/Course.hs | 6 +- app/Database/CourseVideoSeed.hs | 2 +- app/Database/Migrations.hs | 9 +- app/Database/Tables.hs | 6 +- app/Models/Course.hs | 58 +++++------ app/WebParsing/ArtSciParser.hs | 8 +- .../Controllers/CourseControllerTests.hs | 96 +++++++++---------- .../Controllers/GenerateControllerTests.hs | 4 +- backend-test/TestHelpers.hs | 2 +- 10 files changed, 100 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f734a2748..7cfc7ee75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Updated documentation in `app/Util/Blaze.hs` - Moved the `Course` data type from `Database/Tables.hs` into `Models/Course.hs`, renamed it to `CourseData` - Removed `SvgJSON` data type in favour of `([Text], [Shape], [Path])` +- Refactored the `Courses` table to `Course` with a database migration ## [0.7.2] - 2025-12-10 diff --git a/app/Controllers/Course.hs b/app/Controllers/Course.hs index 6921be3b1..ab0333ed2 100644 --- a/app/Controllers/Course.hs +++ b/app/Controllers/Course.hs @@ -6,7 +6,7 @@ import Control.Monad.IO.Class (liftIO) import qualified Data.Text as T (Text, unlines) import Database.Persist (Entity) import Database.Persist.Sqlite (SqlPersistM, entityVal, selectList) -import Database.Tables as Tables (Courses, coursesCode) +import Database.Tables as Tables (Course, courseCode) import Happstack.Server (Response, ServerPart, lookText', notFound, ok, toResponse) import Models.Course (getDeptCourses, returnCourse) import Util.Happstack (createJSONResponse) @@ -25,8 +25,8 @@ retrieveCourse = do index :: ServerPart Response index = do response <- liftIO $ runDb $ do - coursesList :: [Entity Courses] <- selectList [] [] - let codes = map (coursesCode . entityVal) coursesList + coursesList :: [Entity Course] <- selectList [] [] + let codes = map (courseCode . entityVal) coursesList return $ T.unlines codes :: SqlPersistM T.Text return $ toResponse response diff --git a/app/Database/CourseVideoSeed.hs b/app/Database/CourseVideoSeed.hs index ba5f77367..693e40d7b 100644 --- a/app/Database/CourseVideoSeed.hs +++ b/app/Database/CourseVideoSeed.hs @@ -24,7 +24,7 @@ courseVideos = [ seedVideo :: (Text, [Text]) -> SqlPersistM () seedVideo (code, videos) = - updateWhere [CoursesCode ==. code] [CoursesVideoUrls =. videos] + updateWhere [CourseCode ==. code] [CourseVideoUrls =. videos] -- | Sets the video routes of all course rows. seedVideos :: IO () diff --git a/app/Database/Migrations.hs b/app/Database/Migrations.hs index 1ac16ed00..bf27c6373 100644 --- a/app/Database/Migrations.hs +++ b/app/Database/Migrations.hs @@ -31,7 +31,9 @@ applyMigrations currVersion migrations = do -- | List of migrations migrationList :: [MigrationWrapper] -migrationList = [MigrationWrapper {version=2, script=renamePostTables}] +migrationList = [ MigrationWrapper {version=2, script=renamePostTables} + , MigrationWrapper {version=3, script=renameCoursesTable} + ] -- | Migration script which renames the Post tables to Program renamePostTables :: Migration @@ -40,6 +42,11 @@ renamePostTables = do addMigration True "ALTER TABLE post_category RENAME TO program_category;" addMigration True "ALTER TABLE program_category RENAME COLUMN post TO program;" +-- | Migration script which renames the Courses table to Course +renameCoursesTable :: Migration +renameCoursesTable = + addMigration True "ALTER TABLE courses RENAME TO course;" + -- | Gets the current version of the database. -- If no version is defined, initialize the -- version to the latest version and return that. diff --git a/app/Database/Tables.hs b/app/Database/Tables.hs index 75431190d..7a53c8610 100644 --- a/app/Database/Tables.hs +++ b/app/Database/Tables.hs @@ -19,8 +19,8 @@ straightforward. module Database.Tables where -import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), genericToJSON, withObject, - (.!=), (.:), (.:?)) +import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), genericToJSON, withObject, (.!=), (.:), + (.:?)) import Data.Aeson.Types (Options (..), Parser, Value (Object), defaultOptions) import Data.Char (toLower) import qualified Data.Text as T @@ -43,7 +43,7 @@ Department json Primary name UniqueName name -Courses +Course code T.Text Primary code title T.Text Maybe diff --git a/app/Models/Course.hs b/app/Models/Course.hs index 9ce303f69..480d1267b 100644 --- a/app/Models/Course.hs +++ b/app/Models/Course.hs @@ -17,16 +17,16 @@ import Database.Persist.Class (selectKeysList) import Database.Persist.Sqlite (Entity, PersistValue (PersistText), SqlPersistM, entityVal, get, insert_, rawSql, selectFirst, selectList, (<-.), (==.)) import Database.Tables (Breadth (breadthDescription), - Courses (coursesBreadth, coursesCode, coursesCoreqs, coursesDescription, coursesDistribution, coursesExclusions, coursesPrereqString, coursesTitle, coursesVideoUrls), + Course (courseBreadth, courseCode, courseCoreqs, courseDescription, courseDistribution, courseExclusions, coursePrereqString, courseTitle, courseVideoUrls), Distribution (distributionDescription), - EntityField (BreadthDescription, CoursesCode, DistributionDescription, MeetingCode), + EntityField (BreadthDescription, CourseCode, DistributionDescription, MeetingCode), Key, MeetTime', Meeting (meetingCode)) import GHC.Generics (Generic) import Models.Meeting (buildMeetTimes, meetingQuery) -- | The data for a single course, as returned by the back-end to the front-end. --- This is different from the schema-defined 'Courses' type (in "Database.Tables") --- 'Courses' describes how a course is stored in the database, whereas +-- This is different from the schema-defined 'Course' type (in "Database.Tables") +-- 'Course' describes how a course is stored in the database, whereas -- 'CourseData' describes the shape of the information sent to the client -- when a course is requested. data CourseData = @@ -51,7 +51,7 @@ returnCourse lowerStr = runDb $ do let courseStr = T.toUpper lowerStr -- TODO: require the client to pass the full course code let fullCodes = [courseStr, T.append courseStr "H1", T.append courseStr "Y1"] - sqlCourse :: (Maybe (Entity Courses)) <- selectFirst [CoursesCode <-. fullCodes] [] + sqlCourse :: (Maybe (Entity Course)) <- selectFirst [CourseCode <-. fullCodes] [] case sqlCourse of Nothing -> return Nothing Just course -> do @@ -73,48 +73,48 @@ getDescriptionD (Just key) = do maybeDistribution <- get key return $ fmap distributionDescription maybeDistribution --- | Builds a 'CourseData' structure from a tuple from the Courses table. +-- | Builds a 'CourseData' structure from a tuple from the Course table. -- Some fields still need to be added in. -buildCourse :: [MeetTime'] -> Courses -> SqlPersistM CourseData +buildCourse :: [MeetTime'] -> Course -> SqlPersistM CourseData buildCourse allMeetings course = do - cBreadth <- getDescriptionB (coursesBreadth course) - cDistribution <- getDescriptionD (coursesDistribution course) + cBreadth <- getDescriptionB (courseBreadth course) + cDistribution <- getDescriptionD (courseDistribution course) return $ CourseData cBreadth -- TODO: Remove the filter and allow double-quotes - (fmap (T.filter (/='\"')) (coursesDescription course)) - (fmap (T.filter (/='\"')) (coursesTitle course)) - (coursesPrereqString course) + (fmap (T.filter (/='\"')) (courseDescription course)) + (fmap (T.filter (/='\"')) (courseTitle course)) + (coursePrereqString course) (Just allMeetings) - (coursesCode course) - (coursesExclusions course) + (courseCode course) + (courseExclusions course) cDistribution - (coursesCoreqs course) - (coursesVideoUrls course) + (courseCoreqs course) + (courseVideoUrls course) -- | Retrieves the prerequisites for a course (code) as a string. -- Also retrieves the actual course code in the database in case -- the one the user inputs doesn't match it exactly prereqsForCourse :: T.Text -> IO (Either String (T.Text, T.Text)) -prereqsForCourse courseCode = runDb $ do - let upperCaseCourseCode = T.toUpper courseCode - course <- selectFirst [CoursesCode <-. [upperCaseCourseCode, upperCaseCourseCode `T.append` "H1", upperCaseCourseCode `T.append` "Y1"]] [] +prereqsForCourse code = runDb $ do + let upperCaseCourseCode = T.toUpper code + course <- selectFirst [CourseCode <-. [upperCaseCourseCode, upperCaseCourseCode `T.append` "H1", upperCaseCourseCode `T.append` "Y1"]] [] case course of Nothing -> return (Left "Course not found") Just courseEntity -> return (Right - (coursesCode $ entityVal courseEntity, - fromMaybe "" $ coursesPrereqString $ entityVal courseEntity) + (courseCode $ entityVal courseEntity, + fromMaybe "" $ coursePrereqString $ entityVal courseEntity) ) :: SqlPersistM (Either String (T.Text, T.Text)) getDeptCourses :: MonadIO m => T.Text -> m [CourseData] getDeptCourses dept = liftIO $ runDb $ do - courses :: [Entity Courses] <- rawSql "SELECT ?? FROM courses WHERE code LIKE ?" [PersistText $ T.snoc dept '%'] + courses :: [Entity Course] <- rawSql "SELECT ?? FROM course WHERE code LIKE ?" [PersistText $ T.snoc dept '%'] let deptCourses = map entityVal courses - meetings :: [Entity Meeting] <- selectList [MeetingCode <-. map coursesCode deptCourses] [] + meetings :: [Entity Meeting] <- selectList [MeetingCode <-. map courseCode deptCourses] [] mapM (processCourse meetings) deptCourses where processCourse allMeetings course = do - let courseMeetings = filter (\m -> meetingCode (entityVal m) == coursesCode course) allMeetings + let courseMeetings = filter (\m -> meetingCode (entityVal m) == courseCode course) allMeetings allTimes <- mapM buildMeetTimes courseMeetings buildCourse allTimes course @@ -138,13 +138,13 @@ getBreadthKey description_ = do [] -> Nothing (x:_) -> Just x --- | Inserts course into the Courses table. -insertCourse :: (Courses, T.Text, T.Text) -> SqlPersistM () +-- | Inserts course into the Course table. +insertCourse :: (Course, T.Text, T.Text) -> SqlPersistM () insertCourse (course, breadthDesc, distributionDesc) = do - maybeCourse <- selectFirst [CoursesCode ==. coursesCode course] [] + maybeCourse <- selectFirst [CourseCode ==. courseCode course] [] breadthKey <- getBreadthKey breadthDesc distributionKey <- getDistributionKey distributionDesc case maybeCourse of - Nothing -> insert_ $ course {coursesBreadth = breadthKey, - coursesDistribution = distributionKey} + Nothing -> insert_ $ course {courseBreadth = breadthKey, + courseDistribution = distributionKey} Just _ -> return () diff --git a/app/WebParsing/ArtSciParser.hs b/app/WebParsing/ArtSciParser.hs index 74f5b36ab..6ae6cccb8 100644 --- a/app/WebParsing/ArtSciParser.hs +++ b/app/WebParsing/ArtSciParser.hs @@ -10,7 +10,7 @@ import Data.Text.Lazy (toStrict) import Data.Text.Lazy.Encoding (decodeUtf8) import Database.Persist (insertUnique) import Database.Persist.Sqlite (SqlPersistM) -import Database.Tables (Courses (..), Department (..)) +import Database.Tables (Course (..), Department (..)) import Models.Building (parseBuildings) import Models.Course (insertCourse) import Network.HTTP.Simple (getResponseBody, httpLBS, parseRequest) @@ -89,7 +89,7 @@ parsePrograms programs = mapM_ addPostToDatabase $ TS.partitions isAccordionHead isAccordionHeader = tagOpenAttrNameLit "h3" "class" (T.isInfixOf "js-views-accordion-group-header") -- | Parse the section of the course calendar listing the courses offered by a department. -parseCourses :: [Tag T.Text] -> [(Courses, T.Text, T.Text)] +parseCourses :: [Tag T.Text] -> [(Course, T.Text, T.Text)] parseCourses tags = let elems = TS.partitions isAccordion tags courses = map parseCourse elems @@ -98,7 +98,7 @@ parseCourses tags = where isAccordion = tagOpenAttrNameLit "h3" "class" (T.isInfixOf "js-views-accordion-group-header") - parseCourse :: [Tag T.Text] -> (Courses, T.Text, T.Text) + parseCourse :: [Tag T.Text] -> (Course, T.Text, T.Text) parseCourse courseTags = let courseHeader = T.strip . TS.innerText $ takeWhile (not . TS.isTagCloseName "h3") courseTags (code, title) = either (error . show) id $ parse parseCourseTitle "course title" courseHeader @@ -116,7 +116,7 @@ parseCourses tags = distribution = fromMaybe "" $ getValue "Distribution Requirements:" courseContents breadth = fromMaybe "" $ getValue "Breadth Requirements:" courseContents in - (Courses code + (Course code (Just title) (Just description) (fmap (T.pack . show . parseReqs . T.unpack) prereqString) diff --git a/backend-test/Controllers/CourseControllerTests.hs b/backend-test/Controllers/CourseControllerTests.hs index 8e904165f..cdabd5aa4 100644 --- a/backend-test/Controllers/CourseControllerTests.hs +++ b/backend-test/Controllers/CourseControllerTests.hs @@ -18,7 +18,7 @@ import qualified Data.Map as Map import Data.Maybe (fromMaybe, mapMaybe) import qualified Data.Text as T import Database.Persist.Sqlite (SqlPersistM, insert, insertMany_, insert_) -import Database.Tables (Building (..), Courses (..), MeetTime (..), Meeting (..), Time' (..)) +import Database.Tables (Building (..), Course (..), MeetTime (..), Meeting (..), Time' (..)) import Happstack.Server (rsBody, rsCode) import Models.Time (buildTimes) import Test.Tasty (TestTree) @@ -198,17 +198,17 @@ runRetrieveCourseTest (label, courseName, courseData, meetingTimes, expectedCode Nothing -> [] let courseToInsert = - Courses - { coursesCode = currCourseName - , coursesTitle = Map.lookup "title" courseData - , coursesDescription = Map.lookup "description" courseData - , coursesPrereqs = Map.lookup "prereqs" courseData - , coursesExclusions = Map.lookup "exclusions" courseData - , coursesBreadth = Nothing - , coursesDistribution = Nothing - , coursesPrereqString = Map.lookup "prereqString" courseData - , coursesCoreqs = Map.lookup "coreqs" courseData - , coursesVideoUrls = videoUrls + Course + { courseCode = currCourseName + , courseTitle = Map.lookup "title" courseData + , courseDescription = Map.lookup "description" courseData + , coursePrereqs = Map.lookup "prereqs" courseData + , courseExclusions = Map.lookup "exclusions" courseData + , courseBreadth = Nothing + , courseDistribution = Nothing + , coursePrereqString = Map.lookup "prereqString" courseData + , courseCoreqs = Map.lookup "coreqs" courseData + , courseVideoUrls = videoUrls } let buildingCodes = getUniqueBuildings meetingTimes @@ -237,7 +237,7 @@ runRetrieveCourseTests = map runRetrieveCourseTest retrieveCourseTestCases insertCourses :: [T.Text] -> SqlPersistM () insertCourses = mapM_ insertCourse where - insertCourse code = insert_ (Courses code Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing []) + insertCourse code = insert_ (Course code Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing []) -- | Helper function to insert MeetTimes into the database insertMeetingTimes :: [MeetTime] -> SqlPersistM () @@ -284,7 +284,7 @@ runIndexTests :: [TestTree] runIndexTests = map runIndexTest indexTestCases -- | List of test cases as (case, database state, input [dept], expected JSON output) for the courseInfo function -courseInfoTestCases :: [(String, [Courses], T.Text, String)] +courseInfoTestCases :: [(String, [Course], T.Text, String)] courseInfoTestCases = [ ("Empty Database" , [] @@ -308,45 +308,45 @@ courseInfoTestCases = , "[{\"allMeetingTimes\":[],\"breadth\":null,\"coreqs\":null,\"description\":\"Programming in a language such as Python. Elementary data types, lists, maps. Program structure: control flow, functions, classes, objects, methods. Algorithms and problem solving. Searching, sorting, and complexity. Unit testing. Floating-point numbers and numerical computation. No prior programming experience required.\",\"distribution\":null,\"exclusions\":\"CSC110Y1, CSC111H1, CSC120H1, CSC121H1, CSC148H1, CSC108H5, CSC148H5, CSCA08H3, CSCA20H3, CSCA48H3\",\"name\":\"CSC108H1\",\"prereqString\":null,\"title\":\"Introduction to Computer Programming\",\"videoUrls\":[]},{\"allMeetingTimes\":[],\"breadth\":null,\"coreqs\":\"( CSC108H1/ equivalent programming experience)/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to probability using simulation and mathematical frameworks, with emphasis on the probability needed for more advanced study in statistical practice. Topics covered include probability spaces, random variables, discrete and continuous probability distributions, probability mass, density, and distribution functions, expectation and variance, independence, conditional probability, the law of large numbers, the central limit theorem, sampling distributions. Computer simulation will be taught and used extensively for calculations and to guide the theoretical development.\",\"distribution\":null,\"exclusions\":\"STA247H1, STA201H1, STA255H1, STA257H1, ECO227Y1, MAT370H1, STAB52H3, STA256H5, ECO227Y5\",\"name\":\"STA237H1\",\"prereqString\":\"( MAT135H1, MAT136H1)/ MAT137Y1/ MAT157Y1/ ( MATA30H3, MATA36H3)/ ( MATA31H3, MATA37H3)/ ( MAT135H5, MAT136H5)/ MAT137Y5/ MAT157Y5/ ( MAT137H5, MAT139H5)/ ( MAT157H5, MAT159H5)\",\"title\":\"Probability, Statistics and Data Analysis I\",\"videoUrls\":[]},{\"allMeetingTimes\":[],\"breadth\":null,\"coreqs\":\"CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation.\",\"distribution\":null,\"exclusions\":\"ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5\",\"name\":\"STA238H1\",\"prereqString\":\"STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5\",\"title\":\"Probability, Statistics and Data Analysis II\",\"videoUrls\":[\"[https://example.com/video1\",\"https://example.com/video2]\"]}]") ] where - sta237 = Courses - { coursesCode = "STA237H1" - , coursesTitle = Just "Probability, Statistics and Data Analysis I" - , coursesDescription = Just "An introduction to probability using simulation and mathematical frameworks, with emphasis on the probability needed for more advanced study in statistical practice. Topics covered include probability spaces, random variables, discrete and continuous probability distributions, probability mass, density, and distribution functions, expectation and variance, independence, conditional probability, the law of large numbers, the central limit theorem, sampling distributions. Computer simulation will be taught and used extensively for calculations and to guide the theoretical development." - , coursesPrereqs = Just "( MAT135H1, MAT136H1)/ MAT137Y1/ MAT157Y1/ ( MATA30H3, MATA36H3)/ ( MATA31H3, MATA37H3)/ ( MAT135H5, MAT136H5)/ MAT137Y5/ MAT157Y5/ ( MAT137H5, MAT139H5)/ ( MAT157H5, MAT159H5)" - , coursesExclusions = Just "STA247H1, STA201H1, STA255H1, STA257H1, ECO227Y1, MAT370H1, STAB52H3, STA256H5, ECO227Y5" - , coursesBreadth = Nothing - , coursesDistribution = Nothing - , coursesPrereqString = Just "( MAT135H1, MAT136H1)/ MAT137Y1/ MAT157Y1/ ( MATA30H3, MATA36H3)/ ( MATA31H3, MATA37H3)/ ( MAT135H5, MAT136H5)/ MAT137Y5/ MAT157Y5/ ( MAT137H5, MAT139H5)/ ( MAT157H5, MAT159H5)" - , coursesCoreqs = Just "( CSC108H1/ equivalent programming experience)/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance." - , coursesVideoUrls = [] + sta237 = Course + { courseCode = "STA237H1" + , courseTitle = Just "Probability, Statistics and Data Analysis I" + , courseDescription = Just "An introduction to probability using simulation and mathematical frameworks, with emphasis on the probability needed for more advanced study in statistical practice. Topics covered include probability spaces, random variables, discrete and continuous probability distributions, probability mass, density, and distribution functions, expectation and variance, independence, conditional probability, the law of large numbers, the central limit theorem, sampling distributions. Computer simulation will be taught and used extensively for calculations and to guide the theoretical development." + , coursePrereqs = Just "( MAT135H1, MAT136H1)/ MAT137Y1/ MAT157Y1/ ( MATA30H3, MATA36H3)/ ( MATA31H3, MATA37H3)/ ( MAT135H5, MAT136H5)/ MAT137Y5/ MAT157Y5/ ( MAT137H5, MAT139H5)/ ( MAT157H5, MAT159H5)" + , courseExclusions = Just "STA247H1, STA201H1, STA255H1, STA257H1, ECO227Y1, MAT370H1, STAB52H3, STA256H5, ECO227Y5" + , courseBreadth = Nothing + , courseDistribution = Nothing + , coursePrereqString = Just "( MAT135H1, MAT136H1)/ MAT137Y1/ MAT157Y1/ ( MATA30H3, MATA36H3)/ ( MATA31H3, MATA37H3)/ ( MAT135H5, MAT136H5)/ MAT137Y5/ MAT157Y5/ ( MAT137H5, MAT139H5)/ ( MAT157H5, MAT159H5)" + , courseCoreqs = Just "( CSC108H1/ equivalent programming experience)/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance." + , courseVideoUrls = [] } - sta238 = Courses - { coursesCode = "STA238H1" - , coursesTitle = Just "Probability, Statistics and Data Analysis II" - , coursesDescription = Just "An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation." - , coursesPrereqs = Just "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5" - , coursesExclusions = Just "ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5" - , coursesBreadth = Nothing - , coursesDistribution = Nothing - , coursesPrereqString = Just "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5" - , coursesCoreqs = Just "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance." - , coursesVideoUrls = ["[https://example.com/video1", "https://example.com/video2]"] + sta238 = Course + { courseCode = "STA238H1" + , courseTitle = Just "Probability, Statistics and Data Analysis II" + , courseDescription = Just "An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation." + , coursePrereqs = Just "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5" + , courseExclusions = Just "ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5" + , courseBreadth = Nothing + , courseDistribution = Nothing + , coursePrereqString = Just "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5" + , courseCoreqs = Just "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance." + , courseVideoUrls = ["[https://example.com/video1", "https://example.com/video2]"] } - csc108 = Courses - { coursesCode = "CSC108H1" - , coursesTitle = Just "Introduction to Computer Programming" - , coursesDescription = Just "Programming in a language such as Python. Elementary data types, lists, maps. Program structure: control flow, functions, classes, objects, methods. Algorithms and problem solving. Searching, sorting, and complexity. Unit testing. Floating-point numbers and numerical computation. No prior programming experience required." - , coursesPrereqs = Nothing - , coursesExclusions = Just "CSC110Y1, CSC111H1, CSC120H1, CSC121H1, CSC148H1, CSC108H5, CSC148H5, CSCA08H3, CSCA20H3, CSCA48H3" - , coursesBreadth = Nothing - , coursesDistribution = Nothing - , coursesPrereqString = Nothing - , coursesCoreqs = Nothing - , coursesVideoUrls = [] + csc108 = Course + { courseCode = "CSC108H1" + , courseTitle = Just "Introduction to Computer Programming" + , courseDescription = Just "Programming in a language such as Python. Elementary data types, lists, maps. Program structure: control flow, functions, classes, objects, methods. Algorithms and problem solving. Searching, sorting, and complexity. Unit testing. Floating-point numbers and numerical computation. No prior programming experience required." + , coursePrereqs = Nothing + , courseExclusions = Just "CSC110Y1, CSC111H1, CSC120H1, CSC121H1, CSC148H1, CSC108H5, CSC148H5, CSCA08H3, CSCA20H3, CSCA48H3" + , courseBreadth = Nothing + , courseDistribution = Nothing + , coursePrereqString = Nothing + , courseCoreqs = Nothing + , courseVideoUrls = [] } -- | Run a test case (case, database state, input [dept], expected JSON output) on the courseInfo function -runCourseInfoTest :: (String, [Courses], T.Text, String) -> TestTree +runCourseInfoTest :: (String, [Course], T.Text, String) -> TestTree runCourseInfoTest (label, state, dept, expected) = testCase label $ do runDb $ do diff --git a/backend-test/Controllers/GenerateControllerTests.hs b/backend-test/Controllers/GenerateControllerTests.hs index 12cea2aed..3091b9345 100644 --- a/backend-test/Controllers/GenerateControllerTests.hs +++ b/backend-test/Controllers/GenerateControllerTests.hs @@ -18,7 +18,7 @@ import qualified Data.ByteString.Lazy as BSL import Data.Foldable (toList) import qualified Data.Text as T import Database.Persist.Sqlite (SqlPersistM, insert_) -import Database.Tables (Courses (..)) +import Database.Tables (Course (..)) import Happstack.Server (rsBody) import Test.Tasty (TestTree) import Test.Tasty.HUnit (assertEqual, testCase) @@ -28,7 +28,7 @@ import TestHelpers (clearDatabase, mockPutRequest, runServerPartWith, withDataba insertCoursesWithPrerequisites :: [(T.Text, Maybe T.Text)] -> SqlPersistM () insertCoursesWithPrerequisites = mapM_ insertCourse where - insertCourse (code, prereqString) = insert_ (Courses { coursesCode = code, coursesTitle = Nothing, coursesDescription = Nothing, coursesPrereqs = prereqString, coursesExclusions = Nothing, coursesBreadth = Nothing, coursesDistribution = Nothing, coursesPrereqString = prereqString, coursesCoreqs = Nothing, coursesVideoUrls = [] }) + insertCourse (code, prereqString) = insert_ (Course { courseCode = code, courseTitle = Nothing, courseDescription = Nothing, coursePrereqs = prereqString, courseExclusions = Nothing, courseBreadth = Nothing, courseDistribution = Nothing, coursePrereqString = prereqString, courseCoreqs = Nothing, courseVideoUrls = [] }) -- | List of test cases as -- (input course, course/prereq structure, JSON payload, expected # of nodes in prereq graph, expected # of boolean nodes in prereq graph) diff --git a/backend-test/TestHelpers.hs b/backend-test/TestHelpers.hs index 559432001..c3d75894c 100644 --- a/backend-test/TestHelpers.hs +++ b/backend-test/TestHelpers.hs @@ -97,7 +97,7 @@ runServerPart sp = runServerPartWith sp (mockGetRequest "/" [] "") clearDatabase :: SqlPersistM () clearDatabase = do deleteWhere ([] :: [Filter Department]) - deleteWhere ([] :: [Filter Courses]) + deleteWhere ([] :: [Filter Course]) deleteWhere ([] :: [Filter Times]) deleteWhere ([] :: [Filter Meeting]) deleteWhere ([] :: [Filter Breadth]) From eb81bfac7cbd4bd07e0cc5d1ac722d0f48c44df9 Mon Sep 17 00:00:00 2001 From: angelsanddevslol Date: Sat, 13 Jun 2026 20:22:26 -0400 Subject: [PATCH 2/4] fix import for name shadowing from Database.Tables --- app/Export/GetImages.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Export/GetImages.hs b/app/Export/GetImages.hs index 61afd80b0..d8bcee43c 100644 --- a/app/Export/GetImages.hs +++ b/app/Export/GetImages.hs @@ -17,7 +17,7 @@ import qualified Data.Map as M import Data.Maybe (fromMaybe) import qualified Data.Text as T import qualified Data.Text.Lazy.IO as LTIO -import Database.Tables as Tables +import Database.Tables (Time (..)) import Export.ImageConversion (withImageFile) import Export.TimetableImageCreator (renderTableHelper, times) import Models.Meeting (getMeetingTime) From 18dad4a27ccfeb0aa33d63540a30ec44b4c73a36 Mon Sep 17 00:00:00 2001 From: angelsanddevslol Date: Sun, 14 Jun 2026 15:54:42 -0400 Subject: [PATCH 3/4] change changelog.md, fix indentation, and revert some changes --- CHANGELOG.md | 3 ++- app/Export/GetImages.hs | 2 +- app/WebParsing/ArtSciParser.hs | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba9e80013..4099613db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ ### 🔧 Internal changes +- Refactored the `Courses` table to `Course` with a database migration + ## [0.8.0] - 2026-06-09 ### ✨ New features/enhancements @@ -47,7 +49,6 @@ - Updated documentation in `app/Util/Blaze.hs` - Moved the `Course` data type from `Database/Tables.hs` into `Models/Course.hs`, renamed it to `CourseData` - Removed `SvgJSON` data type in favour of `([Text], [Shape], [Path])` -- Refactored the `Courses` table to `Course` with a database migration ## [0.7.2] - 2025-12-10 diff --git a/app/Export/GetImages.hs b/app/Export/GetImages.hs index d8bcee43c..61afd80b0 100644 --- a/app/Export/GetImages.hs +++ b/app/Export/GetImages.hs @@ -17,7 +17,7 @@ import qualified Data.Map as M import Data.Maybe (fromMaybe) import qualified Data.Text as T import qualified Data.Text.Lazy.IO as LTIO -import Database.Tables (Time (..)) +import Database.Tables as Tables import Export.ImageConversion (withImageFile) import Export.TimetableImageCreator (renderTableHelper, times) import Models.Meeting (getMeetingTime) diff --git a/app/WebParsing/ArtSciParser.hs b/app/WebParsing/ArtSciParser.hs index 6ae6cccb8..a9c197f23 100644 --- a/app/WebParsing/ArtSciParser.hs +++ b/app/WebParsing/ArtSciParser.hs @@ -117,16 +117,16 @@ parseCourses tags = breadth = fromMaybe "" $ getValue "Breadth Requirements:" courseContents in (Course code - (Just title) - (Just description) - (fmap (T.pack . show . parseReqs . T.unpack) prereqString) - exclusion - Nothing - Nothing - prereqString - coreq - [], - breadth, distribution) + (Just title) + (Just description) + (fmap (T.pack . show . parseReqs . T.unpack) prereqString) + exclusion + Nothing + Nothing + prereqString + coreq + [], + breadth, distribution) getValue label texts = do i <- findIndex (T.isPrefixOf label) texts From 84dff5d096ff6b4ab1fbdc53c7952e1bba570f79 Mon Sep 17 00:00:00 2001 From: angelsanddevslol Date: Mon, 15 Jun 2026 11:49:07 -0400 Subject: [PATCH 4/4] add back import changes for name shadowing --- app/Export/GetImages.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Export/GetImages.hs b/app/Export/GetImages.hs index 61afd80b0..d8bcee43c 100644 --- a/app/Export/GetImages.hs +++ b/app/Export/GetImages.hs @@ -17,7 +17,7 @@ import qualified Data.Map as M import Data.Maybe (fromMaybe) import qualified Data.Text as T import qualified Data.Text.Lazy.IO as LTIO -import Database.Tables as Tables +import Database.Tables (Time (..)) import Export.ImageConversion (withImageFile) import Export.TimetableImageCreator (renderTableHelper, times) import Models.Meeting (getMeetingTime)