C2S & UI: Allow creation of a new Repo

This commit is contained in:
fr33domlover 2022-09-16 10:34:44 +00:00
parent 8ec98e2a59
commit e78f043f49
13 changed files with 424 additions and 180 deletions

View file

@ -73,17 +73,15 @@ initialRepoTree repo =
createRepo createRepo
:: FilePath :: FilePath
-- ^ Parent directory which already exists -- ^ Parent directory which already exists
-> String -> Text
-- ^ Name of new repo, i.e. new directory to create under the parent -- ^ Repo keyhashid, i.e. new directory to create under the parent
-> FilePath -> FilePath
-- ^ Path of Vervis hook program -- ^ Path of Vervis hook program
-> Text -> Text
-- ^ Instance HTTP authority -- ^ Instance HTTP authority
-> Text
-- ^ Repo key hashid
-> IO () -> IO ()
createRepo parent name cmd authority repo = do createRepo parent repo cmd authority = do
let path = parent </> name let path = parent </> T.unpack repo
createDirectory path createDirectory path
let settings = proc "darcs" ["init", "--no-working-dir", "--repodir", path] let settings = proc "darcs" ["init", "--no-working-dir", "--repodir", path]
(_, _, _, ph) <- createProcess settings (_, _, _, ph) <- createProcess settings

View file

@ -67,9 +67,9 @@ writeHookFile path cmd authority repo = do
TIO.writeFile file $ hookContent cmd authority repo TIO.writeFile file $ hookContent cmd authority repo
setFileMode file ownerModes setFileMode file ownerModes
initialRepoTree :: FilePath -> Text -> Text -> FileName -> DirTree Text initialRepoTree :: FilePath -> Text -> Text -> DirTree Text
initialRepoTree hook authority repo dir = initialRepoTree hook authority repo =
Dir dir Dir (T.unpack repo)
[ Dir "branches" [] [ Dir "branches" []
, File "config" , File "config"
"[core]\n\ "[core]\n\
@ -78,7 +78,7 @@ initialRepoTree hook authority repo dir =
\ bare = true" \ bare = true"
, File "description" , File "description"
"Unnamed repository; edit this file to name the repository." "Unnamed repository; edit this file to name the repository."
, File "HEAD" "ref: refs/heads/master" , File "HEAD" "ref: refs/heads/main"
, Dir "hooks" , Dir "hooks"
[ File "post-receive" $ hookContent hook authority repo [ File "post-receive" $ hookContent hook authority repo
] ]
@ -103,22 +103,22 @@ initialRepoTree hook authority repo dir =
createRepo createRepo
:: FilePath :: FilePath
-- ^ Parent directory which already exists -- ^ Parent directory which already exists
-> String -> Text
-- ^ Name of new repo, i.e. new directory to create under the parent -- ^ Repo hashid, i.e. new directory to create under the parent
-> FilePath -> FilePath
-- ^ Path of Vervis hook program -- ^ Path of Vervis hook program
-> Text -> Text
-- ^ Instance HTTP authority -- ^ Instance HTTP authority
-> Text
-- ^ Repo hashid
-> IO () -> IO ()
createRepo path name cmd authority repo = do createRepo path repo cmd authority = do
let tree = path :/ initialRepoTree cmd authority repo name let tree = path :/ initialRepoTree cmd authority repo
result <- writeDirectoryWith TIO.writeFile tree result <- writeDirectoryWith TIO.writeFile tree
let errs = failures $ dirTree result let errs = failures $ dirTree result
when (not . null $ errs) $ when (not . null $ errs) $
throwIO $ userError $ show errs throwIO $ userError $ show errs
setFileMode (path </> name </> "hooks" </> "post-receive") ownerModes setFileMode
(path </> T.unpack repo </> "hooks" </> "post-receive")
ownerModes
data EntObjType = EntObjBlob | EntObjTree data EntObjType = EntObjBlob | EntObjTree

View file

@ -22,6 +22,7 @@ module Vervis.API
, applyC , applyC
, noteC , noteC
, createNoteC , createNoteC
, createRepositoryC
, createTicketTrackerC , createTicketTrackerC
, followC , followC
, inviteC , inviteC
@ -34,70 +35,38 @@ module Vervis.API
where where
import Control.Applicative import Control.Applicative
import Control.Concurrent.MVar
import Control.Concurrent.STM.TVar
import Control.Exception hiding (Handler, try) import Control.Exception hiding (Handler, try)
import Control.Monad import Control.Monad
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader import Control.Monad.Trans.Reader
import Crypto.Hash
import Data.Aeson
import Data.Barbie import Data.Barbie
import Data.Bifunctor import Data.Bifunctor
import Data.Bifoldable import Data.Bifoldable
import Data.Bitraversable import Data.Bitraversable
import Data.ByteString (ByteString)
import Data.Either
import Data.Foldable import Data.Foldable
import Data.Function
import Data.Functor.Identity import Data.Functor.Identity
import Data.List (sort, deleteBy, nub, union, unionBy, partition) import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import Data.Maybe import Data.Maybe
import Data.Semigroup
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding
import Data.Time.Calendar
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Units
import Data.Traversable import Data.Traversable
import Data.Tuple
import Database.Persist hiding (deleteBy) import Database.Persist hiding (deleteBy)
import Database.Persist.Sql hiding (deleteBy) import Database.Persist.Sql hiding (deleteBy)
import GHC.Generics
import Network.HTTP.Client import Network.HTTP.Client
import Network.HTTP.Types.Header import System.Directory
import Network.HTTP.Types.URI
import Text.Blaze.Html (preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text import Text.Blaze.Html.Renderer.Text
import UnliftIO.Exception (try)
import Yesod.Core hiding (logError, logWarn, logInfo, logDebug) import Yesod.Core hiding (logError, logWarn, logInfo, logDebug)
import Yesod.Persist.Core import Yesod.Persist.Core
import qualified Data.ByteString.Lazy as BL
import qualified Data.CaseInsensitive as CI
import qualified Data.List as L
import qualified Data.List.NonEmpty as NE
import qualified Data.List.Ordered as LO
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import qualified Database.Esqueleto as E
import qualified Network.Wai as W
import Data.Time.Interval
import Network.HTTP.Signature hiding (requestHeaders)
import Yesod.HttpSignature
import Crypto.PublicVerifKey
import Database.Persist.JSON import Database.Persist.JSON
import Development.PatchMediaType import Development.PatchMediaType
import Network.FedURI import Network.FedURI
import Network.HTTP.Digest
import Web.ActivityPub hiding (Patch, Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..)) import Web.ActivityPub hiding (Patch, Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..))
import Yesod.ActivityPub import Yesod.ActivityPub
import Yesod.Auth.Unverified
import Yesod.FedURI import Yesod.FedURI
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
@ -105,32 +74,26 @@ import Yesod.MonadSite
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local import Control.Monad.Trans.Except.Local
import Data.Aeson.Local
import Data.Either.Local import Data.Either.Local
import Data.List.Local
import Data.List.NonEmpty.Local
import Data.Maybe.Local
import Data.Tuple.Local
import Database.Persist.Local import Database.Persist.Local
import Yesod.Persist.Local
import qualified Data.Git.Local as G (createRepo)
import qualified Darcs.Local.Repository as D (createRepo)
import Vervis.Access import Vervis.Access
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.Cloth import Vervis.Cloth
import Vervis.Darcs
import Vervis.Data.Actor import Vervis.Data.Actor
import Vervis.Data.Collab import Vervis.Data.Collab
import Vervis.Delivery import Vervis.Delivery
import Vervis.Discussion
import Vervis.FedURI import Vervis.FedURI
import Vervis.Foundation import Vervis.Foundation
import Vervis.Git
import Vervis.Model import Vervis.Model
import Vervis.Model.Ident import Vervis.Model.Ident
import Vervis.Model.Role import Vervis.Model.Role
import Vervis.Model.Workflow import Vervis.Model.Workflow
import Vervis.Model.Ticket import Vervis.Model.Ticket
import Vervis.Path
import Vervis.Persist.Actor import Vervis.Persist.Actor
import Vervis.Persist.Collab import Vervis.Persist.Collab
import Vervis.Recipient import Vervis.Recipient
@ -1314,6 +1277,269 @@ verifyProjectRecip (Left (WITRepo shr rp _ _ _)) localRecips =
guard $ localRecipRepo $ localRecipRepoDirect repoSet guard $ localRecipRepo $ localRecipRepoDirect repoSet
-} -}
createRepositoryC
:: Entity Person
-> Actor
-> Maybe TextHtml
-> Audience URIMode
-> AP.ActorDetail
-> VersionControlSystem
-> Maybe (Host, AP.ActorLocal URIMode)
-> Maybe FedURI
-> ExceptT Text Handler OutboxItemId
createRepositoryC (Entity pidUser personUser) senderActor summary audience detail vcs mlocal muTarget = do
-- Check input
verifyNothingE mlocal "'id' not allowed in new Repository to create"
(name, msummary) <- parseDetail detail
senderHash <- encodeKeyHashid pidUser
now <- liftIO getCurrentTime
verifyNothingE muTarget "'target' not supported in Create Repository"
ParsedAudience localRecips remoteRecips blinded fwdHosts <- do
mrecips <- parseAudience audience
fromMaybeE mrecips "Create Repository with no recipients"
checkFederation remoteRecips
(obiid, newRepoHash, deliverHttpCreate, deliverHttpGrant) <- runDBExcept $ do
-- Insert new repo to DB
obiidCreate <-
lift $ insertEmptyOutboxItem (actorOutbox senderActor) now
(repoID, Entity repoActorID repoActor) <-
lift $ insertRepo now name msummary obiidCreate
-- Insert the Create activity to author's outbox
repoHash <- encodeKeyHashid repoID
docCreate <- lift $ insertCreateToOutbox senderHash now blinded name msummary obiidCreate repoHash
-- Deliver the Create activity to local recipients, and schedule
-- delivery for unavailable remote recipients
remoteRecipsHttpCreate <- do
let sieve =
makeRecipientSet [] [LocalStagePersonFollowers senderHash]
moreRemoteRecips <-
lift $ deliverLocal' True (LocalActorPerson senderHash) (personActor personUser) obiidCreate $
localRecipSieve sieve False localRecips
checkFederation moreRemoteRecips
lift $ deliverRemoteDB'' fwdHosts obiidCreate remoteRecips moreRemoteRecips
-- Insert collaboration access for repo's creator
let repoOutboxID = actorOutbox repoActor
grantID <- lift $ insertEmptyOutboxItem repoOutboxID now
lift $ insertCollab repoID grantID
-- Insert a Grant activity to repo's outbox
let grantRecipActors = [LocalActorPerson senderHash]
grantRecipStages = [LocalStagePersonFollowers senderHash]
docGrant <-
lift $ insertGrantToOutbox senderHash repoHash obiidCreate grantID grantRecipActors grantRecipStages
-- Deliver the Grant activity to local recipients, and schedule
-- delivery for unavailable remote recipients
remoteRecipsHttpGrant <- do
remoteRecips <-
lift $ deliverLocal' True (LocalActorRepo repoHash) repoActorID grantID $
makeRecipientSet grantRecipActors grantRecipStages
checkFederation remoteRecips
lift $ deliverRemoteDB'' [] grantID [] remoteRecips
-- Insert follow record
obiidFollow <- lift $ insertEmptyOutboxItem (actorOutbox senderActor) now
obiidAccept <- lift $ insertEmptyOutboxItem repoOutboxID now
lift $ insert_ $ Follow (personActor personUser) (actorFollowers repoActor) True obiidFollow obiidAccept
-- Insert a Follow activity to sender's outbox, and an Accept to the
-- repo's outbox
luFollow <- lift $ insertFollowToOutbox senderHash repoHash obiidFollow
lift $ insertAcceptToOutbox senderHash repoHash obiidAccept luFollow
-- Deliver the Follow and Accept by simply manually inserting them to
-- repo and sender inboxes respectively
lift $ do
ibiidF <- insert $ InboxItem False now
insert_ $ InboxItemLocal (actorInbox repoActor) obiidFollow ibiidF
ibiidA <- insert $ InboxItem False now
insert_ $ InboxItemLocal (actorInbox senderActor) obiidAccept ibiidA
-- Return instructions for HTTP delivery to remote recipients
return
( obiidCreate
, repoHash
, deliverRemoteHttp' fwdHosts obiidCreate docCreate remoteRecipsHttpCreate
, deliverRemoteHttp' [] grantID docGrant remoteRecipsHttpGrant
)
-- Insert new repo to filesystem
lift $ createRepo newRepoHash
-- Launch asynchronous HTTP delivery of Create and Grant
lift $ do
forkWorker "createRepositoryC: async HTTP Create delivery" deliverHttpCreate
forkWorker "createRepositoryC: async HTTP Grant delivery" deliverHttpGrant
return obiid
where
parseDetail (AP.ActorDetail typ muser mname msummary) = do
unless (typ == AP.ActorTypeRepo) $
error "createRepositoryC: Create object isn't a Repository"
verifyNothingE muser "Repository can't have a username"
name <- fromMaybeE mname "Repository doesn't specify name"
return (name, msummary)
insertRepo now name msummary createID = do
actor@(Entity actorID _) <-
insertActor now name (fromMaybe "" msummary)
repoID <- insert Repo
{ repoVcs = vcs
, repoProject = Nothing
, repoMainBranch = "main"
, repoCollabUser = Nothing
, repoCollabAnon = Nothing
, repoActor = actorID
, repoCreate = createID
}
return (repoID, actor)
insertCreateToOutbox senderHash now blinded name msummary obiidCreate repoHash = do
encodeRouteLocal <- getEncodeRouteLocal
hLocal <- asksSite siteInstanceHost
obikhid <- encodeKeyHashid obiidCreate
let rdetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeRepo
, AP.actorUsername = Nothing
, AP.actorName = Just name
, AP.actorSummary = msummary
}
rlocal = AP.ActorLocal
{ AP.actorId = encodeRouteLocal $ RepoR repoHash
, AP.actorInbox = encodeRouteLocal $ RepoInboxR repoHash
, AP.actorOutbox = Nothing
, AP.actorFollowers = Nothing
, AP.actorFollowing = Nothing
, AP.actorPublicKeys = []
, AP.actorSshKeys = []
}
create = Doc hLocal Activity
{ activityId = Just $ encodeRouteLocal $ PersonOutboxItemR senderHash obikhid
, activityActor = encodeRouteLocal $ PersonR senderHash
, activityCapability = Nothing
, activitySummary = summary
, activityAudience = blinded
, activityFulfills = []
, activitySpecific = CreateActivity Create
{ createObject = CreateRepository rdetail vcs (Just (hLocal, rlocal))
, createTarget = Nothing
}
}
update obiidCreate [OutboxItemActivity =. persistJSONObjectFromDoc create]
return create
insertCollab repoID grantID = do
collabID <- insert Collab
insert_ $ CollabTopicRepo collabID repoID
insert_ $ CollabEnable collabID grantID
insert_ $ CollabRecipLocal collabID pidUser
insert_ $ CollabFulfillsLocalTopicCreation collabID
insertGrantToOutbox adminHash repoHash obiidCreate obiidGrant actors stages = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
obikhidCreate <- encodeKeyHashid obiidCreate
obikhidGrant <- encodeKeyHashid obiidGrant
let recips =
map encodeRouteHome $
map renderLocalActor actors ++
map renderLocalStage stages
grant = Doc hLocal Activity
{ activityId =
Just $ encodeRouteLocal $
RepoOutboxItemR repoHash obikhidGrant
, activityActor = encodeRouteLocal $ RepoR repoHash
, activityCapability = Nothing
, activitySummary = Nothing
, activityAudience = Audience recips [] [] [] [] []
, activityFulfills =
[encodeRouteHome $ PersonOutboxItemR adminHash obikhidCreate]
, activitySpecific = GrantActivity Grant
{ grantObject = Left RoleAdmin
, grantContext = encodeRouteHome $ RepoR repoHash
, grantTarget = encodeRouteHome $ PersonR adminHash
}
}
update obiidGrant [OutboxItemActivity =. persistJSONObjectFromDoc grant]
return grant
insertFollowToOutbox senderHash repoHash obiidFollow = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hLocal <- asksSite siteInstanceHost
obikhid <- encodeKeyHashid obiidFollow
let luFollow = encodeRouteLocal $ PersonOutboxItemR senderHash obikhid
recips = [encodeRouteHome $ RepoR repoHash]
doc = Doc hLocal Activity
{ activityId = Just luFollow
, activityActor = encodeRouteLocal $ PersonR senderHash
, activityCapability = Nothing
, activitySummary = Nothing
, activityAudience = AP.Audience recips [] [] [] [] []
, activityFulfills = []
, activitySpecific = FollowActivity AP.Follow
{ AP.followObject = encodeRouteHome $ RepoR repoHash
, AP.followContext = Nothing
, AP.followHide = False
}
}
update obiidFollow [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return luFollow
insertAcceptToOutbox senderHash repoHash obiidAccept luFollow = do
hLocal <- asksSite siteInstanceHost
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
obikhid <- encodeKeyHashid obiidAccept
let recips = [encodeRouteHome $ PersonR senderHash]
doc = Doc hLocal Activity
{ activityId = Just $ encodeRouteLocal $ RepoOutboxItemR repoHash obikhid
, activityActor = encodeRouteLocal $ RepoR repoHash
, activityCapability = Nothing
, activitySummary = Nothing
, activityAudience = Audience recips [] [] [] [] []
, activityFulfills = []
, activitySpecific = AcceptActivity Accept
{ acceptObject = ObjURI hLocal luFollow
, acceptResult = Nothing
}
}
update obiidAccept [OutboxItemActivity =. persistJSONObjectFromDoc doc]
createRepo repoHash = do
root <- askRepoRootDir
liftIO $ createDirectoryIfMissing True root
host <- asksSite siteInstanceHost
case vcs of
VCSDarcs -> do
hook <- getsYesod $ appPostApplyHookFile . appSettings
liftIO $
D.createRepo
root
(keyHashidText repoHash)
hook
(renderAuthority host)
VCSGit -> do
hook <- getsYesod $ appPostReceiveHookFile . appSettings
liftIO $
G.createRepo
root
(keyHashidText repoHash)
hook
(renderAuthority host)
createTicketTrackerC createTicketTrackerC
:: Entity Person :: Entity Person
-> Actor -> Actor

View file

@ -30,6 +30,7 @@ module Vervis.Client
--, unresolve --, unresolve
--, offerMR --, offerMR
createDeck createDeck
, createRepo
) )
where where
@ -619,3 +620,28 @@ createDeck senderHash name desc = do
} }
return (Nothing, AP.Audience recips [] [] [] [] [], detail) return (Nothing, AP.Audience recips [] [] [] [] [], detail)
createRepo
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> KeyHashid Person
-> Text
-> Text
-> m (Maybe TextHtml, Audience URIMode, AP.ActorDetail)
createRepo senderHash name desc = do
encodeRouteHome <- getEncodeRouteHome
let audAuthor =
AudLocal [] [LocalStagePersonFollowers senderHash]
(_, _, _, audLocal, audRemote) = collectAudience [audAuthor]
recips = map encodeRouteHome audLocal ++ audRemote
detail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeRepo
, AP.actorUsername = Nothing
, AP.actorName = Just name
, AP.actorSummary = Just desc
}
return (Nothing, AP.Audience recips [] [] [] [] [], detail)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2019, 2022 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.
- -
@ -16,53 +16,41 @@
module Vervis.Form.Repo module Vervis.Form.Repo
( NewRepo (..) ( NewRepo (..)
, newRepoForm , newRepoForm
, NewRepoCollab (..) --, NewRepoCollab (..)
, newRepoCollabForm --, newRepoCollabForm
, editRepoForm --, editRepoForm
) )
where where
import Data.Text (Text) import Data.Text (Text)
import Database.Persist
import Yesod.Form.Fields import Yesod.Form.Fields
import Yesod.Form.Functions import Yesod.Form.Functions
import Yesod.Form.Types import Yesod.Form.Types
import Vervis.Field.Repo
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType import Development.PatchMediaType
import Vervis.Foundation
import Vervis.Model
data NewRepo = NewRepo data NewRepo = NewRepo
{ nrpIdent :: RpIdent { nrpName :: Text
, nrpDesc :: Text
, nrpVcs :: VersionControlSystem , nrpVcs :: VersionControlSystem
, nrpProj :: Maybe ProjectId
, nrpDesc :: Maybe Text
, nrpRole :: Maybe RoleId
} }
newRepoAForm :: SharerId -> Maybe ProjectId -> AForm Handler NewRepo newRepoForm :: Form NewRepo
newRepoAForm sid mjid = NewRepo newRepoForm = renderDivs $ NewRepo
<$> (text2rp <$> areq (mkIdentField sid) "Identifier*" Nothing) <$> areq textField "Name*" Nothing
<*> areq textField "Description" Nothing
<*> areq (selectFieldList vcsList) "Version control system*" Nothing <*> areq (selectFieldList vcsList) "Version control system*" Nothing
<*> aopt (selectProjectForNew sid) "Project" (Just mjid)
<*> aopt textField "Description" Nothing
<*> aopt selectRole "Custom role" Nothing
where where
vcsList :: [(Text, VersionControlSystem)] vcsList :: [(Text, VersionControlSystem)]
vcsList = vcsList =
[ ("Darcs", VCSDarcs) [ ("Darcs", VCSDarcs)
, ("Git" , VCSGit) , ("Git" , VCSGit)
] ]
selectRole =
selectField $
optionsPersistKey [RoleSharer ==. sid] [] $
rl2text . roleIdent
newRepoForm :: SharerId -> Maybe ProjectId -> Form NewRepo
newRepoForm sid mjid = renderDivs $ newRepoAForm sid mjid
{-
data NewRepoCollab = NewRepoCollab data NewRepoCollab = NewRepoCollab
{ ncPerson :: PersonId { ncPerson :: PersonId
, ncRole :: Maybe RoleId , ncRole :: Maybe RoleId
@ -111,3 +99,4 @@ editRepoAForm sid (Entity rid repo) = Repo
editRepoForm :: SharerId -> Entity Repo -> Form Repo editRepoForm :: SharerId -> Entity Repo -> Form Repo
editRepoForm s r = renderDivs $ editRepoAForm s r editRepoForm s r = renderDivs $ editRepoAForm s r
-}

View file

@ -89,6 +89,7 @@ import Control.Monad.Trans.Except.Local
import Data.Either.Local import Data.Either.Local
import Data.Paginate.Local import Data.Paginate.Local
import Database.Persist.Local import Database.Persist.Local
import Yesod.Form.Local
import Yesod.Persist.Local import Yesod.Persist.Local
import Vervis.Access import Vervis.Access
@ -303,20 +304,10 @@ getDeckNewR = do
((_result, widget), enctype) <- runFormPost newProjectForm ((_result, widget), enctype) <- runFormPost newProjectForm
defaultLayout $(widgetFile "project/new") defaultLayout $(widgetFile "project/new")
runForm here form = do
((result, widget), enctype) <- runFormPost $ newProjectForm
case result of
FormMissing -> do
setMessage "Field(s) missing"
redirect here
FormFailure _l -> do
setMessage "Operation failed, see below"
redirect here
FormSuccess v -> return (v, widget, enctype)
postDeckNewR :: Handler Html postDeckNewR :: Handler Html
postDeckNewR = do postDeckNewR = do
(NewProject name desc, _widget, _enctype) <- runForm DeckNewR newProjectForm (NewProject name desc, _widget, _enctype) <-
runFormPostRedirect DeckNewR newProjectForm
personEntity@(Entity personID person) <- requireAuth personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID personHash <- encodeKeyHashid personID

View file

@ -282,6 +282,8 @@ postPersonOutboxR personHash = do
-} -}
AP.CreateTicketTracker detail mlocal -> AP.CreateTicketTracker detail mlocal ->
createTicketTrackerC eperson actorDB summary audience detail mlocal mtarget createTicketTrackerC eperson actorDB summary audience detail mlocal mtarget
AP.CreateRepository detail vcs mlocal ->
createRepositoryC eperson actorDB summary audience detail vcs mlocal mtarget
_ -> throwE "Unsupported Create 'object' type" _ -> throwE "Unsupported Create 'object' type"
AP.InviteActivity invite -> AP.InviteActivity invite ->
inviteC eperson actorDB mcap summary audience invite inviteC eperson actorDB mcap summary audience invite

View file

@ -108,7 +108,7 @@ import System.IO
import System.Process import System.Process
import Text.Blaze.Html (Html) import Text.Blaze.Html (Html)
import Text.Pandoc.Highlighting import Text.Pandoc.Highlighting
import Yesod.Auth (requireAuthId) import Yesod.Auth
import Yesod.Core hiding (joinPath) import Yesod.Core hiding (joinPath)
import Yesod.Core.Content import Yesod.Core.Content
import Yesod.Core.Handler (lookupPostParam, redirect, notFound) import Yesod.Core.Handler (lookupPostParam, redirect, notFound)
@ -143,6 +143,7 @@ import Data.Either.Local
import Data.Git.Local import Data.Git.Local
import Database.Persist.Local import Database.Persist.Local
import Text.FilePath.Local (breakExt) import Text.FilePath.Local (breakExt)
import Yesod.Form.Local
import Yesod.Persist.Local import Yesod.Persist.Local
import qualified Data.Git.Local as G (createRepo) import qualified Data.Git.Local as G (createRepo)
@ -153,6 +154,7 @@ import Vervis.API
import Vervis.Federation.Auth import Vervis.Federation.Auth
import Vervis.Federation.Collab import Vervis.Federation.Collab
import Vervis.FedURI import Vervis.FedURI
import Vervis.Form.Repo
import Vervis.Foundation import Vervis.Foundation
import Vervis.Path import Vervis.Path
import Vervis.Model import Vervis.Model
@ -165,6 +167,7 @@ import Vervis.SourceTree
import Vervis.Style import Vervis.Style
import Vervis.Web.Actor import Vervis.Web.Actor
import qualified Vervis.Client as C
import qualified Vervis.Formatting as F import qualified Vervis.Formatting as F
import qualified Vervis.Hook as H import qualified Vervis.Hook as H
@ -406,86 +409,33 @@ getRepoCommitR repoHash ref = do
getRepoNewR :: Handler Html getRepoNewR :: Handler Html
getRepoNewR = do getRepoNewR = do
error "Temporarily disabled" ((_result, widget), enctype) <- runFormPost newRepoForm
--Entity sid _sharer <- runDB $ getBy404 $ UniqueSharer user defaultLayout $(widgetFile "repo/new")
--((_result, widget), enctype) <- runFormPost $ newRepoForm sid Nothing
--defaultLayout $(widgetFile "repo/new")
postRepoNewR :: Handler Html postRepoNewR :: Handler Html
postRepoNewR = do postRepoNewR = do
error "Temporarily disabled" (NewRepo name desc vcs, _widget, _enctype) <-
{- runFormPostRedirect RepoNewR newRepoForm
Entity sid _sharer <- runDB $ getBy404 $ UniqueSharer user
((result, widget), enctype) <- runFormPost $ newRepoForm sid Nothing
case result of
FormSuccess nrp -> do
now <- liftIO getCurrentTime
parent <- askSharerDir user
liftIO $ createDirectoryIfMissing True parent
let repoName =
unpack $ CI.foldedCase $ unRpIdent $ nrpIdent nrp
host <- asksSite siteInstanceHost
case nrpVcs nrp of
VCSDarcs -> do
hook <- getsYesod $ appPostApplyHookFile . appSettings
liftIO $
D.createRepo
parent
repoName
hook
(renderAuthority host)
(shr2text user)
(rp2text $ nrpIdent nrp)
VCSGit -> do
hook <- getsYesod $ appPostReceiveHookFile . appSettings
liftIO $
G.createRepo
parent
repoName
hook
(renderAuthority host)
(shr2text user)
(rp2text $ nrpIdent nrp)
pid <- requireAuthId
runDB $ do
ibid <- insert Inbox
obid <- insert Outbox
fsid <- insert FollowerSet
let repo = Repo
{ repoIdent = nrpIdent nrp
, repoSharer = sid
, repoVcs = nrpVcs nrp
, repoProject = nrpProj nrp
, repoDesc = nrpDesc nrp
, repoMainBranch = "master"
, repoCollabUser = Nothing
, repoCollabAnon = Nothing
, repoInbox = ibid
, repoOutbox = obid
, repoFollowers = fsid
}
rid <- insert repo
obiid <- personEntity@(Entity personID person) <- requireAuth
insert $ personHash <- encodeKeyHashid personID
OutboxItem (maybeSummary, audience, detail) <- C.createRepo personHash name desc
obid actor <- runDB $ getJust $ personActor person
(persistJSONObjectFromDoc $ Doc host emptyActivity) result <-
now runExceptT $ createRepositoryC personEntity actor maybeSummary audience detail vcs Nothing Nothing
cid <- insert Collab
for_ (nrpRole nrp) $ \ rlid -> insert_ $ CollabRoleLocal cid rlid case result of
insert_ $ CollabTopicLocalRepo cid rid Left e -> do
insert_ $ CollabSenderLocal cid obiid setMessage $ toHtml e
insert_ $ CollabRecipLocal cid pid redirect RepoNewR
setMessage "Repo added." Right createID -> do
redirect $ RepoR user (nrpIdent nrp) maybeRepoID <- runDB $ getKeyBy $ UniqueRepoCreate createID
FormMissing -> do case maybeRepoID of
setMessage "Field(s) missing" Nothing -> error "Can't find the newly created repo"
defaultLayout $(widgetFile "repo/new") Just repoID -> do
FormFailure _l -> do repoHash <- encodeKeyHashid repoID
setMessage "Repo creation failed, see errors below" setMessage "New repository created"
defaultLayout $(widgetFile "repo/new") redirect $ RepoR repoHash
-}
postRepoDeleteR :: KeyHashid Repo -> Handler Html postRepoDeleteR :: KeyHashid Repo -> Handler Html
postRepoDeleteR repoHash = do postRepoDeleteR repoHash = do

View file

@ -17,6 +17,7 @@ module Vervis.Persist.Actor
( getLocalActor ( getLocalActor
, verifyLocalActivityExistsInDB , verifyLocalActivityExistsInDB
, getRemoteActorURI , getRemoteActorURI
, insertActor
) )
where where
@ -77,3 +78,18 @@ getRemoteActorURI actor = do
ObjURI ObjURI
(instanceHost inztance) (instanceHost inztance)
(remoteObjectIdent object) (remoteObjectIdent object)
insertActor now name desc = do
ibid <- insert Inbox
obid <- insert Outbox
fsid <- insert FollowerSet
let actor = Actor
{ actorName = name
, actorDesc = desc
, actorCreatedAt = now
, actorInbox = ibid
, actorOutbox = obid
, actorFollowers = fsid
}
actorID <- insert actor
return $ Entity actorID actor

View file

@ -1440,6 +1440,7 @@ data CreateObject u
= CreateNote (Authority u) (Note u) = CreateNote (Authority u) (Note u)
| CreateTicket (Authority u) (Ticket u) | CreateTicket (Authority u) (Ticket u)
| CreateTicketTracker ActorDetail (Maybe (Authority u, ActorLocal u)) | CreateTicketTracker ActorDetail (Maybe (Authority u, ActorLocal u))
| CreateRepository ActorDetail VersionControlSystem (Maybe (Authority u, ActorLocal u))
parseCreateObject :: UriMode u => Object -> Parser (CreateObject u) parseCreateObject :: UriMode u => Object -> Parser (CreateObject u)
parseCreateObject o parseCreateObject o
@ -1450,12 +1451,22 @@ parseCreateObject o
fail "type isn't TicketTracker" fail "type isn't TicketTracker"
ml <- parseActorLocal o ml <- parseActorLocal o
return $ CreateTicketTracker d ml return $ CreateTicketTracker d ml
<|> do d <- parseActorDetail o
unless (actorType d == ActorTypeRepo) $
fail "type isn't Repository"
vcs <- o .: "versionControlSystem"
ml <- parseActorLocal o
return $ CreateRepository d vcs ml
encodeCreateObject :: UriMode u => CreateObject u -> Series encodeCreateObject :: UriMode u => CreateObject u -> Series
encodeCreateObject (CreateNote h note) = toSeries h note encodeCreateObject (CreateNote h note) = toSeries h note
encodeCreateObject (CreateTicket h ticket) = toSeries h ticket encodeCreateObject (CreateTicket h ticket) = toSeries h ticket
encodeCreateObject (CreateTicketTracker d ml) = encodeCreateObject (CreateTicketTracker d ml) =
encodeActorDetail d <> maybe mempty (uncurry encodeActorLocal) ml encodeActorDetail d <> maybe mempty (uncurry encodeActorLocal) ml
encodeCreateObject (CreateRepository d vcs ml)
= encodeActorDetail d
<> "versionControlSystem" .= vcs
<> maybe mempty (uncurry encodeActorLocal) ml
data Create u = Create data Create u = Create
{ createObject :: CreateObject u { createObject :: CreateObject u
@ -1473,6 +1484,7 @@ parseCreate o a luActor = do
unless (a == h && luActor == ticketAttributedTo ticket) $ unless (a == h && luActor == ticketAttributedTo ticket) $
fail "Create actor != note attrib" fail "Create actor != note attrib"
CreateTicketTracker _ _ -> return () CreateTicketTracker _ _ -> return ()
CreateRepository _ _ _ -> return ()
Create obj <$> o .:? "target" Create obj <$> o .:? "target"
encodeCreate :: UriMode u => Create u -> Series encodeCreate :: UriMode u => Create u -> Series

33
src/Yesod/Form/Local.hs Normal file
View file

@ -0,0 +1,33 @@
{- This file is part of Vervis.
-
- Written in 2022 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 Yesod.Form.Local
( runFormPostRedirect
)
where
import Yesod.Core.Handler
import Yesod.Form
runFormPostRedirect here form = do
((result, widget), enctype) <- runFormPost form
case result of
FormMissing -> do
setMessage "Field(s) missing"
redirect here
FormFailure _l -> do
setMessage "Operation failed, see below"
redirect here
FormSuccess v -> return (v, widget, enctype)

View file

@ -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, 2022 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.
$# $#
@ -12,7 +12,7 @@ $# You should have received a copy of the CC0 Public Domain Dedication along
$# 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/>.
<form method=POST action=@{ReposR user} enctype=#{enctype}> <form method=POST action=@{RepoNewR} enctype=#{enctype}>
^{widget} ^{widget}
<div class="submit"> <div class="submit">
<input type="submit"> <input type="submit">

View file

@ -115,6 +115,7 @@ library
Yesod.Auth.Unverified.Creds Yesod.Auth.Unverified.Creds
Yesod.Auth.Unverified.Internal Yesod.Auth.Unverified.Internal
Yesod.FedURI Yesod.FedURI
Yesod.Form.Local
Yesod.Hashids Yesod.Hashids
Yesod.MonadSite Yesod.MonadSite
Yesod.Paginate.Local Yesod.Paginate.Local
@ -163,7 +164,7 @@ library
--Vervis.Form.Group --Vervis.Form.Group
-- Vervis.Form.Key -- Vervis.Form.Key
Vervis.Form.Project Vervis.Form.Project
--Vervis.Form.Repo Vervis.Form.Repo
--Vervis.Form.Role --Vervis.Form.Role
--Vervis.Form.Ticket --Vervis.Form.Ticket
-- Vervis.Form.Workflow -- Vervis.Form.Workflow