Implement actor-model system and start moving Person actor to it
This patch makes Vervis temporarily unusable, because all actors' inbox POST handlers use the new system, but the actual federation handler code hasn't been ported. The next patches will port all the S2S activities supported so far, as well as C2S.
This commit is contained in:
parent
36c7ae0190
commit
c9db823c8c
47 changed files with 2005 additions and 429 deletions
|
@ -32,10 +32,12 @@ On Debian based distros, installation can be done like this:
|
||||||
|
|
||||||
$ sudo apt install libpq-dev zlib1g-dev libssl-dev libpcre3-dev
|
$ sudo apt install libpq-dev zlib1g-dev libssl-dev libpcre3-dev
|
||||||
|
|
||||||
# (2) The Stack build tool
|
# (2) Haskell development tools
|
||||||
|
|
||||||
Install stack. To install stack, go to its [website](https://haskellstack.org)
|
Go to the [GHCup website](https://www.haskell.org/ghcup) and follow the
|
||||||
and follow the instructions.
|
instructions.
|
||||||
|
|
||||||
|
$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
|
||||||
|
|
||||||
# (3) Version control systems Darcs and Git
|
# (3) Version control systems Darcs and Git
|
||||||
|
|
||||||
|
|
332
src/Control/Concurrent/Actor.hs
Normal file
332
src/Control/Concurrent/Actor.hs
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019, 2020, 2023 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 Control.Concurrent.Actor
|
||||||
|
( Stage (..)
|
||||||
|
, TheaterFor ()
|
||||||
|
, ActFor ()
|
||||||
|
, MonadActor (..)
|
||||||
|
, asksEnv
|
||||||
|
, Next ()
|
||||||
|
, Message (..)
|
||||||
|
, startTheater
|
||||||
|
, callIO
|
||||||
|
, call
|
||||||
|
--, sendIO
|
||||||
|
, send
|
||||||
|
, sendManyIO
|
||||||
|
, sendMany
|
||||||
|
--, spawnIO
|
||||||
|
, spawn
|
||||||
|
, done
|
||||||
|
, doneAnd
|
||||||
|
, stop
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Concurrent
|
||||||
|
import Control.Concurrent.STM.TVar
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Fail
|
||||||
|
import Control.Monad.IO.Unlift
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.STM
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Control.Monad.Trans.Reader
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Hashable
|
||||||
|
import Data.HashMap.Strict (HashMap)
|
||||||
|
import Data.HashSet (HashSet)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Traversable
|
||||||
|
import UnliftIO.Exception
|
||||||
|
|
||||||
|
import qualified Control.Exception.Annotated as AE
|
||||||
|
import qualified Control.Monad.Trans.RWS.Lazy as RWSL
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import qualified Data.HashSet as HS
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Return
|
||||||
|
|
||||||
|
type LogFunc = Loc -> LogSource -> LogLevel -> LogStr -> IO ()
|
||||||
|
|
||||||
|
class Stage a where
|
||||||
|
type StageKey a
|
||||||
|
type StageMessage a
|
||||||
|
type StageReturn a
|
||||||
|
|
||||||
|
-- | A set of live actors responding to messages
|
||||||
|
data TheaterFor s = TheaterFor
|
||||||
|
{ theaterMap :: TVar (HashMap (StageKey s) (Chan (StageMessage s, Either SomeException (StageReturn s) -> IO ())))
|
||||||
|
, theaterLog :: LogFunc
|
||||||
|
, theaterEnv :: s
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Actor monad in which message reponse actions are executed. Supports
|
||||||
|
-- logging, a read-only environment, and IO.
|
||||||
|
newtype ActFor s a = ActFor
|
||||||
|
{ unActFor :: LoggingT (ReaderT (TheaterFor s) IO) a
|
||||||
|
}
|
||||||
|
deriving
|
||||||
|
( Functor, Applicative, Monad, MonadFail, MonadIO, MonadLogger
|
||||||
|
, MonadLoggerIO
|
||||||
|
)
|
||||||
|
|
||||||
|
instance MonadUnliftIO (ActFor s) where
|
||||||
|
askUnliftIO =
|
||||||
|
ActFor $ withUnliftIO $ \ u ->
|
||||||
|
return $ UnliftIO $ unliftIO u . unActFor
|
||||||
|
withRunInIO inner =
|
||||||
|
ActFor $ withRunInIO $ \ run -> inner (run . unActFor)
|
||||||
|
|
||||||
|
runActor :: TheaterFor s -> ActFor s a -> IO a
|
||||||
|
runActor theater (ActFor action) =
|
||||||
|
runReaderT (runLoggingT action $ theaterLog theater) theater
|
||||||
|
|
||||||
|
class Monad m => MonadActor m where
|
||||||
|
type ActorEnv m
|
||||||
|
askEnv :: m (ActorEnv m)
|
||||||
|
liftActor :: ActFor (ActorEnv m) a -> m a
|
||||||
|
|
||||||
|
instance MonadActor (ActFor s) where
|
||||||
|
type ActorEnv (ActFor s) = s
|
||||||
|
askEnv = theaterEnv <$> askTheater
|
||||||
|
liftActor = id
|
||||||
|
|
||||||
|
instance MonadActor m => MonadActor (ReaderT r m) where
|
||||||
|
type ActorEnv (ReaderT r m) = ActorEnv m
|
||||||
|
askEnv = lift askEnv
|
||||||
|
liftActor = lift . liftActor
|
||||||
|
|
||||||
|
instance MonadActor m => MonadActor (MaybeT m) where
|
||||||
|
type ActorEnv (MaybeT m) = ActorEnv m
|
||||||
|
askEnv = lift askEnv
|
||||||
|
liftActor = lift . liftActor
|
||||||
|
|
||||||
|
instance MonadActor m => MonadActor (ExceptT e m) where
|
||||||
|
type ActorEnv (ExceptT e m) = ActorEnv m
|
||||||
|
askEnv = lift askEnv
|
||||||
|
liftActor = lift . liftActor
|
||||||
|
|
||||||
|
instance (Monoid w, MonadActor m) => MonadActor (RWSL.RWST r w s m) where
|
||||||
|
type ActorEnv (RWSL.RWST r w s m) = ActorEnv m
|
||||||
|
askEnv = lift askEnv
|
||||||
|
liftActor = lift . liftActor
|
||||||
|
|
||||||
|
asksEnv :: MonadActor m => (ActorEnv m -> a) -> m a
|
||||||
|
asksEnv f = f <$> askEnv
|
||||||
|
|
||||||
|
data Next = Stop | Proceed
|
||||||
|
|
||||||
|
class Message a where
|
||||||
|
summarize :: a -> Text
|
||||||
|
refer :: a -> Text
|
||||||
|
|
||||||
|
launchActorThread
|
||||||
|
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Hashable k, Eq k, Show k, Message m, Show r
|
||||||
|
)
|
||||||
|
=> Chan (m, Either SomeException r -> IO ())
|
||||||
|
-> TheaterFor s
|
||||||
|
-> k
|
||||||
|
-> (m -> ActFor s (r, ActFor s (), Next))
|
||||||
|
-> IO ()
|
||||||
|
launchActorThread chan theater actor behavior =
|
||||||
|
void $ forkIO $ runActor theater $ do
|
||||||
|
logInfo $ prefix <> "starting"
|
||||||
|
loop
|
||||||
|
logInfo $ prefix <> "bye"
|
||||||
|
where
|
||||||
|
prefix = T.concat ["[Actor '", T.pack $ show actor, "'] "]
|
||||||
|
loop = do
|
||||||
|
(message, respond) <- liftIO $ readChan chan
|
||||||
|
logInfo $ T.concat [prefix, "received: ", summarize message]
|
||||||
|
result <- try $ behavior message
|
||||||
|
proceed <-
|
||||||
|
case result of
|
||||||
|
Left e -> do
|
||||||
|
logError $ T.concat [prefix, "on ", refer message, " exception: ", T.pack $ displayException (e :: SomeException)]
|
||||||
|
liftIO $ respond $ Left e
|
||||||
|
return True
|
||||||
|
Right (value, after, next) -> do
|
||||||
|
logInfo $ T.concat [prefix, "on ", refer message, " result: ", T.pack $ show value]
|
||||||
|
liftIO $ respond $ Right value
|
||||||
|
after
|
||||||
|
case next of
|
||||||
|
Stop -> do
|
||||||
|
logInfo $ T.concat [prefix, "on ", refer message, " stopping"]
|
||||||
|
let tvar = theaterMap theater
|
||||||
|
liftIO $ atomically $ modifyTVar' tvar $ HM.delete actor
|
||||||
|
return False
|
||||||
|
Proceed -> do
|
||||||
|
logInfo $ T.concat [prefix, "on ", refer message, " done"]
|
||||||
|
return True
|
||||||
|
when proceed loop
|
||||||
|
|
||||||
|
-- | Launch the actor system
|
||||||
|
startTheater
|
||||||
|
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Hashable k, Eq k, Show k, Message m, Show r
|
||||||
|
)
|
||||||
|
=> LogFunc
|
||||||
|
-> s
|
||||||
|
-> [(k, m -> ActFor s (r, ActFor s (), Next))]
|
||||||
|
-> IO (TheaterFor s)
|
||||||
|
startTheater logFunc env actors = do
|
||||||
|
actorsWithChans <- for actors $ \ (key, behavior) -> do
|
||||||
|
chan <- newChan
|
||||||
|
return ((key, chan), behavior)
|
||||||
|
tvar <- newTVarIO $ HM.fromList $ map fst actorsWithChans
|
||||||
|
let theater = TheaterFor tvar logFunc env
|
||||||
|
for_ actorsWithChans $ \ ((key, chan), behavior) ->
|
||||||
|
launchActorThread chan theater key behavior
|
||||||
|
return theater
|
||||||
|
|
||||||
|
askTheater :: ActFor s (TheaterFor s)
|
||||||
|
askTheater = ActFor $ lift ask
|
||||||
|
|
||||||
|
lookupActor
|
||||||
|
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Eq k, Hashable k
|
||||||
|
)
|
||||||
|
=> TheaterFor s
|
||||||
|
-> k
|
||||||
|
-> IO (Maybe (Chan (m, Either SomeException r -> IO ())))
|
||||||
|
lookupActor (TheaterFor tvar _ _) actor = HM.lookup actor <$> readTVarIO tvar
|
||||||
|
|
||||||
|
-- | Same as 'call', except it takes the theater as a parameter.
|
||||||
|
callIO
|
||||||
|
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Eq k, Hashable k
|
||||||
|
)
|
||||||
|
=> TheaterFor s -> k -> m -> IO (Maybe r)
|
||||||
|
callIO theater actor msg = do
|
||||||
|
maybeChan <- lookupActor theater actor
|
||||||
|
for maybeChan $ \ chan -> do
|
||||||
|
(returx, wait) <- newReturn
|
||||||
|
writeChan chan (msg, returx)
|
||||||
|
result <- wait
|
||||||
|
case result of
|
||||||
|
Left e -> AE.checkpointCallStack $ throwIO e
|
||||||
|
Right r -> return r
|
||||||
|
|
||||||
|
-- | Send a message to an actor, and wait for the result to arrive. Return
|
||||||
|
-- 'Nothing' if actor doesn't exist, otherwise 'Just' the result.
|
||||||
|
--
|
||||||
|
-- If the called method throws an exception, it is rethrown, wrapped with an
|
||||||
|
-- annotation, in the current thread.
|
||||||
|
call
|
||||||
|
:: ( MonadActor n, ActorEnv n ~ s
|
||||||
|
, StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Eq k, Hashable k
|
||||||
|
)
|
||||||
|
=> k -> m -> n (Maybe r)
|
||||||
|
call key msg = liftActor $ do
|
||||||
|
theater <- askTheater
|
||||||
|
liftIO $ callIO theater key msg
|
||||||
|
|
||||||
|
-- | Like 'send', except it takes the theater as a parameter.
|
||||||
|
sendIO
|
||||||
|
:: (StageKey s ~ k, StageMessage s ~ m, Eq k, Hashable k)
|
||||||
|
=> TheaterFor s -> k -> m -> IO Bool
|
||||||
|
sendIO theater actor msg = do
|
||||||
|
maybeChan <- lookupActor theater actor
|
||||||
|
case maybeChan of
|
||||||
|
Nothing -> return False
|
||||||
|
Just chan -> do
|
||||||
|
writeChan chan (msg, const $ pure ())
|
||||||
|
return True
|
||||||
|
|
||||||
|
-- | Send a message to an actor, without waiting for a result. Return 'True' if
|
||||||
|
-- the given actor exists, 'False' otherwise.
|
||||||
|
send
|
||||||
|
:: ( MonadActor n, ActorEnv n ~ s
|
||||||
|
, StageKey s ~ k, StageMessage s ~ m
|
||||||
|
, Eq k, Hashable k
|
||||||
|
)
|
||||||
|
=> k -> m -> n Bool
|
||||||
|
send key msg = liftActor $ do
|
||||||
|
theater <- askTheater
|
||||||
|
liftIO $ sendIO theater key msg
|
||||||
|
|
||||||
|
-- | Like 'sendMany', except it takes the theater as a parameter.
|
||||||
|
sendManyIO
|
||||||
|
:: (StageKey s ~ k, StageMessage s ~ m, Eq k, Hashable k)
|
||||||
|
=> TheaterFor s -> HashSet k -> m -> IO ()
|
||||||
|
sendManyIO (TheaterFor tvar _ _) recips msg = do
|
||||||
|
allActors <- readTVarIO tvar
|
||||||
|
for_ (HM.intersection allActors (HS.toMap recips)) $
|
||||||
|
\ chan -> writeChan chan (msg, const $ pure ())
|
||||||
|
|
||||||
|
-- | Send a message to each actor in the set that exists in the system,
|
||||||
|
-- without waiting for results.
|
||||||
|
sendMany
|
||||||
|
:: ( MonadActor n, ActorEnv n ~ s
|
||||||
|
, StageKey s ~ k, StageMessage s ~ m
|
||||||
|
, Eq k, Hashable k
|
||||||
|
)
|
||||||
|
=> HashSet k -> m -> n ()
|
||||||
|
sendMany keys msg = liftActor $ do
|
||||||
|
theater <- askTheater
|
||||||
|
liftIO $ sendManyIO theater keys msg
|
||||||
|
|
||||||
|
-- | Same as 'spawn', except it takes the theater as a parameter.
|
||||||
|
spawnIO
|
||||||
|
:: ( StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Eq k, Hashable k, Show k, Message m, Show r
|
||||||
|
)
|
||||||
|
=> TheaterFor s
|
||||||
|
-> k
|
||||||
|
-> (m -> ActFor s (r, ActFor s (), Next))
|
||||||
|
-> IO Bool
|
||||||
|
spawnIO theater@(TheaterFor tvar _ _) actor behavior = do
|
||||||
|
chan <- newChan
|
||||||
|
added <- atomically $ stateTVar tvar $ \ hm ->
|
||||||
|
let hm' = HM.alter (create chan) actor hm
|
||||||
|
in ( not (HM.member actor hm) && HM.member actor hm'
|
||||||
|
, hm'
|
||||||
|
)
|
||||||
|
when added $ launchActorThread chan theater actor behavior
|
||||||
|
return added
|
||||||
|
where
|
||||||
|
create chan Nothing = Just chan
|
||||||
|
create _ j@(Just _) = j
|
||||||
|
|
||||||
|
-- | Launch a new actor with the given ID and behavior. Return 'True' if the ID
|
||||||
|
-- was unused and the actor has been launched. Return 'False' if the ID is
|
||||||
|
-- already in use, thus a new actor hasn't been launched.
|
||||||
|
spawn
|
||||||
|
:: ( MonadActor n, ActorEnv n ~ s
|
||||||
|
, StageKey s ~ k, StageMessage s ~ m, StageReturn s ~ r
|
||||||
|
, Eq k, Hashable k, Show k, Message m, Show r
|
||||||
|
)
|
||||||
|
=> k
|
||||||
|
-> (m -> ActFor s (r, ActFor s (), Next))
|
||||||
|
-> n Bool
|
||||||
|
spawn key behavior = liftActor $ do
|
||||||
|
theater <- askTheater
|
||||||
|
liftIO $ spawnIO theater key behavior
|
||||||
|
|
||||||
|
done :: Monad n => a -> n (a, ActFor s (), Next)
|
||||||
|
done msg = return (msg, return (), Proceed)
|
||||||
|
|
||||||
|
doneAnd :: Monad n => a -> ActFor s () -> n (a, ActFor s (), Next)
|
||||||
|
doneAnd msg act = return (msg, act, Proceed)
|
||||||
|
|
||||||
|
stop :: Monad n => a -> n (a, ActFor s (), Next)
|
||||||
|
stop msg = return (msg, return (), Stop)
|
37
src/Control/Concurrent/Return.hs
Normal file
37
src/Control/Concurrent/Return.hs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2023 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/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Give another thread a way to send a value back to us.
|
||||||
|
module Control.Concurrent.Return
|
||||||
|
( newReturn
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
import Control.Monad
|
||||||
|
|
||||||
|
-- | Produce a pair of IO actions:
|
||||||
|
--
|
||||||
|
-- 1. Setter to give another thread, where it would be called at most once to
|
||||||
|
-- send us a value
|
||||||
|
-- 2. Action that waits until the value arrives
|
||||||
|
newReturn :: IO (a -> IO (), IO a)
|
||||||
|
newReturn = do
|
||||||
|
mvar <- newEmptyMVar
|
||||||
|
return (putReturn mvar, readMVar mvar)
|
||||||
|
where
|
||||||
|
putReturn mvar val = do
|
||||||
|
success <- tryPutMVar mvar val
|
||||||
|
unless success $ error "newReturn: putReturn: MVar is full"
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -80,6 +80,7 @@ import Network.FedURI
|
||||||
import Web.ActivityPub hiding (Patch (..), Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..))
|
import Web.ActivityPub hiding (Patch (..), Ticket, Follow, Repo (..), ActorLocal (..), ActorDetail (..), Actor (..))
|
||||||
import Web.Text
|
import Web.Text
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -781,7 +782,7 @@ createNoteC (Entity senderPersonID senderPerson) senderActor maybeCap localRecip
|
||||||
-- Check input
|
-- Check input
|
||||||
verifyNothingE maybeCap "Capability not needed"
|
verifyNothingE maybeCap "Capability not needed"
|
||||||
Comment maybeParent topic source content <- do
|
Comment maybeParent topic source content <- do
|
||||||
(authorPersonID, comment) <- parseNewLocalComment note
|
(authorPersonID, comment) <- parseNewLocalCommentOld note
|
||||||
unless (authorPersonID == senderPersonID) $
|
unless (authorPersonID == senderPersonID) $
|
||||||
throwE "Note attributed to someone else"
|
throwE "Note attributed to someone else"
|
||||||
return comment
|
return comment
|
||||||
|
@ -1079,7 +1080,7 @@ createPatchTrackerC (Entity pidUser personUser) senderActor maybeCap localRecips
|
||||||
|
|
||||||
parseRepo (ObjURI h lu :| us) = do
|
parseRepo (ObjURI h lu :| us) = do
|
||||||
unless (null us) $ throwE "More than one repo is specified"
|
unless (null us) $ throwE "More than one repo is specified"
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
unless hl $ throwE "A remote repo is specified"
|
unless hl $ throwE "A remote repo is specified"
|
||||||
route <- fromMaybeE (decodeRouteLocal lu) "Not a valid route"
|
route <- fromMaybeE (decodeRouteLocal lu) "Not a valid route"
|
||||||
case route of
|
case route of
|
||||||
|
@ -2712,7 +2713,7 @@ resolveC (Entity senderPersonID senderPerson) senderActor maybeCap localRecips r
|
||||||
-- Check input
|
-- Check input
|
||||||
maybeLocalWorkItem <-
|
maybeLocalWorkItem <-
|
||||||
nameExceptT "Resolve object" $ either Just (const Nothing) <$> do
|
nameExceptT "Resolve object" $ either Just (const Nothing) <$> do
|
||||||
routeOrRemote <- parseFedURI uObject
|
routeOrRemote <- parseFedURIOld uObject
|
||||||
bitraverse
|
bitraverse
|
||||||
(\ r -> do
|
(\ r -> do
|
||||||
wiByHash <-
|
wiByHash <-
|
||||||
|
|
271
src/Vervis/Actor.hs
Normal file
271
src/Vervis/Actor.hs
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019, 2020, 2022, 2023 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/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- These are for the Barbie-based generated instances
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
|
module Vervis.Actor
|
||||||
|
( -- * Local actors
|
||||||
|
LocalActorBy (..)
|
||||||
|
, LocalActor
|
||||||
|
|
||||||
|
-- * Local recipient set
|
||||||
|
, TicketRoutes (..)
|
||||||
|
, ClothRoutes (..)
|
||||||
|
, PersonRoutes (..)
|
||||||
|
, GroupRoutes (..)
|
||||||
|
, RepoRoutes (..)
|
||||||
|
, DeckRoutes (..)
|
||||||
|
, LoomRoutes (..)
|
||||||
|
, DeckFamilyRoutes (..)
|
||||||
|
, LoomFamilyRoutes (..)
|
||||||
|
, RecipientRoutes (..)
|
||||||
|
|
||||||
|
-- * AP system base types
|
||||||
|
, RemoteAuthor (..)
|
||||||
|
, ActivityBody (..)
|
||||||
|
, VerseRemote (..)
|
||||||
|
|
||||||
|
-- * Behavior utility types
|
||||||
|
, Verse
|
||||||
|
, Event (..)
|
||||||
|
, Env (..)
|
||||||
|
, Act
|
||||||
|
, ActE
|
||||||
|
, ActDB
|
||||||
|
, ActDBE
|
||||||
|
, Theater
|
||||||
|
|
||||||
|
-- * Behavior utilities
|
||||||
|
, withDB
|
||||||
|
, withDBExcept
|
||||||
|
, behave
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Data.Barbie
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Hashable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist.Sql
|
||||||
|
import GHC.Generics
|
||||||
|
import UnliftIO.Exception
|
||||||
|
import Web.Hashids
|
||||||
|
|
||||||
|
import qualified Data.Aeson as A
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Web.Actor
|
||||||
|
import Web.Actor.Persist
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Model hiding (Actor, Message)
|
||||||
|
import Vervis.Settings
|
||||||
|
|
||||||
|
data LocalActorBy f
|
||||||
|
= LocalActorPerson (f Person)
|
||||||
|
| LocalActorGroup (f Group)
|
||||||
|
| LocalActorRepo (f Repo)
|
||||||
|
| LocalActorDeck (f Deck)
|
||||||
|
| LocalActorLoom (f Loom)
|
||||||
|
deriving (Generic, FunctorB, ConstraintsB)
|
||||||
|
|
||||||
|
deriving instance AllBF Eq f LocalActorBy => Eq (LocalActorBy f)
|
||||||
|
deriving instance AllBF Ord f LocalActorBy => Ord (LocalActorBy f)
|
||||||
|
deriving instance AllBF Hashable f LocalActorBy => Hashable (LocalActorBy f)
|
||||||
|
deriving instance AllBF Show f LocalActorBy => Show (LocalActorBy f)
|
||||||
|
|
||||||
|
type LocalActor = LocalActorBy KeyHashid
|
||||||
|
|
||||||
|
data TicketRoutes = TicketRoutes
|
||||||
|
{ routeTicketFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data ClothRoutes = ClothRoutes
|
||||||
|
{ routeClothFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data PersonRoutes = PersonRoutes
|
||||||
|
{ routePerson :: Bool
|
||||||
|
, routePersonFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data GroupRoutes = GroupRoutes
|
||||||
|
{ routeGroup :: Bool
|
||||||
|
, routeGroupFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data RepoRoutes = RepoRoutes
|
||||||
|
{ routeRepo :: Bool
|
||||||
|
, routeRepoFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data DeckRoutes = DeckRoutes
|
||||||
|
{ routeDeck :: Bool
|
||||||
|
, routeDeckFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data LoomRoutes = LoomRoutes
|
||||||
|
{ routeLoom :: Bool
|
||||||
|
, routeLoomFollowers :: Bool
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data DeckFamilyRoutes = DeckFamilyRoutes
|
||||||
|
{ familyDeck :: DeckRoutes
|
||||||
|
, familyTickets :: [(KeyHashid TicketDeck, TicketRoutes)]
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data LoomFamilyRoutes = LoomFamilyRoutes
|
||||||
|
{ familyLoom :: LoomRoutes
|
||||||
|
, familyCloths :: [(KeyHashid TicketLoom, ClothRoutes)]
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data RecipientRoutes = RecipientRoutes
|
||||||
|
{ recipPeople :: [(KeyHashid Person, PersonRoutes)]
|
||||||
|
, recipGroups :: [(KeyHashid Group , GroupRoutes)]
|
||||||
|
, recipRepos :: [(KeyHashid Repo , RepoRoutes)]
|
||||||
|
, recipDecks :: [(KeyHashid Deck , DeckFamilyRoutes)]
|
||||||
|
, recipLooms :: [(KeyHashid Loom , LoomFamilyRoutes)]
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
data RemoteAuthor = RemoteAuthor
|
||||||
|
{ remoteAuthorURI :: FedURI
|
||||||
|
, remoteAuthorInstance :: InstanceId
|
||||||
|
, remoteAuthorId :: RemoteActorId
|
||||||
|
}
|
||||||
|
|
||||||
|
data ActivityBody = ActivityBody
|
||||||
|
{ actbBL :: BL.ByteString
|
||||||
|
, actbObject :: A.Object
|
||||||
|
, actbActivity :: AP.Activity URIMode
|
||||||
|
}
|
||||||
|
|
||||||
|
data VerseRemote = VerseRemote
|
||||||
|
{ verseAuthor :: RemoteAuthor
|
||||||
|
, verseBody :: ActivityBody
|
||||||
|
, verseForward :: Maybe (RecipientRoutes, ByteString)
|
||||||
|
, verseActivity :: LocalURI
|
||||||
|
}
|
||||||
|
|
||||||
|
data Event
|
||||||
|
= EventFwdRemoteGrantToSomeoneElse RemoteActivityId
|
||||||
|
| EventRemoteFwdLocalActivity (LocalActorBy Key) OutboxItemId
|
||||||
|
| EventUnknown
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
type Verse = Either Event VerseRemote
|
||||||
|
|
||||||
|
instance Message Verse where
|
||||||
|
summarize (Left event) = T.pack $ show event
|
||||||
|
summarize (Right (VerseRemote author body _fwd uri)) =
|
||||||
|
let ObjURI h _ = remoteAuthorURI author
|
||||||
|
typ = AP.activityType $ AP.activitySpecific $ actbActivity body
|
||||||
|
in T.concat [typ, " ", renderObjURI $ ObjURI h uri]
|
||||||
|
refer (Left event) = T.pack $ show event
|
||||||
|
refer (Right (VerseRemote author _body _fwd uri)) =
|
||||||
|
let ObjURI h _ = remoteAuthorURI author
|
||||||
|
in renderObjURI $ ObjURI h uri
|
||||||
|
|
||||||
|
-- | Data to which every actor has access. Since such data can be passed to the
|
||||||
|
-- behavior function when launching the actor, having a dedicated datatype is
|
||||||
|
-- just convenience. The main reason is to allow 'runDB' not to take a
|
||||||
|
-- connection pool parameter, instead grabbing it from the ReaderT. Another
|
||||||
|
-- reason is to avoid the clutter of passing the same arguments manually
|
||||||
|
-- everywhere.
|
||||||
|
--
|
||||||
|
-- Maybe in the future there won't be data shared by all actors, and then this
|
||||||
|
-- type can be removed.
|
||||||
|
data Env = Env
|
||||||
|
{ envSettings :: AppSettings
|
||||||
|
, envDbPool :: ConnectionPool
|
||||||
|
, envHashidsContext :: HashidsContext
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Stage Env where
|
||||||
|
type StageKey Env = LocalActorBy Key
|
||||||
|
type StageMessage Env = Verse
|
||||||
|
type StageReturn Env = Either Text Text
|
||||||
|
|
||||||
|
instance StageWeb Env where
|
||||||
|
type StageURIMode Env = URIMode
|
||||||
|
stageInstanceHost = appInstanceHost . envSettings
|
||||||
|
|
||||||
|
instance StageHashids Env where
|
||||||
|
stageHashidsContext = envHashidsContext
|
||||||
|
|
||||||
|
type Act = ActFor Env
|
||||||
|
|
||||||
|
type ActE = ActForE Env
|
||||||
|
|
||||||
|
type ActDB = SqlPersistT Act
|
||||||
|
|
||||||
|
type ActDBE = ExceptT Text ActDB
|
||||||
|
|
||||||
|
type Theater = TheaterFor Env
|
||||||
|
|
||||||
|
-- | Run a database transaction. If an exception is thrown, the whole
|
||||||
|
-- transaction is aborted.
|
||||||
|
withDB :: ActDB a -> Act a
|
||||||
|
withDB action = do
|
||||||
|
env <- askEnv
|
||||||
|
runPool (appDatabaseConf $ envSettings env) action (envDbPool env)
|
||||||
|
|
||||||
|
newtype FedError = FedError Text deriving Show
|
||||||
|
|
||||||
|
instance Exception FedError
|
||||||
|
|
||||||
|
-- | Like 'withDB', but supports errors via 'ExceptT. If an exception is
|
||||||
|
-- thrown, either via the 'ExceptT' or via regular throwing, the whole
|
||||||
|
-- transaction is aborted.
|
||||||
|
withDBExcept :: ExceptT Text (SqlPersistT Act) a -> ExceptT Text Act a
|
||||||
|
withDBExcept action = do
|
||||||
|
result <- lift $ try $ withDB $ either abort return =<< runExceptT action
|
||||||
|
case result of
|
||||||
|
Left (FedError t) -> throwE t
|
||||||
|
Right r -> return r
|
||||||
|
where
|
||||||
|
abort = throwIO . FedError
|
||||||
|
|
||||||
|
behave
|
||||||
|
:: (UTCTime -> Key a -> Verse -> ExceptT Text Act (Text, Act (), Next))
|
||||||
|
-> (Key a -> Verse -> Act (Either Text Text, Act (), Next))
|
||||||
|
behave handler key msg = do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
result <- runExceptT $ handler now key msg
|
||||||
|
case result of
|
||||||
|
Left e -> done $ Left e
|
||||||
|
Right (t, after, next) -> return (Right t, after, next)
|
64
src/Vervis/Actor/Deck.hs
Normal file
64
src/Vervis/Actor/Deck.hs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2023 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.Actor.Deck
|
||||||
|
( deckBehavior
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist
|
||||||
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Cloth
|
||||||
|
import Vervis.Data.Discussion
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Federation.Util
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Model
|
||||||
|
import Vervis.Persist.Discussion
|
||||||
|
import Vervis.Ticket
|
||||||
|
|
||||||
|
deckBehavior
|
||||||
|
:: UTCTime -> DeckId -> Verse -> ExceptT Text Act (Text, Act (), Next)
|
||||||
|
deckBehavior now deckID (Left event) =
|
||||||
|
case event of
|
||||||
|
EventRemoteFwdLocalActivity _ _ ->
|
||||||
|
throwE "Got a forwarded local activity, I don't need those"
|
||||||
|
_ -> throwE $ "Unsupported event for Deck: " <> T.pack (show event)
|
||||||
|
deckBehavior now deckID (Right (VerseRemote author body mfwd luActivity)) =
|
||||||
|
case AP.activitySpecific $ actbActivity body of
|
||||||
|
_ -> throwE "Unsupported activity type for Deck"
|
64
src/Vervis/Actor/Group.hs
Normal file
64
src/Vervis/Actor/Group.hs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2023 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.Actor.Group
|
||||||
|
( groupBehavior
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist
|
||||||
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Cloth
|
||||||
|
import Vervis.Data.Discussion
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Federation.Util
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Model
|
||||||
|
import Vervis.Persist.Discussion
|
||||||
|
import Vervis.Ticket
|
||||||
|
|
||||||
|
groupBehavior
|
||||||
|
:: UTCTime -> GroupId -> Verse -> ExceptT Text Act (Text, Act (), Next)
|
||||||
|
groupBehavior now groupID (Left event) =
|
||||||
|
case event of
|
||||||
|
EventRemoteFwdLocalActivity _ _ ->
|
||||||
|
throwE "Got a forwarded local activity, I don't need those"
|
||||||
|
_ -> throwE $ "Unsupported event for Group: " <> T.pack (show event)
|
||||||
|
groupBehavior now groupID (Right (VerseRemote author body mfwd luActivity)) =
|
||||||
|
case AP.activitySpecific $ actbActivity body of
|
||||||
|
_ -> throwE "Unsupported activity type for Group"
|
64
src/Vervis/Actor/Loom.hs
Normal file
64
src/Vervis/Actor/Loom.hs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2023 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.Actor.Loom
|
||||||
|
( loomBehavior
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist
|
||||||
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Cloth
|
||||||
|
import Vervis.Data.Discussion
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Federation.Util
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Model
|
||||||
|
import Vervis.Persist.Discussion
|
||||||
|
import Vervis.Ticket
|
||||||
|
|
||||||
|
loomBehavior
|
||||||
|
:: UTCTime -> LoomId -> Verse -> ExceptT Text Act (Text, Act (), Next)
|
||||||
|
loomBehavior now loomID (Left event) =
|
||||||
|
case event of
|
||||||
|
EventRemoteFwdLocalActivity _ _ ->
|
||||||
|
throwE "Got a forwarded local activity, I don't need those"
|
||||||
|
_ -> throwE $ "Unsupported event for Loom: " <> T.pack (show event)
|
||||||
|
loomBehavior now loomID (Right (VerseRemote author body mfwd luActivity)) =
|
||||||
|
case AP.activitySpecific $ actbActivity body of
|
||||||
|
_ -> throwE "Unsupported activity type for Loom"
|
189
src/Vervis/Actor/Person.hs
Normal file
189
src/Vervis/Actor/Person.hs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016, 2018, 2019, 2020, 2022, 2023
|
||||||
|
- 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.Actor.Person
|
||||||
|
( personBehavior
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Control.Monad.Trans.Reader
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist
|
||||||
|
import Database.Persist.Sql
|
||||||
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Cloth
|
||||||
|
import Vervis.Data.Discussion
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Federation.Util
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Model
|
||||||
|
import Vervis.Persist.Actor
|
||||||
|
import Vervis.Persist.Discussion
|
||||||
|
import Vervis.Ticket
|
||||||
|
|
||||||
|
insertActivityToInbox
|
||||||
|
:: MonadIO m
|
||||||
|
=> UTCTime -> ActorId -> OutboxItemId -> ReaderT SqlBackend m Bool
|
||||||
|
insertActivityToInbox now recipActorID outboxItemID = do
|
||||||
|
inboxID <- actorInbox <$> getJust recipActorID
|
||||||
|
inboxItemID <- insert $ InboxItem True now
|
||||||
|
maybeItem <- insertUnique $ InboxItemLocal inboxID outboxItemID inboxItemID
|
||||||
|
case maybeItem of
|
||||||
|
Nothing -> do
|
||||||
|
delete inboxItemID
|
||||||
|
return False
|
||||||
|
Just _ -> return True
|
||||||
|
|
||||||
|
-- Meaning: Someone commented on an issue/PR
|
||||||
|
-- Behavior: Insert to inbox
|
||||||
|
personCreateNote
|
||||||
|
:: UTCTime
|
||||||
|
-> PersonId
|
||||||
|
-> RemoteAuthor
|
||||||
|
-> ActivityBody
|
||||||
|
-> Maybe (RecipientRoutes, ByteString)
|
||||||
|
-> LocalURI
|
||||||
|
-> AP.Note URIMode
|
||||||
|
-> ExceptT Text Act (Text, Act (), Next)
|
||||||
|
personCreateNote now recipPersonID author body mfwd luCreate note = do
|
||||||
|
|
||||||
|
-- Check input
|
||||||
|
(luNote, published, Comment maybeParent topic source content) <- do
|
||||||
|
(luId, luAuthor, published, comment) <- parseRemoteComment note
|
||||||
|
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
||||||
|
throwE "Create author != note author"
|
||||||
|
return (luId, published, comment)
|
||||||
|
|
||||||
|
mractid <- withDBExcept $ do
|
||||||
|
Entity recipActorID recipActor <- lift $ do
|
||||||
|
person <- getJust recipPersonID
|
||||||
|
let actorID = personActor person
|
||||||
|
Entity actorID <$> getJust actorID
|
||||||
|
|
||||||
|
case topic of
|
||||||
|
|
||||||
|
Right uContext -> do
|
||||||
|
checkContextParent uContext maybeParent
|
||||||
|
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
|
||||||
|
|
||||||
|
Left (CommentTopicTicket deckID taskID) -> do
|
||||||
|
(_, _, Entity _ ticket, _, _) <- do
|
||||||
|
mticket <- lift $ getTicket deckID taskID
|
||||||
|
fromMaybeE mticket "Context: No such deck-ticket"
|
||||||
|
let did = ticketDiscuss ticket
|
||||||
|
_ <- traverse (getMessageParent did) maybeParent
|
||||||
|
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
|
||||||
|
|
||||||
|
Left (CommentTopicCloth loomID clothID) -> do
|
||||||
|
(_, _, Entity _ ticket, _, _, _) <- do
|
||||||
|
mticket <- lift $ getCloth loomID clothID
|
||||||
|
fromMaybeE mticket "Context: No such loom-cloth"
|
||||||
|
let did = ticketDiscuss ticket
|
||||||
|
_ <- traverse (getMessageParent did) maybeParent
|
||||||
|
lift $ insertToInbox now author body (actorInbox recipActor) luCreate True
|
||||||
|
|
||||||
|
done $
|
||||||
|
case mractid of
|
||||||
|
Nothing -> "I already have this activity in my inbox, doing nothing"
|
||||||
|
Just _ -> "Inserted Create{Note} to my inbox"
|
||||||
|
where
|
||||||
|
checkContextParent (ObjURI hContext luContext) mparent = do
|
||||||
|
mdid <- lift $ runMaybeT $ do
|
||||||
|
iid <- MaybeT $ getKeyBy $ UniqueInstance hContext
|
||||||
|
roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid luContext
|
||||||
|
rd <- MaybeT $ getValBy $ UniqueRemoteDiscussionIdent roid
|
||||||
|
return $ remoteDiscussionDiscuss rd
|
||||||
|
for_ mparent $ \ parent ->
|
||||||
|
case parent of
|
||||||
|
Left msg -> do
|
||||||
|
did <- fromMaybeE mdid "Local parent inexistent, no RemoteDiscussion"
|
||||||
|
void $ getLocalParentMessageId did msg
|
||||||
|
Right (ObjURI hParent luParent) -> do
|
||||||
|
mrm <- lift $ runMaybeT $ do
|
||||||
|
iid <- MaybeT $ getKeyBy $ UniqueInstance hParent
|
||||||
|
roid <- MaybeT $ getKeyBy $ UniqueRemoteObject iid luParent
|
||||||
|
MaybeT $ getValBy $ UniqueRemoteMessageIdent roid
|
||||||
|
for_ mrm $ \ rm -> do
|
||||||
|
let mid = remoteMessageRest rm
|
||||||
|
m <- lift $ getJust mid
|
||||||
|
did <- fromMaybeE mdid "Remote parent known, but no context RemoteDiscussion"
|
||||||
|
unless (messageRoot m == did) $
|
||||||
|
throwE "Remote parent belongs to a different discussion"
|
||||||
|
|
||||||
|
personBehavior :: UTCTime -> PersonId -> Verse -> ActE (Text, Act (), Next)
|
||||||
|
personBehavior now personID (Left event) =
|
||||||
|
case event of
|
||||||
|
EventFwdRemoteGrantToSomeoneElse grantID -> do
|
||||||
|
lift $ withDB $ do
|
||||||
|
(_personRecip, actorRecip) <- do
|
||||||
|
p <- getJust personID
|
||||||
|
(p,) <$> getJust (personActor p)
|
||||||
|
let inboxID = actorInbox actorRecip
|
||||||
|
itemID <- insert $ InboxItem True now
|
||||||
|
insert_ $ InboxItemRemote inboxID grantID itemID
|
||||||
|
done "Inserted Grant to inbox"
|
||||||
|
EventRemoteFwdLocalActivity authorByKey outboxItemID -> withDBExcept $ do
|
||||||
|
recipPerson <- lift $ getJust personID
|
||||||
|
verifyLocalActivityExistsInDB authorByKey outboxItemID
|
||||||
|
if LocalActorPerson personID == authorByKey
|
||||||
|
then done "Received activity authored by self, ignoring"
|
||||||
|
else do
|
||||||
|
inserted <- lift $ insertActivityToInbox now (personActor recipPerson) outboxItemID
|
||||||
|
done $
|
||||||
|
if inserted
|
||||||
|
then "Activity inserted to my inbox"
|
||||||
|
else "Activity already exists in my inbox, ignoring"
|
||||||
|
_ -> throwE $ "Unsupported event for Person: " <> T.pack (show event)
|
||||||
|
personBehavior now personID (Right (VerseRemote author body mfwd luActivity)) =
|
||||||
|
case AP.activitySpecific $ actbActivity body of
|
||||||
|
AP.CreateActivity (AP.Create obj mtarget) ->
|
||||||
|
case obj of
|
||||||
|
AP.CreateNote _ note ->
|
||||||
|
personCreateNote now personID author body mfwd luActivity note
|
||||||
|
_ -> throwE "Unsupported create object type for people"
|
||||||
|
{-
|
||||||
|
AP.FollowActivity follow ->
|
||||||
|
personFollowA now personID author body mfwd luActivity follow
|
||||||
|
AP.GrantActivity grant ->
|
||||||
|
personGrantA now personID author body mfwd luActivity grant
|
||||||
|
AP.InviteActivity invite ->
|
||||||
|
personInviteA now personID author body mfwd luActivity invite
|
||||||
|
AP.UndoActivity undo ->
|
||||||
|
(,Nothing) <$> personUndoA now personID author body mfwd luActivity undo
|
||||||
|
-}
|
||||||
|
_ -> throwE "Unsupported activity type for Person"
|
64
src/Vervis/Actor/Repo.hs
Normal file
64
src/Vervis/Actor/Repo.hs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2023 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.Actor.Repo
|
||||||
|
( repoBehavior
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
|
import Control.Monad.Trans.Class
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Foldable
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Database.Persist
|
||||||
|
import Yesod.Persist.Core
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Cloth
|
||||||
|
import Vervis.Data.Discussion
|
||||||
|
import Vervis.FedURI
|
||||||
|
import Vervis.Federation.Util
|
||||||
|
import Vervis.Foundation
|
||||||
|
import Vervis.Model
|
||||||
|
import Vervis.Persist.Discussion
|
||||||
|
import Vervis.Ticket
|
||||||
|
|
||||||
|
repoBehavior
|
||||||
|
:: UTCTime -> RepoId -> Verse -> ExceptT Text Act (Text, Act (), Next)
|
||||||
|
repoBehavior now repoID (Left event) =
|
||||||
|
case event of
|
||||||
|
EventRemoteFwdLocalActivity _ _ ->
|
||||||
|
throwE "Got a forwarded local activity, I don't need those"
|
||||||
|
_ -> throwE $ "Unsupported event for Repo: " <> T.pack (show event)
|
||||||
|
repoBehavior now repoID (Right (VerseRemote author body mfwd luActivity)) =
|
||||||
|
case AP.activitySpecific $ actbActivity body of
|
||||||
|
_ -> throwE "Unsupported activity type for Repo"
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2018, 2019, 2020, 2022
|
- Written in 2016, 2018, 2019, 2020, 2022, 2023
|
||||||
- by fr33domlover <fr33domlover@riseup.net>.
|
- 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.
|
||||||
|
@ -32,10 +32,12 @@ module Vervis.Application
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Concurrent.Chan
|
import Control.Concurrent.Chan
|
||||||
|
import Control.Concurrent.STM
|
||||||
import Control.Concurrent.STM.TVar
|
import Control.Concurrent.STM.TVar
|
||||||
import Control.Exception hiding (Handler)
|
import Control.Exception hiding (Handler)
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Logger (liftLoc, runLoggingT, logInfo, logError)
|
import Control.Monad.Logger (liftLoc, runLoggingT, logInfo, logError)
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
import Control.Monad.Trans.Reader
|
import Control.Monad.Trans.Reader
|
||||||
import Data.Bifunctor
|
import Data.Bifunctor
|
||||||
import Data.Default.Class
|
import Data.Default.Class
|
||||||
|
@ -47,6 +49,7 @@ import Data.Maybe
|
||||||
import Data.Proxy
|
import Data.Proxy
|
||||||
import Data.String
|
import Data.String
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
import Data.Time.Clock
|
||||||
import Data.Traversable
|
import Data.Traversable
|
||||||
import Database.Persist.Postgresql
|
import Database.Persist.Postgresql
|
||||||
import Graphics.SVGFonts.Fonts (lin2)
|
import Graphics.SVGFonts.Fonts (lin2)
|
||||||
|
@ -75,6 +78,7 @@ import Yesod.Persist.Core
|
||||||
import Yesod.Static
|
import Yesod.Static
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
@ -82,6 +86,7 @@ import Database.Persist.Schema.PostgreSQL (schemaBackend)
|
||||||
import Dvara
|
import Dvara
|
||||||
import Yesod.Mail.Send (runMailer)
|
import Yesod.Mail.Send (runMailer)
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
import Control.Concurrent.ResultShare
|
import Control.Concurrent.ResultShare
|
||||||
import Crypto.ActorKey
|
import Crypto.ActorKey
|
||||||
import Data.KeyFile
|
import Data.KeyFile
|
||||||
|
@ -94,8 +99,14 @@ import Control.Concurrent.Local
|
||||||
import Data.List.NonEmpty.Local
|
import Data.List.NonEmpty.Local
|
||||||
import Web.Hashids.Local
|
import Web.Hashids.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
|
import Vervis.Actor.Deck
|
||||||
|
import Vervis.Actor.Group
|
||||||
|
import Vervis.Actor.Loom
|
||||||
|
import Vervis.Actor.Person
|
||||||
|
import Vervis.Actor.Repo
|
||||||
import Vervis.Darcs
|
import Vervis.Darcs
|
||||||
import Vervis.Web.Delivery
|
import Vervis.Data.Actor
|
||||||
import Vervis.Foundation
|
import Vervis.Foundation
|
||||||
import Vervis.Git
|
import Vervis.Git
|
||||||
import Vervis.Hook
|
import Vervis.Hook
|
||||||
|
@ -127,6 +138,7 @@ import Vervis.Path
|
||||||
import Vervis.Persist.Actor
|
import Vervis.Persist.Actor
|
||||||
import Vervis.Settings
|
import Vervis.Settings
|
||||||
import Vervis.Ssh (runSsh)
|
import Vervis.Ssh (runSsh)
|
||||||
|
import Vervis.Web.Delivery
|
||||||
|
|
||||||
-- 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
|
||||||
|
@ -176,6 +188,9 @@ makeFoundation appSettings = do
|
||||||
|
|
||||||
appActorFetchShare <- newResultShare actorFetchShareAction
|
appActorFetchShare <- newResultShare actorFetchShareAction
|
||||||
|
|
||||||
|
-- Temporarily blank actor map, we'll replace it in a moment
|
||||||
|
--appTheatre <- startTheater (error "logFunc") (error "env") []
|
||||||
|
|
||||||
appActivities <-
|
appActivities <-
|
||||||
case appInboxDebugReportLength appSettings of
|
case appInboxDebugReportLength appSettings of
|
||||||
Nothing -> return Nothing
|
Nothing -> return Nothing
|
||||||
|
@ -189,7 +204,8 @@ makeFoundation appSettings = do
|
||||||
let mkFoundation
|
let mkFoundation
|
||||||
appConnPool
|
appConnPool
|
||||||
appCapSignKey
|
appCapSignKey
|
||||||
appHashidsContext =
|
appHashidsContext
|
||||||
|
appTheater =
|
||||||
App {..}
|
App {..}
|
||||||
-- The App {..} syntax is an example of record wild cards. For more
|
-- The App {..} syntax is an example of record wild cards. For more
|
||||||
-- information, see:
|
-- information, see:
|
||||||
|
@ -199,6 +215,7 @@ makeFoundation appSettings = do
|
||||||
(error "connPool forced in tempFoundation")
|
(error "connPool forced in tempFoundation")
|
||||||
(error "capSignKey forced in tempFoundation")
|
(error "capSignKey forced in tempFoundation")
|
||||||
(error "hashidsContext forced in tempFoundation")
|
(error "hashidsContext forced in tempFoundation")
|
||||||
|
(error "theater forced in tempFoundation")
|
||||||
logFunc = loggingFunction tempFoundation
|
logFunc = loggingFunction tempFoundation
|
||||||
|
|
||||||
-- Create the database connection pool
|
-- Create the database connection pool
|
||||||
|
@ -213,7 +230,7 @@ makeFoundation appSettings = do
|
||||||
hashidsSalt <- loadKeyFile loadMode $ appHashidsSaltFile appSettings
|
hashidsSalt <- loadKeyFile loadMode $ appHashidsSaltFile appSettings
|
||||||
let hashidsCtx = hashidsContext hashidsSalt
|
let hashidsCtx = hashidsContext hashidsSalt
|
||||||
|
|
||||||
app = mkFoundation pool capSignKey hashidsCtx
|
app = mkFoundation pool capSignKey hashidsCtx (error "theater")
|
||||||
|
|
||||||
-- Perform database migration using our application's logging settings.
|
-- Perform database migration using our application's logging settings.
|
||||||
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
||||||
|
@ -227,6 +244,11 @@ makeFoundation appSettings = do
|
||||||
writePostReceiveHooks
|
writePostReceiveHooks
|
||||||
writePostApplyHooks
|
writePostApplyHooks
|
||||||
|
|
||||||
|
-- Launch actor threads and fill the actor map
|
||||||
|
actors <- flip runWorker app $ runSiteDB loadTheatre
|
||||||
|
let env = Env appSettings pool hashidsCtx
|
||||||
|
theater <- startTheater logFunc env actors
|
||||||
|
|
||||||
let hostString = T.unpack $ renderAuthority hLocal
|
let hostString = T.unpack $ renderAuthority hLocal
|
||||||
writeHookConfig hostString Config
|
writeHookConfig hostString Config
|
||||||
{ configSecret = hookSecretText appHookSecret
|
{ configSecret = hookSecretText appHookSecret
|
||||||
|
@ -235,7 +257,7 @@ makeFoundation appSettings = do
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Return the foundation
|
-- Return the foundation
|
||||||
return app
|
return app { appTheater = theater }
|
||||||
where
|
where
|
||||||
verifyRepoDir = do
|
verifyRepoDir = do
|
||||||
repos <- lift reposFromDir
|
repos <- lift reposFromDir
|
||||||
|
@ -300,6 +322,23 @@ makeFoundation appSettings = do
|
||||||
, T.pack $ show from, " ==> ", T.pack $ show to
|
, T.pack $ show from, " ==> ", T.pack $ show to
|
||||||
]
|
]
|
||||||
|
|
||||||
|
loadTheatre = concat <$> sequenceA
|
||||||
|
[ selectAll LocalActorPerson personBehavior
|
||||||
|
, selectAll LocalActorGroup groupBehavior
|
||||||
|
, selectAll LocalActorRepo repoBehavior
|
||||||
|
, selectAll LocalActorDeck deckBehavior
|
||||||
|
, selectAll LocalActorLoom loomBehavior
|
||||||
|
]
|
||||||
|
where
|
||||||
|
selectAll
|
||||||
|
:: PersistRecordBackend a SqlBackend
|
||||||
|
=> (Key a -> LocalActorBy Key)
|
||||||
|
-> (UTCTime -> Key a -> Verse -> ExceptT Text Act (Text, Act (), Next))
|
||||||
|
-> WorkerDB [(LocalActorBy Key, Verse -> Act (Either Text Text, Act (), Next))]
|
||||||
|
selectAll makeLocalActor behavior =
|
||||||
|
map (\ xid -> (makeLocalActor xid, behave behavior xid)) <$>
|
||||||
|
selectKeysList [] []
|
||||||
|
|
||||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||||
-- applying some additional middlewares.
|
-- applying some additional middlewares.
|
||||||
makeApplication :: App -> IO Application
|
makeApplication :: App -> IO Application
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -21,17 +21,27 @@ module Vervis.Data.Actor
|
||||||
, parseStampRoute
|
, parseStampRoute
|
||||||
, localActorID
|
, localActorID
|
||||||
, parseLocalURI
|
, parseLocalURI
|
||||||
, parseFedURI
|
, parseFedURIOld
|
||||||
, parseLocalActorE
|
, parseLocalActorE
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Control.Concurrent.Chan
|
||||||
|
import Control.Concurrent.STM.TVar
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
import Control.Monad.Logger.CallStack
|
||||||
import Control.Monad.Trans.Except
|
import Control.Monad.Trans.Except
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Database.Persist.Types
|
import Database.Persist.Types
|
||||||
|
import UnliftIO.Exception (try, SomeException, displayException)
|
||||||
|
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -76,7 +86,7 @@ parseActivityURI
|
||||||
FedURI
|
FedURI
|
||||||
)
|
)
|
||||||
parseActivityURI u@(ObjURI h lu) = do
|
parseActivityURI u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> parseLocalActivityURI lu
|
then Left <$> parseLocalActivityURI lu
|
||||||
else pure $ Right u
|
else pure $ Right u
|
||||||
|
@ -95,6 +105,8 @@ stampRoute (LocalActorRepo r) = RepoStampR r
|
||||||
stampRoute (LocalActorDeck d) = DeckStampR d
|
stampRoute (LocalActorDeck d) = DeckStampR d
|
||||||
stampRoute (LocalActorLoom l) = LoomStampR l
|
stampRoute (LocalActorLoom l) = LoomStampR l
|
||||||
|
|
||||||
|
parseStampRoute
|
||||||
|
:: Route App -> Maybe (LocalActorBy KeyHashid, KeyHashid SigKey)
|
||||||
parseStampRoute (PersonStampR p i) = Just (LocalActorPerson p, i)
|
parseStampRoute (PersonStampR p i) = Just (LocalActorPerson p, i)
|
||||||
parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i)
|
parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i)
|
||||||
parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i)
|
parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i)
|
||||||
|
@ -102,18 +114,23 @@ parseStampRoute (DeckStampR d i) = Just (LocalActorDeck d, i)
|
||||||
parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i)
|
parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i)
|
||||||
parseStampRoute _ = Nothing
|
parseStampRoute _ = Nothing
|
||||||
|
|
||||||
|
localActorID :: LocalActorBy Entity -> ActorId
|
||||||
localActorID (LocalActorPerson (Entity _ p)) = personActor p
|
localActorID (LocalActorPerson (Entity _ p)) = personActor p
|
||||||
localActorID (LocalActorGroup (Entity _ g)) = groupActor g
|
localActorID (LocalActorGroup (Entity _ g)) = groupActor g
|
||||||
localActorID (LocalActorRepo (Entity _ r)) = repoActor r
|
localActorID (LocalActorRepo (Entity _ r)) = repoActor r
|
||||||
localActorID (LocalActorDeck (Entity _ d)) = deckActor d
|
localActorID (LocalActorDeck (Entity _ d)) = deckActor d
|
||||||
localActorID (LocalActorLoom (Entity _ l)) = loomActor l
|
localActorID (LocalActorLoom (Entity _ l)) = loomActor l
|
||||||
|
|
||||||
parseLocalURI :: LocalURI -> ExceptT Text Handler (Route App)
|
parseFedURIOld
|
||||||
parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route"
|
:: ( MonadSite m
|
||||||
|
, SiteEnv m ~ site
|
||||||
parseFedURI :: FedURI -> ExceptT Text Handler (Either (Route App) FedURI)
|
, YesodActivityPub site
|
||||||
parseFedURI u@(ObjURI h lu) = do
|
, SiteFedURIMode site ~ URIMode
|
||||||
hl <- hostIsLocal h
|
)
|
||||||
|
=> FedURI
|
||||||
|
-> ExceptT Text m (Either (Route App) FedURI)
|
||||||
|
parseFedURIOld u@(ObjURI h lu) = do
|
||||||
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> parseLocalURI lu
|
then Left <$> parseLocalURI lu
|
||||||
else pure $ Right u
|
else pure $ Right u
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -42,6 +42,7 @@ import GHC.Generics
|
||||||
|
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -86,7 +87,7 @@ verifyRole (Right _) =
|
||||||
throwE "ForgeFed Admin is the only role allowed currently"
|
throwE "ForgeFed Admin is the only role allowed currently"
|
||||||
|
|
||||||
parseTopic u = do
|
parseTopic u = do
|
||||||
routeOrRemote <- parseFedURI u
|
routeOrRemote <- parseFedURIOld u
|
||||||
bitraverse
|
bitraverse
|
||||||
(\ route -> do
|
(\ route -> do
|
||||||
resourceHash <-
|
resourceHash <-
|
||||||
|
@ -113,7 +114,7 @@ parseInvite sender (AP.Invite instrument object target) = do
|
||||||
<*> nameExceptT "Invite object" (parseRecipient object)
|
<*> nameExceptT "Invite object" (parseRecipient object)
|
||||||
where
|
where
|
||||||
parseRecipient u = do
|
parseRecipient u = do
|
||||||
routeOrRemote <- parseFedURI u
|
routeOrRemote <- parseFedURIOld u
|
||||||
bitraverse
|
bitraverse
|
||||||
(\ route -> do
|
(\ route -> do
|
||||||
recipHash <-
|
recipHash <-
|
||||||
|
@ -158,7 +159,7 @@ parseGrant (AP.Grant object context target) = do
|
||||||
verifyRole (Right _) =
|
verifyRole (Right _) =
|
||||||
throwE "ForgeFed Admin is the only role allowed currently"
|
throwE "ForgeFed Admin is the only role allowed currently"
|
||||||
parseContext u@(ObjURI h lu) = do
|
parseContext u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> do
|
then Left <$> do
|
||||||
route <-
|
route <-
|
||||||
|
@ -179,7 +180,7 @@ parseGrant (AP.Grant object context target) = do
|
||||||
parseGrantResource (LoomR l) = Just $ GrantResourceLoom l
|
parseGrantResource (LoomR l) = Just $ GrantResourceLoom l
|
||||||
parseGrantResource _ = Nothing
|
parseGrantResource _ = Nothing
|
||||||
parseTarget u@(ObjURI h lu) = do
|
parseTarget u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> do
|
then Left <$> do
|
||||||
route <-
|
route <-
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -18,7 +19,8 @@ module Vervis.Data.Discussion
|
||||||
, commentTopicAudience
|
, commentTopicAudience
|
||||||
, commentTopicManagingActor
|
, commentTopicManagingActor
|
||||||
, Comment (..)
|
, Comment (..)
|
||||||
, parseNewLocalComment
|
, parseNewLocalCommentOld
|
||||||
|
, parseRemoteCommentOld
|
||||||
, parseRemoteComment
|
, parseRemoteComment
|
||||||
, messageRoute
|
, messageRoute
|
||||||
)
|
)
|
||||||
|
@ -29,24 +31,53 @@ import Data.Bitraversable
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
|
import Web.Actor.Persist
|
||||||
import Web.Text
|
import Web.Text
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.MonadSite
|
||||||
|
|
||||||
import qualified Web.ActivityPub as AP
|
import qualified Web.ActivityPub as AP
|
||||||
|
import qualified Yesod.Hashids as YH
|
||||||
|
|
||||||
import Control.Monad.Trans.Except.Local
|
import Control.Monad.Trans.Except.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
import Vervis.Foundation
|
import Vervis.Foundation
|
||||||
import Vervis.Model
|
import Vervis.Model
|
||||||
import Vervis.Recipient
|
import Vervis.Recipient
|
||||||
|
|
||||||
parseCommentId
|
parseCommentIdOld
|
||||||
:: Route App -> ExceptT Text Handler (LocalActorBy Key, LocalMessageId)
|
:: ( MonadSite m
|
||||||
|
, SiteEnv m ~ site
|
||||||
|
, YH.YesodHashids site
|
||||||
|
, SiteFedURIMode site ~ URIMode
|
||||||
|
)
|
||||||
|
=> Route App
|
||||||
|
-> ExceptT Text m (LocalActorBy Key, LocalMessageId)
|
||||||
|
parseCommentIdOld (PersonMessageR p m) =
|
||||||
|
(,) <$> (LocalActorPerson <$> YH.decodeKeyHashidE p "Invalid actor keyhashid")
|
||||||
|
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
parseCommentIdOld (GroupMessageR g m) =
|
||||||
|
(,) <$> (LocalActorGroup <$> YH.decodeKeyHashidE g "Invalid actor keyhashid")
|
||||||
|
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
parseCommentIdOld (RepoMessageR r m) =
|
||||||
|
(,) <$> (LocalActorRepo <$> YH.decodeKeyHashidE r "Invalid actor keyhashid")
|
||||||
|
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
parseCommentIdOld (DeckMessageR d m) =
|
||||||
|
(,) <$> (LocalActorDeck <$> YH.decodeKeyHashidE d "Invalid actor keyhashid")
|
||||||
|
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
parseCommentIdOld (LoomMessageR l m) =
|
||||||
|
(,) <$> (LocalActorLoom <$> YH.decodeKeyHashidE l "Invalid actor keyhashid")
|
||||||
|
<*> YH.decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
parseCommentIdOld _ = throwE "Not a message route"
|
||||||
|
|
||||||
|
parseCommentId :: Route App -> ActE (LocalActorBy Key, LocalMessageId)
|
||||||
parseCommentId (PersonMessageR p m) =
|
parseCommentId (PersonMessageR p m) =
|
||||||
(,) <$> (LocalActorPerson <$> decodeKeyHashidE p "Invalid actor keyhashid")
|
(,) <$> (LocalActorPerson <$> decodeKeyHashidE p "Invalid actor keyhashid")
|
||||||
<*> decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
<*> decodeKeyHashidE m "Invalid LocalMessage keyhashid"
|
||||||
|
@ -77,7 +108,24 @@ commentTopicAudience (CommentTopicCloth loomID clothID) =
|
||||||
commentTopicManagingActor :: CommentTopic -> LocalActorBy Key
|
commentTopicManagingActor :: CommentTopic -> LocalActorBy Key
|
||||||
commentTopicManagingActor = fst . commentTopicAudience
|
commentTopicManagingActor = fst . commentTopicAudience
|
||||||
|
|
||||||
parseCommentTopic :: Route App -> ExceptT Text Handler CommentTopic
|
parseCommentTopicOld
|
||||||
|
:: (MonadSite m, YH.YesodHashids (SiteEnv m))
|
||||||
|
=> Route App
|
||||||
|
-> ExceptT Text m CommentTopic
|
||||||
|
parseCommentTopicOld (TicketR dkhid ltkhid) =
|
||||||
|
CommentTopicTicket
|
||||||
|
<$> YH.decodeKeyHashidE dkhid "Invalid dkhid"
|
||||||
|
<*> YH.decodeKeyHashidE ltkhid "Invalid ltkhid"
|
||||||
|
parseCommentTopicOld (ClothR lkhid ltkhid) =
|
||||||
|
CommentTopicCloth
|
||||||
|
<$> YH.decodeKeyHashidE lkhid "Invalid lkhid"
|
||||||
|
<*> YH.decodeKeyHashidE ltkhid "Invalid ltkhid"
|
||||||
|
parseCommentTopicOld _ = throwE "Not a ticket/cloth route"
|
||||||
|
|
||||||
|
parseCommentTopic
|
||||||
|
:: (MonadActor m, StageHashids (ActorEnv m))
|
||||||
|
=> Route App
|
||||||
|
-> ExceptT Text m CommentTopic
|
||||||
parseCommentTopic (TicketR dkhid ltkhid) =
|
parseCommentTopic (TicketR dkhid ltkhid) =
|
||||||
CommentTopicTicket
|
CommentTopicTicket
|
||||||
<$> decodeKeyHashidE dkhid "Invalid dkhid"
|
<$> decodeKeyHashidE dkhid "Invalid dkhid"
|
||||||
|
@ -95,7 +143,28 @@ data Comment = Comment
|
||||||
, commentContent :: HTML
|
, commentContent :: HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
parseComment :: AP.Note URIMode -> ExceptT Text Handler (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment)
|
parseCommentOld
|
||||||
|
:: ( MonadSite m
|
||||||
|
, SiteEnv m ~ site
|
||||||
|
, YH.YesodHashids site
|
||||||
|
, YesodActivityPub site
|
||||||
|
, SiteFedURIMode site ~ URIMode
|
||||||
|
)
|
||||||
|
=> AP.Note URIMode
|
||||||
|
-> ExceptT Text m (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment)
|
||||||
|
parseCommentOld (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do
|
||||||
|
uContext <- fromMaybeE muContext "Note without context"
|
||||||
|
topic <- bitraverse parseCommentTopicOld pure =<< parseFedURIOld uContext
|
||||||
|
maybeParent <- do
|
||||||
|
uParent <- fromMaybeE muParent "Note doesn't specify inReplyTo"
|
||||||
|
if uParent == uContext
|
||||||
|
then pure Nothing
|
||||||
|
else fmap Just . bitraverse parseCommentIdOld pure =<< parseFedURIOld uParent
|
||||||
|
return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content)
|
||||||
|
|
||||||
|
parseComment
|
||||||
|
:: AP.Note URIMode
|
||||||
|
-> ActE (Maybe LocalURI, LocalURI, Maybe UTCTime, Comment)
|
||||||
parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do
|
parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source content) = do
|
||||||
uContext <- fromMaybeE muContext "Note without context"
|
uContext <- fromMaybeE muContext "Note without context"
|
||||||
topic <- bitraverse parseCommentTopic pure =<< parseFedURI uContext
|
topic <- bitraverse parseCommentTopic pure =<< parseFedURI uContext
|
||||||
|
@ -106,10 +175,10 @@ parseComment (AP.Note mluNote luAttrib _aud muParent muContext mpublished source
|
||||||
else fmap Just . bitraverse parseCommentId pure =<< parseFedURI uParent
|
else fmap Just . bitraverse parseCommentId pure =<< parseFedURI uParent
|
||||||
return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content)
|
return (mluNote, luAttrib, mpublished, Comment maybeParent topic source content)
|
||||||
|
|
||||||
parseNewLocalComment
|
parseNewLocalCommentOld
|
||||||
:: AP.Note URIMode -> ExceptT Text Handler (PersonId, Comment)
|
:: AP.Note URIMode -> ExceptT Text Handler (PersonId, Comment)
|
||||||
parseNewLocalComment note = do
|
parseNewLocalCommentOld note = do
|
||||||
(mluId, luAuthor, maybePublished, comment) <- parseComment note
|
(mluId, luAuthor, maybePublished, comment) <- parseCommentOld note
|
||||||
verifyNothingE mluId "Note specifies an id"
|
verifyNothingE mluId "Note specifies an id"
|
||||||
authorPersonID <- do
|
authorPersonID <- do
|
||||||
authorByKey <-
|
authorByKey <-
|
||||||
|
@ -121,9 +190,24 @@ parseNewLocalComment note = do
|
||||||
verifyNothingE maybePublished "Note specifies published"
|
verifyNothingE maybePublished "Note specifies published"
|
||||||
return (authorPersonID, comment)
|
return (authorPersonID, comment)
|
||||||
|
|
||||||
|
parseRemoteCommentOld
|
||||||
|
:: ( MonadSite m
|
||||||
|
, SiteEnv m ~ site
|
||||||
|
, YH.YesodHashids site
|
||||||
|
, YesodActivityPub site
|
||||||
|
, SiteFedURIMode site ~ URIMode
|
||||||
|
)
|
||||||
|
=> AP.Note URIMode
|
||||||
|
-> ExceptT Text m (LocalURI, LocalURI, UTCTime, Comment)
|
||||||
|
parseRemoteCommentOld note = do
|
||||||
|
(mluId, luAuthor, maybePublished, comment) <- parseCommentOld note
|
||||||
|
luId <- fromMaybeE mluId "Note doesn't specify id"
|
||||||
|
published <- fromMaybeE maybePublished "Note doesn't specify published"
|
||||||
|
return (luId, luAuthor, published, comment)
|
||||||
|
|
||||||
parseRemoteComment
|
parseRemoteComment
|
||||||
:: AP.Note URIMode
|
:: AP.Note URIMode
|
||||||
-> ExceptT Text Handler (LocalURI, LocalURI, UTCTime, Comment)
|
-> ExceptT Text Act (LocalURI, LocalURI, UTCTime, Comment)
|
||||||
parseRemoteComment note = do
|
parseRemoteComment note = do
|
||||||
(mluId, luAuthor, maybePublished, comment) <- parseComment note
|
(mluId, luAuthor, maybePublished, comment) <- parseComment note
|
||||||
luId <- fromMaybeE mluId "Note doesn't specify id"
|
luId <- fromMaybeE mluId "Note doesn't specify id"
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -62,7 +62,7 @@ parseFollow
|
||||||
-> ExceptT Text Handler
|
-> ExceptT Text Handler
|
||||||
(Either (FolloweeBy Key) (Host, LocalURI, LocalURI), Bool)
|
(Either (FolloweeBy Key) (Host, LocalURI, LocalURI), Bool)
|
||||||
parseFollow (AP.Follow uObject mluContext hide) = do
|
parseFollow (AP.Follow uObject mluContext hide) = do
|
||||||
routeOrRemote <- parseFedURI uObject
|
routeOrRemote <- parseFedURIOld uObject
|
||||||
(,hide) <$>
|
(,hide) <$>
|
||||||
bitraverse
|
bitraverse
|
||||||
(parseLocal mluContext)
|
(parseLocal mluContext)
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -66,6 +66,7 @@ import Development.PatchMediaType
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Web.Text
|
import Web.Text
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -113,7 +114,7 @@ data WorkItemOffer = WorkItemOffer
|
||||||
|
|
||||||
checkAuthor :: FedURI -> ExceptT Text Handler (Either PersonId FedURI)
|
checkAuthor :: FedURI -> ExceptT Text Handler (Either PersonId FedURI)
|
||||||
checkAuthor u@(ObjURI h lu) = do
|
checkAuthor u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then do
|
then do
|
||||||
route <- fromMaybeE (decodeRouteLocal lu) "Local author not a valid route"
|
route <- fromMaybeE (decodeRouteLocal lu) "Local author not a valid route"
|
||||||
|
@ -143,7 +144,7 @@ checkBundle h (AP.BundleOffer mlocal patches) = do
|
||||||
|
|
||||||
checkTipURI :: FedURI -> ExceptT Text Handler (Either RepoId FedURI)
|
checkTipURI :: FedURI -> ExceptT Text Handler (Either RepoId FedURI)
|
||||||
checkTipURI u@(ObjURI h lu) = do
|
checkTipURI u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> do
|
then Left <$> do
|
||||||
route <- fromMaybeE (decodeRouteLocal lu) "URI is local but isn't a valid route"
|
route <- fromMaybeE (decodeRouteLocal lu) "URI is local but isn't a valid route"
|
||||||
|
@ -177,7 +178,7 @@ checkMR h (AP.MergeRequest muOrigin target mbundle) =
|
||||||
|
|
||||||
checkTracker :: FedURI -> ExceptT Text Handler Tracker
|
checkTracker :: FedURI -> ExceptT Text Handler Tracker
|
||||||
checkTracker u@(ObjURI h lu) = do
|
checkTracker u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then do
|
then do
|
||||||
route <- fromMaybeE (decodeRouteLocal lu) "Local tracker not a valid route"
|
route <- fromMaybeE (decodeRouteLocal lu) "Local tracker not a valid route"
|
||||||
|
@ -230,7 +231,7 @@ checkOfferTicket host ticket uTarget = do
|
||||||
return $ WorkItemOffer author title desc source tam
|
return $ WorkItemOffer author title desc source tam
|
||||||
|
|
||||||
parseBundleRoute name u@(ObjURI h lu) = do
|
parseBundleRoute name u@(ObjURI h lu) = do
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
if hl
|
if hl
|
||||||
then Left <$> do
|
then Left <$> do
|
||||||
route <-
|
route <-
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 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.
|
||||||
-
|
-
|
||||||
|
@ -14,10 +14,10 @@
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module Vervis.Federation.Auth
|
module Vervis.Federation.Auth
|
||||||
( RemoteAuthor (..)
|
( --RemoteAuthor (..)
|
||||||
, ActivityAuthentication (..)
|
--, ActivityAuthentication (..)
|
||||||
, ActivityBody (..)
|
--, ActivityBody (..)
|
||||||
, authenticateActivity
|
authenticateActivity
|
||||||
, checkForwarding
|
, checkForwarding
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
@ -79,6 +79,7 @@ import Network.FedURI
|
||||||
import Network.HTTP.Digest
|
import Network.HTTP.Digest
|
||||||
import Web.ActivityPub hiding (Follow)
|
import Web.ActivityPub hiding (Follow)
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.Auth.Unverified
|
import Yesod.Auth.Unverified
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
|
@ -94,6 +95,7 @@ import Data.Tuple.Local
|
||||||
import Database.Persist.Local
|
import Database.Persist.Local
|
||||||
import Yesod.Persist.Local
|
import Yesod.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
|
@ -104,22 +106,6 @@ import Vervis.Recipient
|
||||||
import Vervis.RemoteActorStore
|
import Vervis.RemoteActorStore
|
||||||
import Vervis.Settings
|
import Vervis.Settings
|
||||||
|
|
||||||
data RemoteAuthor = RemoteAuthor
|
|
||||||
{ remoteAuthorURI :: FedURI
|
|
||||||
, remoteAuthorInstance :: InstanceId
|
|
||||||
, remoteAuthorId :: RemoteActorId
|
|
||||||
}
|
|
||||||
|
|
||||||
data ActivityAuthentication
|
|
||||||
= ActivityAuthLocal (LocalActorBy Key)
|
|
||||||
| ActivityAuthRemote RemoteAuthor
|
|
||||||
|
|
||||||
data ActivityBody = ActivityBody
|
|
||||||
{ actbBL :: BL.ByteString
|
|
||||||
, actbObject :: Object
|
|
||||||
, actbActivity :: Activity URIMode
|
|
||||||
}
|
|
||||||
|
|
||||||
parseKeyId (KeyId k) =
|
parseKeyId (KeyId k) =
|
||||||
case parseRefURI =<< (first displayException . decodeUtf8') k of
|
case parseRefURI =<< (first displayException . decodeUtf8') k of
|
||||||
Left e -> throwE $ "keyId isn't a valid FedURI: " ++ e
|
Left e -> throwE $ "keyId isn't a valid FedURI: " ++ e
|
||||||
|
@ -365,7 +351,7 @@ verifyForwardedSig hAuthor luAuthor (Verification malgo keyid input signature) =
|
||||||
RefURI hKey luKey <- parseKeyId keyid
|
RefURI hKey luKey <- parseKeyId keyid
|
||||||
unless (hAuthor == hKey) $
|
unless (hAuthor == hKey) $
|
||||||
throwE "Author and forwarded sig key on different hosts"
|
throwE "Author and forwarded sig key on different hosts"
|
||||||
local <- hostIsLocal hKey
|
local <- hostIsLocalOld hKey
|
||||||
if local
|
if local
|
||||||
then ActivityAuthLocal <$> verifySelfSig luAuthor luKey input signature
|
then ActivityAuthLocal <$> verifySelfSig luAuthor luKey input signature
|
||||||
else ActivityAuthRemote <$> verifyActorSig' malgo input signature hKey luKey (Just luAuthor)
|
else ActivityAuthRemote <$> verifyActorSig' malgo input signature hKey luKey (Just luAuthor)
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -76,6 +76,7 @@ import Yesod.Persist.Local
|
||||||
|
|
||||||
import Vervis.Access
|
import Vervis.Access
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.Data.Collab
|
import Vervis.Data.Collab
|
||||||
import Vervis.Web.Delivery
|
import Vervis.Web.Delivery
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -67,6 +67,7 @@ import Database.Persist.Local
|
||||||
import Yesod.Persist.Local
|
import Yesod.Persist.Local
|
||||||
|
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Cloth
|
import Vervis.Cloth
|
||||||
import Vervis.Data.Discussion
|
import Vervis.Data.Discussion
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
|
@ -179,7 +180,7 @@ personCreateNoteF now recipPersonHash author body mfwd luCreate note = do
|
||||||
-- Check input
|
-- Check input
|
||||||
recipPersonID <- decodeKeyHashid404 recipPersonHash
|
recipPersonID <- decodeKeyHashid404 recipPersonHash
|
||||||
(luNote, published, Comment maybeParent topic source content) <- do
|
(luNote, published, Comment maybeParent topic source content) <- do
|
||||||
(luId, luAuthor, published, comment) <- parseRemoteComment note
|
(luId, luAuthor, published, comment) <- parseRemoteCommentOld note
|
||||||
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
||||||
throwE "Create author != note author"
|
throwE "Create author != note author"
|
||||||
return (luId, published, comment)
|
return (luId, published, comment)
|
||||||
|
@ -253,7 +254,7 @@ deckCreateNoteF now recipDeckHash author body mfwd luCreate note = do
|
||||||
|
|
||||||
recipDeckID <- decodeKeyHashid404 recipDeckHash
|
recipDeckID <- decodeKeyHashid404 recipDeckHash
|
||||||
(luNote, published, Comment maybeParent topic source content) <- do
|
(luNote, published, Comment maybeParent topic source content) <- do
|
||||||
(luId, luAuthor, published, comment) <- parseRemoteComment note
|
(luId, luAuthor, published, comment) <- parseRemoteCommentOld note
|
||||||
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
||||||
throwE "Create author != note author"
|
throwE "Create author != note author"
|
||||||
return (luId, published, comment)
|
return (luId, published, comment)
|
||||||
|
@ -322,7 +323,7 @@ loomCreateNoteF now recipLoomHash author body mfwd luCreate note = do
|
||||||
|
|
||||||
recipLoomID <- decodeKeyHashid404 recipLoomHash
|
recipLoomID <- decodeKeyHashid404 recipLoomHash
|
||||||
(luNote, published, Comment maybeParent topic source content) <- do
|
(luNote, published, Comment maybeParent topic source content) <- do
|
||||||
(luId, luAuthor, published, comment) <- parseRemoteComment note
|
(luId, luAuthor, published, comment) <- parseRemoteCommentOld note
|
||||||
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
unless (luAuthor == objUriLocal (remoteAuthorURI author)) $
|
||||||
throwE "Create author != note author"
|
throwE "Create author != note author"
|
||||||
return (luId, published, comment)
|
return (luId, published, comment)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -82,6 +82,7 @@ import Yesod.Persist.Local
|
||||||
|
|
||||||
import Vervis.Access
|
import Vervis.Access
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Cloth
|
import Vervis.Cloth
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
|
@ -323,7 +324,7 @@ followF parseFollowee grabActor unread getFollowee getSieve makeLocalActor makeA
|
||||||
recipID <- decodeKeyHashid404 recipHash
|
recipID <- decodeKeyHashid404 recipHash
|
||||||
followee <- nameExceptT "Follow object" $ do
|
followee <- nameExceptT "Follow object" $ do
|
||||||
route <- do
|
route <- do
|
||||||
routeOrRemote <- parseFedURI uObject
|
routeOrRemote <- parseFedURIOld uObject
|
||||||
case routeOrRemote of
|
case routeOrRemote of
|
||||||
Left route -> pure route
|
Left route -> pure route
|
||||||
Right _ -> throwE "Remote, so definitely not me/mine"
|
Right _ -> throwE "Remote, so definitely not me/mine"
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -95,6 +96,7 @@ import Development.PatchMediaType
|
||||||
|
|
||||||
import Vervis.Access
|
import Vervis.Access
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Cloth
|
import Vervis.Cloth
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.Data.Ticket
|
import Vervis.Data.Ticket
|
||||||
|
@ -1902,7 +1904,7 @@ trackerResolveF maybeWorkItem grabActor getWorkItem makeResource trackerFollower
|
||||||
recipID <- decodeKeyHashid404 recipHash
|
recipID <- decodeKeyHashid404 recipHash
|
||||||
wiID <- nameExceptT "Resolve object" $ do
|
wiID <- nameExceptT "Resolve object" $ do
|
||||||
route <- do
|
route <- do
|
||||||
routeOrRemote <- parseFedURI uObject
|
routeOrRemote <- parseFedURIOld uObject
|
||||||
case routeOrRemote of
|
case routeOrRemote of
|
||||||
Left route -> pure route
|
Left route -> pure route
|
||||||
Right _ -> throwE "Remote, so definitely not mine"
|
Right _ -> throwE "Remote, so definitely not mine"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -31,6 +31,7 @@ import Network.FedURI
|
||||||
|
|
||||||
import Database.Persist.Local
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.Federation.Auth
|
import Vervis.Federation.Auth
|
||||||
import Vervis.Foundation
|
import Vervis.Foundation
|
||||||
import Vervis.Model
|
import Vervis.Model
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2018, 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.
|
||||||
-
|
-
|
||||||
|
@ -21,6 +22,8 @@ import Control.Concurrent.STM.TVar
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Logger.CallStack (logWarn)
|
import Control.Monad.Logger.CallStack (logWarn)
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.HashMap.Strict (HashMap)
|
||||||
import Data.List.NonEmpty (NonEmpty (..))
|
import Data.List.NonEmpty (NonEmpty (..))
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text.Encoding
|
import Data.Text.Encoding
|
||||||
|
@ -48,10 +51,11 @@ import Yesod.Core.Types
|
||||||
import Yesod.Default.Util (addStaticContentExternal)
|
import Yesod.Default.Util (addStaticContentExternal)
|
||||||
import Yesod.Form.Fields
|
import Yesod.Form.Fields
|
||||||
import Yesod.Form.Functions
|
import Yesod.Form.Functions
|
||||||
import Yesod.Form.Types
|
import Yesod.Form.Types hiding (Env)
|
||||||
import Yesod.Persist.Core
|
import Yesod.Persist.Core
|
||||||
import Yesod.Static
|
import Yesod.Static
|
||||||
|
|
||||||
|
import qualified Data.Aeson as A
|
||||||
import qualified Data.ByteString.Lazy as BL (ByteString)
|
import qualified Data.ByteString.Lazy as BL (ByteString)
|
||||||
import qualified Data.Time.Units as U
|
import qualified Data.Time.Units as U
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
@ -69,21 +73,25 @@ import Yesod.Mail.Send
|
||||||
|
|
||||||
import qualified Network.HTTP.Signature as S (Algorithm (..))
|
import qualified Network.HTTP.Signature as S (Algorithm (..))
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor hiding (Message)
|
||||||
import Crypto.ActorKey
|
import Crypto.ActorKey
|
||||||
import Crypto.PublicVerifKey
|
import Crypto.PublicVerifKey
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Web.ActivityAccess
|
import Web.ActivityAccess
|
||||||
|
import Web.Actor.Persist
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
|
||||||
import qualified Web.ActivityPub as AP
|
import qualified Web.ActivityPub as AP
|
||||||
|
import qualified Yesod.Hashids as YH
|
||||||
|
|
||||||
import Text.Email.Local
|
import Text.Email.Local
|
||||||
import Text.Jasmine.Local (discardm)
|
import Text.Jasmine.Local (discardm)
|
||||||
import Yesod.Paginate.Local
|
import Yesod.Paginate.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
import Vervis.Hook
|
import Vervis.Hook
|
||||||
import Vervis.Model
|
import Vervis.Model
|
||||||
|
@ -95,6 +103,10 @@ import Vervis.Settings
|
||||||
import Vervis.Style
|
import Vervis.Style
|
||||||
import Vervis.Widget (breadcrumbsW, revisionW)
|
import Vervis.Widget (breadcrumbsW, revisionW)
|
||||||
|
|
||||||
|
data ActivityAuthentication
|
||||||
|
= ActivityAuthLocal (LocalActorBy Key)
|
||||||
|
| ActivityAuthRemote RemoteAuthor
|
||||||
|
|
||||||
data ActivityReport = ActivityReport
|
data ActivityReport = ActivityReport
|
||||||
{ _arTime :: UTCTime
|
{ _arTime :: UTCTime
|
||||||
, _arMessage :: Text
|
, _arMessage :: Text
|
||||||
|
@ -120,6 +132,7 @@ data App = App
|
||||||
, appHashidsContext :: HashidsContext
|
, appHashidsContext :: HashidsContext
|
||||||
, appHookSecret :: HookSecret
|
, appHookSecret :: HookSecret
|
||||||
, appActorFetchShare :: ActorFetchShare App
|
, appActorFetchShare :: ActorFetchShare App
|
||||||
|
, appTheater :: Theater
|
||||||
|
|
||||||
, appActivities :: Maybe (Int, TVar (Vector ActivityReport))
|
, appActivities :: Maybe (Int, TVar (Vector ActivityReport))
|
||||||
}
|
}
|
||||||
|
@ -142,6 +155,9 @@ type TicketDeckKeyHashid = KeyHashid TicketDeck
|
||||||
type TicketLoomKeyHashid = KeyHashid TicketLoom
|
type TicketLoomKeyHashid = KeyHashid TicketLoom
|
||||||
type SigKeyKeyHashid = KeyHashid SigKey
|
type SigKeyKeyHashid = KeyHashid SigKey
|
||||||
|
|
||||||
|
instance StageYesod Env where
|
||||||
|
type StageSite Env = App
|
||||||
|
|
||||||
-- This is where we define all of the routes in our application. For a full
|
-- This is where we define all of the routes in our application. For a full
|
||||||
-- explanation of the syntax, please see:
|
-- explanation of the syntax, please see:
|
||||||
-- http://www.yesodweb.com/book/routing-and-handlers
|
-- http://www.yesodweb.com/book/routing-and-handlers
|
||||||
|
@ -259,7 +275,7 @@ instance Yesod App where
|
||||||
case vs :: [E.Value Int] of
|
case vs :: [E.Value Int] of
|
||||||
[E.Value i] -> return i
|
[E.Value i] -> return i
|
||||||
_ -> error $ "countUnread returned " ++ show vs
|
_ -> error $ "countUnread returned " ++ show vs
|
||||||
hash <- encodeKeyHashid pid
|
hash <- YH.encodeKeyHashid pid
|
||||||
return (p, hash, verified, unread)
|
return (p, hash, verified, unread)
|
||||||
(title, bcs) <- breadcrumbs
|
(title, bcs) <- breadcrumbs
|
||||||
|
|
||||||
|
@ -448,7 +464,7 @@ instance Yesod App where
|
||||||
|
|
||||||
person :: KeyHashid Person -> Handler AuthResult
|
person :: KeyHashid Person -> Handler AuthResult
|
||||||
person hash = personAnd $ \ (Entity pid _) -> do
|
person hash = personAnd $ \ (Entity pid _) -> do
|
||||||
hash' <- encodeKeyHashid pid
|
hash' <- YH.encodeKeyHashid pid
|
||||||
return $ if hash == hash'
|
return $ if hash == hash'
|
||||||
then Authorized
|
then Authorized
|
||||||
else Unauthorized "No access to this operation"
|
else Unauthorized "No access to this operation"
|
||||||
|
@ -770,7 +786,7 @@ unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
|
||||||
-- 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
|
||||||
|
|
||||||
instance YesodHashids App where
|
instance YH.YesodHashids App where
|
||||||
siteHashidsContext = appHashidsContext
|
siteHashidsContext = appHashidsContext
|
||||||
|
|
||||||
instance YesodRemoteActorStore App where
|
instance YesodRemoteActorStore App where
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2018, 2019, 2020, 2022
|
- Written in 2016, 2018, 2019, 2020, 2022, 2023
|
||||||
- by fr33domlover <fr33domlover@riseup.net>.
|
- 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.
|
||||||
|
@ -30,6 +30,9 @@ module Vervis.Handler.Client
|
||||||
, getPublishOfferMergeR
|
, getPublishOfferMergeR
|
||||||
, postPublishOfferMergeR
|
, postPublishOfferMergeR
|
||||||
|
|
||||||
|
--, getPublishCommentR
|
||||||
|
--, postPublishCommentR
|
||||||
|
|
||||||
, getPublishMergeR
|
, getPublishMergeR
|
||||||
, postPublishMergeR
|
, postPublishMergeR
|
||||||
)
|
)
|
||||||
|
@ -1025,7 +1028,7 @@ postPublishOfferMergeR = do
|
||||||
(ep@(Entity pid _), a) <- getSender
|
(ep@(Entity pid _), a) <- getSender
|
||||||
senderHash <- encodeKeyHashid pid
|
senderHash <- encodeKeyHashid pid
|
||||||
|
|
||||||
trackerLocal <- hostIsLocal $ objUriAuthority omgTracker
|
trackerLocal <- hostIsLocalOld $ objUriAuthority omgTracker
|
||||||
edest <- runExceptT $ do
|
edest <- runExceptT $ do
|
||||||
(summary, audience, ticket) <-
|
(summary, audience, ticket) <-
|
||||||
offerMerge
|
offerMerge
|
||||||
|
@ -1056,6 +1059,65 @@ postPublishOfferMergeR = do
|
||||||
else setMessage "Offer published"
|
else setMessage "Offer published"
|
||||||
redirect dest
|
redirect dest
|
||||||
|
|
||||||
|
{-
|
||||||
|
data Comment = Comment
|
||||||
|
{ commentTopic :: FedURI
|
||||||
|
, commentParent :: Maybe FedURI
|
||||||
|
, commentText :: PandocMarkdown
|
||||||
|
}
|
||||||
|
|
||||||
|
commentForm :: Form Comment
|
||||||
|
commentForm = Comment
|
||||||
|
<$> areq fedUriField "Topic" Nothing
|
||||||
|
<*> aopt fedUriField "Replying to" Nothing
|
||||||
|
<*> (pandocMarkdownFromText <$>
|
||||||
|
areq textField "Message" Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
getPublishCommentR :: Handler Html
|
||||||
|
getPublishCommentR = do
|
||||||
|
((_, widget), enctype) <- runFormPost commentForm
|
||||||
|
defaultLayout
|
||||||
|
[whamlet|
|
||||||
|
<h1>Comment on a ticket or a merge request
|
||||||
|
<form method=POST action=@{PublishCommentR} enctype=#{enctype}>
|
||||||
|
^{widget}
|
||||||
|
<input type=submit>
|
||||||
|
|]
|
||||||
|
|
||||||
|
postPublishCommentR :: Handler ()
|
||||||
|
postPublishCommentR = do
|
||||||
|
federation <- getsYesod $ appFederation . appSettings
|
||||||
|
unless federation badMethod
|
||||||
|
|
||||||
|
Comment uTopic uParent source <-
|
||||||
|
runFormPostRedirect PublishCommentR commentForm
|
||||||
|
|
||||||
|
(ep@(Entity pid _), a) <- getSender
|
||||||
|
senderHash <- encodeKeyHashid pid
|
||||||
|
|
||||||
|
result <- runExceptT $ do
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(maybeSummary, audience, apply) <- applyPatches senderHash uBundle
|
||||||
|
(localRecips, remoteRecips, fwdHosts, action) <-
|
||||||
|
makeServerInput (Just uCap) maybeSummary audience (AP.ApplyActivity apply)
|
||||||
|
applyC ep a (Just cap) localRecips remoteRecips fwdHosts action apply
|
||||||
|
|
||||||
|
case result of
|
||||||
|
Left err -> do
|
||||||
|
setMessage $ toHtml err
|
||||||
|
redirect PublishMergeR
|
||||||
|
Right _ -> do
|
||||||
|
setMessage "Apply activity sent"
|
||||||
|
redirect HomeR
|
||||||
|
-}
|
||||||
|
|
||||||
mergeForm = renderDivs $ (,)
|
mergeForm = renderDivs $ (,)
|
||||||
<$> areq fedUriField "Patch bundle to apply" Nothing
|
<$> areq fedUriField "Patch bundle to apply" Nothing
|
||||||
<*> areq capField "Grant activity to use for authorization" Nothing
|
<*> areq capField "Grant activity to use for authorization" Nothing
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -177,19 +177,11 @@ getDeckInboxR :: KeyHashid Deck -> Handler TypedContent
|
||||||
getDeckInboxR = getInbox DeckInboxR deckActor
|
getDeckInboxR = getInbox DeckInboxR deckActor
|
||||||
|
|
||||||
postDeckInboxR :: KeyHashid Deck -> Handler ()
|
postDeckInboxR :: KeyHashid Deck -> Handler ()
|
||||||
postDeckInboxR recipDeckHash =
|
postDeckInboxR deckHash = do
|
||||||
postInbox $ handleRobotInbox (LocalActorDeck recipDeckHash) handle
|
deckID <- decodeKeyHashid404 deckHash
|
||||||
where
|
postInbox $ LocalActorDeck deckID
|
||||||
handle
|
|
||||||
:: UTCTime
|
{-
|
||||||
-> RemoteAuthor
|
|
||||||
-> ActivityBody
|
|
||||||
-> Maybe (RecipientRoutes, ByteString)
|
|
||||||
-> LocalURI
|
|
||||||
-> SpecificActivity URIMode
|
|
||||||
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
|
|
||||||
handle now author body mfwd luActivity specific =
|
|
||||||
case specific of
|
|
||||||
AP.AcceptActivity accept ->
|
AP.AcceptActivity accept ->
|
||||||
deckAcceptF now recipDeckHash author body mfwd luActivity accept
|
deckAcceptF now recipDeckHash author body mfwd luActivity accept
|
||||||
AP.CreateActivity (AP.Create obj mtarget) ->
|
AP.CreateActivity (AP.Create obj mtarget) ->
|
||||||
|
@ -217,6 +209,7 @@ postDeckInboxR recipDeckHash =
|
||||||
AP.UndoActivity undo ->
|
AP.UndoActivity undo ->
|
||||||
(,Nothing) <$> deckUndoF now recipDeckHash author body mfwd luActivity undo
|
(,Nothing) <$> deckUndoF now recipDeckHash author body mfwd luActivity undo
|
||||||
_ -> return ("Unsupported activity type for decks", Nothing)
|
_ -> return ("Unsupported activity type for decks", Nothing)
|
||||||
|
-}
|
||||||
|
|
||||||
getDeckOutboxR :: KeyHashid Deck -> Handler TypedContent
|
getDeckOutboxR :: KeyHashid Deck -> Handler TypedContent
|
||||||
getDeckOutboxR = getOutbox DeckOutboxR DeckOutboxItemR deckActor
|
getDeckOutboxR = getOutbox DeckOutboxR DeckOutboxItemR deckActor
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -115,20 +115,9 @@ getGroupInboxR :: KeyHashid Group -> Handler TypedContent
|
||||||
getGroupInboxR = getInbox GroupInboxR groupActor
|
getGroupInboxR = getInbox GroupInboxR groupActor
|
||||||
|
|
||||||
postGroupInboxR :: KeyHashid Group -> Handler ()
|
postGroupInboxR :: KeyHashid Group -> Handler ()
|
||||||
postGroupInboxR recipGroupHash =
|
postGroupInboxR groupHash = do
|
||||||
postInbox $ handleRobotInbox (LocalActorGroup recipGroupHash) handle
|
groupID <- decodeKeyHashid404 groupHash
|
||||||
where
|
postInbox $ LocalActorGroup groupID
|
||||||
handle
|
|
||||||
:: UTCTime
|
|
||||||
-> RemoteAuthor
|
|
||||||
-> ActivityBody
|
|
||||||
-> Maybe (RecipientRoutes, ByteString)
|
|
||||||
-> LocalURI
|
|
||||||
-> AP.SpecificActivity URIMode
|
|
||||||
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
|
|
||||||
handle _now _author _body _mfwd _luActivity specific =
|
|
||||||
case specific of
|
|
||||||
_ -> return ("Unsupported activity type for groups", Nothing)
|
|
||||||
|
|
||||||
getGroupOutboxR :: KeyHashid Group -> Handler TypedContent
|
getGroupOutboxR :: KeyHashid Group -> Handler TypedContent
|
||||||
getGroupOutboxR = getOutbox GroupOutboxR GroupOutboxItemR groupActor
|
getGroupOutboxR = getOutbox GroupOutboxR GroupOutboxItemR groupActor
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -138,19 +138,11 @@ getLoomInboxR :: KeyHashid Loom -> Handler TypedContent
|
||||||
getLoomInboxR = getInbox LoomInboxR loomActor
|
getLoomInboxR = getInbox LoomInboxR loomActor
|
||||||
|
|
||||||
postLoomInboxR :: KeyHashid Loom -> Handler ()
|
postLoomInboxR :: KeyHashid Loom -> Handler ()
|
||||||
postLoomInboxR recipLoomHash =
|
postLoomInboxR loomHash = do
|
||||||
postInbox $ handleRobotInbox (LocalActorLoom recipLoomHash) handle
|
loomID <- decodeKeyHashid404 loomHash
|
||||||
where
|
postInbox $ LocalActorLoom loomID
|
||||||
handle
|
|
||||||
:: UTCTime
|
{-
|
||||||
-> RemoteAuthor
|
|
||||||
-> ActivityBody
|
|
||||||
-> Maybe (RecipientRoutes, ByteString)
|
|
||||||
-> LocalURI
|
|
||||||
-> AP.SpecificActivity URIMode
|
|
||||||
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
|
|
||||||
handle now author body mfwd luActivity specific =
|
|
||||||
case specific of
|
|
||||||
AP.AcceptActivity accept ->
|
AP.AcceptActivity accept ->
|
||||||
loomAcceptF now recipLoomHash author body mfwd luActivity accept
|
loomAcceptF now recipLoomHash author body mfwd luActivity accept
|
||||||
AP.ApplyActivity apply->
|
AP.ApplyActivity apply->
|
||||||
|
@ -176,6 +168,7 @@ postLoomInboxR recipLoomHash =
|
||||||
AP.UndoActivity undo ->
|
AP.UndoActivity undo ->
|
||||||
(,Nothing) <$> loomUndoF now recipLoomHash author body mfwd luActivity undo
|
(,Nothing) <$> loomUndoF now recipLoomHash author body mfwd luActivity undo
|
||||||
_ -> return ("Unsupported activity type for looms", Nothing)
|
_ -> return ("Unsupported activity type for looms", Nothing)
|
||||||
|
-}
|
||||||
|
|
||||||
getLoomOutboxR :: KeyHashid Loom -> Handler TypedContent
|
getLoomOutboxR :: KeyHashid Loom -> Handler TypedContent
|
||||||
getLoomOutboxR = getOutbox LoomOutboxR LoomOutboxItemR loomActor
|
getLoomOutboxR = getOutbox LoomOutboxR LoomOutboxItemR loomActor
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2016, 2018, 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.
|
||||||
-
|
-
|
||||||
|
@ -57,6 +58,7 @@ import Text.Email.Local
|
||||||
|
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -138,103 +140,10 @@ getPersonR personHash = do
|
||||||
getPersonInboxR :: KeyHashid Person -> Handler TypedContent
|
getPersonInboxR :: KeyHashid Person -> Handler TypedContent
|
||||||
getPersonInboxR = getInbox PersonInboxR personActor
|
getPersonInboxR = getInbox PersonInboxR personActor
|
||||||
|
|
||||||
parseAuthenticatedLocalActivityURI
|
|
||||||
:: (MonadSite m, YesodHashids (SiteEnv m))
|
|
||||||
=> LocalActorBy Key -> Maybe LocalURI -> ExceptT Text m OutboxItemId
|
|
||||||
parseAuthenticatedLocalActivityURI author maybeActivityURI = do
|
|
||||||
luAct <- fromMaybeE maybeActivityURI "No 'id'"
|
|
||||||
(actorByKey, _, outboxItemID) <- parseLocalActivityURI luAct
|
|
||||||
unless (actorByKey == author) $
|
|
||||||
throwE "'actor' actor and 'id' actor mismatch"
|
|
||||||
return outboxItemID
|
|
||||||
|
|
||||||
insertActivityToInbox
|
|
||||||
:: MonadIO m
|
|
||||||
=> UTCTime -> ActorId -> OutboxItemId -> ReaderT SqlBackend m Bool
|
|
||||||
insertActivityToInbox now recipActorID outboxItemID = do
|
|
||||||
inboxID <- actorInbox <$> getJust recipActorID
|
|
||||||
inboxItemID <- insert $ InboxItem True now
|
|
||||||
maybeItem <- insertUnique $ InboxItemLocal inboxID outboxItemID inboxItemID
|
|
||||||
case maybeItem of
|
|
||||||
Nothing -> do
|
|
||||||
delete inboxItemID
|
|
||||||
return False
|
|
||||||
Just _ -> return True
|
|
||||||
|
|
||||||
postPersonInboxR :: KeyHashid Person -> Handler ()
|
postPersonInboxR :: KeyHashid Person -> Handler ()
|
||||||
postPersonInboxR recipPersonHash = postInbox handle
|
postPersonInboxR personHash = do
|
||||||
where
|
personID <- decodeKeyHashid404 personHash
|
||||||
handle
|
postInbox $ LocalActorPerson personID
|
||||||
:: UTCTime
|
|
||||||
-> ActivityAuthentication
|
|
||||||
-> ActivityBody
|
|
||||||
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
|
|
||||||
|
|
||||||
handle now (ActivityAuthLocal authorByKey) body = (,Nothing) <$> do
|
|
||||||
outboxItemID <-
|
|
||||||
parseAuthenticatedLocalActivityURI
|
|
||||||
authorByKey
|
|
||||||
(AP.activityId $ actbActivity body)
|
|
||||||
recipPersonID <- decodeKeyHashid404 recipPersonHash
|
|
||||||
runDBExcept $ do
|
|
||||||
recipPerson <- lift $ get404 recipPersonID
|
|
||||||
verifyLocalActivityExistsInDB authorByKey outboxItemID
|
|
||||||
if LocalActorPerson recipPersonID == authorByKey
|
|
||||||
then return "Received activity authored by self, ignoring"
|
|
||||||
else lift $ do
|
|
||||||
inserted <- insertActivityToInbox now (personActor recipPerson) outboxItemID
|
|
||||||
return $
|
|
||||||
if inserted
|
|
||||||
then "Activity inserted to recipient's inbox"
|
|
||||||
else "Activity already exists in recipient's inbox"
|
|
||||||
|
|
||||||
handle now (ActivityAuthRemote author) body = do
|
|
||||||
luActivity <-
|
|
||||||
fromMaybeE (AP.activityId $ actbActivity body) "Activity without 'id'"
|
|
||||||
localRecips <- do
|
|
||||||
mrecips <- parseAudience $ AP.activityAudience $ actbActivity body
|
|
||||||
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
|
|
||||||
msig <- checkForwarding $ LocalActorPerson recipPersonHash
|
|
||||||
let mfwd = (localRecips,) <$> msig
|
|
||||||
case AP.activitySpecific $ actbActivity body of
|
|
||||||
{-
|
|
||||||
AcceptActivity accept ->
|
|
||||||
(,Nothing) <$> sharerAcceptF shrRecip now author body mfwd luActivity accept
|
|
||||||
AddActivity (AP.Add obj target) ->
|
|
||||||
case obj of
|
|
||||||
Right (AddBundle patches) ->
|
|
||||||
sharerAddBundleF now shrRecip author body mfwd luActivity patches target
|
|
||||||
_ -> return ("Unsupported add object type for sharers", Nothing)
|
|
||||||
-}
|
|
||||||
AP.CreateActivity (AP.Create obj mtarget) ->
|
|
||||||
case obj of
|
|
||||||
AP.CreateNote _ note ->
|
|
||||||
(,Nothing) <$> personCreateNoteF now recipPersonHash author body mfwd luActivity note
|
|
||||||
_ -> return ("Unsupported create object type for people", Nothing)
|
|
||||||
AP.FollowActivity follow ->
|
|
||||||
personFollowF now recipPersonHash author body mfwd luActivity follow
|
|
||||||
AP.GrantActivity grant ->
|
|
||||||
personGrantF now recipPersonHash author body mfwd luActivity grant
|
|
||||||
AP.InviteActivity invite ->
|
|
||||||
personInviteF now recipPersonHash author body mfwd luActivity invite
|
|
||||||
{-
|
|
||||||
OfferActivity (Offer obj target) ->
|
|
||||||
case obj of
|
|
||||||
OfferTicket ticket ->
|
|
||||||
(,Nothing) <$> sharerOfferTicketF now shrRecip author body mfwd luActivity ticket target
|
|
||||||
OfferDep dep ->
|
|
||||||
sharerOfferDepF now shrRecip author body mfwd luActivity dep target
|
|
||||||
_ -> return ("Unsupported offer object type for sharers", Nothing)
|
|
||||||
PushActivity push ->
|
|
||||||
(,Nothing) <$> sharerPushF shrRecip now author body mfwd luActivity push
|
|
||||||
RejectActivity reject ->
|
|
||||||
(,Nothing) <$> sharerRejectF shrRecip now author body mfwd luActivity reject
|
|
||||||
ResolveActivity resolve ->
|
|
||||||
(,Nothing) <$> sharerResolveF now shrRecip author body mfwd luActivity resolve
|
|
||||||
-}
|
|
||||||
AP.UndoActivity undo ->
|
|
||||||
(,Nothing) <$> personUndoF now recipPersonHash author body mfwd luActivity undo
|
|
||||||
_ -> return ("Unsupported activity type for Person", Nothing)
|
|
||||||
|
|
||||||
getPersonOutboxR :: KeyHashid Person -> Handler TypedContent
|
getPersonOutboxR :: KeyHashid Person -> Handler TypedContent
|
||||||
getPersonOutboxR = getOutbox PersonOutboxR PersonOutboxItemR personActor
|
getPersonOutboxR = getOutbox PersonOutboxR PersonOutboxItemR personActor
|
||||||
|
@ -253,7 +162,7 @@ postPersonOutboxR personHash = do
|
||||||
verifyContentTypeAP
|
verifyContentTypeAP
|
||||||
|
|
||||||
AP.Doc h activity <- requireInsecureJsonBody
|
AP.Doc h activity <- requireInsecureJsonBody
|
||||||
hl <- hostIsLocal h
|
hl <- hostIsLocalOld h
|
||||||
unless hl $ invalidArgs ["Activity host isn't the instance host"]
|
unless hl $ invalidArgs ["Activity host isn't the instance host"]
|
||||||
|
|
||||||
result <- runExceptT $ do
|
result <- runExceptT $ do
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2016, 2018, 2019, 2020, 2022
|
- Written in 2016, 2018, 2019, 2020, 2022, 2023
|
||||||
- by fr33domlover <fr33domlover@riseup.net>.
|
- 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.
|
||||||
|
@ -242,19 +242,11 @@ getRepoInboxR :: KeyHashid Repo -> Handler TypedContent
|
||||||
getRepoInboxR = getInbox RepoInboxR repoActor
|
getRepoInboxR = getInbox RepoInboxR repoActor
|
||||||
|
|
||||||
postRepoInboxR :: KeyHashid Repo -> Handler ()
|
postRepoInboxR :: KeyHashid Repo -> Handler ()
|
||||||
postRepoInboxR recipRepoHash =
|
postRepoInboxR repoHash = do
|
||||||
postInbox $ handleRobotInbox (LocalActorRepo recipRepoHash) handle
|
repoID <- decodeKeyHashid404 repoHash
|
||||||
where
|
postInbox $ LocalActorRepo repoID
|
||||||
handle
|
|
||||||
:: UTCTime
|
{-
|
||||||
-> RemoteAuthor
|
|
||||||
-> ActivityBody
|
|
||||||
-> Maybe (RecipientRoutes, ByteString)
|
|
||||||
-> LocalURI
|
|
||||||
-> AP.SpecificActivity URIMode
|
|
||||||
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
|
|
||||||
handle now author body mfwd luActivity specific =
|
|
||||||
case specific of
|
|
||||||
AP.AcceptActivity accept ->
|
AP.AcceptActivity accept ->
|
||||||
repoAcceptF now recipRepoHash author body mfwd luActivity accept
|
repoAcceptF now recipRepoHash author body mfwd luActivity accept
|
||||||
{-
|
{-
|
||||||
|
@ -289,6 +281,7 @@ postRepoInboxR recipRepoHash =
|
||||||
AP.UndoActivity undo->
|
AP.UndoActivity undo->
|
||||||
(,Nothing) <$> repoUndoF now recipRepoHash author body mfwd luActivity undo
|
(,Nothing) <$> repoUndoF now recipRepoHash author body mfwd luActivity undo
|
||||||
_ -> return ("Unsupported activity type for repos", Nothing)
|
_ -> return ("Unsupported activity type for repos", Nothing)
|
||||||
|
-}
|
||||||
|
|
||||||
getRepoOutboxR :: KeyHashid Repo -> Handler TypedContent
|
getRepoOutboxR :: KeyHashid Repo -> Handler TypedContent
|
||||||
getRepoOutboxR = getOutbox RepoOutboxR RepoOutboxItemR repoActor
|
getRepoOutboxR = getOutbox RepoOutboxR RepoOutboxItemR repoActor
|
||||||
|
|
|
@ -85,6 +85,26 @@ instance Hashable RoleId where
|
||||||
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
hash = hash . fromSqlKey
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
|
instance Hashable PersonId where
|
||||||
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
|
instance Hashable GroupId where
|
||||||
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
|
instance Hashable RepoId where
|
||||||
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
|
instance Hashable DeckId where
|
||||||
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
|
instance Hashable LoomId where
|
||||||
|
hashWithSalt salt = hashWithSalt salt . fromSqlKey
|
||||||
|
hash = hash . fromSqlKey
|
||||||
|
|
||||||
{-
|
{-
|
||||||
instance PersistEntityGraph Ticket TicketDependency where
|
instance PersistEntityGraph Ticket TicketDependency where
|
||||||
sourceParam = ticketDependencyParent
|
sourceParam = ticketDependencyParent
|
||||||
|
@ -106,3 +126,20 @@ instance PersistEntityGraphNumbered Ticket TicketDependency where
|
||||||
numberField _ = TicketNumber
|
numberField _ = TicketNumber
|
||||||
uniqueNode _ = UniqueTicket
|
uniqueNode _ = UniqueTicket
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-
|
||||||
|
instance VervisActor Person where
|
||||||
|
type VervisActorForwarder Person = ForwarderPerson
|
||||||
|
|
||||||
|
instance VervisActor Group where
|
||||||
|
type VervisActorForwarder Group = ForwarderGroup
|
||||||
|
|
||||||
|
instance VervisActor Repo where
|
||||||
|
type VervisActorForwarder Repo = ForwarderRepo
|
||||||
|
|
||||||
|
instance VervisActor Deck where
|
||||||
|
type VervisActorForwarder Deck = ForwarderDeck
|
||||||
|
|
||||||
|
instance VervisActor Loom where
|
||||||
|
type VervisActorForwarder Loom = ForwarderLoom
|
||||||
|
-}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import Control.Monad
|
||||||
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.Graph.Inductive.Graph (mkGraph, lab')
|
import Data.Graph.Inductive.Graph (mkGraph, lab')
|
||||||
import Data.Graph.Inductive.PatriciaTree (Gr)
|
import Data.Graph.Inductive.PatriciaTree (Gr)
|
||||||
import Data.Graph.Inductive.Query.DFS (dffWith)
|
import Data.Graph.Inductive.Query.DFS (dffWith)
|
||||||
|
@ -158,9 +159,10 @@ getDiscussionTree getdid =
|
||||||
sortByTime . discussionTree <$> getAllMessages getdid
|
sortByTime . discussionTree <$> getAllMessages getdid
|
||||||
|
|
||||||
getMessageFromRoute
|
getMessageFromRoute
|
||||||
:: LocalActorBy Key
|
:: MonadIO m
|
||||||
|
=> LocalActorBy Key
|
||||||
-> LocalMessageId
|
-> LocalMessageId
|
||||||
-> ExceptT Text AppDB
|
-> ExceptT Text (ReaderT SqlBackend m)
|
||||||
( LocalActorBy Entity
|
( LocalActorBy Entity
|
||||||
, Entity Actor
|
, Entity Actor
|
||||||
, Entity LocalMessage
|
, Entity LocalMessage
|
||||||
|
@ -187,9 +189,10 @@ getMessageFromRoute authorByKey localMsgID = do
|
||||||
)
|
)
|
||||||
|
|
||||||
getLocalParentMessageId
|
getLocalParentMessageId
|
||||||
:: DiscussionId
|
:: MonadIO m
|
||||||
|
=> DiscussionId
|
||||||
-> (LocalActorBy Key, LocalMessageId)
|
-> (LocalActorBy Key, LocalMessageId)
|
||||||
-> ExceptT Text AppDB MessageId
|
-> ExceptT Text (ReaderT SqlBackend m) MessageId
|
||||||
getLocalParentMessageId discussionID (authorByKey, localMsgID) = do
|
getLocalParentMessageId discussionID (authorByKey, localMsgID) = do
|
||||||
(_, _, _, Entity msgID msg) <- getMessageFromRoute authorByKey localMsgID
|
(_, _, _, Entity msgID msg) <- getMessageFromRoute authorByKey localMsgID
|
||||||
unless (messageRoot msg == discussionID) $
|
unless (messageRoot msg == discussionID) $
|
||||||
|
@ -200,9 +203,10 @@ getLocalParentMessageId discussionID (authorByKey, localMsgID) = do
|
||||||
-- know and have this parent note in the DB, and whether the child and parent
|
-- know and have this parent note in the DB, and whether the child and parent
|
||||||
-- belong to the same discussion root.
|
-- belong to the same discussion root.
|
||||||
getMessageParent
|
getMessageParent
|
||||||
:: DiscussionId
|
:: MonadIO m
|
||||||
|
=> DiscussionId
|
||||||
-> Either (LocalActorBy Key, LocalMessageId) FedURI
|
-> Either (LocalActorBy Key, LocalMessageId) FedURI
|
||||||
-> ExceptT Text AppDB (Either MessageId FedURI)
|
-> ExceptT Text (ReaderT SqlBackend m) (Either MessageId FedURI)
|
||||||
getMessageParent did (Left msg) = Left <$> getLocalParentMessageId did msg
|
getMessageParent did (Left msg) = Left <$> getLocalParentMessageId did msg
|
||||||
getMessageParent did (Right p@(ObjURI hParent luParent)) = do
|
getMessageParent did (Right p@(ObjURI hParent luParent)) = do
|
||||||
mrm <- lift $ runMaybeT $ do
|
mrm <- lift $ runMaybeT $ do
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -121,6 +121,7 @@ import qualified Data.Text as T
|
||||||
|
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Yesod.ActivityPub
|
import Yesod.ActivityPub
|
||||||
|
import Yesod.Actor
|
||||||
import Yesod.FedURI
|
import Yesod.FedURI
|
||||||
import Yesod.Hashids
|
import Yesod.Hashids
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
@ -130,6 +131,7 @@ import qualified Web.ActivityPub as AP
|
||||||
import Data.List.Local
|
import Data.List.Local
|
||||||
import Data.List.NonEmpty.Local
|
import Data.List.NonEmpty.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
import Vervis.Foundation
|
import Vervis.Foundation
|
||||||
import Vervis.Model
|
import Vervis.Model
|
||||||
|
@ -142,17 +144,6 @@ import Vervis.Model
|
||||||
-- types, then you can do any further parsing and grouping.
|
-- types, then you can do any further parsing and grouping.
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
data LocalActorBy f
|
|
||||||
= LocalActorPerson (f Person)
|
|
||||||
| LocalActorGroup (f Group)
|
|
||||||
| LocalActorRepo (f Repo)
|
|
||||||
| LocalActorDeck (f Deck)
|
|
||||||
| LocalActorLoom (f Loom)
|
|
||||||
deriving (Generic, FunctorB, ConstraintsB)
|
|
||||||
|
|
||||||
deriving instance AllBF Eq f LocalActorBy => Eq (LocalActorBy f)
|
|
||||||
deriving instance AllBF Ord f LocalActorBy => Ord (LocalActorBy f)
|
|
||||||
|
|
||||||
{-
|
{-
|
||||||
instance (Eq (f Person), Eq (f Group), Eq (f Repo), Eq (f Deck), Eq (f Loom)) => Eq (LocalActorBy f) where
|
instance (Eq (f Person), Eq (f Group), Eq (f Repo), Eq (f Deck), Eq (f Loom)) => Eq (LocalActorBy f) where
|
||||||
(==) (LocalActorPerson p) (LocalActorPerson p') = p == p'
|
(==) (LocalActorPerson p) (LocalActorPerson p') = p == p'
|
||||||
|
@ -175,8 +166,6 @@ instance (Ord (f Person), Ord (f Group), Ord (f Repo), Ord (f Deck), Ord (f Loom
|
||||||
(<=) (LocalActorGroup _) _ = True
|
(<=) (LocalActorGroup _) _ = True
|
||||||
-}
|
-}
|
||||||
|
|
||||||
type LocalActor = LocalActorBy KeyHashid
|
|
||||||
|
|
||||||
parseLocalActor :: Route App -> Maybe LocalActor
|
parseLocalActor :: Route App -> Maybe LocalActor
|
||||||
parseLocalActor (PersonR pkhid) = Just $ LocalActorPerson pkhid
|
parseLocalActor (PersonR pkhid) = Just $ LocalActorPerson pkhid
|
||||||
parseLocalActor (GroupR gkhid) = Just $ LocalActorGroup gkhid
|
parseLocalActor (GroupR gkhid) = Just $ LocalActorGroup gkhid
|
||||||
|
@ -504,67 +493,6 @@ recipientFromStage (LocalStageClothFollowers lkhid ltkhid) =
|
||||||
-- logic rather than plain lists of routes.
|
-- logic rather than plain lists of routes.
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
data TicketRoutes = TicketRoutes
|
|
||||||
{ routeTicketFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data ClothRoutes = ClothRoutes
|
|
||||||
{ routeClothFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data PersonRoutes = PersonRoutes
|
|
||||||
{ routePerson :: Bool
|
|
||||||
, routePersonFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data GroupRoutes = GroupRoutes
|
|
||||||
{ routeGroup :: Bool
|
|
||||||
, routeGroupFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data RepoRoutes = RepoRoutes
|
|
||||||
{ routeRepo :: Bool
|
|
||||||
, routeRepoFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data DeckRoutes = DeckRoutes
|
|
||||||
{ routeDeck :: Bool
|
|
||||||
, routeDeckFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data LoomRoutes = LoomRoutes
|
|
||||||
{ routeLoom :: Bool
|
|
||||||
, routeLoomFollowers :: Bool
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data DeckFamilyRoutes = DeckFamilyRoutes
|
|
||||||
{ familyDeck :: DeckRoutes
|
|
||||||
, familyTickets :: [(KeyHashid TicketDeck, TicketRoutes)]
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data LoomFamilyRoutes = LoomFamilyRoutes
|
|
||||||
{ familyLoom :: LoomRoutes
|
|
||||||
, familyCloths :: [(KeyHashid TicketLoom, ClothRoutes)]
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
data RecipientRoutes = RecipientRoutes
|
|
||||||
{ recipPeople :: [(KeyHashid Person, PersonRoutes)]
|
|
||||||
, recipGroups :: [(KeyHashid Group , GroupRoutes)]
|
|
||||||
, recipRepos :: [(KeyHashid Repo , RepoRoutes)]
|
|
||||||
, recipDecks :: [(KeyHashid Deck , DeckFamilyRoutes)]
|
|
||||||
, recipLooms :: [(KeyHashid Loom , LoomFamilyRoutes)]
|
|
||||||
}
|
|
||||||
deriving Eq
|
|
||||||
|
|
||||||
groupLocalRecipients :: [LocalRecipient] -> RecipientRoutes
|
groupLocalRecipients :: [LocalRecipient] -> RecipientRoutes
|
||||||
groupLocalRecipients = organize . partitionByActor
|
groupLocalRecipients = organize . partitionByActor
|
||||||
where
|
where
|
||||||
|
|
|
@ -511,6 +511,7 @@ actorFetchShareAction u (site, iid) = flip runWorkerT site $ do
|
||||||
for_ mroid $ \ roid ->
|
for_ mroid $ \ roid ->
|
||||||
insertUnique_ $ RemoteCollection roid
|
insertUnique_ $ RemoteCollection roid
|
||||||
return Nothing
|
return Nothing
|
||||||
|
-- TODO see https://vervis.peers.community/decks/br6Go/tickets/r7dDo
|
||||||
|
|
||||||
fetchRemoteActor
|
fetchRemoteActor
|
||||||
:: ( YesodPersist site
|
:: ( YesodPersist site
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -71,6 +71,7 @@ import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
import Crypto.ActorKey
|
import Crypto.ActorKey
|
||||||
import Database.Persist.JSON
|
import Database.Persist.JSON
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
|
@ -94,6 +95,7 @@ import Yesod.Persist.Local
|
||||||
import qualified Data.Aeson.Encode.Pretty.ToEncoding as P
|
import qualified Data.Aeson.Encode.Pretty.ToEncoding as P
|
||||||
import qualified Web.ActivityPub as AP
|
import qualified Web.ActivityPub as AP
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
import Vervis.API
|
import Vervis.API
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
|
@ -226,47 +228,48 @@ getInbox here actor hash = do
|
||||||
where
|
where
|
||||||
ibiidString = "InboxItem #" ++ show (fromSqlKey ibid)
|
ibiidString = "InboxItem #" ++ show (fromSqlKey ibid)
|
||||||
|
|
||||||
postInbox
|
postInbox :: LocalActorBy Key -> Handler ()
|
||||||
:: ( UTCTime
|
postInbox recipByKey = do
|
||||||
-> ActivityAuthentication
|
|
||||||
-> ActivityBody
|
|
||||||
-> ExceptT Text Handler
|
|
||||||
( Text
|
|
||||||
, Maybe (ExceptT Text Worker Text)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
-> Handler ()
|
|
||||||
postInbox handler = do
|
|
||||||
federation <- getsYesod $ appFederation . appSettings
|
federation <- getsYesod $ appFederation . appSettings
|
||||||
unless federation badMethod
|
unless federation badMethod
|
||||||
contentTypes <- lookupHeaders "Content-Type"
|
contentTypes <- lookupHeaders "Content-Type"
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
result <- runExceptT $ do
|
result <- runExceptT $ do
|
||||||
(auth, body) <- authenticateActivity now
|
(auth, body) <- authenticateActivity now
|
||||||
(actbObject body,) <$> handler now auth body
|
verse <-
|
||||||
|
case auth of
|
||||||
|
ActivityAuthLocal authorByKey -> Left <$> do
|
||||||
|
outboxItemID <-
|
||||||
|
parseAuthenticatedLocalActivityURI
|
||||||
|
authorByKey
|
||||||
|
(AP.activityId $ actbActivity body)
|
||||||
|
return $ EventRemoteFwdLocalActivity authorByKey outboxItemID
|
||||||
|
ActivityAuthRemote author -> Right <$> do
|
||||||
|
luActivity <-
|
||||||
|
fromMaybeE (AP.activityId $ actbActivity body) "Activity without 'id'"
|
||||||
|
localRecips <- do
|
||||||
|
mrecips <- parseAudience $ AP.activityAudience $ actbActivity body
|
||||||
|
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
|
||||||
|
recipByHash <- hashLocalActor recipByKey
|
||||||
|
msig <- checkForwarding recipByHash
|
||||||
|
let mfwd = (localRecips,) <$> msig
|
||||||
|
return $ VerseRemote author body mfwd luActivity
|
||||||
|
theater <- getsYesod appTheater
|
||||||
|
r <- liftIO $ callIO theater recipByKey verse
|
||||||
|
case r of
|
||||||
|
Nothing -> notFound
|
||||||
|
Just (Left e) -> throwE e
|
||||||
|
Just (Right t) -> return (actbObject body, t)
|
||||||
recordActivity now result contentTypes
|
recordActivity now result contentTypes
|
||||||
case result of
|
case result of
|
||||||
Left err -> do
|
Left err -> do
|
||||||
logDebug err
|
logDebug err
|
||||||
sendResponseStatus badRequest400 err
|
sendResponseStatus badRequest400 err
|
||||||
Right (obj, (_, mworker)) ->
|
|
||||||
for_ mworker $ \ worker -> forkWorker "postInbox worker" $ do
|
|
||||||
wait <- asyncWorker $ runExceptT worker
|
|
||||||
result' <- wait
|
|
||||||
let result'' =
|
|
||||||
case result' of
|
|
||||||
Left e -> Left $ T.pack $ displayException e
|
|
||||||
Right (Left e) -> Left e
|
|
||||||
Right (Right t) -> Right (obj, (t, Nothing))
|
|
||||||
now' <- liftIO getCurrentTime
|
|
||||||
recordActivity now' result'' contentTypes
|
|
||||||
case result'' of
|
|
||||||
Left err -> logDebug err
|
|
||||||
Right _ -> return ()
|
Right _ -> return ()
|
||||||
where
|
where
|
||||||
recordActivity
|
recordActivity
|
||||||
:: (MonadSite m, SiteEnv m ~ App)
|
:: (MonadSite m, SiteEnv m ~ App)
|
||||||
=> UTCTime -> Either Text (Object, (Text, w)) -> [ContentType] -> m ()
|
=> UTCTime -> Either Text (Object, Text) -> [ContentType] -> m ()
|
||||||
recordActivity now result contentTypes = do
|
recordActivity now result contentTypes = do
|
||||||
macts <- asksSite appActivities
|
macts <- asksSite appActivities
|
||||||
for_ macts $ \ (size, acts) ->
|
for_ macts $ \ (size, acts) ->
|
||||||
|
@ -274,12 +277,21 @@ postInbox handler = do
|
||||||
let (msg, body) =
|
let (msg, body) =
|
||||||
case result of
|
case result of
|
||||||
Left t -> (t, "{?}")
|
Left t -> (t, "{?}")
|
||||||
Right (o, (t, _)) -> (t, encodePretty o)
|
Right (o, t) -> (t, encodePretty o)
|
||||||
item = ActivityReport now msg contentTypes body
|
item = ActivityReport now msg contentTypes body
|
||||||
vec' = item `V.cons` vec
|
vec' = item `V.cons` vec
|
||||||
in if V.length vec' > size
|
in if V.length vec' > size
|
||||||
then V.init vec'
|
then V.init vec'
|
||||||
else vec'
|
else vec'
|
||||||
|
parseAuthenticatedLocalActivityURI
|
||||||
|
:: (MonadSite m, YesodHashids (SiteEnv m))
|
||||||
|
=> LocalActorBy Key -> Maybe LocalURI -> ExceptT Text m OutboxItemId
|
||||||
|
parseAuthenticatedLocalActivityURI author maybeActivityURI = do
|
||||||
|
luAct <- fromMaybeE maybeActivityURI "No 'id'"
|
||||||
|
(actorByKey, _, outboxItemID) <- parseLocalActivityURI luAct
|
||||||
|
unless (actorByKey == author) $
|
||||||
|
throwE "'actor' actor and 'id' actor mismatch"
|
||||||
|
return outboxItemID
|
||||||
|
|
||||||
getOutbox here itemRoute grabActorID hash = do
|
getOutbox here itemRoute grabActorID hash = do
|
||||||
key <- decodeKeyHashid404 hash
|
key <- decodeKeyHashid404 hash
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -31,12 +32,15 @@ module Vervis.Web.Delivery
|
||||||
fixRunningDeliveries
|
fixRunningDeliveries
|
||||||
, retryOutboxDelivery
|
, retryOutboxDelivery
|
||||||
|
|
||||||
|
, deliverActivityDB_Live
|
||||||
, deliverActivityDB
|
, deliverActivityDB
|
||||||
|
, forwardActivityDB_Live
|
||||||
, forwardActivityDB
|
, forwardActivityDB
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
import Control.Concurrent.Chan
|
||||||
import Control.Concurrent.STM.TVar
|
import Control.Concurrent.STM.TVar
|
||||||
import Control.Exception hiding (Handler, try)
|
import Control.Exception hiding (Handler, try)
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
@ -63,11 +67,14 @@ import Database.Persist.Sql
|
||||||
import Yesod.Core hiding (logError, logWarn, logInfo, logDebug)
|
import Yesod.Core hiding (logError, logWarn, logInfo, logDebug)
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import qualified Data.HashMap.Strict as HM
|
||||||
|
import qualified Data.HashSet as HS
|
||||||
import qualified Data.List.NonEmpty as NE
|
import qualified Data.List.NonEmpty as NE
|
||||||
import qualified Data.List.Ordered as LO
|
import qualified Data.List.Ordered as LO
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
import Crypto.ActorKey
|
import Crypto.ActorKey
|
||||||
import Database.Persist.JSON
|
import Database.Persist.JSON
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
|
@ -82,6 +89,7 @@ import Data.Maybe.Local
|
||||||
import Data.Tuple.Local
|
import Data.Tuple.Local
|
||||||
import Database.Persist.Local
|
import Database.Persist.Local
|
||||||
|
|
||||||
|
import Vervis.Actor
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
import Vervis.Data.Actor
|
import Vervis.Data.Actor
|
||||||
import Vervis.FedURI
|
import Vervis.FedURI
|
||||||
|
@ -557,13 +565,23 @@ deliverRemoteHttp hContexts obid envelope (fetched, unfetched, unknown) = do
|
||||||
-- * Insert activity to inboxes of actors
|
-- * Insert activity to inboxes of actors
|
||||||
-- * If collections are listed, insert activity to the local members and return
|
-- * If collections are listed, insert activity to the local members and return
|
||||||
-- the remote members
|
-- the remote members
|
||||||
|
--
|
||||||
|
-- NOTE: This functions is in a transition process! Instead of adding items to
|
||||||
|
-- local inboxes, it will send the items to live actors. At the moment, the
|
||||||
|
-- transition status is:
|
||||||
|
--
|
||||||
|
-- * For person actors, send to live actors
|
||||||
|
-- * For all other types, insert to inboxes
|
||||||
insertActivityToLocalInboxes
|
insertActivityToLocalInboxes
|
||||||
:: ( MonadSite m
|
:: ( MonadSite m
|
||||||
, YesodHashids (SiteEnv m)
|
, YesodHashids (SiteEnv m)
|
||||||
|
, SiteEnv m ~ App
|
||||||
, PersistRecordBackend record SqlBackend
|
, PersistRecordBackend record SqlBackend
|
||||||
)
|
)
|
||||||
=> (InboxId -> InboxItemId -> record)
|
=> Event
|
||||||
-- ^ Database record to insert as an new inbox item to each inbox
|
-- ^ Event to send to local live actors
|
||||||
|
-> (InboxId -> InboxItemId -> record)
|
||||||
|
-- ^ Database record to insert as a new inbox item to each inbox
|
||||||
-> Bool
|
-> Bool
|
||||||
-- ^ Whether to deliver to collection only if owner actor is addressed
|
-- ^ Whether to deliver to collection only if owner actor is addressed
|
||||||
-> Maybe LocalActor
|
-> Maybe LocalActor
|
||||||
|
@ -577,7 +595,7 @@ insertActivityToLocalInboxes
|
||||||
-- author.
|
-- author.
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
||||||
insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recips = do
|
insertActivityToLocalInboxes event makeInboxItem requireOwner mauthor maidAuthor recips = do
|
||||||
|
|
||||||
-- Unhash actor and work item hashids
|
-- Unhash actor and work item hashids
|
||||||
people <- unhashKeys $ recipPeople recips
|
people <- unhashKeys $ recipPeople recips
|
||||||
|
@ -625,7 +643,7 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
|
||||||
loomIDsForSelf =
|
loomIDsForSelf =
|
||||||
[ key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ]
|
[ key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ]
|
||||||
|
|
||||||
-- Grab actor actors whose followers are going to be delivered to
|
-- Grab local actors whose followers are going to be delivered to
|
||||||
let personIDsForFollowers =
|
let personIDsForFollowers =
|
||||||
[ key | (key, routes) <- peopleForStages, routePersonFollowers routes ]
|
[ key | (key, routes) <- peopleForStages, routePersonFollowers routes ]
|
||||||
groupIDsForFollowers =
|
groupIDsForFollowers =
|
||||||
|
@ -658,9 +676,9 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
|
||||||
loomsAndClothsForStages
|
loomsAndClothsForStages
|
||||||
|
|
||||||
-- Get addressed Actor IDs from DB
|
-- Get addressed Actor IDs from DB
|
||||||
|
-- Except for Person actors, we'll send to them via actor system
|
||||||
actorIDsForSelf <- orderedUnion <$> sequenceA
|
actorIDsForSelf <- orderedUnion <$> sequenceA
|
||||||
[ selectActorIDsOrdered personActor PersonActor personIDsForSelf
|
[ selectActorIDsOrdered groupActor GroupActor groupIDsForSelf
|
||||||
, selectActorIDsOrdered groupActor GroupActor groupIDsForSelf
|
|
||||||
, selectActorIDsOrdered repoActor RepoActor repoIDsForSelf
|
, selectActorIDsOrdered repoActor RepoActor repoIDsForSelf
|
||||||
, selectActorIDsOrdered deckActor DeckActor deckIDsForSelf
|
, selectActorIDsOrdered deckActor DeckActor deckIDsForSelf
|
||||||
, selectActorIDsOrdered loomActor LoomActor loomIDsForSelf
|
, selectActorIDsOrdered loomActor LoomActor loomIDsForSelf
|
||||||
|
@ -694,15 +712,27 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Get the local and remote followers of the follower sets from DB
|
-- Get the local and remote followers of the follower sets from DB
|
||||||
localFollowers <-
|
localFollowersDB <-
|
||||||
map (followActor . entityVal) <$>
|
fmap (map E.unValue) $
|
||||||
selectList [FollowTarget <-. followerSetIDs] [Asc FollowActor]
|
E.select $ E.from $ \ (f `E.LeftOuterJoin` p) -> do
|
||||||
|
E.on $ E.just (f E.^. FollowActor) E.==. p E.?. PersonActor
|
||||||
|
E.where_ $
|
||||||
|
f E.^. FollowTarget `E.in_` E.valList followerSetIDs E.&&.
|
||||||
|
E.isNothing (p E.?. PersonId)
|
||||||
|
E.orderBy [E.asc $ f E.^. FollowActor]
|
||||||
|
return $ f E.^. FollowActor
|
||||||
|
localFollowersLivePersonIDs <-
|
||||||
|
fmap (map E.unValue) $
|
||||||
|
E.select $ E.from $ \ (f `E.InnerJoin` p) -> do
|
||||||
|
E.on $ f E.^. FollowActor E.==. p E.^. PersonActor
|
||||||
|
E.where_ $ f E.^. FollowTarget `E.in_` E.valList followerSetIDs
|
||||||
|
return $ p E.^. PersonId
|
||||||
remoteFollowers <- getRemoteFollowers followerSetIDs
|
remoteFollowers <- getRemoteFollowers followerSetIDs
|
||||||
|
|
||||||
-- Insert inbox items to all local recipients, i.e. the local actors
|
-- Insert inbox items to all local recipients, i.e. the local actors
|
||||||
-- directly addressed or listed in a local stage addressed
|
-- directly addressed or listed in a local stage addressed
|
||||||
let localRecipients =
|
let localRecipients =
|
||||||
let allLocal = LO.union localFollowers actorIDsForSelf
|
let allLocal = LO.union localFollowersDB actorIDsForSelf
|
||||||
in case maidAuthor of
|
in case maidAuthor of
|
||||||
Nothing -> allLocal
|
Nothing -> allLocal
|
||||||
Just actorID -> LO.minus' allLocal [actorID]
|
Just actorID -> LO.minus' allLocal [actorID]
|
||||||
|
@ -713,6 +743,14 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
|
||||||
inboxItemIDs <- insertMany $ replicate (length inboxIDs) $ InboxItem True now
|
inboxItemIDs <- insertMany $ replicate (length inboxIDs) $ InboxItem True now
|
||||||
insertMany_ $ zipWith makeInboxItem inboxIDs inboxItemIDs
|
insertMany_ $ zipWith makeInboxItem inboxIDs inboxItemIDs
|
||||||
|
|
||||||
|
-- Insert activity to message queues of live actors
|
||||||
|
let liveRecips =
|
||||||
|
HS.fromList $ map LocalActorPerson $
|
||||||
|
localFollowersLivePersonIDs ++ personIDsForSelf
|
||||||
|
lift $ do
|
||||||
|
theater <- asksSite appTheater
|
||||||
|
liftIO $ sendManyIO theater liveRecips $ Left event
|
||||||
|
|
||||||
-- Return remote followers, to whom we need to deliver via HTTP
|
-- Return remote followers, to whom we need to deliver via HTTP
|
||||||
return remoteFollowers
|
return remoteFollowers
|
||||||
where
|
where
|
||||||
|
@ -814,16 +852,19 @@ insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recip
|
||||||
-- * Insert activity to inboxes of actors
|
-- * Insert activity to inboxes of actors
|
||||||
-- * If collections are listed, insert activity to the local members and return
|
-- * If collections are listed, insert activity to the local members and return
|
||||||
-- the remote members
|
-- the remote members
|
||||||
|
--
|
||||||
|
-- NOTE transition to live actors
|
||||||
deliverLocal'
|
deliverLocal'
|
||||||
:: (MonadSite m, YesodHashids (SiteEnv m))
|
:: (MonadSite m, YesodHashids (SiteEnv m), SiteEnv m ~ App)
|
||||||
=> Bool -- ^ Whether to deliver to collection only if owner actor is addressed
|
=> Bool -- ^ Whether to deliver to collection only if owner actor is addressed
|
||||||
-> LocalActor
|
-> LocalActor
|
||||||
-> ActorId
|
-> ActorId
|
||||||
-> OutboxItemId
|
-> OutboxItemId
|
||||||
|
-> Event
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
||||||
deliverLocal' requireOwner author aidAuthor obiid =
|
deliverLocal' requireOwner author aidAuthor obiid event =
|
||||||
insertActivityToLocalInboxes makeItem requireOwner (Just author) (Just aidAuthor)
|
insertActivityToLocalInboxes event makeItem requireOwner (Just author) (Just aidAuthor)
|
||||||
where
|
where
|
||||||
makeItem ibid ibiid = InboxItemLocal ibid obiid ibiid
|
makeItem ibid ibiid = InboxItemLocal ibid obiid ibiid
|
||||||
|
|
||||||
|
@ -834,30 +875,35 @@ deliverLocal' requireOwner author aidAuthor obiid =
|
||||||
-- * If the author's follower collection is listed, insert activity to the
|
-- * If the author's follower collection is listed, insert activity to the
|
||||||
-- local members and return the remote members
|
-- local members and return the remote members
|
||||||
-- * Ignore other collections
|
-- * Ignore other collections
|
||||||
|
--
|
||||||
|
-- NOTE transition to live actors
|
||||||
deliverLocal
|
deliverLocal
|
||||||
:: KeyHashid Person
|
:: KeyHashid Person
|
||||||
-> ActorId
|
-> ActorId
|
||||||
-> OutboxItemId
|
-> OutboxItemId
|
||||||
|
-> Event
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
-> AppDB
|
-> AppDB
|
||||||
[ ( (InstanceId, Host)
|
[ ( (InstanceId, Host)
|
||||||
, NonEmpty RemoteRecipient
|
, NonEmpty RemoteRecipient
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
deliverLocal authorHash aidAuthor obiid
|
deliverLocal authorHash aidAuthor obiid event
|
||||||
= deliverLocal' True (LocalActorPerson authorHash) aidAuthor obiid
|
= deliverLocal' True (LocalActorPerson authorHash) aidAuthor obiid event
|
||||||
. localRecipSieve sieve True
|
. localRecipSieve sieve True
|
||||||
where
|
where
|
||||||
sieve = RecipientRoutes [(authorHash, PersonRoutes False True)] [] [] [] []
|
sieve = RecipientRoutes [(authorHash, PersonRoutes False True)] [] [] [] []
|
||||||
|
|
||||||
|
-- NOTE transition to live actors
|
||||||
insertRemoteActivityToLocalInboxes
|
insertRemoteActivityToLocalInboxes
|
||||||
:: (MonadSite m, YesodHashids (SiteEnv m))
|
:: (MonadSite m, YesodHashids (SiteEnv m), SiteEnv m ~ App)
|
||||||
=> Bool
|
=> Bool
|
||||||
-> RemoteActivityId
|
-> RemoteActivityId
|
||||||
|
-> Event
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
|
||||||
insertRemoteActivityToLocalInboxes requireOwner ractid =
|
insertRemoteActivityToLocalInboxes requireOwner ractid event =
|
||||||
insertActivityToLocalInboxes makeItem requireOwner Nothing Nothing
|
insertActivityToLocalInboxes event makeItem requireOwner Nothing Nothing
|
||||||
where
|
where
|
||||||
makeItem ibid ibiid = InboxItemRemote ibid ractid ibiid
|
makeItem ibid ibiid = InboxItemRemote ibid ractid ibiid
|
||||||
|
|
||||||
|
@ -1262,7 +1308,8 @@ retryOutboxDelivery = do
|
||||||
|
|
||||||
logInfo "Periodic delivery done"
|
logInfo "Periodic delivery done"
|
||||||
|
|
||||||
deliverActivityDB
|
-- NOTE transition to live actors
|
||||||
|
deliverActivityDB_Live
|
||||||
:: (MonadSite m, SiteEnv m ~ App)
|
:: (MonadSite m, SiteEnv m ~ App)
|
||||||
=> LocalActorBy KeyHashid
|
=> LocalActorBy KeyHashid
|
||||||
-> ActorId
|
-> ActorId
|
||||||
|
@ -1270,10 +1317,11 @@ deliverActivityDB
|
||||||
-> [(Host, NonEmpty LocalURI)]
|
-> [(Host, NonEmpty LocalURI)]
|
||||||
-> [Host]
|
-> [Host]
|
||||||
-> OutboxItemId
|
-> OutboxItemId
|
||||||
|
-> Event
|
||||||
-> AP.Action URIMode
|
-> AP.Action URIMode
|
||||||
-> ExceptT Text (ReaderT SqlBackend m) (Worker ())
|
-> ExceptT Text (ReaderT SqlBackend m) (Worker ())
|
||||||
deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts itemID action = do
|
deliverActivityDB_Live senderByHash senderActorID localRecips remoteRecips fwdHosts itemID event action = do
|
||||||
moreRemoteRecips <- lift $ deliverLocal' True senderByHash senderActorID itemID localRecips
|
moreRemoteRecips <- lift $ deliverLocal' True senderByHash senderActorID itemID event localRecips
|
||||||
checkFederation moreRemoteRecips
|
checkFederation moreRemoteRecips
|
||||||
remoteRecipsHttp <- lift $ deliverRemoteDB fwdHosts itemID remoteRecips moreRemoteRecips
|
remoteRecipsHttp <- lift $ deliverRemoteDB fwdHosts itemID remoteRecips moreRemoteRecips
|
||||||
envelope <- lift $ prepareSendP senderActorID senderByHash itemID action
|
envelope <- lift $ prepareSendP senderActorID senderByHash itemID action
|
||||||
|
@ -1284,7 +1332,12 @@ deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts i
|
||||||
unless (federation || null remoteRecips) $
|
unless (federation || null remoteRecips) $
|
||||||
throwE "Federation disabled, but remote recipients found"
|
throwE "Federation disabled, but remote recipients found"
|
||||||
|
|
||||||
forwardActivityDB
|
-- NOTE transition to live actors
|
||||||
|
deliverActivityDB senderByHash senderActorID localRecips remoteRecips fwdHosts itemID =
|
||||||
|
deliverActivityDB_Live senderByHash senderActorID localRecips remoteRecips fwdHosts itemID EventUnknown
|
||||||
|
|
||||||
|
-- NOTE transition to live actors
|
||||||
|
forwardActivityDB_Live
|
||||||
:: (MonadSite m, SiteEnv m ~ App)
|
:: (MonadSite m, SiteEnv m ~ App)
|
||||||
=> BL.ByteString
|
=> BL.ByteString
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
|
@ -1293,13 +1346,18 @@ forwardActivityDB
|
||||||
-> LocalActorBy KeyHashid
|
-> LocalActorBy KeyHashid
|
||||||
-> RecipientRoutes
|
-> RecipientRoutes
|
||||||
-> RemoteActivityId
|
-> RemoteActivityId
|
||||||
|
-> Event
|
||||||
-> ReaderT SqlBackend m (Worker ())
|
-> ReaderT SqlBackend m (Worker ())
|
||||||
forwardActivityDB body localRecips sig fwderActorID fwderByHash sieve activityID = do
|
forwardActivityDB_Live body localRecips sig fwderActorID fwderByHash sieve activityID event = do
|
||||||
let localRecipsFinal = localRecipSieve' sieve False False localRecips
|
let localRecipsFinal = localRecipSieve' sieve False False localRecips
|
||||||
remoteRecips <-
|
remoteRecips <-
|
||||||
insertRemoteActivityToLocalInboxes False activityID localRecipsFinal
|
insertRemoteActivityToLocalInboxes False activityID event localRecipsFinal
|
||||||
remoteRecipsHttp <-
|
remoteRecipsHttp <-
|
||||||
forwardRemoteDB body activityID fwderActorID sig remoteRecips
|
forwardRemoteDB body activityID fwderActorID sig remoteRecips
|
||||||
errand <- prepareForwardP fwderActorID fwderByHash body sig
|
errand <- prepareForwardP fwderActorID fwderByHash body sig
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
return $ forwardRemoteHttp now errand remoteRecipsHttp
|
return $ forwardRemoteHttp now errand remoteRecipsHttp
|
||||||
|
|
||||||
|
-- NOTE transition to live actors
|
||||||
|
forwardActivityDB body localRecips sig fwderActorID fwderByHash sieve activityID =
|
||||||
|
forwardActivityDB_Live body localRecips sig fwderActorID fwderByHash sieve activityID EventUnknown
|
||||||
|
|
|
@ -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.
|
||||||
-
|
-
|
||||||
|
@ -79,6 +80,7 @@ module Web.ActivityPub
|
||||||
, Undo (..)
|
, Undo (..)
|
||||||
, Audience (..)
|
, Audience (..)
|
||||||
, SpecificActivity (..)
|
, SpecificActivity (..)
|
||||||
|
, activityType
|
||||||
, Action (..)
|
, Action (..)
|
||||||
, makeActivity
|
, makeActivity
|
||||||
, Activity (..)
|
, Activity (..)
|
||||||
|
@ -174,6 +176,119 @@ import Web.Text
|
||||||
|
|
||||||
import Data.Aeson.Local
|
import Data.Aeson.Local
|
||||||
|
|
||||||
|
{-
|
||||||
|
data Link = Link
|
||||||
|
{ linkHref :: URI
|
||||||
|
, linkRel ::
|
||||||
|
, linkMediaType ::
|
||||||
|
, linkName ::
|
||||||
|
, linkHreflang ::
|
||||||
|
, linkHeight ::
|
||||||
|
, linkWidth ::
|
||||||
|
, linkPreview ::
|
||||||
|
, linkRest :: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
data X = X
|
||||||
|
{ xId :: LocalURI
|
||||||
|
, x
|
||||||
|
}
|
||||||
|
|
||||||
|
data Object' u = Object'
|
||||||
|
{ objectId :: ObjURI
|
||||||
|
, objectType ::
|
||||||
|
|
||||||
|
, objectSubject ::
|
||||||
|
, objectRelationship ::
|
||||||
|
, objectActor ::
|
||||||
|
, objectAttributedTo ::
|
||||||
|
, objectAttachment ::
|
||||||
|
, objectBcc ::
|
||||||
|
, objectBto ::
|
||||||
|
, objectCc ::
|
||||||
|
, objectContext ::
|
||||||
|
, objectCurrent ::
|
||||||
|
, objectFirst ::
|
||||||
|
, objectGenerator ::
|
||||||
|
, objectIcon ::
|
||||||
|
, objectImage ::
|
||||||
|
, objectInReplyTo ::
|
||||||
|
, objectItems ::
|
||||||
|
, objectInstrument ::
|
||||||
|
, objectOrderedItems ::
|
||||||
|
, objectLast ::
|
||||||
|
, objectLocation ::
|
||||||
|
, objectNext ::
|
||||||
|
, objectObject ::
|
||||||
|
, objectOneOf ::
|
||||||
|
, objectAnyOf ::
|
||||||
|
, objectClosed ::
|
||||||
|
, objectOrigin ::
|
||||||
|
, objectAccuracy ::
|
||||||
|
, objectPrev ::
|
||||||
|
, objectPreview ::
|
||||||
|
, objectProvider ::
|
||||||
|
, objectReplies ::
|
||||||
|
, objectResult ::
|
||||||
|
, objectAudience ::
|
||||||
|
, objectPartOf ::
|
||||||
|
, objectTag ::
|
||||||
|
, objectTags ::
|
||||||
|
, objectTarget ::
|
||||||
|
, objectTo ::
|
||||||
|
, objectUrl ::
|
||||||
|
, objectAltitude ::
|
||||||
|
, objectContent ::
|
||||||
|
, objectContentMap ::
|
||||||
|
, objectName ::
|
||||||
|
, objectNameMap ::
|
||||||
|
, objectDuration ::
|
||||||
|
, objectEndTime ::
|
||||||
|
, objectHeight ::
|
||||||
|
, objectHref ::
|
||||||
|
, objectHreflang ::
|
||||||
|
, objectLatitude ::
|
||||||
|
, objectLongitude ::
|
||||||
|
, objectMediaType ::
|
||||||
|
, objectPublished ::
|
||||||
|
, objectRadius ::
|
||||||
|
, objectRating ::
|
||||||
|
, objectRel ::
|
||||||
|
, objectStartIndex ::
|
||||||
|
, objectStartTime ::
|
||||||
|
, objectSummary ::
|
||||||
|
, objectSummaryMap ::
|
||||||
|
, objectTotalItems ::
|
||||||
|
, objectUnits ::
|
||||||
|
, objectUpdated ::
|
||||||
|
, objectWidth ::
|
||||||
|
, objectDescribes ::
|
||||||
|
, objectFormerType ::
|
||||||
|
, objectDeleted ::
|
||||||
|
|
||||||
|
, objectEndpoints ::
|
||||||
|
, objectFollowing ::
|
||||||
|
, objectFollowers ::
|
||||||
|
, objectInbox ::
|
||||||
|
, objectLiked ::
|
||||||
|
, objectShares ::
|
||||||
|
, objectLikes ::
|
||||||
|
, objectOauthAuthorizationEndpoint ::
|
||||||
|
, objectOauthTokenEndpoint ::
|
||||||
|
, objectOutbox ::
|
||||||
|
, objectPreferredUsername ::
|
||||||
|
, objectProvideClientKey ::
|
||||||
|
, objectProxyUrl ::
|
||||||
|
, objectSharedInbox ::
|
||||||
|
, objectSignClientKey ::
|
||||||
|
, objectSource ::
|
||||||
|
, objectStreams ::
|
||||||
|
, objectUploadMedia ::
|
||||||
|
|
||||||
|
, objectRest :: Object
|
||||||
|
}
|
||||||
|
-}
|
||||||
|
|
||||||
proxy :: a u -> Proxy a
|
proxy :: a u -> Proxy a
|
||||||
proxy _ = Proxy
|
proxy _ = Proxy
|
||||||
|
|
||||||
|
@ -1712,6 +1827,21 @@ data SpecificActivity u
|
||||||
| ResolveActivity (Resolve u)
|
| ResolveActivity (Resolve u)
|
||||||
| UndoActivity (Undo u)
|
| UndoActivity (Undo u)
|
||||||
|
|
||||||
|
activityType :: SpecificActivity u -> Text
|
||||||
|
activityType (AcceptActivity _) = "Accept"
|
||||||
|
activityType (AddActivity _) = "Add"
|
||||||
|
activityType (ApplyActivity _) = "Apply"
|
||||||
|
activityType (CreateActivity _) = "Create"
|
||||||
|
activityType (FollowActivity _) = "Follow"
|
||||||
|
activityType (GrantActivity _) = "Grant"
|
||||||
|
activityType (InviteActivity _) = "Invite"
|
||||||
|
activityType (JoinActivity _) = "Join"
|
||||||
|
activityType (OfferActivity _) = "Offer"
|
||||||
|
activityType (PushActivity _) = "Push"
|
||||||
|
activityType (RejectActivity _) = "Reject"
|
||||||
|
activityType (ResolveActivity _) = "Resolve"
|
||||||
|
activityType (UndoActivity _) = "Undo"
|
||||||
|
|
||||||
data Action u = Action
|
data Action u = Action
|
||||||
{ actionCapability :: Maybe (ObjURI u)
|
{ actionCapability :: Maybe (ObjURI u)
|
||||||
, actionSummary :: Maybe HTML
|
, actionSummary :: Maybe HTML
|
||||||
|
@ -1782,20 +1912,6 @@ instance ActivityPub Activity where
|
||||||
<> "fulfills" .=% fulfills
|
<> "fulfills" .=% fulfills
|
||||||
<> encodeSpecific authority actor specific
|
<> encodeSpecific authority actor specific
|
||||||
where
|
where
|
||||||
activityType :: SpecificActivity u -> Text
|
|
||||||
activityType (AcceptActivity _) = "Accept"
|
|
||||||
activityType (AddActivity _) = "Add"
|
|
||||||
activityType (ApplyActivity _) = "Apply"
|
|
||||||
activityType (CreateActivity _) = "Create"
|
|
||||||
activityType (FollowActivity _) = "Follow"
|
|
||||||
activityType (GrantActivity _) = "Grant"
|
|
||||||
activityType (InviteActivity _) = "Invite"
|
|
||||||
activityType (JoinActivity _) = "Join"
|
|
||||||
activityType (OfferActivity _) = "Offer"
|
|
||||||
activityType (PushActivity _) = "Push"
|
|
||||||
activityType (RejectActivity _) = "Reject"
|
|
||||||
activityType (ResolveActivity _) = "Resolve"
|
|
||||||
activityType (UndoActivity _) = "Undo"
|
|
||||||
encodeSpecific h _ (AcceptActivity a) = encodeAccept h a
|
encodeSpecific h _ (AcceptActivity a) = encodeAccept h a
|
||||||
encodeSpecific h _ (AddActivity a) = encodeAdd h a
|
encodeSpecific h _ (AddActivity a) = encodeAdd h a
|
||||||
encodeSpecific _ _ (ApplyActivity a) = encodeApply a
|
encodeSpecific _ _ (ApplyActivity a) = encodeApply a
|
||||||
|
|
51
src/Web/Actor.hs
Normal file
51
src/Web/Actor.hs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019, 2022, 2023 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/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Reusable library for building decentralized actor-model-based web apps,
|
||||||
|
-- with 'Control.Concurrent.Actor' for the local actor system, and ActivityPub
|
||||||
|
-- as the network protocol.
|
||||||
|
--
|
||||||
|
-- At the time of writing (April 2023), this module is collecting the pieces
|
||||||
|
-- that aren't tied to a specific web framework. Yesod-specific parts are in
|
||||||
|
-- separate modules.
|
||||||
|
--
|
||||||
|
-- Ideally, the whole application structure would be specified using
|
||||||
|
-- framework-independent tools, and framework integration (right now just
|
||||||
|
-- Yesod, might also be Servant in the future) would be an automatic or
|
||||||
|
-- auto-generated nearly-seamless part. I hope to get there, gradually, in
|
||||||
|
-- steps of refactoring.
|
||||||
|
module Web.Actor
|
||||||
|
( StageWeb (..)
|
||||||
|
, ActForE
|
||||||
|
, hostIsLocal
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Network.FedURI
|
||||||
|
|
||||||
|
class (Stage s, UriMode (StageURIMode s)) => StageWeb s where
|
||||||
|
type StageURIMode s
|
||||||
|
stageInstanceHost :: s -> Authority (StageURIMode s)
|
||||||
|
|
||||||
|
type ActForE s = ExceptT Text (ActFor s)
|
||||||
|
|
||||||
|
hostIsLocal
|
||||||
|
:: (MonadActor m, ActorEnv m ~ s, StageWeb s)
|
||||||
|
=> Authority (StageURIMode s) -> m Bool
|
||||||
|
hostIsLocal h = asksEnv $ (== h) . stageInstanceHost
|
137
src/Web/Actor/Persist.hs
Normal file
137
src/Web/Actor/Persist.hs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019, 2020, 2022, 2023 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 Web.Actor.Persist
|
||||||
|
( StageHashids (..)
|
||||||
|
, KeyHashid ()
|
||||||
|
, keyHashidText
|
||||||
|
|
||||||
|
, encodeKeyHashidPure
|
||||||
|
--, getEncodeKeyHashid
|
||||||
|
--, encodeKeyHashid
|
||||||
|
|
||||||
|
, decodeKeyHashidPure
|
||||||
|
--, decodeKeyHashid
|
||||||
|
--, decodeKeyHashidF
|
||||||
|
--, decodeKeyHashidM
|
||||||
|
, decodeKeyHashidE
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude hiding (fail)
|
||||||
|
|
||||||
|
import Control.Monad.Fail
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Control.Monad.Trans.Maybe
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Text.Encoding
|
||||||
|
import Database.Persist.Class
|
||||||
|
import Database.Persist.Sql
|
||||||
|
import Web.Hashids
|
||||||
|
import Web.PathPieces
|
||||||
|
|
||||||
|
import Control.Concurrent.Actor
|
||||||
|
import Web.Actor
|
||||||
|
--import Yesod.MonadActor
|
||||||
|
|
||||||
|
import Web.Hashids.Local
|
||||||
|
|
||||||
|
class StageWeb s => StageHashids s where
|
||||||
|
stageHashidsContext :: s -> HashidsContext
|
||||||
|
|
||||||
|
newtype KeyHashid record = KeyHashid
|
||||||
|
{ keyHashidText :: Text
|
||||||
|
}
|
||||||
|
deriving (Eq, Ord, Read, Show)
|
||||||
|
|
||||||
|
instance PersistEntity record => PathPiece (KeyHashid record) where
|
||||||
|
fromPathPiece t = KeyHashid <$> fromPathPiece t
|
||||||
|
toPathPiece (KeyHashid t) = toPathPiece t
|
||||||
|
|
||||||
|
encodeKeyHashidPure
|
||||||
|
:: ToBackendKey SqlBackend record
|
||||||
|
=> HashidsContext -> Key record -> KeyHashid record
|
||||||
|
encodeKeyHashidPure ctx = KeyHashid . decodeUtf8 . encodeInt64 ctx . fromSqlKey
|
||||||
|
|
||||||
|
getEncodeKeyHashid
|
||||||
|
:: ( MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> m (Key record -> KeyHashid record)
|
||||||
|
getEncodeKeyHashid = do
|
||||||
|
ctx <- asksEnv stageHashidsContext
|
||||||
|
return $ encodeKeyHashidPure ctx
|
||||||
|
|
||||||
|
encodeKeyHashid
|
||||||
|
:: ( MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> Key record
|
||||||
|
-> m (KeyHashid record)
|
||||||
|
encodeKeyHashid k = do
|
||||||
|
enc <- getEncodeKeyHashid
|
||||||
|
return $ enc k
|
||||||
|
|
||||||
|
decodeKeyHashidPure
|
||||||
|
:: ToBackendKey SqlBackend record
|
||||||
|
=> HashidsContext
|
||||||
|
-> KeyHashid record
|
||||||
|
-> Maybe (Key record)
|
||||||
|
decodeKeyHashidPure ctx (KeyHashid t) =
|
||||||
|
fmap toSqlKey $ decodeInt64 ctx $ encodeUtf8 t
|
||||||
|
|
||||||
|
decodeKeyHashid
|
||||||
|
:: ( MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> KeyHashid record
|
||||||
|
-> m (Maybe (Key record))
|
||||||
|
decodeKeyHashid khid = do
|
||||||
|
ctx <- asksEnv stageHashidsContext
|
||||||
|
return $ decodeKeyHashidPure ctx khid
|
||||||
|
|
||||||
|
decodeKeyHashidF
|
||||||
|
:: ( MonadFail m
|
||||||
|
, MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> KeyHashid record
|
||||||
|
-> String
|
||||||
|
-> m (Key record)
|
||||||
|
decodeKeyHashidF khid e = maybe (fail e) return =<< decodeKeyHashid khid
|
||||||
|
|
||||||
|
decodeKeyHashidM
|
||||||
|
:: ( MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> KeyHashid record
|
||||||
|
-> MaybeT m (Key record)
|
||||||
|
decodeKeyHashidM = MaybeT . decodeKeyHashid
|
||||||
|
|
||||||
|
decodeKeyHashidE
|
||||||
|
:: ( MonadActor m
|
||||||
|
, StageHashids (ActorEnv m)
|
||||||
|
, ToBackendKey SqlBackend record
|
||||||
|
)
|
||||||
|
=> KeyHashid record
|
||||||
|
-> e
|
||||||
|
-> ExceptT e m (Key record)
|
||||||
|
decodeKeyHashidE khid e =
|
||||||
|
ExceptT $ maybe (Left e) Right <$> decodeKeyHashid khid
|
|
@ -34,7 +34,7 @@ module Yesod.ActivityPub
|
||||||
, provideHtmlAndAP''
|
, provideHtmlAndAP''
|
||||||
, provideHtmlFeedAndAP
|
, provideHtmlFeedAndAP
|
||||||
|
|
||||||
, hostIsLocal
|
, hostIsLocalOld
|
||||||
, verifyHostLocal
|
, verifyHostLocal
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
@ -576,14 +576,14 @@ provideHtmlFeedAndAP object feed widget = do
|
||||||
widget
|
widget
|
||||||
(Just feed)
|
(Just feed)
|
||||||
|
|
||||||
hostIsLocal
|
hostIsLocalOld
|
||||||
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
|
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
|
||||||
=> Authority (SiteFedURIMode site) -> m Bool
|
=> Authority (SiteFedURIMode site) -> m Bool
|
||||||
hostIsLocal h = asksSite $ (== h) . siteInstanceHost
|
hostIsLocalOld h = asksSite $ (== h) . siteInstanceHost
|
||||||
|
|
||||||
verifyHostLocal
|
verifyHostLocal
|
||||||
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
|
:: (MonadSite m, SiteEnv m ~ site, YesodActivityPub site)
|
||||||
=> Authority (SiteFedURIMode site) -> Text -> ExceptT Text m ()
|
=> Authority (SiteFedURIMode site) -> Text -> ExceptT Text m ()
|
||||||
verifyHostLocal h t = do
|
verifyHostLocal h t = do
|
||||||
local <- hostIsLocal h
|
local <- hostIsLocalOld h
|
||||||
unless local $ throwE t
|
unless local $ throwE t
|
||||||
|
|
56
src/Yesod/Actor.hs
Normal file
56
src/Yesod/Actor.hs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written 2019, 2023 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/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Tools for integrating 'Web.Actor' with the Yesod web framework.
|
||||||
|
module Yesod.Actor
|
||||||
|
( decodeRouteLocal
|
||||||
|
, parseLocalURI
|
||||||
|
, StageYesod (..)
|
||||||
|
, parseFedURI
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Text.Encoding
|
||||||
|
import Network.HTTP.Types.URI
|
||||||
|
import Yesod.Core
|
||||||
|
|
||||||
|
import Network.FedURI
|
||||||
|
import Web.Actor
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Except.Local
|
||||||
|
|
||||||
|
decodeRouteLocal :: ParseRoute site => LocalURI -> Maybe (Route site)
|
||||||
|
decodeRouteLocal =
|
||||||
|
parseRoute . (,[]) . decodePathSegments . encodeUtf8 . localUriPath
|
||||||
|
|
||||||
|
parseLocalURI
|
||||||
|
:: (Monad m, ParseRoute site)
|
||||||
|
=> LocalURI -> ExceptT Text m (Route site)
|
||||||
|
parseLocalURI lu = fromMaybeE (decodeRouteLocal lu) "Not a valid route"
|
||||||
|
|
||||||
|
class (StageWeb s, Yesod (StageSite s)) => StageYesod s where
|
||||||
|
type StageSite s
|
||||||
|
|
||||||
|
parseFedURI
|
||||||
|
:: (StageYesod s, ParseRoute (StageSite s))
|
||||||
|
=> ObjURI (StageURIMode s)
|
||||||
|
-> ActForE s (Either (Route (StageSite s)) (ObjURI (StageURIMode s)))
|
||||||
|
parseFedURI u@(ObjURI h lu) = do
|
||||||
|
hl <- lift $ hostIsLocal h
|
||||||
|
if hl
|
||||||
|
then Left <$> parseLocalURI lu
|
||||||
|
else pure $ Right u
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written 2019 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019 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.
|
||||||
-
|
-
|
||||||
|
@ -18,19 +18,14 @@ module Yesod.FedURI
|
||||||
, getEncodeRouteLocal
|
, getEncodeRouteLocal
|
||||||
, getEncodeRouteHome
|
, getEncodeRouteHome
|
||||||
, getEncodeRouteFed
|
, getEncodeRouteFed
|
||||||
, decodeRouteLocal
|
|
||||||
, getEncodeRoutePageLocal
|
, getEncodeRoutePageLocal
|
||||||
, getEncodeRoutePageHome
|
, getEncodeRoutePageHome
|
||||||
, getEncodeRoutePageFed
|
, getEncodeRoutePageFed
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Text.Encoding
|
|
||||||
import Network.HTTP.Types.URI
|
|
||||||
import Yesod.Core
|
import Yesod.Core
|
||||||
|
|
||||||
import qualified Data.Text as T
|
|
||||||
|
|
||||||
import Network.FedURI
|
import Network.FedURI
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
|
||||||
|
@ -63,10 +58,6 @@ getEncodeRouteFed
|
||||||
=> m (Authority u -> Route site -> ObjURI u)
|
=> m (Authority u -> Route site -> ObjURI u)
|
||||||
getEncodeRouteFed = (\ f a -> ObjURI a . f) <$> getEncodeRouteLocal
|
getEncodeRouteFed = (\ f a -> ObjURI a . f) <$> getEncodeRouteLocal
|
||||||
|
|
||||||
decodeRouteLocal :: ParseRoute site => LocalURI -> Maybe (Route site)
|
|
||||||
decodeRouteLocal =
|
|
||||||
parseRoute . (,[]) . decodePathSegments . encodeUtf8 . localUriPath
|
|
||||||
|
|
||||||
getEncodeRoutePageLocal
|
getEncodeRoutePageLocal
|
||||||
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, YesodPaginate site)
|
:: (MonadSite m, SiteEnv m ~ site, SiteFedURI site, YesodPaginate site)
|
||||||
=> m (Route site -> Int -> LocalPageURI)
|
=> m (Route site -> Int -> LocalPageURI)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- This file is part of Vervis.
|
{- This file is part of Vervis.
|
||||||
-
|
-
|
||||||
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
|
- Written in 2019, 2020, 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.
|
||||||
-
|
-
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
module Yesod.Hashids
|
module Yesod.Hashids
|
||||||
( YesodHashids (..)
|
( YesodHashids (..)
|
||||||
, KeyHashid ()
|
, KeyHashid
|
||||||
, keyHashidText
|
, keyHashidText
|
||||||
|
|
||||||
, encodeKeyHashidPure
|
, encodeKeyHashidPure
|
||||||
|
@ -44,6 +44,7 @@ import Web.Hashids
|
||||||
import Web.PathPieces
|
import Web.PathPieces
|
||||||
import Yesod.Core
|
import Yesod.Core
|
||||||
|
|
||||||
|
import Web.Actor.Persist (KeyHashid, keyHashidText, encodeKeyHashidPure, decodeKeyHashidPure)
|
||||||
import Yesod.MonadSite
|
import Yesod.MonadSite
|
||||||
|
|
||||||
import Web.Hashids.Local
|
import Web.Hashids.Local
|
||||||
|
@ -51,20 +52,6 @@ import Web.Hashids.Local
|
||||||
class Yesod site => YesodHashids site where
|
class Yesod site => YesodHashids site where
|
||||||
siteHashidsContext :: site -> HashidsContext
|
siteHashidsContext :: site -> HashidsContext
|
||||||
|
|
||||||
newtype KeyHashid record = KeyHashid
|
|
||||||
{ keyHashidText :: Text
|
|
||||||
}
|
|
||||||
deriving (Eq, Ord, Read, Show)
|
|
||||||
|
|
||||||
instance PersistEntity record => PathPiece (KeyHashid record) where
|
|
||||||
fromPathPiece t = KeyHashid <$> fromPathPiece t
|
|
||||||
toPathPiece (KeyHashid t) = toPathPiece t
|
|
||||||
|
|
||||||
encodeKeyHashidPure
|
|
||||||
:: ToBackendKey SqlBackend record
|
|
||||||
=> HashidsContext -> Key record -> KeyHashid record
|
|
||||||
encodeKeyHashidPure ctx = KeyHashid . decodeUtf8 . encodeInt64 ctx . fromSqlKey
|
|
||||||
|
|
||||||
getEncodeKeyHashid
|
getEncodeKeyHashid
|
||||||
:: ( MonadSite m
|
:: ( MonadSite m
|
||||||
, YesodHashids (SiteEnv m)
|
, YesodHashids (SiteEnv m)
|
||||||
|
@ -86,14 +73,6 @@ encodeKeyHashid k = do
|
||||||
enc <- getEncodeKeyHashid
|
enc <- getEncodeKeyHashid
|
||||||
return $ enc k
|
return $ enc k
|
||||||
|
|
||||||
decodeKeyHashidPure
|
|
||||||
:: ToBackendKey SqlBackend record
|
|
||||||
=> HashidsContext
|
|
||||||
-> KeyHashid record
|
|
||||||
-> Maybe (Key record)
|
|
||||||
decodeKeyHashidPure ctx (KeyHashid t) =
|
|
||||||
fmap toSqlKey $ decodeInt64 ctx $ encodeUtf8 t
|
|
||||||
|
|
||||||
decodeKeyHashid
|
decodeKeyHashid
|
||||||
:: ( MonadSite m
|
:: ( MonadSite m
|
||||||
, YesodHashids (SiteEnv m)
|
, YesodHashids (SiteEnv m)
|
||||||
|
|
|
@ -15,6 +15,12 @@ extra-deps:
|
||||||
# yesod-auth-account
|
# yesod-auth-account
|
||||||
- git: https://vervis.peers.community/repos/VE2Kr
|
- git: https://vervis.peers.community/repos/VE2Kr
|
||||||
commit: 70024e76cafb95bfa50b456efcf0970d720207bd
|
commit: 70024e76cafb95bfa50b456efcf0970d720207bd
|
||||||
|
# - git: https://notabug.org/fr33domlover/haskell-persistent
|
||||||
|
# commit: 9cc700b540a680ac1fdc9df94847a631013cb3ca
|
||||||
|
# subdirs:
|
||||||
|
# - persistent
|
||||||
|
# - persistent-postgresql
|
||||||
|
|
||||||
- ./lib/darcs-lights
|
- ./lib/darcs-lights
|
||||||
- ./lib/darcs-rev
|
- ./lib/darcs-rev
|
||||||
- ./lib/dvara
|
- ./lib/dvara
|
||||||
|
@ -49,6 +55,7 @@ extra-deps:
|
||||||
- time-interval-0.1.1
|
- time-interval-0.1.1
|
||||||
- time-units-1.0.0
|
- time-units-1.0.0
|
||||||
- url-2.1.3
|
- url-2.1.3
|
||||||
|
- annotated-exception-0.2.0.4
|
||||||
|
|
||||||
# Override default flag values for local packages and extra-deps
|
# Override default flag values for local packages and extra-deps
|
||||||
flags:
|
flags:
|
||||||
|
|
|
@ -35,6 +35,9 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
<li>
|
<li>
|
||||||
<a href=@{PublishOfferMergeR}>
|
<a href=@{PublishOfferMergeR}>
|
||||||
Open a merge request
|
Open a merge request
|
||||||
|
$# <li>
|
||||||
|
$# <a href=@{PublishCommentR}>
|
||||||
|
$# Comment on a ticket or merge request
|
||||||
<li>
|
<li>
|
||||||
<a href=@{PublishMergeR}>
|
<a href=@{PublishMergeR}>
|
||||||
Merge a merge request
|
Merge a merge request
|
||||||
|
|
|
@ -25,6 +25,11 @@ Instance
|
||||||
RemoteObject
|
RemoteObject
|
||||||
instance InstanceId
|
instance InstanceId
|
||||||
ident LocalURI
|
ident LocalURI
|
||||||
|
-- fetched UTCTime Maybe
|
||||||
|
|
||||||
|
-- type Text Maybe
|
||||||
|
-- followers LocalURI Maybe
|
||||||
|
-- team LocalURI Maybe
|
||||||
|
|
||||||
UniqueRemoteObject instance ident
|
UniqueRemoteObject instance ident
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@
|
||||||
/ssh-keys KeysR GET POST
|
/ssh-keys KeysR GET POST
|
||||||
/ssh-keys/#SshKeyKeyHashid/delete KeyDeleteR POST
|
/ssh-keys/#SshKeyKeyHashid/delete KeyDeleteR POST
|
||||||
|
|
||||||
|
--/publish/comment PublishCommentR GET POST
|
||||||
/publish/offer-merge PublishOfferMergeR GET POST
|
/publish/offer-merge PublishOfferMergeR GET POST
|
||||||
/publish/merge PublishMergeR GET POST
|
/publish/merge PublishMergeR GET POST
|
||||||
|
|
||||||
|
|
13
vervis.cabal
13
vervis.cabal
|
@ -43,8 +43,10 @@ library
|
||||||
Vervis.Hook
|
Vervis.Hook
|
||||||
other-modules:
|
other-modules:
|
||||||
Control.Applicative.Local
|
Control.Applicative.Local
|
||||||
|
Control.Concurrent.Actor
|
||||||
Control.Concurrent.Local
|
Control.Concurrent.Local
|
||||||
Control.Concurrent.ResultShare
|
Control.Concurrent.ResultShare
|
||||||
|
Control.Concurrent.Return
|
||||||
Control.Monad.Trans.Except.Local
|
Control.Monad.Trans.Except.Local
|
||||||
Crypto.ActorKey
|
Crypto.ActorKey
|
||||||
Crypto.PubKey.Encoding
|
Crypto.PubKey.Encoding
|
||||||
|
@ -109,11 +111,14 @@ library
|
||||||
Text.Jasmine.Local
|
Text.Jasmine.Local
|
||||||
Web.ActivityAccess
|
Web.ActivityAccess
|
||||||
Web.ActivityPub
|
Web.ActivityPub
|
||||||
|
Web.Actor
|
||||||
|
Web.Actor.Persist
|
||||||
-- Web.Capability
|
-- Web.Capability
|
||||||
Web.Text
|
Web.Text
|
||||||
Web.Hashids.Local
|
Web.Hashids.Local
|
||||||
Web.PathPieces.Local
|
Web.PathPieces.Local
|
||||||
Yesod.ActivityPub
|
Yesod.ActivityPub
|
||||||
|
Yesod.Actor
|
||||||
Yesod.Auth.Unverified
|
Yesod.Auth.Unverified
|
||||||
Yesod.Auth.Unverified.Creds
|
Yesod.Auth.Unverified.Creds
|
||||||
Yesod.Auth.Unverified.Internal
|
Yesod.Auth.Unverified.Internal
|
||||||
|
@ -128,6 +133,12 @@ library
|
||||||
|
|
||||||
Vervis.Access
|
Vervis.Access
|
||||||
Vervis.ActivityPub
|
Vervis.ActivityPub
|
||||||
|
Vervis.Actor
|
||||||
|
Vervis.Actor.Deck
|
||||||
|
Vervis.Actor.Group
|
||||||
|
Vervis.Actor.Loom
|
||||||
|
Vervis.Actor.Person
|
||||||
|
Vervis.Actor.Repo
|
||||||
Vervis.API
|
Vervis.API
|
||||||
Vervis.Avatar
|
Vervis.Avatar
|
||||||
Vervis.BinaryBody
|
Vervis.BinaryBody
|
||||||
|
@ -270,6 +281,8 @@ library
|
||||||
build-depends: aeson
|
build-depends: aeson
|
||||||
-- For activity JSOn display in /inbox test page
|
-- For activity JSOn display in /inbox test page
|
||||||
, aeson-pretty
|
, aeson-pretty
|
||||||
|
-- For rethrowing in Control.Concurrent.Actor
|
||||||
|
, annotated-exception
|
||||||
-- for encoding and decoding of crypto public keys
|
-- for encoding and decoding of crypto public keys
|
||||||
, asn1-encoding
|
, asn1-encoding
|
||||||
, asn1-types
|
, asn1-types
|
||||||
|
|
Loading…
Reference in a new issue