S2S: Loom: Port Offer{MR} handler from old federation code

This is to allow getting rid of the old C2S offerTicketC and write a C2S
Offer handler in the new actor system.

And that is to allow ticket opening and closing to work, so that it can
use delegated OCAPs too, as a first demo of delegated OCAPs in action.
This commit is contained in:
Pere Lev 2023-11-03 14:18:41 +02:00
parent 909ba94b49
commit a06003c361
No known key found for this signature in database
GPG key ID: 5252C5C863E5E57D
7 changed files with 444 additions and 351 deletions

View file

@ -18,7 +18,7 @@
module Vervis.API module Vervis.API
( handleViaActor ( handleViaActor
, acceptC --, acceptC
--, addBundleC --, addBundleC
, applyC , applyC
--, noteC --, noteC
@ -188,6 +188,7 @@ verifyRemoteAddressed remoteRecips u =
lus <- lookup h remoteRecips lus <- lookup h remoteRecips
guard $ lu `elem` lus guard $ lu `elem` lus
{-
acceptC acceptC
:: Entity Person :: Entity Person
-> Actor -> Actor
@ -203,8 +204,6 @@ acceptC
-> AP.Accept URIMode -> AP.Accept URIMode
-> ExceptT Text Handler OutboxItemId -> ExceptT Text Handler OutboxItemId
acceptC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips remoteRecips fwdHosts action accept = do acceptC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips remoteRecips fwdHosts action accept = do
error "acceptC temporarily disabled due to actor refactoring"
{-
-- Check input -- Check input
verifyNothingE maybeCap "Capability not needed" verifyNothingE maybeCap "Capability not needed"
acceptee <- parseAccept accept acceptee <- parseAccept accept
@ -1922,7 +1921,7 @@ offerTicketC (Entity senderPersonID senderPerson) senderActor maybeCap localReci
lift $ forkWorker "offerTicketC: async HTTP Offer delivery" deliverHttpOffer lift $ forkWorker "offerTicketC: async HTTP Offer delivery" deliverHttpOffer
for_ maybeAcceptMaybePull $ \ (deliverHttpAccept, maybePull) -> do for_ maybeAcceptMaybePull $ \ (deliverHttpAccept, maybePull) -> do
lift $ forkWorker "offerTicketC: async HTTP Accept delivery" deliverHttpAccept lift $ forkWorker "offerTicketC: async HTTP Accept delivery" deliverHttpAccept
traverse generatePatches maybePull VA2.runActE $ traverse generatePatches maybePull
return offerID return offerID

View file

@ -18,23 +18,40 @@ module Vervis.Actor.Loom
) )
where where
import Control.Applicative
import Control.Exception.Base
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Logger.CallStack import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Class import Control.Monad.Trans.Class
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 Data.Align
import Data.Bifunctor
import Data.Bitraversable
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.Foldable import Data.Foldable
import Data.List.NonEmpty (NonEmpty (..))
import Data.Maybe
import Data.Text (Text) import Data.Text (Text)
import Data.These
import Data.Time.Clock import Data.Time.Clock
import Data.Traversable
import Database.Persist import Database.Persist
import Database.Persist.Sql
import Optics.Core
import Yesod.Persist.Core import Yesod.Persist.Core
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.Esqueleto as E
import Control.Concurrent.Actor import Control.Concurrent.Actor
import Development.PatchMediaType
import Network.FedURI import Network.FedURI
import Web.Actor
import Web.Actor.Persist
import Yesod.MonadSite import Yesod.MonadSite
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
@ -42,19 +59,360 @@ import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local import Control.Monad.Trans.Except.Local
import Database.Persist.Local import Database.Persist.Local
import Vervis.Access
import Vervis.ActivityPub
import Vervis.Actor import Vervis.Actor
import Vervis.Actor.Common
import Vervis.Actor2
import Vervis.Cloth import Vervis.Cloth
import Vervis.Data.Actor
import Vervis.Data.Collab
import Vervis.Data.Discussion import Vervis.Data.Discussion
import Vervis.Data.Ticket
import Vervis.FedURI import Vervis.FedURI
import Vervis.Federation.Util import Vervis.Federation.Util
import Vervis.Fetch
import Vervis.Foundation import Vervis.Foundation
import Vervis.Model import Vervis.Model hiding (deckCreate)
import Vervis.Recipient (makeRecipientSet, LocalStageBy (..), Aud (..), collectAudience, localActorFollowers)
import Vervis.Persist.Actor
import Vervis.Persist.Collab
import Vervis.Persist.Discussion import Vervis.Persist.Discussion
import Vervis.RemoteActorStore
import Vervis.Ticket import Vervis.Ticket
import Vervis.Web.Repo
-- Meaning: An actor A is offering a ticket or a ticket dependency
-- Behavior:
-- * Verify I'm the target
-- * Insert the Offer to my inbox
-- * Create the new ticket in my DB
-- * Forward the Offer to my followers
-- * Publish an Accept to:
-- - My followers
-- - Offer sender+followers
loomOffer
:: UTCTime
-> LoomId
-> Verse
-> AP.Offer URIMode
-> ActE (Text, Act (), Next)
loomOffer now loomID (Verse authorIdMsig body) (AP.Offer object uTarget) = do
-- Check input
(title, desc, source, originTipOrBundle, targetRepoID, maybeTargetBranch) <- do
ticket <-
case object of
AP.OfferTicket t -> pure t
_ -> throwE "Unsupported Offer.object type"
ObjURI hAuthor _ <- lift $ getActorURI authorIdMsig
let author = bimap (view _1) (remoteAuthorURI . view _1) authorIdMsig
WorkItemOffer {..} <- checkOfferTicket hAuthor ticket uTarget
unless (bimap LocalActorPerson id wioAuthor == author) $
throwE "Offering a Ticket attributed to someone else"
Merge maybeOriginTip maybeBundle targetTip <- case wioRest of
TAM_Task _ ->
throwE
"Offer target is some local deck, so I have no use for \
\this Offer. Was I supposed to receive it?"
TAM_Merge loomID' merge ->
if loomID' == loomID
then return merge
else throwE
"Offer target is some other local loom, so I have \
\no use for this Offer. Was I supposed to receive \
\it?"
TAM_Remote _ _ ->
throwE
"Offer target is some remote tracker, so I have no use \
\for this Offer. Was I supposed to receive it?"
originTipOrBundle <-
fromMaybeE
(align maybeOriginTip maybeBundle)
"MR provides neither origin nor patches"
(targetRepoID, maybeTargetBranch) <-
case targetTip of
TipLocalRepo repoID -> pure (repoID, Nothing)
TipLocalBranch repoID branch -> pure (repoID, Just branch)
_ -> throwE "MR target is a remote repo (this tracker serves only local repos)"
return (wioTitle, wioDesc, wioSource, originTipOrBundle, targetRepoID, maybeTargetBranch)
-- Verify the capability URI, if provided, is one of:
-- * Outbox item URI of a local actor, i.e. a local activity
-- * A remote URI
maybeCapability <-
for (AP.activityCapability $ actbActivity body) $ \ uCap ->
nameExceptT "Offer.capability" $
first (\ (actor, _, item) -> (actor, item)) <$>
parseActivityURI' uCap
-- If origin repo is remote, HTTP GET its AP representation and
-- remember it in our DB
--
-- Why do we need to HTTP GET it? Because:
-- * No support for providing a signed repo object directly in the
-- Offer activity
-- * It may be nice to make sure a remote origin repo's VCS type
-- matches the target repo's VCS, even if patches are provided too
-- + However there's no support for caching VCS type when
-- remembering remote repo in our DB, so we'd have to check this
-- every time
-- * If origin is remote and no patches are provided, we'll need to
-- know the clone URL to generate the patches ourselves
-- + However the code here, for some simplicity, doesn't have a
-- way to skip that and do the whole handler synchronously in
-- case patches are provided or the origin is a local repo
-- + And no support for caching the clone URI in DB when
-- remembering the remote repo, so we'd need to do this every
-- time
let originTipOrBundle' =
bimap
(\case
TipLocalRepo repoID -> Left (repoID, Nothing)
TipLocalBranch repoID branch -> Left (repoID, Just branch)
TipRemote uOrigin -> Right (uOrigin, Nothing)
TipRemoteBranch uRepo branch -> Right (uRepo, Just branch)
)
id
originTipOrBundle
originTipOrBundle'' <-
bitraverse
(bitraverse
pure
(\ (uOrigin, maybeOriginBranch) -> do
case maybeOriginBranch of
Nothing -> do
(vcs, raid, uClone, mb) <- withExceptT (T.pack . show) $ httpGetRemoteTip' uOrigin
return (vcs, (raid, uClone, first Just <$> mb))
Just branch -> do
(vcs, raid, uClone) <- withExceptT (T.pack . show) $ httpGetRemoteRepo' uOrigin
return (vcs, (raid, uClone, Just (Nothing, branch)))
)
)
pure
originTipOrBundle'
maybeNew <- withDBExcept $ do
-- Grab me from DB
(loomRecip, actorRecip) <- lift $ do
d <- getJust loomID
(d,) <$> getJust (loomActor d)
-- Grab loom's repo from DB and verify that it consents to be served by
-- the loom, otherwise this loom doesn't accept tickets
let recipLoomRepoID = loomRepo loomRecip
unless (targetRepoID == recipLoomRepoID) $
throwE "MR target repo isn't the one served by the Offer target loom"
targetRepo <- lift $ getJust targetRepoID
unless (repoLoom targetRepo == Just loomID) $
throwE "Offer target loom doesn't have repo's consent to serve it"
-- Verify VCS type match between patch bundle and target repo
let targetRepoVCS = repoVcs targetRepo
for_ (justThere originTipOrBundle) $ \ (Material typ diffs) -> do
unless (targetRepoVCS == patchMediaTypeVCS typ) $
throwE "Patch type and local target repo VCS mismatch"
case (typ, diffs) of
(PatchMediaTypeDarcs, _ :| _ : _) ->
throwE "More than one Darcs dpatch file provided"
_ -> pure ()
-- If origin repo is local, find it in our DB.
--
-- Verify the (local or remote) origin repo's VCS type matches the
-- target repo.
originOrBundle' <-
bitraverse
(bitraverse
(\ origin@(repoID, maybeBranch) -> do
repo <- getE repoID "MR origin local repo not found in DB"
unless (repoVcs repo == targetRepoVCS) $
throwE "Local origin repo VCS differs from target repo VCS"
return origin
)
(\ (vcs, origin) -> do
unless (vcs == targetRepoVCS) $
throwE "Remote origin repo VCS differs from target repo VCS"
return origin
)
)
pure
originTipOrBundle''
-- Verify that branches are specified for Git and aren't specified for
-- Darcs
-- Also, produce a data structure separating by VCS rather than by
-- local/remote origin, which we'll need for generating patches
tipInfo <- case targetRepoVCS of
VCSGit -> do
targetBranch <- fromMaybeE maybeTargetBranch "Local target repo is Git but no target branch specified"
maybeOrigin <- for (justHere originOrBundle') $ \case
Left (originRepoID, maybeOriginBranch) -> do
originBranch <- fromMaybeE maybeOriginBranch "Local origin repo is Git but no origin branch specified"
return (Left originRepoID, originBranch)
Right (_remoteActorID, uClone, maybeOriginBranch) -> do
(_maybeURI, originBranch) <- fromMaybeE maybeOriginBranch "Remote origin repo is Git but no origin branch specified"
return (Right uClone, originBranch)
return $ Left (targetBranch, maybeOrigin)
VCSDarcs -> do
verifyNothingE maybeTargetBranch "Local target repo is Darcs but target branch specified"
maybeOriginRepo <- for (justHere originOrBundle') $ \case
Left (originRepoID, maybeOriginBranch) -> do
verifyNothingE maybeOriginBranch "Local origin repo is Darcs but origin branch specified"
return $ Left originRepoID
Right (_remoteActorID, uClone, maybeOriginBranch) -> do
verifyNothingE maybeOriginBranch "Remote origin repo is Darcs but origin branch specified"
return $ Right uClone
return $ Right $ maybeOriginRepo
-- Insert the Offer to my inbox
mractid <- lift $ insertToInbox now authorIdMsig body (actorInbox actorRecip) False
for mractid $ \ offerDB -> do
-- If a capability is provided, check it
for_ maybeCapability $ \ cap -> do
lcap <-
case cap of
Left c -> pure c
Right _ -> throwE "Capability is a remote URI, i.e. not authored by me"
verifyCapability'
lcap
authorIdMsig
(GrantResourceLoom loomID)
AP.RoleReport
-- Prepare forwarding the Offer to my followers
let recipByID = grantResourceLocalActor $ GrantResourceLoom loomID
recipByHash <- hashLocalActor recipByID
let sieve = makeRecipientSet [] [localActorFollowers recipByHash]
-- Insert the new ticket to our DB
acceptID <- lift $ insertEmptyOutboxItem' (actorOutbox actorRecip) now
offerDB' <-
bitraverse
(traverseOf _1 $ \case
LocalActorPerson personID -> pure personID
_ -> throwE "Local non-Person ticket authors not allowed"
)
pure
offerDB
ticketID <- lift $ insertTask title desc source offerDB' acceptID
clothID <- lift $ insertMerge loomID ticketID maybeTargetBranch originOrBundle'
let maybePull =
let maybeTipInfo =
case tipInfo of
Left (b, mo) -> Left . (b,) <$> mo
Right mo -> Right <$> mo
hasBundle = isJust $ justThere originOrBundle'
in (clothID, targetRepoID, hasBundle,) <$> maybeTipInfo
-- Prepare an Accept activity and insert to my outbox
accept@(actionAccept, _, _, _) <- lift $ prepareAccept clothID
let recipByKey = LocalActorLoom loomID
_luAccept <- lift $ updateOutboxItem' recipByKey acceptID actionAccept
return (loomActor loomRecip, sieve, acceptID, accept, maybePull)
case maybeNew of
Nothing -> done "I already have this activity in my inbox"
Just (loomActorID, sieve, acceptID, (actionAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept), maybePull) -> do
traverse_ generatePatches maybePull
forwardActivity
authorIdMsig body (LocalActorLoom loomID) loomActorID sieve
lift $ sendActivity
(LocalActorLoom loomID) loomActorID localRecipsAccept
remoteRecipsAccept fwdHostsAccept acceptID actionAccept
done "Opened a MR and forwarded the Offer"
where
insertTask title desc source offerDB acceptID = do
did <- insert Discussion
fsid <- insert FollowerSet
tid <- insert Ticket
{ ticketNumber = Nothing
, ticketCreated = now
, ticketTitle = title
, ticketSource = source
, ticketDescription = desc
, ticketDiscuss = did
, ticketFollowers = fsid
, ticketAccept = acceptID
}
case offerDB of
Left (personID, _, offerID) ->
insert_ TicketAuthorLocal
{ ticketAuthorLocalTicket = tid
, ticketAuthorLocalAuthor = personID
, ticketAuthorLocalOpen = offerID
}
Right (author, _, offerID) ->
insert_ TicketAuthorRemote
{ ticketAuthorRemoteTicket = tid
, ticketAuthorRemoteAuthor = remoteAuthorId author
, ticketAuthorRemoteOpen = offerID
}
return tid
insertMerge
:: LoomId
-> TicketId
-> Maybe Text
-> These
(Either
(RepoId, Maybe Text)
(RemoteActorId, FedURI, Maybe (Maybe LocalURI, Text))
)
Material
-> ActDB TicketLoomId
insertMerge loomID ticketID maybeTargetBranch originOrBundle = do
clothID <- insert $ TicketLoom ticketID loomID maybeTargetBranch
for_ (justHere originOrBundle) $ \case
Left (repoID, maybeOriginBranch) ->
insert_ $ MergeOriginLocal clothID repoID maybeOriginBranch
Right (remoteActorID, _uClone, maybeOriginBranch) -> do
originID <- insert $ MergeOriginRemote clothID remoteActorID
for_ maybeOriginBranch $ \ (mlu, b) ->
insert_ $ MergeOriginRemoteBranch originID mlu b
for_ (justThere originOrBundle) $ \ (Material typ diffs) -> do
bundleID <- insert $ Bundle clothID False
insertMany_ $ NE.toList $ NE.reverse $
NE.map (Patch bundleID now typ) diffs
return clothID
prepareAccept clothID = do
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
audSender <- makeAudSenderWithFollowers authorIdMsig
loomHash <- encodeKeyHashid loomID
clothHash <- encodeKeyHashid clothID
let audLoom = AudLocal [] [LocalStageLoomFollowers loomHash]
uOffer <- lift $ getActivityURI authorIdMsig
let (recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
collectAudience [audSender, audLoom]
recips = map encodeRouteHome audLocal ++ audRemote
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = [uOffer]
, AP.actionSpecific = AP.AcceptActivity AP.Accept
{ AP.acceptObject = uOffer
, AP.acceptResult =
Just $ encodeRouteLocal $ ClothR loomHash clothHash
}
}
return (action, recipientSet, remoteActors, fwdHosts)
loomBehavior :: UTCTime -> LoomId -> VerseExt -> ActE (Text, Act (), Next) loomBehavior :: UTCTime -> LoomId -> VerseExt -> ActE (Text, Act (), Next)
loomBehavior now loomID (Left _verse@(Verse _authorIdMsig body)) = loomBehavior now loomID (Left verse@(Verse _authorIdMsig body)) =
case AP.activitySpecific $ actbActivity body of case AP.activitySpecific $ actbActivity body of
AP.OfferActivity offer -> loomOffer now loomID verse offer
_ -> throwE "Unsupported activity type for Loom" _ -> throwE "Unsupported activity type for Loom"
loomBehavior _ _ (Right _) = throwE "ClientMsgs aren't supported for Loom" loomBehavior _ _ (Right _) = throwE "ClientMsgs aren't supported for Loom"

View file

@ -18,11 +18,10 @@
module Vervis.Federation.Ticket module Vervis.Federation.Ticket
( --personOfferTicketF ( --personOfferTicketF
loomOfferTicketF
--, repoAddBundleF --, repoAddBundleF
, loomApplyF loomApplyF
--, deckOfferDepF --, deckOfferDepF
--, repoOfferDepF --, repoOfferDepF
@ -328,336 +327,6 @@ activityAlreadyInInbox hAct luAct inboxID = fmap isJust . runMaybeT $ do
remoteActivityID <- MaybeT $ getKeyBy $ UniqueRemoteActivity remoteObjectID remoteActivityID <- MaybeT $ getKeyBy $ UniqueRemoteActivity remoteObjectID
MaybeT $ getBy $ UniqueInboxItemRemote inboxID remoteActivityID MaybeT $ getBy $ UniqueInboxItemRemote inboxID remoteActivityID
loomOfferTicketF
:: UTCTime
-> KeyHashid Loom
-> RemoteAuthor
-> ActivityBody
-> Maybe (RecipientRoutes, ByteString)
-> LocalURI
-> AP.Ticket URIMode
-> FedURI
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
loomOfferTicketF now recipLoomHash author body mfwd luOffer ticket uTarget = do
error "loomOfferTicketF disabled for refactoring"
{-
-- Check input
recipLoomID <- decodeKeyHashid404 recipLoomHash
(title, desc, source, originTipOrBundle, targetRepoID, maybeTargetBranch) <- do
let uAuthor@(ObjURI hAuthor _) = remoteAuthorURI author
WorkItemOffer {..} <- checkOfferTicket hAuthor ticket uTarget
unless (wioAuthor == Right (remoteAuthorURI author)) $
throwE "Offering a Ticket attributed to someone else"
Merge maybeOriginTip maybeBundle targetTip <- case wioRest of
TAM_Task _ ->
throwE
"Offer target is some local deck, so I have no use for \
\this Offer. Was I supposed to receive it?"
TAM_Merge loomID merge ->
if loomID == recipLoomID
then return merge
else throwE
"Offer target is some other local loom, so I have \
\no use for this Offer. Was I supposed to receive \
\it?"
TAM_Remote _ _ ->
throwE
"Offer target is some remote tracker, so I have no use \
\for this Offer. Was I supposed to receive it?"
originTipOrBundle <-
fromMaybeE
(align maybeOriginTip maybeBundle)
"MR provides neither origin nor patches"
(targetRepoID, maybeTargetBranch) <-
case targetTip of
TipLocalRepo repoID -> pure (repoID, Nothing)
TipLocalBranch repoID branch -> pure (repoID, Just branch)
_ -> throwE "MR target is a remote repo (this tracker serves only local repos)"
return (wioTitle, wioDesc, wioSource, originTipOrBundle, targetRepoID, maybeTargetBranch)
-- Soon we're going to proceed asynchronously to be able to HTTP GET the
-- origin repo AP object, because:
--
-- * No support for providing a signed repo object directly in the
-- Offer activity
-- * It may be nice to make sure a remote origin repo's VCS type
-- matches the target repo's VCS, even if patches are provided too
-- + However there's no support for caching VCS type when
-- remembering remote repo in our DB, so we'd have to check this
-- every time
-- * If origin is remote and no patches are provided, we'll need to
-- know the clone URL to generate the patches ourselves
-- + However the code here, for some simplicity, doesn't have a
-- way to skip that and do the whole handler synchronously in
-- case patches are provided or the origin is a local repo
-- + And no support for caching the clone URI in DB when
-- remembering the remote repo, so we'd need to do this every
-- time
--
-- So first let's do some checks using the DB, on the loom, on the target
-- repo (which is always local), and on the origin repo if it's local
(recipLoomRepoID, Entity recipLoomActorID recipLoomActor, alreadyInInbox) <- lift $ runDB $ do
-- Find recipient loom in DB, returning 404 if doesn't exist because
-- we're in the loom's inbox post handler
(recipLoomRepoID, recipLoomActor@(Entity _ actor)) <- do
loom <- get404 recipLoomID
let actorID = loomActor loom
(loomRepo loom,) . Entity actorID <$> getJust actorID
-- Has the loom already received this activity to its inbox? If yes, we
-- won't process it again
alreadyInInbox <- do
let hOffer = objUriAuthority $ remoteAuthorURI author
activityAlreadyInInbox hOffer luOffer (actorInbox actor)
return (recipLoomRepoID, recipLoomActor, alreadyInInbox)
if alreadyInInbox
then return ("I already have this activity in my inbox, ignoring", Nothing)
else do
(targetRepoVCS, originOrBundle) <- runDBExcept $ do
-- Grab loom's repo from DB and verify that it consents to be served by
-- the loom, otherwise this loom doesn't accept tickets
unless (targetRepoID == recipLoomRepoID) $
throwE "MR target repo isn't the one served by the Offer target loom"
targetRepo <- lift $ getJust targetRepoID
unless (repoLoom targetRepo == Just recipLoomID) $
throwE "Offer target loom doesn't have repo's consent to serve it"
-- Verify VCS type match between patch bundle and target repo
for_ (justThere originTipOrBundle) $ \ (Material typ diffs) -> do
unless (repoVcs targetRepo == patchMediaTypeVCS typ) $
throwE "Patch type and local target repo VCS mismatch"
case (typ, diffs) of
(PatchMediaTypeDarcs, _ :| _ : _) ->
throwE "More than one Darcs dpatch file provided"
_ -> pure ()
-- If origin repo is local, find it in our DB and verify its VCS type
-- matches the target repo
originOrBundle <- flip (bifor originTipOrBundle) pure $ \ originTip -> do
let origin =
case originTip of
TipLocalRepo repoID -> Left (repoID, Nothing)
TipLocalBranch repoID branch -> Left (repoID, Just branch)
TipRemote uOrigin -> Right (uOrigin, Nothing)
TipRemoteBranch uRepo branch -> Right (uRepo, Just branch)
bitraverse_
(\ (repoID, maybeBranch) -> do
repo <- getE repoID "MR origin local repo not found in DB"
unless (repoVcs repo == repoVcs targetRepo) $
throwE "Local origin repo VCS differs from target repo VCS"
)
pure
origin
return origin
return (repoVcs targetRepo, originOrBundle)
return $ (,) "Ran initial checks, doing the rest asynchronously" $ Just $ do
-- If origin repo is remote, HTTP GET its AP representation and
-- remember it in our DB
originOrBundle' <-
bitraverse
(bitraverse
pure
(\ (uOrigin, maybeOriginBranch) -> do
(vcs, remoteOrigin) <-
case maybeOriginBranch of
Nothing -> do
(vcs, raid, uClone, mb) <- withExceptT (T.pack . show) $ httpGetRemoteTip uOrigin
return (vcs, (raid, uClone, first Just <$> mb))
Just branch -> do
(vcs, raid, uClone) <- withExceptT (T.pack . show) $ httpGetRemoteRepo uOrigin
return (vcs, (raid, uClone, Just (Nothing, branch)))
unless (vcs == targetRepoVCS) $
throwE "Remote origin repo VCS differs from target repo VCS"
return remoteOrigin
)
)
pure
originOrBundle
-- Verify that branches are specified for Git and aren't specified for
-- Darcs
-- Also, produce a data structure separating by VCS rather than by
-- local/remote origin, which we'll need for generating patches
tipInfo <- case targetRepoVCS of
VCSGit -> do
targetBranch <- fromMaybeE maybeTargetBranch "Local target repo is Git but no target branch specified"
maybeOrigin <- for (justHere originOrBundle') $ \case
Left (originRepoID, maybeOriginBranch) -> do
originBranch <- fromMaybeE maybeOriginBranch "Local origin repo is Git but no origin branch specified"
return (Left originRepoID, originBranch)
Right (_remoteActorID, uClone, maybeOriginBranch) -> do
(_maybeURI, originBranch) <- fromMaybeE maybeOriginBranch "Remote origin repo is Git but no origin branch specified"
return (Right uClone, originBranch)
return $ Left (targetBranch, maybeOrigin)
VCSDarcs -> do
verifyNothingE maybeTargetBranch "Local target repo is Darcs but target branch specified"
maybeOriginRepo <- for (justHere originOrBundle') $ \case
Left (originRepoID, maybeOriginBranch) -> do
verifyNothingE maybeOriginBranch "Local origin repo is Darcs but origin branch specified"
return $ Left originRepoID
Right (_remoteActorID, uClone, maybeOriginBranch) -> do
verifyNothingE maybeOriginBranch "Remote origin repo is Darcs but origin branch specified"
return $ Right uClone
return $ Right $ maybeOriginRepo
maybeHttp <- runSiteDBExcept $ do
-- Insert the Offer to loom's inbox
mractid <- lift $ insertToInbox now author body (actorInbox recipLoomActor) luOffer False
for mractid $ \ offerID -> do
-- Forward the Offer activity to relevant local stages, and
-- schedule delivery for unavailable remote members of them
maybeHttpFwdOffer <- lift $ for mfwd $ \ (localRecips, sig) -> do
let sieve =
makeRecipientSet
[]
[LocalStageLoomFollowers recipLoomHash]
forwardActivityDB
(actbBL body) localRecips sig
recipLoomActorID (LocalActorLoom recipLoomHash)
sieve offerID
-- Insert the new ticket to our DB
acceptID <- lift $ insertEmptyOutboxItem (actorOutbox recipLoomActor) now
ticketID <- lift $ insertTicket now title desc source offerID acceptID
clothID <- lift $ insertMerge recipLoomID ticketID maybeTargetBranch originOrBundle'
let maybePull =
let maybeTipInfo =
case tipInfo of
Left (b, mo) -> Left . (b,) <$> mo
Right mo -> Right <$> mo
hasBundle = isJust $ justThere originOrBundle'
in (clothID, targetRepoID, hasBundle,) <$> maybeTipInfo
-- Prepare an Accept activity and insert to loom's outbox
(actionAccept, localRecipsAccept, remoteRecipsAccept, fwdHostsAccept) <-
lift $ prepareAccept clothID
_luAccept <- lift $ updateOutboxItem (LocalActorLoom recipLoomID) acceptID actionAccept
-- Deliver the Accept to local recipients, and schedule delivery
-- for unavailable remote recipients
deliverHttpAccept <-
deliverActivityDB
(LocalActorLoom recipLoomHash) recipLoomActorID
localRecipsAccept remoteRecipsAccept
fwdHostsAccept acceptID actionAccept
-- Return instructions for HTTP inbox-forwarding of the Offer
-- activity, and for HTTP delivery of the Accept activity to
-- remote recipients, and for generating patches from
-- the origin repo
return
(maybeHttpFwdOffer, deliverHttpAccept, maybePull)
-- Launch asynchronous HTTP forwarding of the Offer activity and HTTP
-- delivery of the Accept activity, and generate patches if we opened
-- a local MR that mentions just an origin
case maybeHttp of
Nothing ->
return
"When I started serving this activity, I didn't have it in my inbox, \
\but now suddenly it seems I already do, so ignoring"
Just (maybeHttpFwdOffer, deliverHttpAccept, maybePull) -> do
forkWorker "loomOfferTicketF Accept HTTP delivery" deliverHttpAccept
traverse generatePatches maybePull
case maybeHttpFwdOffer of
Nothing -> return "Opened a merge request, no inbox-forwarding to do"
Just forwardHttpOffer -> do
forkWorker "loomOfferTicketF inbox-forwarding" forwardHttpOffer
return "Opened a merge request and ran inbox-forwarding of the Offer"
where
insertTicket now title desc source offerID acceptID = do
did <- insert Discussion
fsid <- insert FollowerSet
tid <- insert Ticket
{ ticketNumber = Nothing
, ticketCreated = now
, ticketTitle = title
, ticketSource = source
, ticketDescription = desc
, ticketDiscuss = did
, ticketFollowers = fsid
, ticketAccept = acceptID
}
insert_ TicketAuthorRemote
{ ticketAuthorRemoteTicket = tid
, ticketAuthorRemoteAuthor = remoteAuthorId author
, ticketAuthorRemoteOpen = offerID
}
return tid
insertMerge
:: LoomId
-> TicketId
-> Maybe Text
-> These
(Either
(RepoId, Maybe Text)
(RemoteActorId, FedURI, Maybe (Maybe LocalURI, Text))
)
Material
-> WorkerDB TicketLoomId
insertMerge loomID ticketID maybeTargetBranch originOrBundle = do
clothID <- insert $ TicketLoom ticketID loomID maybeTargetBranch
for_ (justHere originOrBundle) $ \case
Left (repoID, maybeOriginBranch) ->
insert_ $ MergeOriginLocal clothID repoID maybeOriginBranch
Right (remoteActorID, _uClone, maybeOriginBranch) -> do
originID <- insert $ MergeOriginRemote clothID remoteActorID
for_ maybeOriginBranch $ \ (mlu, b) ->
insert_ $ MergeOriginRemoteBranch originID mlu b
for_ (justThere originOrBundle) $ \ (Material typ diffs) -> do
bundleID <- insert $ Bundle clothID False
insertMany_ $ NE.toList $ NE.reverse $
NE.map (Patch bundleID now typ) diffs
return clothID
prepareAccept clothID = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
clothHash <- encodeKeyHashid clothID
ra <- getJust $ remoteAuthorId author
let ObjURI hAuthor luAuthor = remoteAuthorURI author
audSender =
AudRemote hAuthor
[luAuthor]
(maybeToList $ remoteActorFollowers ra)
audTracker = AudLocal [] [LocalStageLoomFollowers recipLoomHash]
(recipientSet, remoteActors, fwdHosts, audLocal, audRemote) =
collectAudience [audSender, audTracker]
recips = map encodeRouteHome audLocal ++ audRemote
action = AP.Action
{ AP.actionCapability = Nothing
, AP.actionSummary = Nothing
, AP.actionAudience = AP.Audience recips [] [] [] [] []
, AP.actionFulfills = []
, AP.actionSpecific = AP.AcceptActivity AP.Accept
{ AP.acceptObject = ObjURI hAuthor luOffer
, AP.acceptResult =
Just $ encodeRouteLocal $
ClothR recipLoomHash clothHash
}
}
return (action, recipientSet, remoteActors, fwdHosts)
-}
repoOfferTicketF repoOfferTicketF
:: UTCTime :: UTCTime
-> KeyHashid Repo -> KeyHashid Repo

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2022, 2023 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,7 +16,9 @@
module Vervis.Fetch module Vervis.Fetch
( Result (..) ( Result (..)
, httpGetRemoteTip , httpGetRemoteTip
, httpGetRemoteTip'
, httpGetRemoteRepo , httpGetRemoteRepo
, httpGetRemoteRepo'
) )
where where
@ -60,6 +62,7 @@ import qualified Data.Text as T
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import Control.Concurrent.Actor
import Database.Persist.JSON import Database.Persist.JSON
import Development.PatchMediaType import Development.PatchMediaType
import Network.FedURI import Network.FedURI
@ -81,6 +84,7 @@ import qualified Data.Text.UTF8.Local as TU
import qualified Darcs.Local.Repository as D (createRepo) import qualified Darcs.Local.Repository as D (createRepo)
--import Vervis.Access --import Vervis.Access
import Vervis.Actor
import Vervis.ActivityPub import Vervis.ActivityPub
import Vervis.Cloth import Vervis.Cloth
import Vervis.Data.Actor import Vervis.Data.Actor
@ -116,6 +120,13 @@ fetchRepoE h lu = do
ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$> ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$>
fetchAPID' manager apRepoId h lu fetchAPID' manager apRepoId h lu
fetchRepoE' :: Host -> LocalURI -> ExceptT Result Act (AP.Repo URIMode)
fetchRepoE' h lu = do
manager <- asksEnv envHttpManager
let apRepoId = AP.actorId . AP.actorLocal . AP.repoActor
ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$>
fetchAPID' manager apRepoId h lu
insertRemoteActor insertRemoteActor
:: MonadIO m :: MonadIO m
=> Host => Host
@ -167,6 +178,36 @@ httpGetRemoteTip (ObjURI host localURI) = do
ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$> ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$>
fetchTip manager h lu fetchTip manager h lu
httpGetRemoteTip'
:: FedURI
-> ExceptT Result Act
( VersionControlSystem
, RemoteActorId
, FedURI
, Maybe (LocalURI, Text)
)
httpGetRemoteTip' (ObjURI host localURI) = do
repoOrBranch <- fetchTipE host localURI
case repoOrBranch of
Left repo -> do
remoteActorID <-
lift $ withDB $
insertRemoteActor host localURI $ AP.repoActor repo
let uClone = ObjURI host $ NE.head $ AP.repoClone repo
return (AP.repoVcs repo, remoteActorID, uClone, Nothing)
Right (AP.Branch name _ luRepo) -> do
repo <- fetchRepoE' host luRepo
remoteActorID <-
lift $ withDB $
insertRemoteActor host luRepo $ AP.repoActor repo
let uClone = ObjURI host $ NE.head $ AP.repoClone repo
return (AP.repoVcs repo, remoteActorID, uClone, Just (localURI, name))
where
fetchTipE h lu = do
manager <- asksEnv envHttpManager
ExceptT $ first (maybe ResultIdMismatch ResultGetError) <$>
fetchTip manager h lu
httpGetRemoteRepo httpGetRemoteRepo
:: (MonadUnliftIO m, MonadSite m, SiteEnv m ~ App) :: (MonadUnliftIO m, MonadSite m, SiteEnv m ~ App)
=> FedURI => FedURI
@ -178,3 +219,14 @@ httpGetRemoteRepo (ObjURI host localURI) = do
insertRemoteActor host localURI $ AP.repoActor repo insertRemoteActor host localURI $ AP.repoActor repo
let uClone = ObjURI host $ NE.head $ AP.repoClone repo let uClone = ObjURI host $ NE.head $ AP.repoClone repo
return (AP.repoVcs repo, remoteActorID, uClone) return (AP.repoVcs repo, remoteActorID, uClone)
httpGetRemoteRepo'
:: FedURI
-> ExceptT Result Act (VersionControlSystem, RemoteActorId, FedURI)
httpGetRemoteRepo' (ObjURI host localURI) = do
repo <- fetchRepoE' host localURI
remoteActorID <-
lift $ withDB $
insertRemoteActor host localURI $ AP.repoActor repo
let uClone = ObjURI host $ NE.head $ AP.repoClone repo
return (AP.repoVcs repo, remoteActorID, uClone)

View file

@ -222,7 +222,6 @@ postPersonOutboxR personHash = do
-> t -> t
run f = f eperson actorDB maybeCap localRecips remoteRecips fwdHosts action run f = f eperson actorDB maybeCap localRecips remoteRecips fwdHosts action
case specific of case specific of
AP.AcceptActivity accept -> run acceptC accept
AP.ApplyActivity apply -> run applyC apply AP.ApplyActivity apply -> run applyC apply
AP.CreateActivity (AP.Create obj mtarget) -> AP.CreateActivity (AP.Create obj mtarget) ->
case obj of case obj of
@ -246,7 +245,10 @@ postPersonOutboxR personHash = do
AP.FollowActivity follow -> run followC follow AP.FollowActivity follow -> run followC follow
AP.OfferActivity (AP.Offer obj target) -> AP.OfferActivity (AP.Offer obj target) ->
case obj of case obj of
AP.OfferTicket ticket -> run offerTicketC ticket target AP.OfferTicket _ ->
handleViaActor
(entityKey eperson) maybeCap localRecips remoteRecips
fwdHosts action
{- {-
OfferDep dep -> OfferDep dep ->
offerDepC eperson sharer summary audience dep target offerDepC eperson sharer summary audience dep target

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2016, 2019, 2022, 2023 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.
- -
@ -17,6 +17,7 @@ module Vervis.Path
( askRepoRootDir ( askRepoRootDir
, repoDir , repoDir
, askRepoDir , askRepoDir
, askRepoDir'
) )
where where
@ -26,9 +27,11 @@ import System.FilePath ((</>))
import qualified Data.CaseInsensitive as CI (foldedCase) import qualified Data.CaseInsensitive as CI (foldedCase)
import qualified Data.Text as T (unpack) import qualified Data.Text as T (unpack)
import Control.Concurrent.Actor
import Yesod.Hashids import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
import Vervis.Actor
import Vervis.Foundation import Vervis.Foundation
import Vervis.Model import Vervis.Model
import Vervis.Settings import Vervis.Settings
@ -36,6 +39,9 @@ import Vervis.Settings
askRepoRootDir :: (MonadSite m, SiteEnv m ~ App) => m FilePath askRepoRootDir :: (MonadSite m, SiteEnv m ~ App) => m FilePath
askRepoRootDir = asksSite $ appRepoDir . appSettings askRepoRootDir = asksSite $ appRepoDir . appSettings
askRepoRootDir' :: Act FilePath
askRepoRootDir' = asksEnv $ appRepoDir . envSettings
repoDir :: FilePath -> KeyHashid Repo -> FilePath repoDir :: FilePath -> KeyHashid Repo -> FilePath
repoDir root repo = root </> (T.unpack $ keyHashidText repo) repoDir root repo = root </> (T.unpack $ keyHashidText repo)
@ -44,3 +50,8 @@ askRepoDir
askRepoDir repo = do askRepoDir repo = do
root <- askRepoRootDir root <- askRepoRootDir
return $ repoDir root repo return $ repoDir root repo
askRepoDir' :: KeyHashid Repo -> Act FilePath
askRepoDir' repo = do
root <- askRepoRootDir'
return $ repoDir root repo

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis. {- This file is part of Vervis.
- -
- Written in 2019, 2020, 2021, 2022 by fr33domlover <fr33domlover@riseup.net>. - Written in 2019, 2020, 2021, 2022, 2023
- 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.
- -
@ -45,11 +46,13 @@ import Yesod.Hashids
import Yesod.MonadSite import Yesod.MonadSite
import qualified Web.ActivityPub as AP import qualified Web.ActivityPub as AP
import qualified Web.Actor.Persist as WAP
import Data.Patch.Local hiding (Patch) import Data.Patch.Local hiding (Patch)
import qualified Data.Patch.Local as P import qualified Data.Patch.Local as P
import Vervis.Actor
import Vervis.Darcs import Vervis.Darcs
import Vervis.FedURI import Vervis.FedURI
import Vervis.Foundation import Vervis.Foundation
@ -110,36 +113,35 @@ serveCommit repoHash ref patch parents = do
Right $ encodeRouteHome $ PersonR $ hashPerson personID Right $ encodeRouteHome $ PersonR $ hashPerson personID
generatePatches generatePatches
:: (MonadUnliftIO m, MonadSite m, SiteEnv m ~ App) :: ( TicketLoomId
=> ( TicketLoomId
, RepoId , RepoId
, Bool , Bool
, Either , Either
(Text, (Either RepoId FedURI, Text)) (Text, (Either RepoId FedURI, Text))
(Either RepoId FedURI) (Either RepoId FedURI)
) )
-> ExceptT Text m () -> ActE ()
generatePatches (clothID, targetRepoID, hasBundle, tipInfo) = unless hasBundle $ do generatePatches (clothID, targetRepoID, hasBundle, tipInfo) = unless hasBundle $ do
patches <- patches <-
case tipInfo of case tipInfo of
Right _ -> error "Auto-pulling from Darcs remote origin not supported yet" Right _ -> error "Auto-pulling from Darcs remote origin not supported yet"
Left (targetBranch, (originRepo, originBranch)) -> do Left (targetBranch, (originRepo, originBranch)) -> do
targetPath <- do targetPath <- do
repoHash <- encodeKeyHashid targetRepoID repoHash <- WAP.encodeKeyHashid targetRepoID
repoDir <- askRepoDir repoHash repoDir <- lift $ askRepoDir' repoHash
liftIO $ makeAbsolute repoDir liftIO $ makeAbsolute repoDir
originURI <- originURI <-
case originRepo of case originRepo of
Left repoID -> do Left repoID -> do
repoHash <- encodeKeyHashid repoID repoHash <- WAP.encodeKeyHashid repoID
repoDir <- askRepoDir repoHash repoDir <- lift $ askRepoDir' repoHash
liftIO $ makeAbsolute repoDir liftIO $ makeAbsolute repoDir
Right uClone -> pure $ T.unpack $ renderObjURI uClone Right uClone -> pure $ T.unpack $ renderObjURI uClone
ExceptT $ liftIO $ runExceptT $ ExceptT $ liftIO $ runExceptT $
withSystemTempDirectory "vervis-generatePatches" $ withSystemTempDirectory "vervis-generatePatches" $
generateGitPatches targetPath (T.unpack targetBranch) originURI (T.unpack originBranch) generateGitPatches targetPath (T.unpack targetBranch) originURI (T.unpack originBranch)
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
lift $ runSiteDB $ do lift $ withDB $ do
bundleID <- insert $ Bundle clothID True bundleID <- insert $ Bundle clothID True
insertMany_ $ NE.toList $ NE.map (Patch bundleID now PatchMediaTypeGit) $ NE.reverse patches insertMany_ $ NE.toList $ NE.map (Patch bundleID now PatchMediaTypeGit) $ NE.reverse patches