Skip to content

Commit 77b9721

Browse files
authored
Renamed post to program in database schema and implement migration action (#1607)
1 parent 6383a88 commit 77b9721

File tree

15 files changed

+153
-97
lines changed

15 files changed

+153
-97
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Added a route to access all POST codes stored in the database with test coverage
1414
- Added the `js/components/generate/AutocompleteDropdown.js` component to the program field of Generate
1515
- Updated the colour of the rendered options for the AutocompleteDropdown component
16+
- Added a `database-migrate` CLI option that runs SQL migrations
1617
- Updated the course info modal timetable styling and layout
1718

1819
### 🐛 Bug fixes
@@ -37,6 +38,7 @@
3738
- Added test cases for the `ExportModal` component in `js/components/common`
3839
- Updated backend tests to use `tasty-discover`
3940
- Added documentation for running a subset of the backend tests
41+
- Renamed `Post` and `PostCategory` database tables to `Program` and `ProgramCategory` respectively through a database migration
4042
- Deleted `app/Response/Image` file and refactored `app/Util/Helpers` to include `returnImageData`
4143
- Added test cases for the retrieveProgram function in `Controllers/Program`
4244
- Removed duplicate code from `mockRequest` and `runServerPartWith` in `backend-test/TestHelpers.hs`

app/Controllers/Generate.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ findAndSavePrereqsResponse = do
4242
requestBody <- getBody
4343
let coursesOptions :: CourseGraphOptions = fromJust $ decode requestBody
4444

45-
postResults <- liftIO $ mapM (\code -> do
46-
post <- returnProgram (TL.toStrict code)
47-
return (TL.toStrict code, post))
45+
programResults <- liftIO $ mapM (\code -> do
46+
program <- returnProgram (TL.toStrict code)
47+
return (TL.toStrict code, program))
4848
(programs coursesOptions)
4949

50-
let invalidPrograms = map fst $ filter (isNothing . snd) postResults
51-
validPrograms = mapMaybe snd postResults
50+
let invalidPrograms = map fst $ filter (isNothing . snd) programResults
51+
validPrograms = mapMaybe snd programResults
5252

5353
allCourses <- liftIO $ nub <$>
5454
if all (== TL.empty) (courses coursesOptions)

app/Controllers/Program.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import qualified Data.Set as S
66
import qualified Data.Text as T (Text, null, strip, unlines)
77
import Database.Persist (Entity)
88
import Database.Persist.Sqlite (SqlPersistM, entityVal, selectList)
9-
import Database.Tables as Tables (Post, postCode, postModified)
9+
import Database.Tables as Tables (Program, programCode, programModified)
1010
import Happstack.Server (Request, Response, ServerPart, askRq, ifModifiedSince, lookText',
1111
toResponse)
1212
import Models.Program (returnProgram)
@@ -16,8 +16,8 @@ import Util.Happstack (createJSONResponse)
1616
index :: ServerPart Response
1717
index = do
1818
response <- liftIO $ runDb $ do
19-
programsList :: [Entity Post] <- selectList [] []
20-
let codes = map (postCode . entityVal) programsList
19+
programsList :: [Entity Program] <- selectList [] []
20+
let codes = map (programCode . entityVal) programsList
2121
rmEmpty = filter (not . T.null . T.strip) codes
2222
rmDups = S.toList (S.fromList rmEmpty)
2323
return $ T.unlines rmDups :: SqlPersistM T.Text
@@ -39,5 +39,5 @@ queryProgram :: Request -> T.Text -> IO Response
3939
queryProgram req code = do
4040
programMaybe <- returnProgram code
4141
case programMaybe of
42-
Nothing -> return $ createJSONResponse (Nothing :: Maybe Post)
43-
Just program -> return $ ifModifiedSince (postModified program) req (createJSONResponse program)
42+
Nothing -> return $ createJSONResponse (Nothing :: Maybe Program)
43+
Just program -> return $ ifModifiedSince (programModified program) req (createJSONResponse program)

app/Database/DataType.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ instance ToJSON ShapeType
3131
-- .
3232
instance FromJSON ShapeType
3333

34-
data PostType = Specialist | Major | Minor | Focus | Certificate | Other
34+
data ProgramType = Specialist | Major | Minor | Focus | Certificate | Other
3535
deriving (Show, Read, Eq, Generic)
36-
derivePersistField "PostType"
36+
derivePersistField "ProgramType"
3737

38-
instance ToJSON PostType
39-
instance FromJSON PostType
38+
instance ToJSON ProgramType
39+
instance FromJSON ProgramType

app/Database/Database.hs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ module Database.Database
1111

1212
import Config (databasePath, runDb)
1313
import Control.Monad (void)
14-
import Control.Monad.IO.Class (MonadIO, liftIO)
14+
import Control.Monad.IO.Class (liftIO)
1515
import Data.Maybe (fromMaybe)
1616
import Data.Text as T (findIndex, length, reverse, take, unpack)
1717
import Database.CourseVideoSeed (seedVideos)
18-
import Database.Persist.Sqlite (SqlPersistT, entityVal, insert_, runMigration, runMigrationQuiet,
19-
selectFirst)
18+
import Database.Migrations (getDatabaseVersion)
19+
import Database.Persist.Sqlite (insert_, runMigration, runMigrationQuiet)
2020
import Database.Tables
2121
import System.Directory (createDirectoryIfMissing)
2222
import WebParsing.ArtSciParser (parseCalendar)
@@ -41,19 +41,6 @@ setupDatabase quiet = do
4141
let migrateFunction = if quiet then void . runMigrationQuiet else runMigration
4242
runDb $ void $ migrateFunction migrateAll >> getDatabaseVersion
4343

44-
-- | Gets the current version of the database.
45-
-- If no version is defined, initialize the
46-
-- version to 1 and return that.
47-
getDatabaseVersion :: MonadIO m => SqlPersistT m Int
48-
getDatabaseVersion = do
49-
result <- selectFirst [] []
50-
case result of
51-
Just entity -> pure $ schemaVersionVersion $ entityVal entity
52-
Nothing -> do
53-
let initialVersion = 1
54-
insert_ $ SchemaVersion initialVersion
55-
pure initialVersion
56-
5744
-- | Sets up the course information from Artsci Calendar
5845
populateCalendar :: IO ()
5946
populateCalendar = do

app/Database/Migrations.hs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module Database.Migrations
2+
(migrateDatabase, getDatabaseVersion, setDatabaseVersion, migrationList) where
3+
4+
import Control.Monad.Reader (MonadIO)
5+
import Data.List (sortOn)
6+
import Database.Persist.Sql (Entity (..), Migration, SqlPersistT, addMigration, insert_,
7+
runMigrationUnsafe, selectFirst, update, (=.))
8+
import Database.Tables
9+
10+
data MigrationWrapper = MigrationWrapper {
11+
version :: Int,
12+
script :: Migration
13+
}
14+
15+
-- | Migrates the database
16+
migrateDatabase :: MonadIO m => SqlPersistT m ()
17+
migrateDatabase = do
18+
currVersion <- getDatabaseVersion
19+
applyMigrations currVersion migrationList
20+
21+
-- | Migrates the database by applying only migrations newer than the current version number
22+
applyMigrations :: MonadIO m => Int -> [MigrationWrapper] -> SqlPersistT m ()
23+
applyMigrations currVersion migrations = do
24+
mapM_ (runMigrationUnsafe . script)
25+
$ sortOn version
26+
$ filter (\migration -> version migration > currVersion) migrations
27+
28+
case migrations of
29+
[] -> return ()
30+
_ -> setDatabaseVersion $ maximum $ map version migrations
31+
32+
-- | List of migrations
33+
migrationList :: [MigrationWrapper]
34+
migrationList = [MigrationWrapper {version=2, script=renamePostTables}]
35+
36+
-- | Migration script which renames the Post tables to Program
37+
renamePostTables :: Migration
38+
renamePostTables = do
39+
addMigration True "ALTER TABLE post RENAME TO program;"
40+
addMigration True "ALTER TABLE post_category RENAME TO program_category;"
41+
addMigration True "ALTER TABLE program_category RENAME COLUMN post TO program;"
42+
43+
-- | Gets the current version of the database.
44+
-- If no version is defined, initialize the
45+
-- version to the latest version and return that.
46+
getDatabaseVersion :: MonadIO m => SqlPersistT m Int
47+
getDatabaseVersion = do
48+
result <- selectFirst [] []
49+
case result of
50+
Just entity -> pure $ schemaVersionVersion $ entityVal entity
51+
Nothing -> do
52+
let latestVersion = maximum $ map version migrationList
53+
setDatabaseVersion latestVersion
54+
pure latestVersion
55+
56+
-- | Sets the database version number to newVersion
57+
setDatabaseVersion :: MonadIO m => Int -> SqlPersistT m ()
58+
setDatabaseVersion newVersion = do
59+
result <- selectFirst [] []
60+
case result of
61+
Just (Entity key _) -> update key [SchemaVersionVersion =. newVersion]
62+
Nothing -> insert_ $ SchemaVersion newVersion

app/Database/Tables.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,20 @@ Path json
128128
deriving Show
129129
transform [Double] default=[1,0,0,1,0,0]
130130

131-
Post
132-
name PostType
131+
Program
132+
name ProgramType
133133
department T.Text
134134
code T.Text
135-
--UniquePostCode code
135+
--UniqueProgramCode code
136136
--Primary code
137137
description T.Text
138138
requirements T.Text
139139
created UTCTime
140140
modified UTCTime
141141
deriving Show Eq Generic
142142

143-
PostCategory
144-
post PostId
143+
ProgramCategory
144+
program ProgramId
145145
name T.Text
146146
deriving Show
147147

@@ -216,7 +216,7 @@ data Course =
216216
} deriving (Show, Generic)
217217

218218
instance ToJSON Course
219-
instance ToJSON Post
219+
instance ToJSON Program
220220
instance ToJSON Time
221221
instance ToJSON MeetTime'
222222
instance ToJSON Building

app/Main.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import System.Environment (getArgs)
1616
import System.IO (hPutStrLn, stderr)
1717

1818
-- internal dependencies
19+
import Config (runDb)
1920
import Database.Database (populateCalendar, setupDatabase)
21+
import Database.Migrations (migrateDatabase)
2022
import Server (runServer)
2123
import Svg.Parser (parsePrebuiltSvgs)
2224
import Util.Documentation (generateDocs)
@@ -34,7 +36,8 @@ taskMap = Map.fromList [
3436
("database-graphs", const parsePrebuiltSvgs),
3537
("docs", const generateDocs),
3638
("generate", generate),
37-
("database-setup", const (setupDatabase False))]
39+
("database-setup", const (setupDatabase False)),
40+
("database-migrate", const (runDb migrateDatabase))]
3841

3942
-- | Courseography entry point.
4043
main :: IO ()

app/Models/Program.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ import qualified Data.Text as T (Text, unpack)
88
import Database.Persist.Sqlite (entityVal, selectFirst, (==.))
99
import Database.Tables
1010

11-
-- | Queries the database for information about the program then returns the post value
12-
returnProgram :: T.Text -> IO (Maybe Post)
11+
-- | Queries the database for information about the program then returns the program value
12+
returnProgram :: T.Text -> IO (Maybe Program)
1313
returnProgram code = runDb $ do
14-
sqlProgram <- selectFirst [PostCode ==. code] []
14+
sqlProgram <- selectFirst [ProgramCode ==. code] []
1515
case sqlProgram of
1616
Nothing -> return Nothing
1717
Just program -> return $ Just $ entityVal program
1818

19-
-- | Retrieves the course requirements for a Post (program) as a list of course codes
20-
reqsForProgram :: Post -> [String]
19+
-- | Retrieves the course requirements for a Program as a list of course codes
20+
reqsForProgram :: Program -> [String]
2121
reqsForProgram program = do
22-
let requirementsText = T.unpack $ postRequirements program
22+
let requirementsText = T.unpack $ programRequirements program
2323
cleaned = filter (`notElem` ("<>" :: String)) $ filter (not . isPunctuation) requirementsText
2424
potentialCodes = words cleaned
2525
filter isCourseCode potentialCodes

app/WebParsing/PostParser.hs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Data.List.Split (keepDelimsL, split, splitWhen, whenElt)
1313
import Data.Text (strip)
1414
import qualified Data.Text as T
1515
import Data.Time.Clock (getCurrentTime)
16-
import Database.DataType (PostType (..))
16+
import Database.DataType (ProgramType (..))
1717
import Database.Persist (insertUnique)
1818
import Database.Persist.Sqlite (SqlPersistM, insert_)
1919
import Database.Tables
@@ -41,18 +41,18 @@ addPostToDatabase programElements = do
4141
Left _ -> return ()
4242
Right (department, code) -> do
4343
currTime <- liftIO getCurrentTime
44-
postExists <- insertUnique Post {
45-
postName = getPostType code department,
46-
postDepartment = department,
47-
postCode = code,
48-
postDescription = descriptionText,
49-
postRequirements = renderTags requirementLines,
50-
postCreated = currTime,
51-
postModified = currTime
44+
programExists <- insertUnique Program {
45+
programName = getPostType code department,
46+
programDepartment = department,
47+
programCode = code,
48+
programDescription = descriptionText,
49+
programRequirements = renderTags requirementLines,
50+
programCreated = currTime,
51+
programModified = currTime
5252
}
53-
case postExists of
53+
case programExists of
5454
Just key ->
55-
mapM_ (insert_ . PostCategory key) requirements
55+
mapM_ (insert_ . ProgramCategory key) requirements
5656
Nothing -> return ()
5757
where
5858
isDescriptionSection tag = tagOpenAttrNameLit "div" "class" (T.isInfixOf "views-field-body") tag || isRequirementSection tag
@@ -73,12 +73,12 @@ postInfoParser = do
7373

7474
-- | Extracts the post type (eg. major) from a post code if it is non-empty,
7575
-- | or from a dept name otherwise
76-
getPostType :: T.Text -> T.Text -> PostType
76+
getPostType :: T.Text -> T.Text -> ProgramType
7777
getPostType "" deptName = getPostTypeFromName deptName
7878
getPostType code _ = getPostTypeFromCode code
7979

8080
-- | Extracts the post type (eg. major) from a post name (eg. "Biology Specialist")
81-
getPostTypeFromName :: T.Text -> PostType
81+
getPostTypeFromName :: T.Text -> ProgramType
8282
getPostTypeFromName deptName
8383
| T.isInfixOf "Specialist" deptName = Specialist
8484
| T.isInfixOf "Major" deptName = Major
@@ -87,12 +87,12 @@ getPostTypeFromName deptName
8787
| T.isInfixOf "Certificate" deptName = Certificate
8888
| otherwise = Other
8989

90-
-- | Extracts the post type (eg. major) from a post code (eg. ASMAJ1689)
91-
getPostTypeFromCode :: T.Text -> PostType
90+
-- | Extracts the program type (eg. major) from a program code (eg. ASMAJ1689)
91+
getPostTypeFromCode :: T.Text -> ProgramType
9292
getPostTypeFromCode = abbrevToPost . T.take 3 . T.drop 2
9393

94-
-- | Maps the post type abbreviations to their corresponding PostType
95-
abbrevToPost :: T.Text -> PostType
94+
-- | Maps the post type abbreviations to their corresponding ProgramType
95+
abbrevToPost :: T.Text -> ProgramType
9696
abbrevToPost "SPE" = Specialist
9797
abbrevToPost "MAJ" = Major
9898
abbrevToPost "MIN" = Minor

0 commit comments

Comments
 (0)