yammat/Foundation.hs
2017-06-03 02:37:22 +02:00

286 lines
10 KiB
Haskell

-- yammat - Yet Another MateMAT
-- Copyright (C) 2015 Amedeo Molnár
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module Foundation where
import Import.NoFoundation hiding (tail)
import Database.Persist.Sql (ConnectionPool, runSqlPool)
import Text.Hamlet (hamletFile)
import Text.Jasmine (minifym)
import Yesod.Default.Util (addStaticContentExternal)
import Yesod.Core.Types (Logger)
--snip
import Yesod.Auth.Hardcoded
import Yesod.Auth.Message
import qualified Data.Text as T
import Network.Wai as Wai
import Data.List (tail)
import Data.List.Split
import Text.Printf
prependZero :: Text -> Text
prependZero t0
| T.null t1 = t1
| T.head t1 == '.' = '0' `T.cons` t1
| "-." `T.isPrefixOf` t1 = "-0." `T.append` T.drop 2 t1
| otherwise = t1
where t1 = T.dropWhile (' ' ==) t0
formatFloat :: Double -> Text
formatFloat d = T.pack (pre ++ t ++ c)
where
t = reverse (intercalate "." $ chunksOf 3 $ reverse $ fst sp)
c = "," ++ tail (snd sp)
sp = break (== '.') (printf "%.2f" (abs d))
pre = if d < 0
then "-"
else ""
-- T.pack . (splitEvery 3) . (printf "%,2f")
formatIntCurrency :: Int -> Text
formatIntCurrency x = formatFloat $ fromIntegral x / 100
-- | The foundation datatype for your application. This can be a good place to
-- keep settings and values requiring initialization before your application
-- starts running, such as database connections. Every handler will have
-- access to the data present here.
data App = App
{ appSettings :: AppSettings
, appStatic :: Static -- ^ Settings for static file serving.
, appConnPool :: ConnectionPool -- ^ Database connection pool.
, appHttpManager :: Manager
, appLogger :: Logger
}
instance HasHttpManager App where
getHttpManager = appHttpManager
mkMessage "App" "messages" "de"
renderMessage' :: (RenderMessage (HandlerSite m) message, MonadHandler m) => message -> m Text
renderMessage' e = do
m <- getYesod
l <- languages
return $ renderMessage m l e
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
-- http://www.yesodweb.com/book/routing-and-handlers
--
-- Note that this is really half the story; in Application.hs, mkYesodDispatch
-- generates the rest of the code. Please see the linked documentation for an
-- explanation for this split.
mkYesodData "App" $(parseRoutesFile "config/routes")
-- | A convenient synonym for creating forms.
type Form x = Html -> MForm (HandlerT App IO) (FormResult x, Widget)
approotRequest :: App -> Wai.Request -> T.Text
approotRequest master req =
case requestHeaderHost req of
Just a -> prefix `T.append` decodeUtf8 a
Nothing -> appRoot $ appSettings master
where
prefix =
if isSecure req
then "https://"
else "http://"
-- Please see the documentation for the Yesod typeclass. There are a number
-- of settings which can be configured by overriding methods here.
instance Yesod App where
-- Controls the base of generated URLs. For more information on modifying,
-- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot
--approot = ApprootMaster $ appRoot . appSettings
approot = ApprootRequest approotRequest
-- Store session data on the client in encrypted cookies,
-- default session idle timeout is 120 minutes
makeSessionBackend _ = Just <$> defaultClientSessionBackend
120 -- timeout in minutes
"config/client_session_key.aes"
defaultLayout widget = do
master <- getYesod
mmsg <- getMessage
musername <- maybeAuthId
-- We break up the default layout into two components:
-- default-layout is the contents of the body tag, and
-- default-layout-wrapper is the entire page. Since the final
-- value passed to hamletToRepHtml cannot be a widget, this allows
-- you to use normal widget features in default-layout.
copyrightWidget <- widgetToPageContent $
$(widgetFile "copyright")
pc <- widgetToPageContent $ do
mapM_ addStylesheet $ map StaticR
[ css_bootstrap_min_css
, css_main_css
]
mapM_ addScript $ map StaticR
[ js_crementing_js
-- , js_barcode_js
]
$(widgetFile "default-layout")
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
-- The page to be redirected to when authentication is required.
-- authRoute _ = Just $ AuthR LoginR
-- Routes not requiring authentication.
isAuthorized (AuthR _) _ = return Authorized
isAuthorized FaviconR _ = return Authorized
isAuthorized RobotsR _ = return Authorized
-- Routes requiring authentication
isAuthorized RestockR _ = isAdmin
isAuthorized (UpstockR _) _ = isAdmin
isAuthorized NewArticleR _ = isAdmin
isAuthorized JournalR _ = isAdmin
isAuthorized (JournalPageR _) _ = isAdmin
isAuthorized PayoutR _ = isAdmin
isAuthorized SummaryR _ = isAdmin
isAuthorized SummaryJsonR _ = isAdmin
isAuthorized (ModifyR _) _ = isAdmin
isAuthorized (DeleteBeverageR _) _ = isAdmin
isAuthorized CashCheckR _ = isAdmin
isAuthorized UploadInventoryJsonR _ = isAdmin
isAuthorized InventoryJsonR _ = isAdmin
isAuthorized AvatarR _ = isAdmin
isAuthorized (ModifyAvatarR _) _ = isAdmin
isAuthorized (AvatarDeleteR _) _ = isAdmin
isAuthorized SupplierR _ = isAdmin
isAuthorized NewSupplierR _ = isAdmin
isAuthorized (SupplierActionsR _) _ = isAdmin
isAuthorized (ModifySupplierR _) _ = isAdmin
isAuthorized (SupplierDigestR _) _ = isAdmin
isAuthorized (DeleteSupplierR _) _ = isAdmin
-- Default to Authorized for now.
isAuthorized _ _ = return Authorized
-- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows
-- expiration dates to be set far in the future without worry of
-- users receiving stale content.
addStaticContent ext mime content = do
master <- getYesod
let staticDir = appStaticDir $ appSettings master
addStaticContentExternal
minifym
genFileName
staticDir
(StaticR . flip StaticRoute [])
ext
mime
content
where
-- Generate a unique filename based on the content itself
genFileName lbs = "autogen-" ++ base64md5 lbs
-- What messages should be logged. The following includes all messages when
-- in development, and warnings and errors in production.
shouldLog app _source level =
appShouldLogAll (appSettings app)
|| level == LevelWarn
|| level == LevelError
makeLogger = return . appLogger
isAdmin :: YesodAuth master => HandlerT master IO AuthResult
isAdmin = do
mu <- maybeAuthId
return $ case mu of
Nothing -> AuthenticationRequired
Just _ -> Authorized
-- How to run database actions.
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
master <- getYesod
runSqlPool action $ appConnPool master
instance YesodPersistRunner App where
getDBRunner = defaultGetDBRunner appConnPool
instance YesodAuth App where
type AuthId App = Text
-- Where to send a user after successful login
loginDest _ = HomeR
-- Where to send a user after logout
logoutDest _ = HomeR
-- Override the above two destinations when a Referer: header is present
redirectToReferer _ = True
-- getAuthId = return . Just . credsIdent
-- getAuthId creds = runDB $ do
-- x <- getBy $ UniqueUser $ credsIdent creds
-- case x of
-- Just (Entity uid _) -> return $ Just uid
-- Nothing -> do
-- fmap Just $ insert User
-- { userIdent = credsIdent creds
-- , userPassword = Nothing
-- }
authenticate Creds{..} = do
settings <- getsYesod appSettings
maybe (return $ Authenticated "nobody") (\login ->
if loginName login == credsIdent
then return (Authenticated credsIdent)
else return (UserError InvalidLogin)
) (appAdminCreds settings)
-- You can add other plugins like BrowserID, email or OAuth here
authPlugins _ = [authHardcoded]
authHttpManager = getHttpManager
maybeAuthId = do
settings <- getsYesod appSettings
maybe (return $ Just "nobody")
(const $ lookupSession credsKey)
(appAdminCreds settings)
instance YesodAuthHardcoded App where
validatePassword u p = do
settings <- getsYesod appSettings
maybe (return True) (\login ->
return (u == loginName login && p == loginPass login)
) (appAdminCreds settings)
doesUserNameExist u = do
settings <- getsYesod appSettings
let name = fromMaybe "nobody" $ loginName <$> appAdminCreds settings
return (u == name)
-- instance YesodAuthPersist App
-- This instance is required to use forms. You can modify renderMessage to
-- achieve customized and internationalized form validation messages.
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
-- Note: Some functionality previously present in the scaffolding has been
-- moved to documentation in the Wiki. Following are some hopefully helpful
-- links:
--
-- https://github.com/yesodweb/yesod/wiki/Sending-email
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding