Add email sending capability to Vervis
This commit is contained in:
parent
c6d49da143
commit
c2d1bb444b
7 changed files with 188 additions and 8 deletions
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2018 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
-
|
-
|
||||||
- ♡ Copying is an act of love. Please copy, reuse and share.
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
-
|
-
|
||||||
|
@ -65,6 +65,7 @@ import Vervis.Handler.Ticket
|
||||||
import Vervis.Handler.Wiki
|
import Vervis.Handler.Wiki
|
||||||
import Vervis.Handler.Workflow
|
import Vervis.Handler.Workflow
|
||||||
|
|
||||||
|
import Vervis.Mail (runMailer)
|
||||||
import Vervis.Migration (migrateDB)
|
import Vervis.Migration (migrateDB)
|
||||||
import Vervis.Ssh (runSsh)
|
import Vervis.Ssh (runSsh)
|
||||||
|
|
||||||
|
@ -90,6 +91,11 @@ makeFoundation appSettings = do
|
||||||
(if appMutableStatic appSettings then staticDevel else static)
|
(if appMutableStatic appSettings then staticDevel else static)
|
||||||
(appStaticDir appSettings)
|
(appStaticDir appSettings)
|
||||||
|
|
||||||
|
appMailQueue <-
|
||||||
|
case appSmtp appSettings of
|
||||||
|
Nothing -> return Nothing
|
||||||
|
Just _ -> Just <$> newChan
|
||||||
|
|
||||||
-- We need a log function to create a connection pool. We need a connection
|
-- We need a log function to create a connection pool. We need a connection
|
||||||
-- pool to create our foundation. And we need our foundation to get a
|
-- pool to create our foundation. And we need our foundation to get a
|
||||||
-- logging function. To get out of this loop, we initially create a
|
-- logging function. To get out of this loop, we initially create a
|
||||||
|
@ -174,6 +180,19 @@ sshServer foundation =
|
||||||
(appConnPool foundation)
|
(appConnPool foundation)
|
||||||
(loggingFunction foundation)
|
(loggingFunction foundation)
|
||||||
|
|
||||||
|
mailer :: App -> IO ()
|
||||||
|
mailer foundation =
|
||||||
|
case (appSmtp $ appSettings foundation, appMailQueue foundation) of
|
||||||
|
(Nothing , Nothing) -> return ()
|
||||||
|
(Nothing , Just _) -> error "Mail queue unnecessarily created"
|
||||||
|
(Just _ , Nothing) -> error "Mail queue wasn't created"
|
||||||
|
(Just smtp, Just queue) ->
|
||||||
|
runMailer
|
||||||
|
smtp
|
||||||
|
(appConnPool foundation)
|
||||||
|
(loggingFunction foundation)
|
||||||
|
(readChan queue)
|
||||||
|
|
||||||
-- | The @main@ function for an executable running this site.
|
-- | The @main@ function for an executable running this site.
|
||||||
appMain :: IO ()
|
appMain :: IO ()
|
||||||
appMain = do
|
appMain = do
|
||||||
|
@ -192,9 +211,12 @@ appMain = do
|
||||||
-- Generate a WAI Application from the foundation
|
-- Generate a WAI Application from the foundation
|
||||||
app <- makeApplication foundation
|
app <- makeApplication foundation
|
||||||
|
|
||||||
-- [experimental] Run SSH server and pray
|
-- Run SSH server
|
||||||
forkIO $ sshServer foundation
|
forkIO $ sshServer foundation
|
||||||
|
|
||||||
|
-- Run mailer if SMTP is enabled
|
||||||
|
forkIO $ mailer foundation
|
||||||
|
|
||||||
-- Run the application with Warp
|
-- Run the application with Warp
|
||||||
runSettings (warpSettings foundation) app
|
runSettings (warpSettings foundation) app
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2018 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
-
|
-
|
||||||
- ♡ Copying is an act of love. Please copy, reuse and share.
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
-
|
-
|
||||||
|
@ -33,6 +33,7 @@ import Data.Text as T (pack, intercalate)
|
||||||
|
|
||||||
import Text.Jasmine.Local (discardm)
|
import Text.Jasmine.Local (discardm)
|
||||||
import Vervis.Import.NoFoundation hiding (last)
|
import Vervis.Import.NoFoundation hiding (last)
|
||||||
|
import Vervis.Mail.Types
|
||||||
import Vervis.Model.Group
|
import Vervis.Model.Group
|
||||||
import Vervis.Model.Ident
|
import Vervis.Model.Ident
|
||||||
import Vervis.Model.Role
|
import Vervis.Model.Role
|
||||||
|
@ -49,6 +50,7 @@ data App = App
|
||||||
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
||||||
, appHttpManager :: Manager
|
, appHttpManager :: Manager
|
||||||
, appLogger :: Logger
|
, appLogger :: Logger
|
||||||
|
, appMailQueue :: Maybe (Chan MailMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
-- This is where we define all of the routes in our application. For a full
|
-- This is where we define all of the routes in our application. For a full
|
||||||
|
|
99
src/Vervis/Mail.hs
Normal file
99
src/Vervis/Mail.hs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2018 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | This modules provides email support for Vervis. It allows handler code to
|
||||||
|
-- send email messages, synchronously (i.e. instantly in the same thread) and
|
||||||
|
-- asynchronously (i.e. pass the work to a separate thread, so that the user
|
||||||
|
-- can have their HTTP response without waiting for the mail to be sent).
|
||||||
|
module Vervis.Mail
|
||||||
|
( sendMail
|
||||||
|
, submitMail
|
||||||
|
, runMailer
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Concurrent.Chan (writeChan)
|
||||||
|
import Control.Monad (forever)
|
||||||
|
import Control.Monad.IO.Class (liftIO)
|
||||||
|
import Control.Monad.Logger
|
||||||
|
import Control.Monad.Trans.Class (lift)
|
||||||
|
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Database.Persist
|
||||||
|
import Database.Persist.Sql
|
||||||
|
import Network.Mail.Mime (Mail, emptyMail)
|
||||||
|
import Network.Mail.SMTP hiding (sendMail)
|
||||||
|
import Yesod.Core.Handler (getsYesod)
|
||||||
|
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Mail.Types
|
||||||
|
import Vervis.Settings
|
||||||
|
|
||||||
|
type Mailer = LoggingT (ReaderT ConnectionPool IO)
|
||||||
|
type MailerDB = SqlPersistT Mailer
|
||||||
|
|
||||||
|
src :: Text
|
||||||
|
src = "Mail"
|
||||||
|
|
||||||
|
runMailerDB :: MailerDB a -> Mailer a
|
||||||
|
runMailerDB action = do
|
||||||
|
pool <- lift ask
|
||||||
|
runSqlPool action pool
|
||||||
|
|
||||||
|
formatMessage :: MailMessage -> Mail
|
||||||
|
formatMessage MailMessage = emptyMail $ Address Nothing "vervis"
|
||||||
|
|
||||||
|
smtp :: SmtpSettings -> Mail -> IO ()
|
||||||
|
smtp (SmtpSettings mlogin host port) =
|
||||||
|
case mlogin of
|
||||||
|
Nothing -> sendMail' host port
|
||||||
|
Just (SmtpLogin user pass) -> sendMailWithLogin' host port user pass
|
||||||
|
|
||||||
|
send :: SmtpSettings -> MailMessage -> IO ()
|
||||||
|
send settings = smtp settings . formatMessage
|
||||||
|
|
||||||
|
-- | Send an email message through an SMTP server and return once it's sent.
|
||||||
|
-- Returns 'True' if sent, 'False' if email is disabled in settings.
|
||||||
|
sendMail :: MailMessage -> Handler Bool
|
||||||
|
sendMail msg = do
|
||||||
|
msettings <- getsYesod $ appSmtp . appSettings
|
||||||
|
case msettings of
|
||||||
|
Nothing -> return False
|
||||||
|
Just settings -> liftIO $ send settings msg >> return True
|
||||||
|
|
||||||
|
-- | Submit an email message into the queue for delivery through an SMTP
|
||||||
|
-- server, and return without waiting for it to be sent. Returns 'True' if
|
||||||
|
-- submitted, 'False' if email is disabled in settings.
|
||||||
|
submitMail :: MailMessage -> Handler Bool
|
||||||
|
submitMail msg = do
|
||||||
|
mchan <- getsYesod appMailQueue
|
||||||
|
case mchan of
|
||||||
|
Nothing -> return False
|
||||||
|
Just chan -> liftIO $ writeChan chan msg >> return True
|
||||||
|
|
||||||
|
-- | Run mailer loop which reads messages from a queue and sends them to SMTP
|
||||||
|
-- server.
|
||||||
|
runMailer
|
||||||
|
:: SmtpSettings -- ^ Details of SMTP server
|
||||||
|
-> ConnectionPool -- ^ DB connection pool for DB access
|
||||||
|
-> LogFunc -- ^ What to do with log messages
|
||||||
|
-> IO MailMessage -- ^ IO action that reads a message for sending
|
||||||
|
-> IO ()
|
||||||
|
runMailer settings pool logFunc readMail =
|
||||||
|
flip runReaderT pool $ flip runLoggingT logFunc $ do
|
||||||
|
$logInfoS src "Mailer component starting"
|
||||||
|
forever $ liftIO $ readMail >>= send settings
|
23
src/Vervis/Mail/Types.hs
Normal file
23
src/Vervis/Mail/Types.hs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2018 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | These types were moved here from Vervis.Mail to avoid Vervis.Mail and
|
||||||
|
-- Vervis.Foundation importing each other.
|
||||||
|
module Vervis.Mail.Types
|
||||||
|
( MailMessage (..)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
data MailMessage = MailMessage
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2018 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
-
|
-
|
||||||
- ♡ Copying is an act of love. Please copy, reuse and share.
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
-
|
-
|
||||||
|
@ -34,11 +34,36 @@ import Data.FileEmbed (embedFile)
|
||||||
import Data.Yaml (decodeEither')
|
import Data.Yaml (decodeEither')
|
||||||
import Database.Persist.Postgresql (PostgresConf)
|
import Database.Persist.Postgresql (PostgresConf)
|
||||||
import Language.Haskell.TH.Syntax (Exp, Name, Q)
|
import Language.Haskell.TH.Syntax (Exp, Name, Q)
|
||||||
|
import Network.Socket (HostName, PortNumber)
|
||||||
import Network.Wai.Handler.Warp (HostPreference)
|
import Network.Wai.Handler.Warp (HostPreference)
|
||||||
import Yesod.Default.Config2 (applyEnvValue, configSettingsYml)
|
import Yesod.Default.Config2 (applyEnvValue, configSettingsYml)
|
||||||
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
|
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload,
|
||||||
widgetFileReload)
|
widgetFileReload)
|
||||||
|
|
||||||
|
data SmtpLogin = SmtpLogin
|
||||||
|
{ smtpUser :: String
|
||||||
|
, smtpPassword :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
instance FromJSON SmtpLogin where
|
||||||
|
parseJSON = withObject "SmtpLogin" $ \ o ->
|
||||||
|
SmtpLogin
|
||||||
|
<$> o .: "user"
|
||||||
|
<*> o .: "password"
|
||||||
|
|
||||||
|
data SmtpSettings = SmtpSettings
|
||||||
|
{ smtpLogin :: Maybe SmtpLogin
|
||||||
|
, smtpHost :: HostName
|
||||||
|
, smtpPort :: PortNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
instance FromJSON SmtpSettings where
|
||||||
|
parseJSON = withObject "SmtpSettings" $ \ o ->
|
||||||
|
SmtpSettings
|
||||||
|
<$> o .:? "login"
|
||||||
|
<*> o .: "host"
|
||||||
|
<*> (fromInteger <$> o .: "port")
|
||||||
|
|
||||||
-- | Runtime settings to configure this application. These settings can be
|
-- | Runtime settings to configure this application. These settings can be
|
||||||
-- loaded from various sources: defaults, environment variables, config files,
|
-- loaded from various sources: defaults, environment variables, config files,
|
||||||
-- theoretically even a database.
|
-- theoretically even a database.
|
||||||
|
@ -79,6 +104,9 @@ data AppSettings = AppSettings
|
||||||
, appRegister :: Bool
|
, appRegister :: Bool
|
||||||
-- | The maximal number of user accounts that can be registered.
|
-- | The maximal number of user accounts that can be registered.
|
||||||
, appAccounts :: Maybe Int
|
, appAccounts :: Maybe Int
|
||||||
|
-- | SMTP server details for sending email. If set to 'Nothing', no email
|
||||||
|
-- will be sent.
|
||||||
|
, appSmtp :: Maybe SmtpSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON AppSettings where
|
instance FromJSON AppSettings where
|
||||||
|
@ -107,6 +135,7 @@ instance FromJSON AppSettings where
|
||||||
appSshKeyFile <- o .: "ssh-key-file"
|
appSshKeyFile <- o .: "ssh-key-file"
|
||||||
appRegister <- o .: "registration"
|
appRegister <- o .: "registration"
|
||||||
appAccounts <- o .: "max-accounts"
|
appAccounts <- o .: "max-accounts"
|
||||||
|
appSmtp <- o .:? "smtp"
|
||||||
|
|
||||||
return AppSettings {..}
|
return AppSettings {..}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ resolver: lts-6.5
|
||||||
# Local packages, usually specified by relative directory name
|
# Local packages, usually specified by relative directory name
|
||||||
packages:
|
packages:
|
||||||
- '.'
|
- '.'
|
||||||
- '../../../other-work/ssh'
|
- '../ssh'
|
||||||
- '../hit-graph'
|
- '../hit-graph'
|
||||||
- '../hit-harder'
|
- '../hit-harder'
|
||||||
- '../hit-network'
|
- '../hit-network'
|
||||||
|
@ -18,9 +18,6 @@ packages:
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- diagrams-svg-1.4.0.2
|
- diagrams-svg-1.4.0.2
|
||||||
- highlighter2-0.2.5
|
- highlighter2-0.2.5
|
||||||
- hit-graph-0.1
|
|
||||||
- hit-harder-0.1
|
|
||||||
- hit-network-0.1
|
|
||||||
- libravatar-0.4
|
- libravatar-0.4
|
||||||
- monad-hash-0.1
|
- monad-hash-0.1
|
||||||
# for 'tuple' package, remove once I use lenses instead
|
# for 'tuple' package, remove once I use lenses instead
|
||||||
|
@ -28,6 +25,9 @@ extra-deps:
|
||||||
- SimpleAES-0.4.2
|
- SimpleAES-0.4.2
|
||||||
# for text drawing with 'diagrams'
|
# for text drawing with 'diagrams'
|
||||||
- SVGFonts-1.5.0.1
|
- SVGFonts-1.5.0.1
|
||||||
|
- tagged-0.8.5
|
||||||
|
- transformers-0.4.3.0
|
||||||
|
- transformers-compat-0.5.1.4
|
||||||
# remove once I use lenses instead
|
# remove once I use lenses instead
|
||||||
- tuple-0.3.0.2
|
- tuple-0.3.0.2
|
||||||
# - ssh-0.3.2
|
# - ssh-0.3.2
|
||||||
|
|
|
@ -145,6 +145,8 @@ library
|
||||||
Vervis.Handler.Workflow
|
Vervis.Handler.Workflow
|
||||||
Vervis.Import
|
Vervis.Import
|
||||||
Vervis.Import.NoFoundation
|
Vervis.Import.NoFoundation
|
||||||
|
Vervis.Mail
|
||||||
|
Vervis.Mail.Types
|
||||||
Vervis.MediaType
|
Vervis.MediaType
|
||||||
Vervis.Migration
|
Vervis.Migration
|
||||||
Vervis.Model
|
Vervis.Model
|
||||||
|
@ -262,10 +264,12 @@ library
|
||||||
, libravatar
|
, libravatar
|
||||||
-- for converting Darcs patch hash Digest to ByteString
|
-- for converting Darcs patch hash Digest to ByteString
|
||||||
, memory
|
, memory
|
||||||
|
, mime-mail
|
||||||
, monad-control
|
, monad-control
|
||||||
, monad-logger
|
, monad-logger
|
||||||
-- for Database.Persist.Local
|
-- for Database.Persist.Local
|
||||||
, mtl
|
, mtl
|
||||||
|
, network
|
||||||
, pandoc
|
, pandoc
|
||||||
, pandoc-types
|
, pandoc-types
|
||||||
-- for PathPiece instance for CI, Web.PathPieces.Local
|
-- for PathPiece instance for CI, Web.PathPieces.Local
|
||||||
|
@ -278,6 +282,7 @@ library
|
||||||
, resourcet
|
, resourcet
|
||||||
, safe
|
, safe
|
||||||
, shakespeare
|
, shakespeare
|
||||||
|
, smtp-mail
|
||||||
, ssh
|
, ssh
|
||||||
-- for rendering diagrams
|
-- for rendering diagrams
|
||||||
, svg-builder
|
, svg-builder
|
||||||
|
|
Loading…
Reference in a new issue