Hagia
log in
morj / hagia
overview
files
history
wiki
Viewing at
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

module Database
( migrateAll

-- * User types
, User (..)
, UserId

-- * Repository types
, Repository (..)
, RepositoryId

-- * SessionKey types
, SessionKey (..)

-- * Associated types for Unique: 'UniqueUsername', 'UniqueUserRepo'
, Persistent.Unique (..)
, Sqlite.PersistEntity (..)
, Sqlite.EntityField (..)

-- * Convenience function for migration
, insertOrIgnoreUser
, insertOrIgnoreRepository
, runMigrationPool
) where

import ClassyPrelude hiding (onException)

import qualified Database.Persist as Persistent
import qualified Database.Persist.SqlBackend.Internal as SqlBackend
import qualified Database.Persist.Sqlite as Sqlite
import qualified Database.Persist.TH as Persistent

#if __GLASGOW_HASKELL__ >= 904
import Data.Type.Equality (type (~))
#endif
import Database.Persist.Sql (ConnectionPool, SqlBackend)
import Database.Persist.TH (mkMigrate, mkPersist, persistLowerCase)
import Yesod (sqlSettings)
import Yesod.Auth.HashDB (HashDBUser (..))

Persistent.share
[mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
User
username Text
password Text

UniqueUsername username

Repository
name Text
summary Text Maybe
user UserId
private Bool

UniqueUserRepo name user

SessionKey
value ByteString
|]

insertOrIgnoreUser
:: ( Persistent.BaseBackend backend ~ SqlBackend
, MonadIO m
, Persistent.PersistUniqueRead backend
, Persistent.PersistStoreWrite backend
)
=> User
-> ReaderT backend m (Persistent.Key User)
insertOrIgnoreUser user@User{userUsername} =
Persistent.getBy (UniqueUsername userUsername) >>= \case
Nothing -> Persistent.insert user
Just x -> pure $ Persistent.entityKey x

insertOrIgnoreRepository
:: ( Persistent.BaseBackend backend ~ SqlBackend
, MonadIO m
, Persistent.PersistUniqueRead backend
, Persistent.PersistStoreWrite backend
)
=> Repository
-> ReaderT backend m (Persistent.Key Repository)
insertOrIgnoreRepository repo@Repository{repositoryName, repositoryUser} =
Persistent.getBy (UniqueUserRepo repositoryName repositoryUser) >>= \case
Nothing -> Persistent.insert repo
Just x -> pure $ Persistent.entityKey x

-- | Runs a migration action on a pool. Exactly like 'runSqlPool', but it will
-- disable foreign keys if running on a sqlite database per the recommendation
-- in the <https://sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
-- relevant sqlite documentation>.
--
-- Workaround for <https://github.com/yesodweb/persistent/issues/1125>
-- Taken from https://github.com/yesodweb/persistent/issues/1125
runMigrationPool
:: forall m a
. (MonadUnliftIO m)
=> ReaderT SqlBackend m a
-> ConnectionPool
-> m a
runMigrationPool r pconn =
Sqlite.runSqlPoolWithHooks r pconn Nothing before after onException
where
before conn = do
let sqlBackend = Sqlite.projectBackend conn
let getter = Sqlite.getStmtConn sqlBackend
whenSqlite conn $ Sqlite.rawExecute "PRAGMA foreign_keys=OFF" []
liftIO $ SqlBackend.connBegin sqlBackend getter Nothing
after conn = do
let sqlBackend = Sqlite.projectBackend conn
let getter = Sqlite.getStmtConn sqlBackend
whenSqlite conn $ Sqlite.rawExecute "PRAGMA foreign_keys=ON" []
liftIO $ SqlBackend.connCommit sqlBackend getter
onException conn _ = do
let sqlBackend = Sqlite.projectBackend conn
let getter = Sqlite.getStmtConn sqlBackend
liftIO $ SqlBackend.connRollback sqlBackend getter

whenSqlite conn act | SqlBackend.connRDBMS conn == "sqlite" = runReaderT `flip` conn $ act
whenSqlite _ _ = pure ()

instance HashDBUser User where
userPasswordHash = Just . userPassword
setPasswordHash h u = u{userPassword = h}