diff --git a/scaffold.hs b/scaffold.hs index 6bd636a6..a1d68b6a 100644 --- a/scaffold.hs +++ b/scaffold.hs @@ -4,6 +4,8 @@ import System.IO import System.Directory import qualified Data.ByteString.Char8 as S import Language.Haskell.TH.Syntax +import Data.Time (getCurrentTime, utctDay, toGregorian) +import Control.Applicative ((<$>)) main :: IO () main = do @@ -37,6 +39,9 @@ main = do putStrLn "That's it! I'm creating your files now..." + let fst3 (x, _, _) = x + year <- show . fst3 . toGregorian . utctDay <$> getCurrentTime + let writeFile' fp s = do putStrLn $ "Generating " ++ fp writeFile (dir ++ '/' : fp) s diff --git a/scaffold/Controller_hs.cg b/scaffold/Controller_hs.cg index b0ef7f7d..35ff7506 100644 --- a/scaffold/Controller_hs.cg +++ b/scaffold/Controller_hs.cg @@ -10,16 +10,26 @@ import Yesod.Helpers.Static import Yesod.Helpers.Auth import Database.Persist.GenericSql +-- Import all relevant handler modules here. import Handler.Root +-- This line actually creates our YesodSite instance. It is the second half +-- of the call to mkYesodData which occurs in ~sitearg~.hs. Please see +-- the comments there for more details. mkYesodDispatch "~sitearg~" resources~sitearg~ +-- Some default handlers that ship with the Yesod site template. You will +-- very rarely need to modify this. getFaviconR :: Handler () getFaviconR = sendFile "image/x-icon" "favicon.ico" getRobotsR :: Handler RepPlain getRobotsR = return $ RepPlain $ toContent "User-agent: *" +-- This function allocates resources (such as a database connection pool), +-- performs initialization and creates a WAI application. This is also the +-- place to put your migrate statements to have automatic database +-- migrations handled by Yesod. with~sitearg~ :: (Application -> IO a) -> IO a with~sitearg~ f = Settings.withConnectionPool $ \p -> do flip runConnectionPool p $ runMigration $ do diff --git a/scaffold/LICENSE.cg b/scaffold/LICENSE.cg index 049c97b2..7830a89e 100644 --- a/scaffold/LICENSE.cg +++ b/scaffold/LICENSE.cg @@ -1,7 +1,7 @@ The following license covers this documentation, and the source code, except where otherwise indicated. -Copyright 2010, ~name~. All rights reserved. +Copyright ~year~, ~name~. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/scaffold/Model_hs.cg b/scaffold/Model_hs.cg index e7c25078..3602e7ce 100644 --- a/scaffold/Model_hs.cg +++ b/scaffold/Model_hs.cg @@ -3,6 +3,9 @@ module Model where import Yesod +-- You can define all of your database entities here. You can find more +-- information on persistent and how to declare entities at: +-- http://docs.yesodweb.com/book/persistent/ mkPersist [$persist| User ident String @@ -13,5 +16,5 @@ Email user UserId null update verkey String null update UniqueEmail email -|~~] +|] diff --git a/scaffold/Root_hs.cg b/scaffold/Root_hs.cg index 88b16022..d442d350 100644 --- a/scaffold/Root_hs.cg +++ b/scaffold/Root_hs.cg @@ -3,6 +3,13 @@ module Handler.Root where import ~sitearg~ +-- This is a handler function for the GET request method on the RootR +-- resource pattern. All of your resource patterns are defined in +-- ~sitearg~.hs; look for the line beginning with mkYesodData. +-- +-- The majority of the code you will write in Yesod lives in these handler +-- functions. You can spread them across multiple files if you are so +-- inclined, or create a single monolithic file. getRootR :: Handler RepHtml getRootR = do mu <- maybeAuth diff --git a/scaffold/Settings_hs.cg b/scaffold/Settings_hs.cg index 24aab568..64cce2cf 100644 --- a/scaffold/Settings_hs.cg +++ b/scaffold/Settings_hs.cg @@ -1,4 +1,9 @@ {-# LANGUAGE CPP #-} +-- | Settings are centralized, as much as possible, into this file. This +-- includes database connection settings, static file locations, etc. +-- In addition, you can configure a number of different aspects of Yesod +-- by overriding methods in the Yesod typeclass. That instance is +-- declared in the ~sitearg~.hs file. module Settings ( hamletFile , cassiusFile @@ -19,6 +24,76 @@ import Language.Haskell.TH.Syntax import Database.Persist.~upper~ import Yesod (MonadCatchIO) +-- | The base URL for your application. This will usually be different for +-- development and production. Yesod automatically constructs URLs for you, +-- so this value must be accurate to create valid links. +approot :: String +#ifdef PRODUCTION +-- You probably want to change this. If your domain name was "yesod.com", +-- you would probably want it to be: +-- > approot = "http://www.yesod.com" +-- Please note that there is no trailing slash. +approot = "http://localhost:3000" +#else +approot = "http://localhost:3000" +#endif + +-- | The location of static files on your system. This is a file system +-- path. The default value works properly with your scaffolded site. +staticdir :: FilePath +staticdir = "static" + +-- | The base URL for your static files. As you can see by the default +-- value, this can simply be "static" appended to your application root. +-- A powerful optimization can be serving static files from a separate +-- domain name. This allows you to use a web server optimized for static +-- files, more easily set expires and cache values, and avoid possibly +-- costly transference of cookies on static files. For more information, +-- please see: +-- http://code.google.com/speed/page-speed/docs/request.html#ServeFromCookielessDomain +-- +-- If you change the resource pattern for StaticR in ~sitearg~.hs, you will +-- have to make a corresponding change here. +-- +-- To see how this value is used, see urlRenderOverride in ~sitearg~.hs +staticroot :: String +staticroot = approot ++ "/static" + +-- | The database connection string. The meaning of this string is backend- +-- specific. +connStr :: String +#ifdef PRODUCTION +connStr = "~connstr2~" +#else +connStr = "~connstr1~" +#endif + +-- | Your application will keep a connection pool and take connections from +-- there as necessary instead of continually creating new connections. This +-- value gives the maximum number of connections to be open at a given time. +-- If your application requests a connection when all connections are in +-- use, that request will fail. Try to choose a number that will work well +-- with the system resources available to you while providing enough +-- connections for your expected load. +-- +-- Also, connections are returned to the pool as quickly as possible by +-- Yesod to avoid resource exhaustion. A connection is only considered in +-- use while within a call to runDB. +connectionCount :: Int +connectionCount = 10 + +-- The rest of this file contains settings which rarely need changing by a +-- user. + +-- The following three functions are used for calling HTML, CSS and +-- Javascript templates from your Haskell code. During development, +-- the "Debug" versions of these functions are used so that changes to +-- the templates are immediately reflected in an already running +-- application. When making a production compile, the non-debug version +-- is used for increased performance. +-- +-- You can see an example of how to call these functions in Handler/Root.hs + hamletFile :: FilePath -> Q Exp #ifdef PRODUCTION hamletFile x = H.hamletFile $ "hamlet/" ++ x ++ ".hamlet" @@ -40,32 +115,13 @@ juliusFile x = H.juliusFile $ "julius/" ++ x ++ ".julius" juliusFile x = H.juliusFileDebug $ "julius/" ++ x ++ ".julius" #endif -connStr :: String -#ifdef PRODUCTION -connStr = "~connstr2~" -#else -connStr = "~connstr1~" -#endif - -connectionCount :: Int -connectionCount = 10 - +-- The next two functions are for allocating a connection pool and running +-- database actions using a pool, respectively. It is used internally +-- by the scaffolded application, and therefore you will rarely need to use +-- them yourself. withConnectionPool :: MonadCatchIO m => (ConnectionPool -> m a) -> m a withConnectionPool = with~upper~Pool connStr connectionCount runConnectionPool :: MonadCatchIO m => SqlPersist m a -> ConnectionPool -> m a runConnectionPool = runSqlPool -approot :: String -#ifdef PRODUCTION -approot = "http://localhost:3000" -#else -approot = "http://localhost:3000" -#endif - -staticroot :: String -staticroot = approot ++ "/static" - -staticdir :: FilePath -staticdir = "static" - diff --git a/scaffold/cabal.cg b/scaffold/cabal.cg index 452ae3db..b9ee9e41 100644 --- a/scaffold/cabal.cg +++ b/scaffold/cabal.cg @@ -10,7 +10,7 @@ category: Web stability: Experimental cabal-version: >= 1.6 build-type: Simple -homepage: http://www.yesodweb.com/~project~ +homepage: http://~project~.yesodweb.com/ Flag production Description: Build the production executable. @@ -35,8 +35,9 @@ executable simple-server executable devel-server if flag(production) Buildable: False + else + build-depends: wai-handler-devel >= 0.1.0 && < 0.2 main-is: devel-server.hs - build-depends: wai-handler-devel >= 0.1.0 && < 0.2 ghc-options: -Wall -O2 executable fastcgi diff --git a/scaffold/devel-server_hs.cg b/scaffold/devel-server_hs.cg index 0d118d7a..8c32765b 100644 --- a/scaffold/devel-server_hs.cg +++ b/scaffold/devel-server_hs.cg @@ -9,7 +9,7 @@ main = do , "You can view your app at http://localhost:3000/" , "" ] - _ <- forkIO () run 3000 "Controller" "with~sitearg~" + _ <- forkIO $ run 3000 "Controller" "with~sitearg~" [ "hamlet" , "cassius" , "julius" @@ -21,3 +21,4 @@ main = do case x of 'q':_ -> putStrLn "Quitting, goodbye!" _ -> go + diff --git a/scaffold/sitearg_hs.cg b/scaffold/sitearg_hs.cg index 3c7530a5..3dbecd70 100644 --- a/scaffold/sitearg_hs.cg +++ b/scaffold/sitearg_hs.cg @@ -27,13 +27,38 @@ import Model import Control.Monad (join) import Data.Maybe (isJust) +-- | The site argument 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 ~sitearg~ = ~sitearg~ - { getStatic :: Static - , connPool :: Settings.ConnectionPool + { getStatic :: Static -- ^ Settings for static file serving. + , connPool :: Settings.ConnectionPool -- ^ Database connection pool. } +-- | A useful synonym; most of the handler functions in your application +-- will need to be of this type. type Handler = GHandler ~sitearg~ ~sitearg~ +-- This is where we define all of the routes in our application. For a full +-- explanation of the syntax, please see: +-- http://docs.yesodweb.com/book/web-routes-quasi/ +-- +-- This function does three things: +-- +-- * Creates the route datatype ~sitearg~Route. Every valid URL in your +-- application can be represented as a value of this type. +-- * Creates the associated type: +-- type instance Route ~sitearg~ = ~sitearg~Route +-- * Creates the value resources~sitearg~ which contains information on the +-- resources declared below. This is used in Controller.hs by the call to +-- mkYesodDispatch +-- +-- What this function does *not* do is create a YesodSite instance for +-- ~sitearg~. Creating that instance requires all of the handler functions +-- for our application to be in scope. However, the handler functions +-- usually require access to the ~sitearg~Route datatype. Therefore, we +-- split these actions into two functions and place them in separate files. mkYesodData "~sitearg~" [$parseRoutes| /static StaticR Static getStatic /auth AuthR Auth getAuth @@ -42,16 +67,22 @@ mkYesodData "~sitearg~" [$parseRoutes| /robots.txt RobotsR GET / RootR GET -|~~] +|] +-- Please see the documentation for the Yesod typeclass. There are a number +-- of settings which can be configured by overriding methods here. instance Yesod ~sitearg~ where approot _ = Settings.approot + defaultLayout widget = do mmsg <- getMessage pc <- widgetToPageContent $ do widget addStyle $(Settings.cassiusFile "default-layout") hamletToRepHtml $(Settings.hamletFile "default-layout") + + -- This is done to provide an optimization for serving static files from + -- a separate domain. Please see the staticroot setting in Settings.hs urlRenderOverride a (StaticR s) = Just $ uncurry (joinPath a Settings.staticroot) $ format s where @@ -59,7 +90,14 @@ instance Yesod ~sitearg~ where ss :: Site StaticRoute (String -> Maybe (GHandler Static ~sitearg~ ChooseRep)) ss = getSubSite urlRenderOverride _ _ = Nothing + + -- The page to be redirected to when authentication is required. authRoute _ = Just $ AuthR LoginR + + -- 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' _ content = do let fn = base64md5 content ++ '.' : ext' let statictmp = Settings.staticdir ++ "/tmp/" @@ -67,6 +105,7 @@ instance Yesod ~sitearg~ where liftIO $ L.writeFile (statictmp ++ fn) content return $ Just $ Right (StaticR $ StaticRoute ["tmp", fn] [], []) +-- How to run database actions. instance YesodPersist ~sitearg~ where type YesodDB ~sitearg~ = SqlPersist runDB db = fmap connPool getYesod >>= Settings.runConnectionPool db