Add repo pages and repo creation form

This commit is contained in:
fr33domlover 2016-02-27 05:41:36 +00:00
parent 9b686c6db0
commit ec4c7de582
16 changed files with 352 additions and 19 deletions

View file

@ -121,3 +121,6 @@
^static/combined(/|$) ^static/combined(/|$)
^config/client_session_key.aes$ ^config/client_session_key.aes$
^yesod-devel(/|$) ^yesod-devel(/|$)
### vervis
^repos(/|$)

View file

@ -47,6 +47,7 @@ Project
Repo Repo
ident Text --CI ident Text --CI
project ProjectId project ProjectId
desc Text Maybe
irc IrcChannelId Maybe irc IrcChannelId Maybe
ml Text Maybe ml Text Maybe

View file

@ -40,8 +40,13 @@
/u/#Text/p/!new ProjectNewR GET /u/#Text/p/!new ProjectNewR GET
/u/#Text/p/#Text ProjectR GET /u/#Text/p/#Text ProjectR GET
-- /u/#Text/p/#Text/r ReposR GET -- IDEA: if there's /u/john/p/proj/r/repo, then make /u/john/r/proj-repo
-- /u/#Text/p/#Text/r/#Text RepoR GET -- redirect there. consider having a clean way to refer to repos
-- independently of projects...
/u/#Text/p/#Text/r ReposR GET POST
/u/#Text/p/#Text/r/!new RepoNewR GET
/u/#Text/p/#Text/r/#Text RepoR GET
-- /u/#Text/p/#Text/t TicketsR GET -- /u/#Text/p/#Text/t TicketsR GET
-- /u/#Text/p/#Text/t/#TicketId TicketR GET -- /u/#Text/p/#Text/t/#TicketId TicketR GET
-- /u/#Text/p/#Text/w WikiR GET -- /u/#Text/p/#Text/w WikiR GET

View file

@ -38,4 +38,5 @@ database:
database: "_env:PGDATABASE:vervis_dev" database: "_env:PGDATABASE:vervis_dev"
poolsize: "_env:PGPOOLSIZE:10" poolsize: "_env:PGPOOLSIZE:10"
repo-dir: repos
copyright: Insert your statement against copyright here copyright: Insert your statement against copyright here

30
src/Data/List/Local.hs Normal file
View file

@ -0,0 +1,30 @@
{- 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 Data.List.Local
( -- groupByFst
)
where
import Prelude
-- | Takes a list of pairs and groups them by consecutive ranges with equal
-- first element. Returns a list of pairs, where each pair corresponds to one
-- such range.
groupByFst :: Eq a => [(a, b)] -> [(a, [b])]
groupByFst [] = []
groupByFst ((x, y):ps) =
let (same, rest) = span ((== x) . fst) ps
in (x, y : map snd same) : groupByFst rest

View file

@ -53,6 +53,7 @@ import Vervis.Handler.Common
import Vervis.Handler.Home import Vervis.Handler.Home
import Vervis.Handler.Person import Vervis.Handler.Person
import Vervis.Handler.Project import Vervis.Handler.Project
import Vervis.Handler.Repo
-- This line actually creates our YesodDispatch instance. It is the second half -- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the -- of the call to mkYesodData which occurs in Foundation.hs. Please see the

59
src/Vervis/Field/Repo.hs Normal file
View file

@ -0,0 +1,59 @@
{- 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.Field.Repo
( mkIdentField
)
where
import Vervis.Import hiding ((==.))
import Data.Char (isDigit)
import Data.Char.Local (isAsciiLetter)
import Data.Text (split)
import Database.Esqueleto hiding (isNothing)
checkIdentTemplate :: Field Handler Text -> Field Handler Text
checkIdentTemplate =
let charOk c = isAsciiLetter c || isDigit c
wordOk w = (not . null) w && all charOk w
identOk t = (not . null) t && all wordOk (split (== '-') t)
msg :: Text
msg = "The repo identifier must be a sequence of one or more words \
\separated by hyphens (-), and each such word may contain \
\ASCII letters and digits."
in checkBool identOk msg
-- | Make sure the repo identifier is unique. The DB schema only requires that
-- a repo identifier is unique within its project, but I'd like to enforce a
-- stronger condition: A repo identifier must be unique within its sharer's
-- repos. I'm not yet sure it's a good thing, but it's much easier to maintain
-- now and relax later, than relax now and have problems later when there are
-- already conflicting names.
checkIdentUnique :: SharerId -> Field Handler Text -> Field Handler Text
checkIdentUnique sid = checkM $ \ ident -> do
l <- runDB $ select $ from $ \ (project, repo) -> do
where_ $
project ^. ProjectSharer ==. val sid &&.
repo ^. RepoProject ==. project ^. ProjectId &&.
repo ^. RepoIdent ==. val ident
limit 1
return ()
return $ if isNothing $ listToMaybe l
then Right ident
else Left ("You already have a repo by that name" :: Text)
mkIdentField :: SharerId -> Field Handler Text
mkIdentField sid = checkIdentUnique sid . checkIdentTemplate $ textField

33
src/Vervis/Form/Repo.hs Normal file
View file

@ -0,0 +1,33 @@
{- 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.Form.Repo
( newRepoForm
)
where
import Vervis.Import
import Vervis.Field.Repo
newRepoAForm :: SharerId -> ProjectId -> AForm Handler Repo
newRepoAForm sid pid = Repo
<$> areq (mkIdentField sid) "Identifier*" Nothing
<*> pure pid
<*> aopt textField "Description" Nothing
<*> pure Nothing
<*> pure Nothing
newRepoForm :: SharerId -> ProjectId -> Form Repo
newRepoForm sid pid = renderDivs $ newRepoAForm sid pid

View file

@ -105,23 +105,10 @@ instance Yesod App where
authRoute _ = Just $ AuthR LoginR authRoute _ = Just $ AuthR LoginR
-- Who can access which pages. -- Who can access which pages.
isAuthorized (ProjectNewR ident) _ = do isAuthorized (ProjectNewR user) _ =
mp <- maybeAuth loggedInAs user "You cant create projects for other users"
case mp of isAuthorized (RepoNewR user _proj) _ =
Nothing -> return AuthenticationRequired loggedInAs user "You cant create repos for other users"
Just (Entity _pid person) -> do
let sid = personIdent person
msharer <- runDB $ get sid
case msharer of
Nothing -> return $ Unauthorized $
"Integrity error: User " <>
personLogin person <>
" specified a nonexistent sharer ID"
Just sharer ->
if ident == sharerIdent sharer
then return Authorized
else return $ Unauthorized
"You cant create projects for other users"
isAuthorized _ _ = return Authorized isAuthorized _ _ = return Authorized
-- This function creates static content files in the static folder -- This function creates static content files in the static folder
@ -223,3 +210,21 @@ unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
-- https://github.com/yesodweb/yesod/wiki/Sending-email -- 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/Serve-static-files-from-a-separate-domain
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding -- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
loggedInAs :: Text -> Text -> Handler AuthResult
loggedInAs ident msg = do
mp <- maybeAuth
case mp of
Nothing -> return AuthenticationRequired
Just (Entity _pid person) -> do
let sid = personIdent person
msharer <- runDB $ get sid
case msharer of
Nothing -> return $ Unauthorized $
"Integrity error: User " <>
personLogin person <>
" specified a nonexistent sharer ID"
Just sharer ->
if ident == sharerIdent sharer
then return Authorized
else return $ Unauthorized msg

View file

@ -115,3 +115,17 @@ timeAgo dt = do
let sec = timeDiff now dt let sec = timeDiff now dt
(period, duration) = fromSec sec (period, duration) = fromSec sec
return $ showAgo period duration return $ showAgo period duration
{-commits' :: Git -> Ref -> Int -> IO [(Text, Text, Text, Text)]
commits' git r l = go r l
where
go _ 0 = return []
go ref lim = do
commit <- getCommit git ref
commits :: Git -> String -> Int -> IO [(Text, Text, Text, Text)]
commits git branch lim = do
mref <- resolveRevision git $ Revision branch []
case mref of
Nothing -> return []
Just ref -> commits' git ref lim-}

View file

@ -0,0 +1,98 @@
{- 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.Handler.Repo
( getReposR
, postReposR
, getRepoNewR
, getRepoR
)
where
--TODO CONTINUE HERE
--
-- [/] maybe list project repos in personal overview too
-- [x] make repo list page
-- [x] add new repo creation link
-- [x] make new repo form
-- [x] write the git and mkdir parts that actually create the repo
-- [ ] make repo view that shows a table of commits
import Data.Git.Repository (initRepo)
import Database.Esqueleto
import System.Directory (createDirectoryIfMissing)
--import System.FilePath ((</>))
import Vervis.Import hiding ((==.))
import Vervis.Form.Repo
getReposR :: Text -> Text -> Handler Html
getReposR user proj = do
repos <- runDB $ select $ from $ \ (sharer, project, repo) -> do
where_ $
sharer ^. SharerIdent ==. val user &&.
sharer ^. SharerId ==. project ^. ProjectSharer &&.
repo ^. RepoProject ==. project ^. ProjectId
orderBy [asc $ repo ^. RepoIdent]
return $ repo ^. RepoIdent
defaultLayout $ do
setTitle $ toHtml $ mconcat
["Vervis > People > ", user, " > Projects > ", proj, " Repos"]
$(widgetFile "repos")
postReposR :: Text -> Text -> Handler Html
postReposR user proj = do
Entity _pid person <- requireAuth
let sid = personIdent person
Entity pid _project <- runDB $ getBy404 $ UniqueProject proj sid
((result, widget), enctype) <- runFormPost $ newRepoForm sid pid
case result of
FormSuccess repo -> do
root <- appRepoDir . appSettings <$> getYesod
let parent = root </> unpack user </> unpack proj
path = parent </> unpack (repoIdent repo)
liftIO $ createDirectoryIfMissing True parent
liftIO $ initRepo $ fromString path
runDB $ insert_ repo
setMessage "Repo added."
redirectUltDest HomeR
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "repo-new")
FormFailure l -> do
setMessage $ toHtml $ intercalate "; " l
defaultLayout $(widgetFile "repo-new")
getRepoNewR :: Text -> Text -> Handler Html
getRepoNewR user proj = do
Entity _pid person <- requireAuth
let sid = personIdent person
Entity pid _project <- runDB $ getBy404 $ UniqueProject proj sid
((_result, widget), enctype) <- runFormPost $ newRepoForm sid pid
defaultLayout $ do
setTitle $ toHtml $ mconcat
["Vervis > People > ", user, " > Projects > ", proj, " > New Repo"]
$(widgetFile "repo-new")
getRepoR :: Text -> Text -> Text -> Handler Html
getRepoR user proj repo = do
repository <- runDB $ do
Entity sid _s <- getBy404 $ UniqueSharerIdent user
Entity pid _p <- getBy404 $ UniqueProject proj sid
Entity _rid r <- getBy404 $ UniqueRepo repo pid
return r
defaultLayout $ do
setTitle $ toHtml $ intercalate " > " $
["Vervis", "People", user, "Projects", proj, "Repos", repo]
$(widgetFile "repo")

View file

@ -66,6 +66,8 @@ data AppSettings = AppSettings
-- ^ Perform no stylesheet/script combining -- ^ Perform no stylesheet/script combining
-- Example app-specific configuration values. -- Example app-specific configuration values.
, appRepoDir :: FilePath
-- ^ Path to the directory under which git repos are placed
, appCopyright :: Text , appCopyright :: Text
-- ^ Copyright text to appear in the footer of the page -- ^ Copyright text to appear in the footer of the page
} }
@ -91,6 +93,7 @@ instance FromJSON AppSettings where
appMutableStatic <- o .:? "mutable-static" .!= defaultDev appMutableStatic <- o .:? "mutable-static" .!= defaultDev
appSkipCombining <- o .:? "skip-combining" .!= defaultDev appSkipCombining <- o .:? "skip-combining" .!= defaultDev
appRepoDir <- o .: "repo-dir"
appCopyright <- o .: "copyright" appCopyright <- o .: "copyright"
return AppSettings {..} return AppSettings {..}

21
templates/repo-new.hamlet Normal file
View file

@ -0,0 +1,21 @@
$# 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/>.
<h1>Vervis > People > #{user} > Projects > #{proj} > New Repo
Enter your details and click "Submit" to create a new repo.
<form method=POST action=@{ReposR user proj} enctype=#{enctype}>
^{widget}
<input type=submit>

30
templates/repo.hamlet Normal file
View file

@ -0,0 +1,30 @@
$# 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/>.
<h1>Vervis > People > #{user} > Projects > #{proj} > Repos > #{repo}
<h2>About
<p>
This is the repo page for <b>#{repo}</b>, which is part of project
<b>#{proj}</b>, shared by user <b>#{user}</b>.
<h2>Details
<table>
<tr>
<td>Description
<td>
$maybe desc <- repoDesc repository
#{desc}
$nothing
(none)

24
templates/repos.hamlet Normal file
View file

@ -0,0 +1,24 @@
$# 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/>.
<h1>Vervis > People > #{user} > Projects > #{proj} > Repos
<p>These are the repositories of project #{proj} shared by #{user}.
<ul>
$forall Value repo <- repos
<li>
<a href=@{RepoR user proj repo}>#{repo}
<li>
<a href=@{RepoNewR user proj}>Create new...

View file

@ -35,11 +35,14 @@ flag library-only
library library
exposed-modules: Data.Char.Local exposed-modules: Data.Char.Local
Data.List.Local
Vervis.Application Vervis.Application
Vervis.Field.Person Vervis.Field.Person
Vervis.Field.Project Vervis.Field.Project
Vervis.Field.Repo
Vervis.Form.Person Vervis.Form.Person
Vervis.Form.Project Vervis.Form.Project
Vervis.Form.Repo
Vervis.Foundation Vervis.Foundation
Vervis.Git Vervis.Git
Vervis.Import Vervis.Import
@ -51,6 +54,7 @@ library
Vervis.Handler.Home Vervis.Handler.Home
Vervis.Handler.Person Vervis.Handler.Person
Vervis.Handler.Project Vervis.Handler.Project
Vervis.Handler.Repo
Vervis.Handler.Util Vervis.Handler.Util
Vervis.Style Vervis.Style
-- other-modules: -- other-modules:
@ -94,6 +98,7 @@ library
, esqueleto , esqueleto
, fast-logger >= 2.2 && < 2.5 , fast-logger >= 2.2 && < 2.5
, file-embed , file-embed
, filepath
, hit , hit
, hjsmin >= 0.1 && < 0.2 , hjsmin >= 0.1 && < 0.2
, hourglass , hourglass