Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cf949fe
initial changes to Tables + migration script
Purplegaze May 19, 2026
dc460de
Merge branch 'master' of https://github.com/Courseography/courseograp…
Purplegaze May 19, 2026
f8ea373
changelog
Purplegaze May 21, 2026
aa12db7
Added tests for JSON parsing of Time' data type (#1706)
r-weng May 22, 2026
e21e084
Add tests for Database.Tables FromJSON Meeting instance (#1707)
AngelsandDevsLOL May 22, 2026
3a2ebe7
build(deps): bump tmp from 0.2.5 to 0.2.7 (#1717)
dependabot[bot] May 30, 2026
bc71bea
Refactored graph helper functions from Svg.Database to Models.Graph (…
r-weng May 31, 2026
61112ab
Updated documentation in Util.Blaze (#1719)
r-weng May 31, 2026
4921e7c
Refactored Building and Time functions into Models (#1718)
AngelsandDevsLOL Jun 2, 2026
70767e4
Removed SvgJSON data type (#1720)
r-weng Jun 3, 2026
150bafe
build(deps-dev): bump the jest group with 2 updates (#1724)
dependabot[bot] Jun 5, 2026
2bac4c6
build(deps): bump the babel group across 1 directory with 5 updates (…
dependabot[bot] Jun 6, 2026
432042e
Renamed Course data type to CourseData (#1721)
AngelsandDevsLOL Jun 7, 2026
14b37a4
Updated CS graph and config for 2026-27 (#1726)
david-yz-liu Jun 9, 2026
95edff2
Added privacy-preserving analytics using GoatCounter and updated priv…
david-yz-liu Jun 9, 2026
a46556b
build(deps): bump the react group across 1 directory with 2 updates (…
dependabot[bot] Jun 9, 2026
35a47ad
build(deps): bump the ag-grid group across 1 directory with 2 updates…
dependabot[bot] Jun 9, 2026
07281cf
build(deps): bump shell-quote from 1.8.3 to 1.8.4 (#1728)
dependabot[bot] Jun 9, 2026
8358a26
chore: Run yarn dedupe (#1729)
david-yz-liu Jun 9, 2026
fd7c78c
Update backend wiring
Purplegaze Jun 9, 2026
4129868
Update frontend wiring
Purplegaze Jun 9, 2026
97b1fa0
fix course controller tests
Purplegaze Jun 9, 2026
ba03e3e
add mini changelog
Purplegaze Jun 9, 2026
87f08d6
Merge branch 'y-course-refactor' into y-session-compilation
Purplegaze Jun 9, 2026
3f3894e
Release version 0.8.0 (#1730)
david-yz-liu Jun 9, 2026
5807048
fix shadowing in Models/Meeting
Purplegaze Jun 9, 2026
9fb3f60
Merge branch 'y-session-compilation' of https://github.com/Purplegaze…
Purplegaze Jun 9, 2026
69b0aab
fix getimages shadowing
Purplegaze Jun 9, 2026
56e925d
skip test instead of commenting it out
Purplegaze Jun 13, 2026
94819cd
rename fields
Purplegaze Jun 13, 2026
c445897
fix tests 2
Purplegaze Jun 13, 2026
ded4583
rename in frontend
Purplegaze Jun 13, 2026
31a48ae
Renamed Courses datatype and table to Course (#1732)
AngelsandDevsLOL Jun 17, 2026
054afb9
build(deps): bump launch-editor from 2.11.1 to 2.14.1 (#1733)
dependabot[bot] Jun 19, 2026
2a568d4
build(deps-dev): bump webpack-dev-server from 5.2.4 to 5.2.5 (#1734)
dependabot[bot] Jun 21, 2026
fca3906
build(deps): bump form-data from 4.0.4 to 4.0.6 (#1735)
dependabot[bot] Jun 21, 2026
112ebfb
build(deps): bump tar from 7.5.11 to 7.5.16 (#1736)
dependabot[bot] Jun 21, 2026
5b8463c
Changed background colour of application (#1737)
AngelsandDevsLOL Jun 22, 2026
81d6fb0
Merge branch 'master' of https://github.com/Courseography/courseograp…
Purplegaze Jun 23, 2026
4fbfb9d
resolve buildTimes
Purplegaze Jun 23, 2026
d06db08
resolve TablesTests
Purplegaze Jun 24, 2026
886a85e
Merge branch 'y-course-refactor' into y-session-parse
Purplegaze Jun 24, 2026
65528f6
fix duplicate changelog lines
Purplegaze Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,27 @@

### ✨ New features/enhancements

### 🐛 Bug fixes

- Fixed CSS styling of background colour and let the application have full height

### 🔧 Internal changes

- Refactored the `Courses` table to `Course` with a database migration

## [0.8.0] - 2026-06-09

### ✨ New features/enhancements

- Added 404 status code return to `retrieveCourse` in `Controllers/Course` and added front-end tests for affected components
- Updated Computer Science graph and configuration for 2026-27
- Added privacy-preserving analytics using GoatCounter and updated privacy policy

### 🐛 Bug fixes

- Fixed a bug where duplicate graph components were being added
- Fixed CSS styling to center course modal and lighten overlay
- Fixed a bug in SVG parsing where `path` elements with a `Z` path directive were not being ignored

### 🔧 Internal changes

Expand All @@ -28,6 +43,14 @@
- Removed `Location` datatype in favour of `Building`
- Refactor tests to run directly on tuple input to prevent unnecessary unpacking and repacking
- Renamed usages of the word "room" to "location" in the codebase to better reflect the data represented
- Added test cases for JSON parsing of Meeting data type in `backend-test/Database/TablesTests.hs`
- Added test cases for JSON parsing of Time' data type in `backend-test/Database/TablesTests.hs`
- Refactored functions relating to `Building` and `Time` into `Models/Building` and `Models/Time` respectively
- Refactor graph helper functions from `app/Svg/Database.hs` to `app/Models/Graph.hs`
- Refactor functions for performing matrix operations from `app/Svg/Parser.hs` to `app/Util/Matrix.hs`
- 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])`

#### y-course-refactor mini changelog (to replace):
- Refactor `Times` database schema to encompass only a single session of a course
Expand Down
12 changes: 6 additions & 6 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#Privacy Policy
# Privacy Policy

Courseography does not store any information about you: none of your actions in the various components of this website are recorded, nor is any of your information accessed through Facebook.
Courseography does not collect or store any information about you, and does not require sign-in to use any website features.

There is an option for you to post schedules and graphs to your Facebook account. At no point will Courseography post anything to Facebook without your instruction to do so. Courseography does not interact with your Facebook account in any way other than these posts, and logging in and out. Logging into your Facebook account is not required to use any other feature of this website.
Courseography uses [GoatCounter](https://www.goatcounter.com/), an open-source web analytics platform to track page views. This platform does not store any personal information about you, and does not use cookies or persistent storage to track you. For information about GoatCounter, please see its [Privacy Policy](https://www.goatcounter.com/help/privacy).

##Changes to this privacy statement
## Changes to this privacy statement

We may amend this Privacy Statement from time to time. If we make changes in the way we use personal information, we will notify you by posting an announcement on our website. Users are bound by any changes to the Privacy Statement when he or she uses or otherwise accesses Courseography after such changes have been first posted.

##Questions or concerns
## Questions or concerns

If you have any questions or concerns regarding privacy on our website, please send us a detailed message at courseography@cs.toronto.edu. We will make every effort to resolve your concerns.

Effective Date: April 15, 2015
Effective Date: June 6, 2025
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Nazanin Ghazitabatabai,
Sidharth Gupta,
Parker Hutcheson,
Yoonie Jang,
Angelina Jiang,
Jai Joshi,
Aayush Karki,
Japleen Kaur,
Expand All @@ -143,6 +144,7 @@ Ian Stewart-Binks,
Alan Su,
Maryam Taj,
Betty Wang,
Rui Weng,
Fullchee Zhang,
Minfan Zhang,
Alex Shih,
Expand Down
2 changes: 1 addition & 1 deletion app/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ createReqBody page = object [ "campuses" .= ([] :: [T.Text]),
"page" .= page,
"pageSize" .= (300 :: Int),
"requirementProps" .= ([] :: [T.Text]),
"sessions" .= (["20259", "20261"] :: [T.Text]),
"sessions" .= (["20269", "20271"] :: [T.Text]),
"timePreferences" .= ([] :: [T.Text])
]

Expand Down
6 changes: 3 additions & 3 deletions app/Controllers/Course.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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

Expand Down
12 changes: 6 additions & 6 deletions app/Controllers/Graph.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Controllers.Graph (graphResponse, index, getGraphJSON, graphImageResponse, saveGraphJSON) where

import Control.Monad.IO.Class (liftIO)
import Data.Aeson (decode, object, (.=))
import Data.Aeson (object, (.=))
import Data.Maybe (fromMaybe)
import Export.ImageConversion (withImageFile)
import Happstack.Server (Response, ServerPart, lookBS, lookText', ok, toResponse)
Expand All @@ -15,9 +15,9 @@ import qualified Text.Blaze.Html5.Attributes as A

import Config (runDb)
import Database.Persist.Sqlite (Entity, SelectOpt (Asc), SqlPersistM, selectList, (==.))
import Database.Tables as Tables (EntityField (GraphDynamic, GraphTitle), Graph, SvgJSON, Text)
import Database.Tables as Tables (EntityField(GraphTitle, GraphDynamic), Text, Graph)
import Export.GetImages (writeActiveGraphImage)
import Models.Graph (getGraph, insertGraph)
import Models.Graph (getGraph, insertGraph, parseGraphComponentsJSON)
import Util.Happstack (createJSONResponse)
import Util.Helpers (readImageData)

Expand Down Expand Up @@ -64,9 +64,9 @@ saveGraphJSON :: ServerPart Response
saveGraphJSON = do
jsonStr <- lookBS "jsonData"
nameStr <- lookText' "nameData"
let jsonObj = decode jsonStr :: Maybe SvgJSON
let jsonObj = parseGraphComponentsJSON jsonStr
case jsonObj of
Nothing -> return $ toResponse ("Error" :: String)
Just svg -> do
_ <- liftIO $ runDb $ insertGraph nameStr svg
Just components -> do
_ <- liftIO $ runDb $ insertGraph nameStr components
return $ toResponse ("Success" :: String)
1 change: 1 addition & 0 deletions app/Controllers/Timetable.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Export.PdfGenerator
import Happstack.Server
import MasterTemplate
import Models.Meeting (returnMeeting)
import Models.Time (buildTime)
import Scripts
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
Expand Down
2 changes: 1 addition & 1 deletion app/Database/CourseVideoSeed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()
Expand Down
10 changes: 8 additions & 2 deletions app/Database/Migrations.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ applyMigrations currVersion migrations = do
migrationList :: [MigrationWrapper]
migrationList = [
MigrationWrapper {version=2, script=renamePostTables},
MigrationWrapper {version=3, script=splitTimes}
]
MigrationWrapper {version=3, script=renameCoursesTable},
MigrationWrapper {version=4, script=splitTimes}
]

-- | Migration script which renames the Post tables to Program
renamePostTables :: Migration
Expand All @@ -43,6 +44,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;"

-- | Migration script to add proper support for year-long courses
splitTimes :: Migration
splitTimes = do
Expand Down
65 changes: 3 additions & 62 deletions app/Database/Tables.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import Data.Char (toLower)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime)
import Database.DataType
import Database.Persist.Sqlite (Key, SqlPersistM, entityVal, selectFirst, (==.))
import Database.Persist.TH
import GHC.Generics

Expand All @@ -44,7 +43,7 @@ Department json
Primary name
UniqueName name

Courses
Course
code T.Text
Primary code
title T.Text Maybe
Expand All @@ -67,7 +66,7 @@ Meeting
enrol Int
wait Int
extra Int
deriving Generic Show
deriving Generic Show Eq
UniqueMeeting code session section

Times
Expand Down Expand Up @@ -161,20 +160,13 @@ SchemaVersion

-- ** TODO: Remove these extra types and class instances

-- | JSON SVG data
data SvgJSON =
SvgJSON { texts :: [Text],
shapes :: [Shape],
paths :: [Path]
} deriving (Show, Generic)

data Time' =
Time' { timeSession' :: Maybe T.Text,
weekDay' :: Double,
startHour' :: Double,
endHour' :: Double,
timeLocation' :: Maybe T.Text
} deriving (Show, Generic)
} deriving (Show, Eq, Generic)

data Time =
Time { timeSession :: Maybe T.Text,
Expand All @@ -191,30 +183,11 @@ data MeetTime = MeetTime {meetInfo :: Meeting, timeInfo :: [Time'] }
data MeetTime' = MeetTime' { meetData :: Meeting, timeData :: [Time] }
deriving (Show, Generic)

-- | A Course. TODO: remove this data type (it's redundant).
data Course =
Course { breadth :: Maybe T.Text,
description :: Maybe T.Text,
title :: Maybe T.Text,
prereqString :: Maybe T.Text,
allMeetingTimes :: Maybe [MeetTime'],
name :: !T.Text,
exclusions :: Maybe T.Text,
distribution :: Maybe T.Text,
coreqs :: Maybe T.Text,
videoUrls :: [T.Text]
} deriving (Show, Generic)

instance ToJSON Course
instance ToJSON Program
instance ToJSON Time
instance ToJSON MeetTime'
instance ToJSON Building

-- instance FromJSON required so that tables can be parsed into JSON,
-- not necessary otherwise.
instance FromJSON SvgJSON

instance ToJSON Meeting where
toJSON = genericToJSON defaultOptions {
fieldLabelModifier =
Expand Down Expand Up @@ -307,35 +280,3 @@ convertTimeVals (Just day) (Just start) (Just end) =
endDbl = getHourVal end
in (dayDbl, startDbl, endDbl)
convertTimeVals _ _ _ = (5.0, 25.0, 25.0)

-- | Convert Times into Time
buildTime :: Times -> SqlPersistM Time
buildTime t = do
building <- getBuilding (timesLocation t)
return $ Time (timesSession t)
(timesWeekDay t)
(timesStartHour t)
(timesEndHour t)
building

buildTimes :: Key Meeting -> Time' -> Times
buildTimes meetingKey t =
Times (timeSession' t)
(weekDay' t)
(startHour' t)
(endHour' t)
meetingKey
(timeLocation' t)

-- | Given a building code, get the persistent Building associated with it
getBuilding :: Maybe T.Text -> SqlPersistM (Maybe Building)
getBuilding rm = do
case rm of
Nothing -> return Nothing
Just r -> do
maybeEntityBuilding <- selectFirst [BuildingCode ==. T.take 2 r] []
case maybeEntityBuilding of
Nothing -> return Nothing
Just entBuilding -> do
let building = entityVal entBuilding
return $ Just building
2 changes: 1 addition & 1 deletion app/Export/GetImages.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions app/MasterTemplate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ masterTemplate title headers body scripts =
! A.href "/static/res/ico/favicon.png"
sequence_ headers
toStylesheet "/static/style/app.css"
H.script ! H.customAttribute "data-goatcounter" "https://david-yz-liu.goatcounter.com/count"
! H.customAttribute "async" ""
! A.src "//gc.zgo.at/count.js"
$ mempty
H.body $ do
body
scripts
Expand Down
55 changes: 55 additions & 0 deletions app/Models/Building.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Models.Building
(buildingsCSV,
parseBuildings,
getBuildingsFromCSV,
getBuilding) where

import Config (runDb)
import Control.Monad.IO.Class (liftIO)
import Data.CSV (csvFile)
import qualified Data.Text as T
import Database.Persist.Sqlite (Filter, SqlPersistM, deleteWhere, entityVal, insertMany_,
selectFirst, (==.))
import Database.Tables (Building (Building), EntityField (BuildingCode))
import Filesystem.Path.CurrentOS as Path (append, decodeString, encodeString)
import System.Directory (getCurrentDirectory)
import Text.ParserCombinators.Parsec (parseFromFile)
import Util.Helpers (safeHead)

buildingsCSV :: IO Prelude.FilePath
buildingsCSV = do
curDir <- getCurrentDirectory
return $ Path.encodeString $ Path.append (Path.decodeString curDir) $ Path.append (Path.decodeString "db") (Path.decodeString "building.csv")

parseBuildings :: IO ()
parseBuildings = do
buildingInfo <- getBuildingsFromCSV =<< buildingsCSV
runDb $ do
liftIO $ putStrLn "Inserting buildings"
deleteWhere ([] :: [Filter Building]) :: SqlPersistM ()
insertMany_ buildingInfo :: SqlPersistM ()

-- | Extract building names, codes, addresses, postal codes, latitude and longitude from csv file
getBuildingsFromCSV :: String -> IO [Building]
getBuildingsFromCSV buildingCSVFile = do
buildingCSVData <- parseFromFile csvFile buildingCSVFile
case buildingCSVData of
Left _ -> error "csv parse error"
Right buildingData ->
return $ map (\b -> Building (T.pack $ safeHead "" b)
(T.pack (b !! 1))
(T.pack (b !! 2))
(T.pack (b !! 3))
(read (b !! 4) :: Double)
(read (b !! 5) :: Double)) $ drop 1 buildingData

-- | Given a building code, get the persistent Building associated with it
getBuilding :: Maybe T.Text -> SqlPersistM (Maybe Building)
getBuilding rm =
case rm of
Nothing -> return Nothing
Just r -> do
maybeEntityBuilding <- selectFirst [BuildingCode ==. T.take 2 r] []
case maybeEntityBuilding of
Nothing -> return Nothing
Just entBuilding -> return $ Just (entityVal entBuilding)
Loading