Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- 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` into `Models/Building`

## [0.7.2] - 2025-12-10

Expand Down
1 change: 1 addition & 0 deletions app/Controllers/Timetable.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Export.LatexGenerator
import Export.PdfGenerator
import Happstack.Server
import MasterTemplate
import Models.Building (buildTime)
import Models.Meeting (returnMeeting)
import Scripts
import System.FilePath ((</>))
Expand Down
34 changes: 0 additions & 34 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 Down Expand Up @@ -308,36 +307,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
room1 <- getBuilding (timesFirstRoom t)
room2 <- getBuilding (timesSecondRoom t)
return $ Time (timesWeekDay t)
(timesStartHour t)
(timesEndHour t)
room1
room2

buildTimes :: Key Meeting -> Time' -> Times
buildTimes meetingKey t =
Times (weekDay' t)
(startHour' t)
(endHour' t)
meetingKey
(firstLocation' t)
(secondLocation' 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
77 changes: 77 additions & 0 deletions app/Models/Building.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module Models.Building
(buildingsCSV,
parseBuildings,
getBuildingsFromCSV,
getBuilding,
buildTime,
buildTimes) where

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

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)

-- | Convert a Times record into a Time by resolving room codes to Buildings
buildTime :: Times -> SqlPersistM Time
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leave buildTime and buildTimes in the Tables.hs module. You're correct that there's no Time model, which is something we can improve on later.

buildTime t = do
room1 <- getBuilding (timesFirstRoom t)
room2 <- getBuilding (timesSecondRoom t)
return $ Time (timesWeekDay t)
(timesStartHour t)
(timesEndHour t)
room1
room2

buildTimes :: Key Meeting -> Time' -> Times
buildTimes meetingKey t =
Times (weekDay' t)
(startHour' t)
(endHour' t)
meetingKey
(firstLocation' t)
(secondLocation' t)
1 change: 1 addition & 0 deletions app/Models/Meeting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import qualified Data.Text as T (Text, append, isPrefixOf, tail, take, toUpper)
import Database.Persist.Sqlite (Entity, SqlPersistM, entityKey, entityVal, selectFirst, selectList,
(<-.), (==.))
import Database.Tables as Tables
import Models.Building (buildTime)

-- | Queries the database for all matching lectures, tutorials,
meetingQuery :: [T.Text] -> SqlPersistM [MeetTime']
Expand Down
38 changes: 3 additions & 35 deletions app/WebParsing/ArtSciParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,23 @@ module WebParsing.ArtSciParser

import Config (fasCalendarUrl, programsUrl, runDb)
import Control.Monad.IO.Class (liftIO)
import Data.CSV
import Data.List (findIndex, nubBy)
import Data.Maybe (fromMaybe, mapMaybe)
import qualified Data.Text as T
import Data.Text.Lazy (toStrict)
import Data.Text.Lazy.Encoding (decodeUtf8)
import Database.Persist (insertUnique)
import Database.Persist.Sqlite (Filter, SqlPersistM, deleteWhere, insertMany_)
import Database.Tables (Building (..), Courses (..), Department (..))
import Filesystem.Path.CurrentOS as Path
import Database.Persist.Sqlite (SqlPersistM)
import Database.Tables (Courses (..), Department (..))
import Models.Building (parseBuildings)
import Models.Course (insertCourse)
import Network.HTTP.Simple (getResponseBody, httpLBS, parseRequest)
import System.Directory (getCurrentDirectory)
import qualified Text.HTML.TagSoup as TS
import Text.HTML.TagSoup (Tag)
import Text.HTML.TagSoup.Match (anyAttrValue, tagOpen, tagOpenAttrLit, tagOpenAttrNameLit)
import Text.Parsec (count, many, parse)
import qualified Text.Parsec.Char as P
import Text.Parsec.Text (Parser)
import Text.ParserCombinators.Parsec (parseFromFile)
import Util.Helpers
import WebParsing.ParsecCombinators (text)
import WebParsing.PostParser (addPostToDatabase)
import WebParsing.ReqParser (parseReqs)
Expand All @@ -33,34 +29,6 @@ parseCalendar = do
parseArtSci
parseBuildings

-- The file name is building.csv and it is in the courseography/db folder
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 -> do
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

-- | Parses the entire Arts & Science Course Calendar and inserts courses
-- into the database.
parseArtSci :: IO ()
Expand Down
3 changes: 2 additions & 1 deletion app/WebParsing/UtsgJsonParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import Data.Default.Class (def)
import qualified Data.Text as T
import Database.Persist.Sqlite (SqlPersistM, Update, deleteWhere, entityKey, insert, insertMany_,
selectFirst, upsert, (=.), (==.))
import Database.Tables (EntityField (..), MeetTime (..), Meeting (..), buildTimes)
import Database.Tables (EntityField (..), MeetTime (..), Meeting (..))
import Models.Building (buildTimes)
import Network.Connection (TLSSettings (TLSSettingsSimple))
import Network.HTTP.Conduit (RequestBody (RequestBodyLBS), httpLbs, method, mkManagerSettings,
newManager, parseRequest, requestBody, requestHeaders, responseBody)
Expand Down
4 changes: 2 additions & 2 deletions backend-test/Controllers/CourseControllerTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ 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' (..),
buildTimes)
import Database.Tables (Building (..), Courses (..), MeetTime (..), Meeting (..), Time' (..))
import Happstack.Server (rsBody, rsCode)
import Models.Building (buildTimes)
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (assertEqual, testCase)
import TestHelpers (clearDatabase, mockGetRequest, runServerPart, runServerPartWith, withDatabase)
Expand Down
2 changes: 2 additions & 0 deletions courseography.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ library
Export.ImageConversion,
Export.TimetableImageCreator,
MasterTemplate,
Models.Building,
Models.Course,
Models.Graph,
Models.Meeting,
Expand Down Expand Up @@ -188,6 +189,7 @@ executable courseography
Export.PdfGenerator,
Export.TimetableImageCreator,
MasterTemplate,
Models.Building,
Models.Course,
Models.Graph,
Models.Meeting,
Expand Down