{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Gauge.DBSetup where
import Data.ByteString ( ByteString )
import qualified Data.ByteString.Char8 as C
import Control.Monad ( void )
import GHC.Generics
import Squeal.PostgreSQL
-- Project imports
import Gauge.Schema ( Schemas
, DeviceOS
, IPLocation
)
-- First create enums as they're needed in the Schema
setup :: Definition (Public '[]) Schemas
setup =
createTypeEnumFrom @DeviceOS #device_os
>>> createTypeCompositeFrom @IPLocation #ip_location
>>> createTable
#users
( serial8
`as` #id
:* (text & notNullable)
`as` #email
:* (text & notNullable)
`as` #password
:* (text & nullable)
`as` #first_name
:* (int2 & nullable)
`as` #birthyear
)
(primaryKey #id `as` #pk_users :* unique #email `as` #email)
>>> createTable
#user_devices
( serial8
`as` #id
:* notNullable int8
`as` #user_id
:* (text & notNullable)
`as` #token
:* (typedef #device_os & notNullable)
`as` #os
)
( primaryKey #id
`as` #pk_user_devices
:* foreignKey #user_id #users #id (OnDelete Cascade) (OnUpdate Cascade)
`as` #fk_user_id
:* unique #token
`as` #token
)
-- Drop types last because tables depend on them
teardown :: Definition Schemas (Public '[])
teardown =
dropTableCascade #user_devices
>>> dropTableCascade #users
>>> dropType #ip_location
>>> dropType #device_os
-- With env vars, we could use the commented keys
data PGConfig = PGConfig
{ pgHost :: String -- "PG_HOST"
, pgPort :: Int -- "PG_PORT"
, pgDbname :: String -- "PG_DBNAME"
, pgUser :: String -- "PG_USER"
, pgPassword :: String -- "PG_PASSWORD"
}
deriving (Generic, Show)
-- | Helper: unused now, but primarily for testing locally
makeConnStr :: PGConfig -> ByteString
makeConnStr PGConfig { pgHost = host, pgPort = portNumber, pgDbname = dbName, pgUser = user, pgPassword = pw }
= C.pack
$ "host="
<> host
<> " dbname="
<> dbName
<> " user="
<> user
<> " password="
<> pw
<> " port="
<> show portNumber
connectionString :: ByteString
connectionString = "host=localhost port=5432 dbname=exampledb user=postgres password=postgres"
performDBAction :: Definition a b -> String -> IO ()
performDBAction action message = do
void
$ withConnection connectionString
$ manipulate_ (UnsafeManipulation "SET client_min_messages TO WARNING;")
& pqThen (define action)
putStrLn message
initDB :: IO ()
initDB =
performDBAction setup "Initialized Schema & corresponding tables for Database"
teardownDB :: IO ()
teardownDB = performDBAction teardown "Dropped all database tables"
dbSchema :: Definition '["public" ::: '[]] (Drop "public" '["public" ::: '[]])
dbSchema = dropSchemaCascade #public
dropDBSchema :: IO ()
dropDBSchema = performDBAction dbSchema "Dropped Public schema from database"
-- | Concatenate two `ByteString`s with a space between.
(<+>) :: ByteString -> ByteString -> ByteString
infixr 7 <+>
str1 <+> str2 = str1 <> " " <> str2
-- | Drop table custom SQL statement with 'cascade'
dropTableCascade
:: (Has sch schemas schema, Has tab schema ( 'Table table))
=> QualifiedAlias sch tab -- ^ table to remove
-> Definition schemas (Alter sch (Drop tab schema) schemas)
dropTableCascade tab =
UnsafeDefinition $ "DROP TABLE" <+> renderSQL tab <> " cascade;"