Make initial homepage with table and simple login
This commit is contained in:
parent
3da488b3a2
commit
7857a8a964
12 changed files with 285 additions and 394 deletions
|
@ -12,20 +12,48 @@
|
||||||
-- with this software. If not, see
|
-- with this software. If not, see
|
||||||
-- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
-- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
|
||||||
User
|
IrcChannel
|
||||||
ident Text
|
network Text
|
||||||
password Text Maybe
|
name Text
|
||||||
UniqueUser ident
|
|
||||||
deriving Typeable
|
|
||||||
Email
|
|
||||||
email Text
|
|
||||||
userId UserId Maybe
|
|
||||||
verkey Text Maybe
|
|
||||||
UniqueEmail email
|
|
||||||
Comment json -- Adding "json" causes ToJSON and FromJSON instances to be derived.
|
|
||||||
message Text
|
|
||||||
userId UserId Maybe
|
|
||||||
deriving Eq
|
|
||||||
deriving Show
|
|
||||||
|
|
||||||
-- By default this file is used in Model.hs (which is imported by Foundation.hs)
|
Sharer
|
||||||
|
ident Text --CI
|
||||||
|
name Text Maybe
|
||||||
|
|
||||||
|
UniqueIdent ident
|
||||||
|
|
||||||
|
Person
|
||||||
|
ident SharerId
|
||||||
|
login Text
|
||||||
|
hash Text Maybe
|
||||||
|
email Text Maybe
|
||||||
|
|
||||||
|
UniquePersonIdent ident
|
||||||
|
UniquePersonLogin login
|
||||||
|
|
||||||
|
Group
|
||||||
|
ident SharerId
|
||||||
|
|
||||||
|
UniqueGroupIdent ident
|
||||||
|
|
||||||
|
Project
|
||||||
|
ident Text --CI
|
||||||
|
sharer SharerId
|
||||||
|
name Text Maybe
|
||||||
|
desc Text Maybe
|
||||||
|
|
||||||
|
UniqueProject ident sharer
|
||||||
|
|
||||||
|
Repo
|
||||||
|
ident Text --CI
|
||||||
|
project ProjectId
|
||||||
|
irc IrcChannelId Maybe
|
||||||
|
ml Text Maybe
|
||||||
|
|
||||||
|
UniqueRepo ident project
|
||||||
|
|
||||||
|
PersonInGroup
|
||||||
|
person PersonId
|
||||||
|
group GroupId
|
||||||
|
|
||||||
|
UniquePersonInGroup person group
|
||||||
|
|
|
@ -18,4 +18,4 @@
|
||||||
/favicon.ico FaviconR GET
|
/favicon.ico FaviconR GET
|
||||||
/robots.txt RobotsR GET
|
/robots.txt RobotsR GET
|
||||||
|
|
||||||
/ HomeR GET POST
|
/ HomeR GET
|
||||||
|
|
|
@ -31,11 +31,11 @@ ip-from-header: "_env:IP_FROM_HEADER:false"
|
||||||
# page.
|
# page.
|
||||||
|
|
||||||
database:
|
database:
|
||||||
user: "_env:PGUSER:vervis"
|
user: "_env:PGUSER:vervis_dev"
|
||||||
password: "_env:PGPASS:vervis_password_here"
|
password: "_env:PGPASS:vervis_dev_password"
|
||||||
host: "_env:PGHOST:localhost"
|
host: "_env:PGHOST:localhost"
|
||||||
port: "_env:PGPORT:5432"
|
port: "_env:PGPORT:5432"
|
||||||
database: "_env:PGDATABASE:vervis"
|
database: "_env:PGDATABASE:vervis_dev"
|
||||||
poolsize: "_env:PGPOOLSIZE:10"
|
poolsize: "_env:PGPOOLSIZE:10"
|
||||||
|
|
||||||
copyright: Insert your statement against copyright here
|
copyright: Insert your statement against copyright here
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
{- This file is part of Vervis.
|
|
||||||
-
|
|
||||||
- Written in 2016 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/>.
|
|
||||||
-}
|
|
||||||
|
|
||||||
module Vervis
|
|
||||||
(
|
|
||||||
)
|
|
||||||
where
|
|
|
@ -1,159 +0,0 @@
|
||||||
{- This file is part of Vervis.
|
|
||||||
-
|
|
||||||
- Written in 2016 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/>.
|
|
||||||
-}
|
|
||||||
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
|
|
||||||
module Vervis.Git
|
|
||||||
( subdirs
|
|
||||||
, lastChange
|
|
||||||
, timeAgo
|
|
||||||
--, timesAgo
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
import Control.Monad (join)
|
|
||||||
import Control.Monad.Fix (MonadFix)
|
|
||||||
import Control.Monad.IO.Class
|
|
||||||
import Control.Monad.Trans.RWS (RWST (..))
|
|
||||||
import Data.CaseInsensitive (CI)
|
|
||||||
import Data.Foldable (toList)
|
|
||||||
import Data.Git
|
|
||||||
import Data.Git.Revision
|
|
||||||
import Data.Git.Repository
|
|
||||||
import Data.Hashable (Hashable)
|
|
||||||
import Data.HashMap.Lazy (HashMap)
|
|
||||||
import Data.HashSet (HashSet)
|
|
||||||
import Data.Hourglass
|
|
||||||
import Data.Maybe (fromMaybe, mapMaybe)
|
|
||||||
import Data.Text (Text)
|
|
||||||
import Data.Time.Units
|
|
||||||
import GHC.Generics
|
|
||||||
import System.Directory.Tree hiding (name, file, err)
|
|
||||||
import System.FilePath ((</>))
|
|
||||||
import System.Hourglass (dateCurrent)
|
|
||||||
|
|
||||||
import qualified Control.Monad.Trans.RWS as RWS
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
|
||||||
import qualified Data.HashMap.Lazy as M
|
|
||||||
import qualified Data.Text as T
|
|
||||||
|
|
||||||
{-data Server = Server
|
|
||||||
{ serverName :: Text
|
|
||||||
, serverDir :: FilePath
|
|
||||||
, serverUsers :: HashMap Int User
|
|
||||||
, serverGroups :: HashMap Int Group
|
|
||||||
, serverRepos :: HashMap (Either Int Int) [Repository]
|
|
||||||
}-}
|
|
||||||
|
|
||||||
-- | Return the subdirs of a given dir
|
|
||||||
subdirs :: FilePath -> IO [FilePath]
|
|
||||||
subdirs dir = do
|
|
||||||
_base :/ tree <- buildL dir
|
|
||||||
return $ case tree of
|
|
||||||
Dir _ cs ->
|
|
||||||
let dirName (Dir n _) = Just n
|
|
||||||
dirName _ = Nothing
|
|
||||||
in mapMaybe dirName cs
|
|
||||||
_ -> []
|
|
||||||
|
|
||||||
-- | Determine the time of the last commit in a given git branch
|
|
||||||
lastBranchChange :: Git -> String -> IO GitTime
|
|
||||||
lastBranchChange git branch = do
|
|
||||||
mref <- resolveRevision git $ Revision branch []
|
|
||||||
mco <- traverse (getCommitMaybe git) mref
|
|
||||||
let mtime = fmap (personTime . commitCommitter) (join mco)
|
|
||||||
return $ fromMaybe (error "mtime is Nothing") mtime
|
|
||||||
|
|
||||||
-- | Determine the time of the last commit in any branch for a given repo
|
|
||||||
lastChange :: FilePath -> IO DateTime
|
|
||||||
lastChange path = withRepo (fromString path) $ \ git -> do
|
|
||||||
--TODO add a better intro to json-state, the docs are bad there
|
|
||||||
|
|
||||||
names <- branchList git
|
|
||||||
times <- traverse (lastBranchChange git) $ map refNameRaw $ toList names
|
|
||||||
let datetimes = map timeConvert times
|
|
||||||
return $ maximum datetimes
|
|
||||||
|
|
||||||
showPeriod :: Period -> String
|
|
||||||
showPeriod (Period 0 0 d) = show d ++ " days"
|
|
||||||
showPeriod (Period 0 m _) = show m ++ " months"
|
|
||||||
showPeriod (Period y _ _) = show y ++ " years"
|
|
||||||
|
|
||||||
showDuration :: Duration -> String
|
|
||||||
showDuration (Duration (Hours h) (Minutes m) (Seconds s) _) =
|
|
||||||
case (h, m, s) of
|
|
||||||
(0, 0, 0) -> "now"
|
|
||||||
(0, 0, _) -> show s ++ " seconds"
|
|
||||||
(0, _, _) -> show m ++ " minutes"
|
|
||||||
_ -> show h ++ " hours"
|
|
||||||
|
|
||||||
showAgo :: Period -> Duration -> String
|
|
||||||
showAgo (Period 0 0 0) d = showDuration d
|
|
||||||
showAgo p _ = showPeriod p
|
|
||||||
|
|
||||||
fromSec :: Seconds -> (Period, Duration)
|
|
||||||
fromSec sec =
|
|
||||||
let d = 3600 * 24
|
|
||||||
m = 30 * d
|
|
||||||
y = 365 * d
|
|
||||||
fs (Seconds n) = fromIntegral n
|
|
||||||
(years, yrest) = sec `divMod` Seconds y
|
|
||||||
(months, mrest) = yrest `divMod` Seconds m
|
|
||||||
(days, drest) = mrest `divMod` Seconds d
|
|
||||||
in (Period (fs years) (fs months) (fs days), fst $ fromSeconds drest)
|
|
||||||
|
|
||||||
timeAgo :: DateTime -> IO String
|
|
||||||
timeAgo dt = do
|
|
||||||
now <- dateCurrent
|
|
||||||
let sec = timeDiff now dt
|
|
||||||
(period, duration) = fromSec sec
|
|
||||||
return $ showAgo period duration
|
|
||||||
|
|
||||||
{-repoPaths :: Server -> Either Int Int -> [Repository] -> [FilePath]
|
|
||||||
repoPaths server (Left uid) repos =
|
|
||||||
case M.lookup uid $ serverUsers server of
|
|
||||||
Nothing -> error "';..;'"
|
|
||||||
Just user ->
|
|
||||||
let dir = serverDir server
|
|
||||||
ns = T.unpack $ CI.original $ unUsername $ userName user
|
|
||||||
prefix = dir </> ns
|
|
||||||
repoNames =
|
|
||||||
map (T.unpack . CI.original . unRepoName . repoName) repos
|
|
||||||
in map (prefix </>) repoNames
|
|
||||||
repoPaths server (Right gid) repos =
|
|
||||||
case M.lookup gid $ serverGroups server of
|
|
||||||
Nothing -> error "';..;'"
|
|
||||||
Just group ->
|
|
||||||
let dir = serverDir server
|
|
||||||
ns = T.unpack $ CI.original $ unGroupName $ groupName group
|
|
||||||
prefix = dir </> ns
|
|
||||||
repoNames =
|
|
||||||
map (T.unpack . CI.original . unRepoName . repoName) repos
|
|
||||||
in map (prefix </>) repoNames-}
|
|
||||||
|
|
||||||
{-timesAgo :: Server -> IO [(Text, Text)]
|
|
||||||
timesAgo server = do
|
|
||||||
-- make list of file paths
|
|
||||||
let paths = uncurry $ repoPaths server
|
|
||||||
nsRepos = map paths $ M.toList $ serverRepos server
|
|
||||||
repos = concat nsRepos
|
|
||||||
-- run lastChange on each
|
|
||||||
times <- traverse lastChange repos
|
|
||||||
-- run timeAgo on each result
|
|
||||||
agos <- traverse timeAgo times
|
|
||||||
-- return
|
|
||||||
return $ zip (map T.pack repos) (map T.pack agos)-}
|
|
|
@ -43,69 +43,6 @@ import Yesod hiding ((==.))
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
|
|
||||||
|
|
||||||
IrcChannel
|
|
||||||
network Text
|
|
||||||
name Text
|
|
||||||
|
|
||||||
Sharer
|
|
||||||
ident Text --CI
|
|
||||||
name Text Maybe
|
|
||||||
|
|
||||||
UniqueIdent ident
|
|
||||||
|
|
||||||
Person
|
|
||||||
ident SharerId
|
|
||||||
hash Text Maybe
|
|
||||||
email Text Maybe
|
|
||||||
|
|
||||||
UniquePersonIdent ident
|
|
||||||
|
|
||||||
Group
|
|
||||||
ident SharerId
|
|
||||||
|
|
||||||
UniqueGroupIdent ident
|
|
||||||
|
|
||||||
Project
|
|
||||||
ident Text --CI
|
|
||||||
sharer SharerId
|
|
||||||
name Text Maybe
|
|
||||||
desc Text Maybe
|
|
||||||
|
|
||||||
UniqueProject ident sharer
|
|
||||||
|
|
||||||
Repo
|
|
||||||
ident Text --CI
|
|
||||||
project ProjectId
|
|
||||||
irc IrcChannelId Maybe
|
|
||||||
ml Text Maybe
|
|
||||||
|
|
||||||
UniqueRepo ident project
|
|
||||||
|
|
||||||
PersonInGroup
|
|
||||||
person PersonId
|
|
||||||
group GroupId
|
|
||||||
|
|
||||||
UniquePersonInGroup person group
|
|
||||||
|
|
||||||
|]
|
|
||||||
|
|
||||||
data MainView = MainView ConnectionPool
|
|
||||||
|
|
||||||
mkYesod "MainView" [parseRoutes|
|
|
||||||
/ HomeR GET
|
|
||||||
|]
|
|
||||||
|
|
||||||
instance Yesod MainView
|
|
||||||
|
|
||||||
instance YesodPersist MainView where
|
|
||||||
type YesodPersistBackend MainView = SqlBackend
|
|
||||||
|
|
||||||
runDB action = do
|
|
||||||
MainView pool <- getYesod
|
|
||||||
runSqlPool action pool
|
|
||||||
|
|
||||||
getHomeR :: Handler Html
|
getHomeR :: Handler Html
|
||||||
getHomeR = do
|
getHomeR = do
|
||||||
rows <- runDB $ do
|
rows <- runDB $ do
|
||||||
|
@ -136,20 +73,6 @@ getHomeR = do
|
||||||
ago <- timeAgo dt
|
ago <- timeAgo dt
|
||||||
return (sharer, project, repo, T.pack ago)
|
return (sharer, project, repo, T.pack ago)
|
||||||
|
|
||||||
defaultLayout
|
|
||||||
[whamlet|
|
|
||||||
<table>
|
|
||||||
$forall (sharer, proj, repo, ago) <- rows
|
|
||||||
<tr>
|
|
||||||
<td>#{sharer}
|
|
||||||
<td>#{proj}
|
|
||||||
<td>#{repo}
|
|
||||||
<td>#{ago}
|
|
||||||
|]
|
|
||||||
|
|
||||||
openConnectionCount :: Int
|
|
||||||
openConnectionCount = 10
|
|
||||||
|
|
||||||
mainView :: IO ()
|
mainView :: IO ()
|
||||||
mainView =
|
mainView =
|
||||||
runStderrLoggingT $
|
runStderrLoggingT $
|
||||||
|
|
|
@ -19,8 +19,8 @@ import Import.NoFoundation
|
||||||
import Database.Persist.Sql (ConnectionPool, runSqlPool)
|
import Database.Persist.Sql (ConnectionPool, runSqlPool)
|
||||||
import Text.Hamlet (hamletFile)
|
import Text.Hamlet (hamletFile)
|
||||||
import Text.Jasmine (minifym)
|
import Text.Jasmine (minifym)
|
||||||
import Yesod.Auth.BrowserId (authBrowserId)
|
import Yesod.Auth.HashDB (authHashDB)
|
||||||
import Yesod.Auth.Message (AuthMessage (InvalidLogin))
|
import Yesod.Auth.Message (AuthMessage (IdentifierNotFound))
|
||||||
import Yesod.Default.Util (addStaticContentExternal)
|
import Yesod.Default.Util (addStaticContentExternal)
|
||||||
import Yesod.Core.Types (Logger)
|
import Yesod.Core.Types (Logger)
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ instance YesodPersistRunner App where
|
||||||
getDBRunner = defaultGetDBRunner appConnPool
|
getDBRunner = defaultGetDBRunner appConnPool
|
||||||
|
|
||||||
instance YesodAuth App where
|
instance YesodAuth App where
|
||||||
type AuthId App = UserId
|
type AuthId App = PersonId
|
||||||
|
|
||||||
-- Where to send a user after successful login
|
-- Where to send a user after successful login
|
||||||
loginDest _ = HomeR
|
loginDest _ = HomeR
|
||||||
|
@ -153,17 +153,31 @@ instance YesodAuth App where
|
||||||
-- Override the above two destinations when a Referer: header is present
|
-- Override the above two destinations when a Referer: header is present
|
||||||
redirectToReferer _ = True
|
redirectToReferer _ = True
|
||||||
|
|
||||||
authenticate creds = runDB $ do
|
authenticate creds = do
|
||||||
x <- getBy $ UniqueUser $ credsIdent creds
|
let ident = credsIdent creds
|
||||||
case x of
|
mpid <- runDB $ getBy $ UniquePersonLogin $ credsIdent creds
|
||||||
|
return $ case mpid of
|
||||||
|
Nothing -> UserError $ IdentifierNotFound ident
|
||||||
|
Just (Entity pid _) -> Authenticated pid
|
||||||
|
{-ps <- select $ from $ \ (sharer, person) -> do
|
||||||
|
where_ $
|
||||||
|
sharer ^. SharerIdent ==. val ident &&.
|
||||||
|
sharer ^. SharerId ==. person ^. PersonIdent
|
||||||
|
return (person ^. PersonId, person ^. PersonHash)-}
|
||||||
|
{-case x of
|
||||||
Just (Entity uid _) -> return $ Authenticated uid
|
Just (Entity uid _) -> return $ Authenticated uid
|
||||||
Nothing -> Authenticated <$> insert User
|
Nothing -> Authenticated <$> insert User
|
||||||
{ userIdent = credsIdent creds
|
{ userIdent = credsIdent creds
|
||||||
, userPassword = Nothing
|
, userPassword = Nothing
|
||||||
}
|
}-}
|
||||||
|
{-return $ case ps of
|
||||||
|
[] -> UserError $ IdentifierNotFound ident
|
||||||
|
[(pid, phash)] ->
|
||||||
|
_ -> ServerError "Data model error, non-unique ident"
|
||||||
|
-}
|
||||||
|
|
||||||
-- You can add other plugins like BrowserID, email or OAuth here
|
-- You can add other plugins like BrowserID, email or OAuth here
|
||||||
authPlugins _ = [authBrowserId def]
|
authPlugins _ = [authHashDB $ Just . UniquePersonLogin]
|
||||||
|
|
||||||
authHttpManager = getHttpManager
|
authHttpManager = getHttpManager
|
||||||
|
|
||||||
|
@ -176,7 +190,8 @@ instance RenderMessage App FormMessage where
|
||||||
|
|
||||||
-- Useful when writing code that is re-usable outside of the Handler context.
|
-- Useful when writing code that is re-usable outside of the Handler context.
|
||||||
-- An example is background jobs that send email.
|
-- An example is background jobs that send email.
|
||||||
-- This can also be useful for writing code that works across multiple Yesod applications.
|
-- This can also be useful for writing code that works across multiple Yesod
|
||||||
|
-- applications.
|
||||||
instance HasHttpManager App where
|
instance HasHttpManager App where
|
||||||
getHttpManager = appHttpManager
|
getHttpManager = appHttpManager
|
||||||
|
|
||||||
|
|
117
src/Git.hs
Normal file
117
src/Git.hs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 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/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
{- LANGUAGE OverloadedStrings #-}
|
||||||
|
{- LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{- LANGUAGE DeriveGeneric #-}
|
||||||
|
|
||||||
|
module Git
|
||||||
|
( lastChange
|
||||||
|
, timeAgo
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad (join)
|
||||||
|
-- import Control.Monad.Fix (MonadFix)
|
||||||
|
-- import Control.Monad.IO.Class
|
||||||
|
-- import Control.Monad.Trans.RWS (RWST (..))
|
||||||
|
-- import Data.CaseInsensitive (CI)
|
||||||
|
import Data.Foldable (toList)
|
||||||
|
import Data.Git
|
||||||
|
import Data.Git.Revision
|
||||||
|
import Data.Git.Repository
|
||||||
|
-- import Data.Hashable (Hashable)
|
||||||
|
-- import Data.HashMap.Lazy (HashMap)
|
||||||
|
-- import Data.HashSet (HashSet)
|
||||||
|
import Data.Hourglass
|
||||||
|
import Data.Maybe (fromMaybe{-, mapMaybe-})
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Data.Text (Text)
|
||||||
|
-- import Data.Time.Units
|
||||||
|
-- import GHC.Generics
|
||||||
|
-- import System.Directory.Tree hiding (name, file, err)
|
||||||
|
-- import System.FilePath ((</>))
|
||||||
|
import System.Hourglass (dateCurrent)
|
||||||
|
|
||||||
|
-- import qualified Control.Monad.Trans.RWS as RWS
|
||||||
|
-- import qualified Data.CaseInsensitive as CI
|
||||||
|
-- import qualified Data.HashMap.Lazy as M
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
-- | Return the subdirs of a given dir
|
||||||
|
{-subdirs :: FilePath -> IO [FilePath]
|
||||||
|
subdirs dir = do
|
||||||
|
_base :/ tree <- buildL dir
|
||||||
|
return $ case tree of
|
||||||
|
Dir _ cs ->
|
||||||
|
let dirName (Dir n _) = Just n
|
||||||
|
dirName _ = Nothing
|
||||||
|
in mapMaybe dirName cs
|
||||||
|
_ -> []-}
|
||||||
|
|
||||||
|
-- | Determine the time of the last commit in a given git branch
|
||||||
|
lastBranchChange :: Git -> String -> IO GitTime
|
||||||
|
lastBranchChange git branch = do
|
||||||
|
mref <- resolveRevision git $ Revision branch []
|
||||||
|
mco <- traverse (getCommitMaybe git) mref
|
||||||
|
let mtime = fmap (personTime . commitCommitter) (join mco)
|
||||||
|
return $ fromMaybe (error "mtime is Nothing") mtime
|
||||||
|
|
||||||
|
-- | Determine the time of the last commit in any branch for a given repo
|
||||||
|
lastChange :: FilePath -> IO DateTime
|
||||||
|
lastChange path = withRepo (fromString path) $ \ git -> do
|
||||||
|
--TODO add a better intro to json-state, the docs are bad there
|
||||||
|
|
||||||
|
names <- branchList git
|
||||||
|
times <- traverse (lastBranchChange git) $ map refNameRaw $ toList names
|
||||||
|
let datetimes = map timeConvert times
|
||||||
|
return $ maximum datetimes
|
||||||
|
|
||||||
|
showPeriod :: Period -> Text
|
||||||
|
showPeriod (Period 0 0 d) = T.pack (show d) <> " days"
|
||||||
|
showPeriod (Period 0 m _) = T.pack (show m) <> " months"
|
||||||
|
showPeriod (Period y _ _) = T.pack (show y) <> " years"
|
||||||
|
|
||||||
|
showDuration :: Duration -> Text
|
||||||
|
showDuration (Duration (Hours h) (Minutes m) (Seconds s) _) =
|
||||||
|
case (h, m, s) of
|
||||||
|
(0, 0, 0) -> "now"
|
||||||
|
(0, 0, _) -> T.pack (show s) <> " seconds"
|
||||||
|
(0, _, _) -> T.pack (show m) <> " minutes"
|
||||||
|
_ -> T.pack (show h) <> " hours"
|
||||||
|
|
||||||
|
showAgo :: Period -> Duration -> Text
|
||||||
|
showAgo (Period 0 0 0) d = showDuration d
|
||||||
|
showAgo p _ = showPeriod p
|
||||||
|
|
||||||
|
fromSec :: Seconds -> (Period, Duration)
|
||||||
|
fromSec sec =
|
||||||
|
let d = 3600 * 24
|
||||||
|
m = 30 * d
|
||||||
|
y = 365 * d
|
||||||
|
fs (Seconds n) = fromIntegral n
|
||||||
|
(years, yrest) = sec `divMod` Seconds y
|
||||||
|
(months, mrest) = yrest `divMod` Seconds m
|
||||||
|
(days, drest) = mrest `divMod` Seconds d
|
||||||
|
in (Period (fs years) (fs months) (fs days), fst $ fromSeconds drest)
|
||||||
|
|
||||||
|
timeAgo :: DateTime -> IO Text
|
||||||
|
timeAgo dt = do
|
||||||
|
now <- dateCurrent
|
||||||
|
let sec = timeDiff now dt
|
||||||
|
(period, duration) = fromSec sec
|
||||||
|
return $ showAgo period duration
|
|
@ -13,43 +13,46 @@
|
||||||
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Handler.Home where
|
module Handler.Home
|
||||||
|
( getHomeR
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
import Import
|
import Import hiding ((==.))
|
||||||
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3,
|
|
||||||
withSmallInput)
|
import Database.Esqueleto
|
||||||
|
import Git
|
||||||
|
|
||||||
-- This is a handler function for the GET request method on the HomeR
|
|
||||||
-- resource pattern. All of your resource patterns are defined in
|
|
||||||
-- config/routes
|
|
||||||
--
|
|
||||||
-- 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.
|
|
||||||
getHomeR :: Handler Html
|
getHomeR :: Handler Html
|
||||||
getHomeR = do
|
getHomeR = do
|
||||||
(formWidget, formEnctype) <- generateFormPost sampleForm
|
rows <- do
|
||||||
let submission = Nothing :: Maybe (FileInfo, Text)
|
repos <- runDB $ select $ from $ \ (sharer, project, repo) -> do
|
||||||
handlerName = "getHomeR" :: Text
|
where_ $
|
||||||
|
project ^. ProjectSharer ==. sharer ^. SharerId &&.
|
||||||
|
repo ^. RepoProject ==. project ^. ProjectId
|
||||||
|
orderBy
|
||||||
|
[ asc $ sharer ^. SharerIdent
|
||||||
|
, asc $ project ^. ProjectIdent
|
||||||
|
, asc $ repo ^. RepoIdent
|
||||||
|
]
|
||||||
|
return
|
||||||
|
( sharer ^. SharerIdent
|
||||||
|
, project ^. ProjectIdent
|
||||||
|
, repo ^. RepoIdent
|
||||||
|
)
|
||||||
|
liftIO $ forM repos $ \ (Value sharer, Value project, Value repo) -> do
|
||||||
|
let path =
|
||||||
|
unpack $
|
||||||
|
intercalate "/"
|
||||||
|
[ "state2"
|
||||||
|
, sharer
|
||||||
|
, project
|
||||||
|
, repo
|
||||||
|
]
|
||||||
|
dt <- lastChange path
|
||||||
|
ago <- timeAgo dt
|
||||||
|
return (sharer, project, repo, ago)
|
||||||
|
mp <- maybeAuth
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
aDomId <- newIdent
|
setTitle "Welcome to Vervis!"
|
||||||
setTitle "Welcome To Yesod!"
|
|
||||||
$(widgetFile "homepage")
|
$(widgetFile "homepage")
|
||||||
|
|
||||||
postHomeR :: Handler Html
|
|
||||||
postHomeR = do
|
|
||||||
((result, formWidget), formEnctype) <- runFormPost sampleForm
|
|
||||||
let handlerName = "postHomeR" :: Text
|
|
||||||
submission = case result of
|
|
||||||
FormSuccess res -> Just res
|
|
||||||
_ -> Nothing
|
|
||||||
|
|
||||||
defaultLayout $ do
|
|
||||||
aDomId <- newIdent
|
|
||||||
setTitle "Welcome To Yesod!"
|
|
||||||
$(widgetFile "homepage")
|
|
||||||
|
|
||||||
sampleForm :: Form (FileInfo, Text)
|
|
||||||
sampleForm = renderBootstrap3 BootstrapBasicForm $ (,)
|
|
||||||
<$> fileAFormReq "Choose a file"
|
|
||||||
<*> areq textField (withSmallInput "What's on the file?") Nothing
|
|
||||||
|
|
|
@ -19,10 +19,14 @@ module Model where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod
|
||||||
import Database.Persist.Quasi
|
import Database.Persist.Quasi
|
||||||
|
import Yesod.Auth.HashDB (HashDBUser (..))
|
||||||
|
|
||||||
-- You can define all of your database entities in the entities file.
|
-- You can define all of your database entities in the entities file.
|
||||||
-- You can find more information on persistent and how to declare entities
|
-- You can find more information on persistent and how to declare entities at:
|
||||||
-- at:
|
|
||||||
-- http://www.yesodweb.com/book/persistent/
|
-- http://www.yesodweb.com/book/persistent/
|
||||||
share [mkPersist sqlSettings, mkMigrate "migrateAll"]
|
share [mkPersist sqlSettings, mkMigrate "migrateAll"]
|
||||||
$(persistFileWith lowerCaseSettings "config/models")
|
$(persistFileWith lowerCaseSettings "config/models")
|
||||||
|
|
||||||
|
instance HashDBUser Person where
|
||||||
|
userPasswordHash = personHash
|
||||||
|
setPasswordHash hash person = person { personHash = Just hash }
|
||||||
|
|
|
@ -1,49 +1,23 @@
|
||||||
<h1.jumbotron>
|
<h1>Vervis
|
||||||
Welcome to Yesod!
|
|
||||||
|
|
||||||
<.page-header><h2>Starting
|
<p>
|
||||||
|
Vervis is hopefully going to be, eventually, a decentralized project hosting
|
||||||
|
platform. At the time of writing (2016-02-14), it is a simple scaffolded
|
||||||
|
Yesod web application which displays a table of Git repositories.
|
||||||
|
|
||||||
<section.list-group>
|
$maybe Entity _pid person <- mp
|
||||||
<span .list-group-item>
|
<p>
|
||||||
Now that you have a working project you should use the
|
You are logged in as #{personLogin person}.
|
||||||
<a href=http://www.yesodweb.com/book/>
|
<a href=@{AuthR LogoutR}>Log out.
|
||||||
Yesod book <span class="glyphicon glyphicon-book"></span>
|
$nothing
|
||||||
to learn more.
|
<p>
|
||||||
You can also use this scaffolded site to explore some basic concepts.
|
You are not logged in.
|
||||||
|
<a href=@{AuthR LoginR}>Log in.
|
||||||
|
|
||||||
<span .list-group-item>
|
<table>
|
||||||
This page was generated by the <tt>#{handlerName}</tt> handler in
|
$forall (sharer, proj, repo, ago) <- rows
|
||||||
<tt>Handler/Home.hs</tt>.
|
<tr>
|
||||||
|
<td>#{sharer}
|
||||||
<span .list-group-item>
|
<td>#{proj}
|
||||||
The <tt>#{handlerName}</tt> handler is set to generate your
|
<td>#{repo}
|
||||||
site's home screen in Routes file
|
<td>#{ago}
|
||||||
<tt>config/routes
|
|
||||||
|
|
||||||
<span .list-group-item>
|
|
||||||
The HTML you are seeing now is actually composed by a number of <em>widgets</em>, #
|
|
||||||
most of them are brought together by the <tt>defaultLayout</tt> function which #
|
|
||||||
is defined in the <tt>Foundation.hs</tt> module, and used by <tt>#{handlerName}</tt>. #
|
|
||||||
All the files for templates and wigdets are in <tt>templates</tt>.
|
|
||||||
|
|
||||||
<span .list-group-item>
|
|
||||||
A Widget's Html, Css and Javascript are separated in three files with the
|
|
||||||
<tt>.hamlet</tt>, <tt>.lucius</tt> and <tt>.julius</tt> extensions.
|
|
||||||
|
|
||||||
<span .list-group-item ##{aDomId}>
|
|
||||||
If you had javascript enabled then you wouldn't be seeing this.
|
|
||||||
|
|
||||||
<section.page-header>
|
|
||||||
<h2>Forms
|
|
||||||
|
|
||||||
<div>
|
|
||||||
This is an example trivial Form. Read the
|
|
||||||
<a href="http://www.yesodweb.com/book/forms">Forms chapter<span class="glyphicon glyphicon-bookmark"></span></a> #
|
|
||||||
on the yesod book to learn more about them.
|
|
||||||
$maybe (info,con) <- submission
|
|
||||||
<div .message .alert .alert-success>
|
|
||||||
Your file's type was <em>#{fileContentType info}</em>. You say it has: <em>#{con}</em>
|
|
||||||
<form method=post action=@{HomeR}#form enctype=#{formEnctype}>
|
|
||||||
^{formWidget}
|
|
||||||
<button .btn .btn-primary type="submit">
|
|
||||||
Send it! <span class="glyphicon glyphicon-upload"></span>
|
|
||||||
|
|
61
vervis.cabal
61
vervis.cabal
|
@ -36,6 +36,7 @@ flag library-only
|
||||||
library
|
library
|
||||||
exposed-modules: Application
|
exposed-modules: Application
|
||||||
Foundation
|
Foundation
|
||||||
|
Git
|
||||||
Import
|
Import
|
||||||
Import.NoFoundation
|
Import.NoFoundation
|
||||||
Model
|
Model
|
||||||
|
@ -69,43 +70,47 @@ library
|
||||||
-- , hourglass
|
-- , hourglass
|
||||||
-- , time-units
|
-- , time-units
|
||||||
-- , unordered-containers >=0.2.5
|
-- , unordered-containers >=0.2.5
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: aeson >= 0.6 && < 0.11
|
||||||
, yesod >= 1.4.1 && < 1.5
|
, base >= 4 && < 5
|
||||||
, yesod-core >= 1.4.17 && < 1.5
|
, bytestring >= 0.9 && < 0.11
|
||||||
, yesod-auth >= 1.4.0 && < 1.5
|
, case-insensitive
|
||||||
, yesod-static >= 1.4.0.3 && < 1.6
|
|
||||||
, yesod-form >= 1.4.0 && < 1.5
|
|
||||||
, classy-prelude >= 0.10.2
|
, classy-prelude >= 0.10.2
|
||||||
, classy-prelude-conduit >= 0.10.2
|
, classy-prelude-conduit >= 0.10.2
|
||||||
, classy-prelude-yesod >= 0.10.2
|
, classy-prelude-yesod >= 0.10.2
|
||||||
, bytestring >= 0.9 && < 0.11
|
, conduit >= 1.0 && < 2.0
|
||||||
, text >= 0.11 && < 2.0
|
, containers
|
||||||
|
, data-default
|
||||||
|
, directory >= 1.1 && < 1.3
|
||||||
|
, esqueleto
|
||||||
|
, fast-logger >= 2.2 && < 2.5
|
||||||
|
, file-embed
|
||||||
|
, hit
|
||||||
|
, hjsmin >= 0.1 && < 0.2
|
||||||
|
, hourglass
|
||||||
|
, http-conduit >= 2.1 && < 2.2
|
||||||
|
, monad-control >= 0.3 && < 1.1
|
||||||
|
, monad-logger >= 0.3 && < 0.4
|
||||||
, persistent >= 2.0 && < 2.3
|
, persistent >= 2.0 && < 2.3
|
||||||
, persistent-postgresql >= 2.1.1 && < 2.3
|
, persistent-postgresql >= 2.1.1 && < 2.3
|
||||||
, persistent-template >= 2.0 && < 2.3
|
, persistent-template >= 2.0 && < 2.3
|
||||||
, template-haskell
|
|
||||||
, shakespeare >= 2.0 && < 2.1
|
|
||||||
, hjsmin >= 0.1 && < 0.2
|
|
||||||
, monad-control >= 0.3 && < 1.1
|
|
||||||
, wai-extra >= 3.0 && < 3.1
|
|
||||||
, yaml >= 0.8 && < 0.9
|
|
||||||
, http-conduit >= 2.1 && < 2.2
|
|
||||||
, directory >= 1.1 && < 1.3
|
|
||||||
, warp >= 3.0 && < 3.3
|
|
||||||
, data-default
|
|
||||||
, aeson >= 0.6 && < 0.11
|
|
||||||
, conduit >= 1.0 && < 2.0
|
|
||||||
, monad-logger >= 0.3 && < 0.4
|
|
||||||
, fast-logger >= 2.2 && < 2.5
|
|
||||||
, wai-logger >= 2.2 && < 2.3
|
|
||||||
, file-embed
|
|
||||||
, safe
|
, safe
|
||||||
, unordered-containers
|
, shakespeare >= 2.0 && < 2.1
|
||||||
, containers
|
, template-haskell
|
||||||
, vector
|
, text >= 0.11 && < 2.0
|
||||||
, time
|
, time
|
||||||
, case-insensitive
|
, unordered-containers
|
||||||
|
, vector
|
||||||
, wai
|
, wai
|
||||||
|
, wai-extra >= 3.0 && < 3.1
|
||||||
|
, wai-logger >= 2.2 && < 2.3
|
||||||
|
, warp >= 3.0 && < 3.3
|
||||||
|
, yaml >= 0.8 && < 0.9
|
||||||
|
, yesod >= 1.4.1 && < 1.5
|
||||||
|
, yesod-auth >= 1.4.0 && < 1.5
|
||||||
|
, yesod-auth-hashdb
|
||||||
|
, yesod-core >= 1.4.17 && < 1.5
|
||||||
|
, yesod-form >= 1.4.0 && < 1.5
|
||||||
|
, yesod-static >= 1.4.0.3 && < 1.6
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue