diff --git a/src/Vervis/Client.hs b/src/Vervis/Client.hs index f1a3862..9d48d22 100644 --- a/src/Vervis/Client.hs +++ b/src/Vervis/Client.hs @@ -28,16 +28,19 @@ module Vervis.Client --, undoFollowTicket --, undoFollowRepo --, unresolve - --, offerMR - createDeck + offerPatches + , offerMerge + , createDeck , createLoom , createRepo ) where +import Control.Exception.Base import Control.Monad import Control.Monad.Trans.Except import Control.Monad.Trans.Reader +import Data.Bifunctor import Data.Bitraversable import Data.List.NonEmpty (NonEmpty (..)) import Data.Maybe @@ -51,6 +54,7 @@ import Yesod.Core import Yesod.Core.Handler import Yesod.Persist.Core +import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import qualified Data.Text.Lazy as TL @@ -62,7 +66,6 @@ import Yesod.ActivityPub import Yesod.FedURI import Yesod.Hashids import Yesod.MonadSite -import Yesod.RenderSource import qualified Web.ActivityPub as AP @@ -71,10 +74,12 @@ import Data.Either.Local import Database.Persist.Local import Vervis.ActivityPub +import Vervis.Data.Ticket import Vervis.FedURI import Vervis.Foundation import Vervis.Model import Vervis.Recipient +import Vervis.RemoteActorStore import Vervis.Ticket import Vervis.WorkItem @@ -529,75 +534,193 @@ unresolve shrUser uTicket = runExceptT $ do recips = map encodeRouteHome audLocal ++ audRemote return (Nothing, Audience recips [] [] [] [] [], Undo uResolve) -} +-} -offerMR - :: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App) - => ShrIdent - -> TextHtml - -> TextPandocMarkdown - -> FedURI - -> Maybe FedURI - -> PatchMediaType +offerPatches + :: KeyHashid Person -> Text - -> m (Either Text (Maybe TextHtml, Audience URIMode, AP.Ticket URIMode)) -offerMR shrAuthor title desc uContext muBranch typ diff = runExceptT $ do - error "Temporarily disabled" - {- + -> PandocMarkdown + -> FedURI + -> FedURI + -> Maybe Text + -> PatchMediaType + -> NonEmpty Text + -> ExceptT Text Handler (Maybe HTML, AP.Audience URIMode, AP.Ticket URIMode) +offerPatches senderHash title desc uTracker uTargetRepo maybeBranch typ diffs = do + + tracker <- do + tracker <- checkTracker uTracker + case tracker of + TrackerDeck _ -> throwE "Local ticket tracker doesn't take patches" + TrackerLoom loomID -> Left <$> encodeKeyHashid loomID + TrackerRemote (ObjURI hTracker luTracker) -> Right <$> do + instanceID <- lift $ runDB $ either entityKey id <$> insertBy' (Instance hTracker) + result <- ExceptT $ first (T.pack . displayException) <$> fetchRemoteActor instanceID hTracker luTracker + case result of + Left Nothing -> throwE "Tracker @id mismatch" + Left (Just err) -> throwE $ T.pack $ displayException err + Right Nothing -> throwE "Tracker isn't an actor" + Right (Just actor) -> return (entityVal actor, uTracker) + + descHtml <- ExceptT . pure $ renderPandocMarkdown desc + encodeRouteLocal <- getEncodeRouteLocal encodeRouteHome <- getEncodeRouteHome - manager <- asksSite appHttpManager hLocal <- asksSite siteInstanceHost - context <- parseTicketContext uContext - descHtml <- - ExceptT . pure $ renderPandocMarkdown $ unTextPandocMarkdown desc - context' <- bitraverse pure (getRemoteContextHttp "Context") context - let audAuthor = - AudLocal - [] - [LocalPersonCollectionSharerFollowers shrAuthor] - audContext = contextAudience context' + AudLocal [] [LocalStagePersonFollowers senderHash] + audTracker = + case tracker of + Left loomHash -> + AudLocal + [LocalActorLoom loomHash] + [LocalStageLoomFollowers loomHash] + Right (remoteActor, ObjURI hTracker luTracker) -> + AudRemote hTracker + [luTracker] + (maybeToList $ remoteActorFollowers remoteActor) + (_, _, _, audLocal, audRemote) = - collectAudience $ audAuthor : audContext - + collectAudience [audAuthor, audTracker] recips = map encodeRouteHome audLocal ++ audRemote - ObjURI hBranch luBranch = fromMaybe uContext muBranch - luAuthor = encodeRouteLocal $ SharerR shrAuthor + luSender = encodeRouteLocal $ PersonR senderHash + ObjURI hTargetRepo luTargetRepo = uTargetRepo ticket = AP.Ticket { AP.ticketLocal = Nothing - , AP.ticketAttributedTo = luAuthor + , AP.ticketAttributedTo = luSender , AP.ticketPublished = Nothing , AP.ticketUpdated = Nothing , AP.ticketContext = Nothing - , AP.ticketSummary = title - , AP.ticketContent = TextHtml descHtml + , AP.ticketSummary = encodeEntities title + , AP.ticketContent = descHtml , AP.ticketSource = desc , AP.ticketAssignedTo = Nothing , AP.ticketResolved = Nothing , AP.ticketAttachment = Just - ( hBranch + ( hTargetRepo , MergeRequest { mrOrigin = Nothing - , mrTarget = luBranch - , mrBundle = Right + , mrTarget = + case maybeBranch of + Nothing -> Left luTargetRepo + Just b -> Right AP.Branch + { AP.branchName = b + , AP.branchRef = "refs/heads/" <> b + , AP.branchRepo = luTargetRepo + } + , mrBundle = Just $ Right ( hLocal - , BundleOffer Nothing $ pure AP.Patch - { AP.patchLocal = Nothing - , AP.patchAttributedTo = luAuthor - , AP.patchPublished = Nothing - , AP.patchType = typ - , AP.patchContent = diff - } + , BundleOffer Nothing $ NE.reverse $ NE.map + (\ diff -> AP.Patch + { AP.patchLocal = Nothing + , AP.patchAttributedTo = luSender + , AP.patchPublished = Nothing + , AP.patchType = typ + , AP.patchContent = diff + } + ) + diffs ) } ) } - return (Nothing, Audience recips [] [] [] [] [], ticket) - -} --} + + return (Nothing, AP.Audience recips [] [] [] [] [], ticket) + +offerMerge + :: KeyHashid Person + -> Text + -> PandocMarkdown + -> FedURI + -> FedURI + -> Maybe Text + -> FedURI + -> Maybe Text + -> ExceptT Text Handler (Maybe HTML, AP.Audience URIMode, AP.Ticket URIMode) +offerMerge senderHash title desc uTracker uTargetRepo maybeTargetBranch uOriginRepo maybeOriginBranch = do + + tracker <- do + tracker <- checkTracker uTracker + case tracker of + TrackerDeck _ -> throwE "Local ticket tracker doesn't take patches" + TrackerLoom loomID -> Left <$> encodeKeyHashid loomID + TrackerRemote (ObjURI hTracker luTracker) -> Right <$> do + instanceID <- lift $ runDB $ either entityKey id <$> insertBy' (Instance hTracker) + result <- ExceptT $ first (T.pack . displayException) <$> fetchRemoteActor instanceID hTracker luTracker + case result of + Left Nothing -> throwE "Tracker @id mismatch" + Left (Just err) -> throwE $ T.pack $ displayException err + Right Nothing -> throwE "Tracker isn't an actor" + Right (Just actor) -> return (entityVal actor, uTracker) + + descHtml <- ExceptT . pure $ renderPandocMarkdown desc + + encodeRouteLocal <- getEncodeRouteLocal + encodeRouteHome <- getEncodeRouteHome + hLocal <- asksSite siteInstanceHost + + let audAuthor = + AudLocal [] [LocalStagePersonFollowers senderHash] + audTracker = + case tracker of + Left loomHash -> + AudLocal + [LocalActorLoom loomHash] + [LocalStageLoomFollowers loomHash] + Right (remoteActor, ObjURI hTracker luTracker) -> + AudRemote hTracker + [luTracker] + (maybeToList $ remoteActorFollowers remoteActor) + + + (_, _, _, audLocal, audRemote) = + collectAudience [audAuthor, audTracker] + recips = map encodeRouteHome audLocal ++ audRemote + + ObjURI hTargetRepo luTargetRepo = uTargetRepo + ObjURI hOriginRepo luOriginRepo = uOriginRepo + ticket = AP.Ticket + { AP.ticketLocal = Nothing + , AP.ticketAttributedTo = encodeRouteLocal $ PersonR senderHash + , AP.ticketPublished = Nothing + , AP.ticketUpdated = Nothing + , AP.ticketContext = Nothing + , AP.ticketSummary = encodeEntities title + , AP.ticketContent = descHtml + , AP.ticketSource = desc + , AP.ticketAssignedTo = Nothing + , AP.ticketResolved = Nothing + , AP.ticketAttachment = Just + ( hTargetRepo + , MergeRequest + { mrOrigin = + Just $ case maybeOriginBranch of + Nothing -> Left uOriginRepo + Just b -> Right + ( hOriginRepo + , AP.Branch + { AP.branchName = b + , AP.branchRef = "refs/heads/" <> b + , AP.branchRepo = luOriginRepo + } + ) + , mrTarget = + case maybeTargetBranch of + Nothing -> Left luTargetRepo + Just b -> Right AP.Branch + { AP.branchName = b + , AP.branchRef = "refs/heads/" <> b + , AP.branchRepo = luTargetRepo + } + , mrBundle = Nothing + } + ) + } + + return (Nothing, AP.Audience recips [] [] [] [] [], ticket) createDeck :: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App) diff --git a/src/Vervis/Data/Ticket.hs b/src/Vervis/Data/Ticket.hs index f1ff7c3..05f3daf 100644 --- a/src/Vervis/Data/Ticket.hs +++ b/src/Vervis/Data/Ticket.hs @@ -20,6 +20,10 @@ module Vervis.Data.Ticket , TrackerAndMerge (..) , WorkItemOffer (..) , checkOfferTicket + + -- These are exported only for Vervis.Client + , Tracker (..) + , checkTracker ) where diff --git a/src/Vervis/Foundation.hs b/src/Vervis/Foundation.hs index c1ebdd0..a1004d9 100644 --- a/src/Vervis/Foundation.hs +++ b/src/Vervis/Foundation.hs @@ -308,8 +308,8 @@ instance Yesod App where -- Client - (NotificationsR, _ ) -> personAny - (PublishR , True) -> personAny + (NotificationsR, _ ) -> personAny + (PublishOfferMergeR, True) -> personAny -- Person diff --git a/src/Vervis/Handler/Client.hs b/src/Vervis/Handler/Client.hs index 107b25c..ef7ccf0 100644 --- a/src/Vervis/Handler/Client.hs +++ b/src/Vervis/Handler/Client.hs @@ -26,6 +26,9 @@ module Vervis.Handler.Client , getPublishR , postPublishR , getInboxDebugR + + , getPublishOfferMergeR + , postPublishOfferMergeR ) where @@ -64,23 +67,28 @@ import Dvara import Database.Persist.JSON import Network.FedURI +import Web.Text import Yesod.ActivityPub import Yesod.Auth.Unverified import Yesod.FedURI import Yesod.Hashids +import Yesod.MonadSite import Yesod.RenderSource import qualified Web.ActivityPub as AP +import Control.Monad.Trans.Except.Local import Data.Either.Local import Data.EventTime.Local import Data.Time.Clock.Local import Database.Persist.Local +import Yesod.Form.Local import Yesod.Persist.Local import Vervis.ActivityPub import Vervis.ActorKey import Vervis.API +import Vervis.Client import Vervis.FedURI import Vervis.Foundation import Vervis.Model @@ -1010,3 +1018,127 @@ postProjectTicketOpenR shr prj ltkhid = do Right _obiid -> setMessage "Ticket reopened" redirect $ ProjectTicketR shr prj ltkhid -} + +fedUriField + :: (Monad m, RenderMessage (HandlerSite m) FormMessage) => Field m FedURI +fedUriField = Field + { fieldParse = parseHelper $ \ t -> + case parseObjURI t of + Left e -> Left $ MsgInvalidUrl $ T.pack e <> ": " <> t + Right u -> Right u + , fieldView = \theId name attrs val isReq -> + [whamlet||] + , fieldEnctype = UrlEncoded + } + +getSender :: Handler (Entity Person, Actor) +getSender = do + ep@(Entity _ p) <- requireAuth + a <- runDB $ getJust $ personActor p + return (ep, a) + +data OfferMergeGit = OfferMergeGit + { omgTracker :: FedURI + , omgTargetRepo :: FedURI + , omgTargetBranch :: Text + , omgOriginRepo :: FedURI + , omgOriginBranch :: Text + , omgTitle :: Text + , omgDesc :: PandocMarkdown + } + +offerMergeGitForm :: Form OfferMergeGit +offerMergeGitForm = renderDivs $ OfferMergeGit + <$> areq fedUriField "Patch tracker URL" Nothing + <*> areq fedUriField "Target repo URL" Nothing + <*> areq textField "Target branch (e.g. main)" Nothing + <*> areq fedUriField "Origin repo URL" Nothing + <*> areq textField "Origin branch (e.g. fix-the-bug)" Nothing + <*> areq textField "Title" Nothing + <*> (pandocMarkdownFromText . T.filter (/= '\r') . unTextarea <$> + areq textareaField "Description" Nothing + ) + +{- +data OfferMergeGit = OfferMergeGit + { omgTracker :: FedURI + , omgTarget :: (FedURI, Text) + , omgOrigin :: (FedURI, Text) + , omgTitle :: Text + , omgDesc :: PandocMarkdown + PatchMediaType + FileInfo +-} + +{- +offerMergeForm :: Form (FedURI, Maybe FedURI, TextHtml, TextPandocMarkdown, PatchMediaType, FileInfo) +offerMergeForm = renderDivs $ (,,,,,) + <$> areq fedUriField "Repo" (Just defaultRepo) + <*> aopt fedUriField "Branch URI (for Git repos)" Nothing + <*> (TextHtml . sanitizeBalance <$> areq textField "Title" Nothing) + <*> (TextPandocMarkdown . T.filter (/= '\r') . unTextarea <$> + areq textareaField "Description" Nothing + ) + <*> areq (selectFieldList pmtList) "Type" Nothing + <*> areq fileField "Patch" Nothing + where + defaultRepo = + ObjURI + (Authority "forge.angeley.es" Nothing) + (LocalURI "/s/fr33/r/one-more-darcs") + pmtList :: [(Text, PatchMediaType)] + pmtList = + [ ("Darcs", PatchMediaTypeDarcs) + ] +-} + +getPublishOfferMergeR :: Handler Html +getPublishOfferMergeR = do + ((_, widget), enctype) <- runFormPost offerMergeGitForm + defaultLayout + [whamlet| +