Switch to new actor layout

This is such a huge patch, it's probably impossible to tell what it does by
looking at the code. One thing is clear: It changes *everything* :P so here's
an overview:

- There are now 5 types of actors, each having its own top-level route
- So projects, repos, etc. are no longer "under" sharers
- Actor routes are now based on their KeyHashid, there are no "idents" anymore,
  i.e. URLs look random and don't contain user or repo names
- No sharers anymore; people and groups are distinct entities not sharing a
  common namespace or anything like that
- Project has been renamed to Deck and it simply means a ticket tracker; repos
  are no longer "under" projects
- In addition to Person, Group, Repo and Deck, there's a new actor type Loom,
  which is a patch tracker; i.e. Repo actors don't manage MRs anymore
- All C2S and S2S is temporarily disabled, because huge changes to the whole
  code are required and I'll do them gradually in the next patches
- Since form-based actions are implemented using C2S, they're disabled as well,
  so Vervis is now essentially read-only
- Some views have been temporarily removed, e.g. repo history and commit view
- A huge set of DB migrations has been added to adapt the DB to these changes;
  I haven't tested them yet on a read DB so there may be errors there; I'll fix
  them in the next patches if I find any (probably going to test on the main
  instance where Vervis itself is hosted...)
- Some modules got tech upgrades, e.g. LocalActor became a higher-kinded type
  and a similar pattern is probably relevant for several other types
- There's an 'Actor' entity in the DB schema now, and all 5 actor types use it
  for common things like inbox and outbox
- Although inbox and outbox are used only by Actor, so essentially could be
  removed, I haven't removed them; that's because I wonder if at some point
  users can have a tree of inboxes much like in email; I don't have an excuse
  for Outbox, but anyway, leaving them as is for now
- Workflows, roles and collaborators are partially removed/unused until I
  figure out a sane federated way to provide these features
- Since repo routes don't contain a "sharer" anymore, SSH URIs are now simpler,
  they already look like user@host/repo regardless of who "controls" that repo
This commit is contained in:
fr33domlover 2022-08-15 13:57:42 +00:00
parent 91b2d36a19
commit 2e72684fd5
94 changed files with 8767 additions and 7728 deletions

View file

@ -0,0 +1,33 @@
Inbox
Outbox
FollowerSet
Sharer
ident Text
name Text Maybe
created UTCTime
UniqueSharer ident
Person
ident SharerId
username Text
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
about Text
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniquePersonIdent ident
UniquePersonLogin login
UniquePersonEmail email
UniquePersonInbox inbox
UniquePersonOutbox outbox
UniquePersonFollowers followers

View file

@ -0,0 +1,130 @@
Ticket
FollowerSet
RemoteActor
RemoteObject
Person
OutboxItem
RemoteActivity
Project
Repo
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers
TicketProjectRemote
ticket TicketAuthorLocalId
tracker RemoteActorId
project RemoteObjectId Maybe -- specify if not same as tracker
-- For MRs it may be either a remote repo or
-- a branch of it
UniqueTicketProjectRemote ticket
TicketAuthorLocal
ticket LocalTicketId
author PersonId
open OutboxItemId
UniqueTicketAuthorLocal ticket
UniqueTicketAuthorLocalOpen open
Message
created UTCTime
source Text -- Pandoc Markdown
content Text -- HTML
parent MessageId Maybe
root DiscussionId
LocalMessage
author PersonId
rest MessageId
create OutboxItemId
unlinkedParent FedURI Maybe
UniqueLocalMessage rest
UniqueLocalMessageCreate create
RemoteMessage
author RemoteActorId
ident RemoteObjectId
rest MessageId
create RemoteActivityId
lostParent FedURI Maybe
UniqueRemoteMessageIdent ident
UniqueRemoteMessage rest
UniqueRemoteMessageCreate create
Follow
person PersonId
target FollowerSetId
public Bool
follow OutboxItemId
accept OutboxItemId
UniqueFollow person target
UniqueFollowFollow follow
UniqueFollowAccept accept
RemoteFollow
actor RemoteActorId
target FollowerSetId
public Bool
follow RemoteActivityId
accept OutboxItemId
UniqueRemoteFollow actor target
UniqueRemoteFollowFollow follow
UniqueRemoteFollowAccept accept
RemoteTicket
ticket TicketAuthorRemoteId
ident RemoteObjectId
discuss RemoteDiscussionId
UniqueRemoteTicket ticket
UniqueRemoteTicketIdent ident
UniqueRemoteTicketDiscuss discuss
Discussion
RemoteDiscussion
ident RemoteObjectId
discuss DiscussionId
UniqueRemoteDiscussionIdent ident
UniqueRemoteDiscussion discuss
TicketAuthorRemote
ticket TicketContextLocalId
author RemoteActorId
open RemoteActivityId
UniqueTicketAuthorRemote ticket
UniqueTicketAuthorRemoteOpen open
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept
TicketProjectLocal
context TicketContextLocalId
project ProjectId
UniqueTicketProjectLocal context
TicketRepoLocal
context TicketContextLocalId
repo RepoId
branch Text Maybe
UniqueTicketRepoLocal context

View file

@ -0,0 +1,23 @@
Person
FollowerSet
Discussion
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,26 @@
Person
Discussion
FollowerSet
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
UniqueTicketDiscuss discuss
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,32 @@
Person
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers

View file

@ -0,0 +1,46 @@
Person
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketAuthorLocal
ticket LocalTicketId
ticketNew TicketId
author PersonId
open OutboxItemId
UniqueTicketAuthorLocal ticket
UniqueTicketAuthorLocalOpen open
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,46 @@
Person
RemoteActor
RemoteActivity
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketAuthorRemote
ticket TicketContextLocalId
ticketNew TicketId
author RemoteActorId
open RemoteActivityId
UniqueTicketAuthorRemote ticket
UniqueTicketAuthorRemoteOpen open
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept

View file

@ -0,0 +1,43 @@
Person
Project
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketProjectLocal
ticket TicketId
context TicketContextLocalId
project ProjectId
UniqueTicketProjectLocal context
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept

View file

@ -0,0 +1,44 @@
Person
Repo
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketRepoLocal
ticket TicketId
context TicketContextLocalId
repo RepoId
branch Text Maybe
UniqueTicketRepoLocal context
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept

View file

@ -0,0 +1,48 @@
Person
RemoteObject
RemoteActivity
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
RemoteTicketDependency
ident RemoteObjectId
child LocalTicketId
childNew TicketId
accept RemoteActivityId
UniqueRemoteTicketDependency ident
UniqueRemoteTicketDependencyAccept accept
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,45 @@
Person
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
LocalTicketDependency
parent LocalTicketId
parentNew TicketId
created UTCTime
accept OutboxItemId
UniqueLocalTicketDependencyAccept accept
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,45 @@
Person
LocalTicketDependency
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketDependencyChildLocal
dep LocalTicketDependencyId
child LocalTicketId
childNew TicketId
UniqueTicketDependencyChildLocal dep
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,45 @@
Person
Discussion
FollowerSet
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketResolve
ticket LocalTicketId
ticketNew TicketId
accept OutboxItemId
UniqueTicketResolve ticket
UniqueTicketResolveAccept accept
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers

View file

@ -0,0 +1,41 @@
Outbox
Inbox
FollowerSet
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
about Text
actor ActorId
inbox InboxId
outbox OutboxId
followers FollowerSetId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonInbox inbox
UniquePersonOutbox outbox
UniquePersonFollowers followers

View file

@ -0,0 +1,30 @@
Inbox
Outbox
FollowerSet
Group
ident SharerId
actor ActorId
UniqueGroup ident
Sharer
ident ShrIdent
name Text Maybe
created UTCTime
UniqueSharer ident
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers

View file

@ -0,0 +1,40 @@
Deck
Role
Sharer
Inbox
Outbox
FollowerSet
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Repo
vcs VersionControlSystem
project DeckId Maybe
mainBranch Text
collabUser RoleId Maybe
collabAnon RoleId Maybe
actor ActorId
desc Text Maybe
ident RpIdent
inbox InboxId
outbox OutboxId
followers FollowerSetId
sharer SharerId
UniqueRepo ident sharer
UniqueRepoInbox inbox
UniqueRepoOutbox outbox
UniqueRepoFollowers followers

View file

@ -0,0 +1,9 @@
Loom
nextTicket Int
actor ActorId
repo RepoId
create OutboxItemId
UniqueLoomActor actor
UniqueLoomRepo repo
UniqueLoomCreate create

View file

@ -0,0 +1,5 @@
TicketAssignee
ticket TicketId
person PersonId
UniqueTicketAssignee ticket person

View file

@ -0,0 +1,129 @@
Repo
OutboxItem
Person
RemoteActor
RemoteObject
RemoteActivity
TicketRepoLocal
ticket TicketId
repo RepoId
branch Text Maybe
UniqueTicketRepoLocal ticket
Ticket
number Int Maybe
created UTCTime
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
status TicketStatus
discuss DiscussionId
followers FollowerSetId
accept OutboxItemId
-- UniqueTicket project number
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
Message
created UTCTime
source Text -- Pandoc Markdown
content Text -- HTML
parent MessageId Maybe
root DiscussionId
LocalMessage
author PersonId
rest MessageId
create OutboxItemId
unlinkedParent FedURI Maybe
UniqueLocalMessage rest
UniqueLocalMessageCreate create
RemoteMessage
author RemoteActorId
ident RemoteObjectId
rest MessageId
create RemoteActivityId
lostParent FedURI Maybe
UniqueRemoteMessageIdent ident
UniqueRemoteMessage rest
UniqueRemoteMessageCreate create
Bundle
ticket TicketId
Patch
bundle BundleId
created UTCTime
type PatchMediaType
content Text
TicketResolve
ticket TicketId
accept OutboxItemId
UniqueTicketResolve ticket
UniqueTicketResolveAccept accept
TicketResolveLocal
ticket TicketResolveId
activity OutboxItemId
UniqueTicketResolveLocal ticket
UniqueTicketResolveLocalActivity activity
TicketResolveRemote
ticket TicketResolveId
activity RemoteActivityId
actor RemoteActorId
UniqueTicketResolveRemote ticket
UniqueTicketResolveRemoteActivity activity
Discussion
FollowerSet
Follow
person PersonId
target FollowerSetId
public Bool
follow OutboxItemId
accept OutboxItemId
UniqueFollow person target
UniqueFollowFollow follow
UniqueFollowAccept accept
RemoteFollow
actor RemoteActorId
target FollowerSetId
public Bool
follow RemoteActivityId
accept OutboxItemId
UniqueRemoteFollow actor target
UniqueRemoteFollowFollow follow
UniqueRemoteFollowAccept accept
TicketAuthorLocal
ticket TicketId
author PersonId
open OutboxItemId
UniqueTicketAuthorLocal ticket
UniqueTicketAuthorLocalOpen open
TicketAuthorRemote
ticket TicketId
author RemoteActorId
open RemoteActivityId
UniqueTicketAuthorRemote ticket
UniqueTicketAuthorRemoteOpen open

View file

@ -0,0 +1,36 @@
Deck
Role
OutboxItem
Outbox
Inbox
FollowerSet
Repo
vcs VersionControlSystem
project DeckId Maybe
mainBranch Text
collabUser RoleId Maybe
collabAnon RoleId Maybe
actor ActorId
sharer SharerId
UniqueRepoActor actor
Sharer
ident ShrIdent
name Text Maybe
created UTCTime
UniqueSharer ident
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers

View file

@ -0,0 +1,17 @@
ForwarderPerson
task ForwardingId
sender PersonId
UniqueForwarderPerson task
ForwarderGroup
task ForwardingId
sender GroupId
UniqueForwarderGroup task
ForwarderLoom
task ForwardingId
sender LoomId
UniqueForwarderLoom task

View file

@ -0,0 +1,5 @@
CollabTopicLocalLoom
collab CollabId
loom LoomId
UniqueCollabTopicLocalLoom collab

View file

@ -0,0 +1,52 @@
Deck
Role
Inbox
FollowerSet
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Outbox
OutboxItem
outbox OutboxId
activity PersistJSONObject
published UTCTime
Repo
vcs VersionControlSystem
project DeckId Maybe
mainBranch Text
collabUser RoleId Maybe
collabAnon RoleId Maybe
actor ActorId
create OutboxItemId
UniqueRepoActor actor
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
actor ActorId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonActor actor

View file

@ -0,0 +1,52 @@
RemoteActor
OutboxItem
RemoteActivity
Inbox
Outbox
FollowerSet
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
FollowRemote
person PersonId
actor ActorId
recip RemoteActorId -- actor managing the followed object
target FedURI -- the followed object
public Bool
follow OutboxItemId
accept RemoteActivityId
UniqueFollowRemote person target
UniqueFollowRemoteFollow follow
UniqueFollowRemoteAccept accept
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
actor ActorId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonActor actor

View file

@ -0,0 +1,49 @@
OutboxItem
Inbox
Outbox
FollowerSet
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Follow
person PersonId
actor ActorId -- TODO CONTINUE write a migration adding this field
target FollowerSetId
public Bool
follow OutboxItemId
accept OutboxItemId
UniqueFollow person target
UniqueFollowFollow follow
UniqueFollowAccept accept
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
actor ActorId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonActor actor

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -34,17 +34,17 @@ import qualified Data.ByteString as B
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
writeDefaultsFile :: FilePath -> FilePath -> Text -> Text -> Text -> IO ()
writeDefaultsFile path cmd authority sharer repo = do
writeDefaultsFile :: FilePath -> FilePath -> Text -> Text -> IO ()
writeDefaultsFile path cmd authority repo = do
let file = path </> "_darcs" </> "prefs" </> "defaults"
TIO.writeFile file $ defaultsContent cmd authority sharer repo
TIO.writeFile file $ defaultsContent cmd authority repo
setFileMode file $ ownerReadMode .|. ownerWriteMode
where
defaultsContent :: FilePath -> Text -> Text -> Text -> Text
defaultsContent hook authority sharer repo =
defaultsContent :: FilePath -> Text -> Text -> Text
defaultsContent hook authority repo =
T.concat
[ "apply posthook "
, T.pack hook, " ", authority, " ", sharer, " ", repo
, T.pack hook, " ", authority, " ", repo
]
{-
@ -80,18 +80,16 @@ createRepo
-> Text
-- ^ Instance HTTP authority
-> Text
-- ^ Repo sharer textual ID
-> Text
-- ^ Repo textual ID
-- ^ Repo key hashid
-> IO ()
createRepo parent name cmd authority sharer repo = do
createRepo parent name cmd authority repo = do
let path = parent </> name
createDirectory path
let settings = proc "darcs" ["init", "--no-working-dir", "--repodir", path]
(_, _, _, ph) <- createProcess settings
ec <- waitForProcess ph
case ec of
ExitSuccess -> writeDefaultsFile path cmd authority sharer repo
ExitSuccess -> writeDefaultsFile path cmd authority repo
ExitFailure n -> error $ "darcs init failed with exit code " ++ show n
readPristineRoot :: FilePath -> IO (Maybe Int, Hash)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -54,21 +54,21 @@ instance SpecToEventTime GitTime where
specToEventTime = specToEventTime . gitTimeUTC
specsToEventTimes = specsToEventTimes . fmap gitTimeUTC
hookContent :: FilePath -> Text -> Text -> Text -> Text
hookContent hook authority sharer repo =
hookContent :: FilePath -> Text -> Text -> Text
hookContent hook authority repo =
T.concat
[ "#!/bin/sh\nexec ", T.pack hook
, " ", authority, " ", sharer, " ", repo
, " ", authority, " ", repo
]
writeHookFile :: FilePath -> FilePath -> Text -> Text -> Text -> IO ()
writeHookFile path cmd authority sharer repo = do
writeHookFile :: FilePath -> FilePath -> Text -> Text -> IO ()
writeHookFile path cmd authority repo = do
let file = path </> "hooks" </> "post-receive"
TIO.writeFile file $ hookContent cmd authority sharer repo
TIO.writeFile file $ hookContent cmd authority repo
setFileMode file ownerModes
initialRepoTree :: FilePath -> Text -> Text -> Text -> FileName -> DirTree Text
initialRepoTree hook authority sharer repo dir =
initialRepoTree :: FilePath -> Text -> Text -> FileName -> DirTree Text
initialRepoTree hook authority repo dir =
Dir dir
[ Dir "branches" []
, File "config"
@ -80,7 +80,7 @@ initialRepoTree hook authority sharer repo dir =
"Unnamed repository; edit this file to name the repository."
, File "HEAD" "ref: refs/heads/master"
, Dir "hooks"
[ File "post-receive" $ hookContent hook authority sharer repo
[ File "post-receive" $ hookContent hook authority repo
]
, Dir "info"
[ File "exclude" ""
@ -110,12 +110,10 @@ createRepo
-> Text
-- ^ Instance HTTP authority
-> Text
-- ^ Repo sharer textual ID
-> Text
-- ^ Repo textual ID
-- ^ Repo hashid
-> IO ()
createRepo path name cmd authority sharer repo = do
let tree = path :/ initialRepoTree cmd authority sharer repo name
createRepo path name cmd authority repo = do
let tree = path :/ initialRepoTree cmd authority repo name
result <- writeDirectoryWith TIO.writeFile tree
let errs = failures $ dirTree result
when (not . null $ errs) $

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -21,6 +21,7 @@ module Database.Persist.Local
, insertUnique_
, insertBy'
, insertByEntity'
, getE
)
where
@ -28,6 +29,8 @@ import Control.Applicative
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.Bifunctor
@ -95,3 +98,15 @@ insertByEntity'
)
=> record -> ReaderT backend m (Either (Entity record) (Entity record))
insertByEntity' val = second (flip Entity val) <$> insertBy' val
getE
:: ( PersistStoreRead backend
, MonadIO m
, PersistRecordBackend record backend
)
=> Key record -> e -> ExceptT e (ReaderT backend m) record
getE key msg = do
mval <- lift $ get key
case mval of
Nothing -> throwE msg
Just val -> return val

File diff suppressed because it is too large Load diff

View file

@ -54,6 +54,7 @@
-- operations.
module Vervis.Access
( ObjectAccessStatus (..)
, checkRepoAccess'
, checkRepoAccess
, checkProjectAccess
)
@ -64,12 +65,15 @@ import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.Maybe
import Database.Persist.Class (getBy)
import Database.Persist.Class
import Database.Persist.Sql (SqlBackend)
import Database.Persist.Types (Entity (..))
import qualified Database.Esqueleto as E
import Yesod.Hashids
import Yesod.MonadSite
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Model.Role
@ -114,17 +118,53 @@ status :: Bool -> ObjectAccessStatus
status True = ObjectAccessAllowed
status False = ObjectAccessDenied
checkRepoAccess
checkRepoAccess'
:: MonadIO m
=> Maybe PersonId
-> ProjectOperation
-> ShrIdent
-> RpIdent
-> RepoId
-> ReaderT SqlBackend m ObjectAccessStatus
checkRepoAccess mpid op shr rp = do
checkRepoAccess' mpid op repoID = do
mer <- runMaybeT $ do
Entity sid _sharer <- MaybeT $ getBy $ UniqueSharer shr
MaybeT $ getBy $ UniqueRepo rp sid
repo <- MaybeT $ get repoID
return $ Entity repoID repo
case mer of
Nothing -> return NoSuchObject
Just (Entity rid repo) -> do
role <- do
case mpid of
Just pid ->
fromMaybe User . (<|> asUser repo) <$> asCollab rid pid
Nothing -> pure $ fromMaybe Guest $ asAnon repo
status <$> roleHasAccess role op
where
asCollab rid pid = do
fmap (maybe Developer RoleID . E.unValue . snd) . listToMaybe <$> do
E.select $ E.from $ \ (topic `E.InnerJoin` recip `E.LeftOuterJoin` role) -> do
E.on $ E.just (topic E.^. CollabTopicLocalRepoCollab) E.==. role E.?. CollabRoleLocalCollab
E.on $ topic E.^. CollabTopicLocalRepoCollab E.==. recip E.^. CollabRecipLocalCollab
E.where_ $
topic E.^. CollabTopicLocalRepoRepo E.==. E.val rid E.&&.
recip E.^. CollabRecipLocalPerson E.==. E.val pid
E.limit 1
return
( topic E.^. CollabTopicLocalRepoCollab
, role E.?. CollabRoleLocalRole
)
asUser = fmap RoleID . repoCollabUser
asAnon = fmap RoleID . repoCollabAnon
checkRepoAccess
:: (MonadIO m, MonadSite m, YesodHashids (SiteEnv m))
=> Maybe PersonId
-> ProjectOperation
-> KeyHashid Repo
-> ReaderT SqlBackend m ObjectAccessStatus
checkRepoAccess mpid op repoHash = do
mer <- runMaybeT $ do
repoID <- decodeKeyHashidM repoHash
repo <- MaybeT $ get repoID
return $ Entity repoID repo
case mer of
Nothing -> return NoSuchObject
Just (Entity rid repo) -> do
@ -152,16 +192,16 @@ checkRepoAccess mpid op shr rp = do
asAnon = fmap RoleID . repoCollabAnon
checkProjectAccess
:: MonadIO m
:: (MonadIO m, MonadSite m, YesodHashids (SiteEnv m))
=> Maybe PersonId
-> ProjectOperation
-> ShrIdent
-> PrjIdent
-> KeyHashid Deck
-> ReaderT SqlBackend m ObjectAccessStatus
checkProjectAccess mpid op shr prj = do
checkProjectAccess mpid op deckHash = do
mej <- runMaybeT $ do
Entity sid _sharer <- MaybeT $ getBy $ UniqueSharer shr
MaybeT $ getBy $ UniqueProject prj sid
deckID <- decodeKeyHashidM deckHash
deck <- MaybeT $ get deckID
return $ Entity deckID deck
case mej of
Nothing -> return NoSuchObject
Just (Entity jid project) -> do
@ -176,15 +216,15 @@ checkProjectAccess mpid op shr prj = do
asCollab jid pid = do
fmap (maybe Developer RoleID . E.unValue . snd) . listToMaybe <$> do
E.select $ E.from $ \ (topic `E.InnerJoin` recip `E.LeftOuterJoin` role) -> do
E.on $ E.just (topic E.^. CollabTopicLocalProjectCollab) E.==. role E.?. CollabRoleLocalCollab
E.on $ topic E.^. CollabTopicLocalProjectCollab E.==. recip E.^. CollabRecipLocalCollab
E.on $ E.just (topic E.^. CollabTopicLocalDeckCollab) E.==. role E.?. CollabRoleLocalCollab
E.on $ topic E.^. CollabTopicLocalDeckCollab E.==. recip E.^. CollabRecipLocalCollab
E.where_ $
topic E.^. CollabTopicLocalProjectProject E.==. E.val jid E.&&.
topic E.^. CollabTopicLocalDeckDeck E.==. E.val jid E.&&.
recip E.^. CollabRecipLocalPerson E.==. E.val pid
E.limit 1
return
( topic E.^. CollabTopicLocalProjectCollab
( topic E.^. CollabTopicLocalDeckCollab
, role E.?. CollabRoleLocalRole
)
asUser = fmap RoleID . projectCollabUser
asAnon = fmap RoleID . projectCollabAnon
asUser = fmap RoleID . deckCollabUser
asAnon = fmap RoleID . deckCollabAnon

File diff suppressed because it is too large Load diff

View file

@ -1,654 +0,0 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020 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.ActivityPub.Recipient
( LocalActor (..)
, LocalPersonCollection (..)
, LocalTicketDirectSet (..)
, LocalPatchDirectSet (..)
, LocalProjectDirectSet (..)
, LocalProjectRelatedSet (..)
, LocalRepoDirectSet (..)
, LocalRepoRelatedSet (..)
, LocalSharerDirectSet (..)
, LocalSharerRelatedSet (..)
, LocalRecipientSet
, concatRecipients
, parseLocalActor
, renderLocalActor
, renderLocalPersonCollection
, makeRecipientSet
, ParsedAudience (..)
, parseAudience
, actorRecips
, localRecipSieve
, localRecipSieve'
, Aud (..)
, collectAudience
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Except
import Data.Bifunctor
import Data.Either
import Data.Foldable
import Data.List ((\\))
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.Maybe
import Data.Semigroup
import Data.Text (Text)
import Data.These
import Data.Traversable
import qualified Data.List.NonEmpty as NE
import qualified Data.List.Ordered as LO
import qualified Data.Text as T
import Network.FedURI
import Web.ActivityPub hiding (Ticket)
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Data.List.Local
import Data.List.NonEmpty.Local
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
concatRecipients :: Audience u -> [ObjURI u]
concatRecipients (Audience to bto cc bcc gen _) =
concat [to, bto, cc, bcc, gen]
-------------------------------------------------------------------------------
-- Actor and collection-of-persons types
--
-- These are the 2 kinds of local recipients. This is the starting point for
-- grouping and checking recipient lists: First parse recipient URIs into these
-- types, then you can do any further parsing and grouping.
-------------------------------------------------------------------------------
data LocalActor
= LocalActorSharer ShrIdent
| LocalActorProject ShrIdent PrjIdent
| LocalActorRepo ShrIdent RpIdent
deriving (Eq, Ord)
parseLocalActor :: Route App -> Maybe LocalActor
parseLocalActor (SharerR shr) = Just $ LocalActorSharer shr
parseLocalActor (ProjectR shr prj) = Just $ LocalActorProject shr prj
parseLocalActor (RepoR shr rp) = Just $ LocalActorRepo shr rp
parseLocalActor _ = Nothing
renderLocalActor :: LocalActor -> Route App
renderLocalActor (LocalActorSharer shr) = SharerR shr
renderLocalActor (LocalActorProject shr prj) = ProjectR shr prj
renderLocalActor (LocalActorRepo shr rp) = RepoR shr rp
data LocalPersonCollection
= LocalPersonCollectionSharerFollowers ShrIdent
| LocalPersonCollectionSharerTicketTeam ShrIdent (KeyHashid TicketAuthorLocal)
| LocalPersonCollectionSharerTicketFollowers ShrIdent (KeyHashid TicketAuthorLocal)
| LocalPersonCollectionSharerProposalFollowers ShrIdent (KeyHashid TicketAuthorLocal)
| LocalPersonCollectionProjectTeam ShrIdent PrjIdent
| LocalPersonCollectionProjectFollowers ShrIdent PrjIdent
| LocalPersonCollectionProjectTicketTeam ShrIdent PrjIdent (KeyHashid LocalTicket)
| LocalPersonCollectionProjectTicketFollowers ShrIdent PrjIdent (KeyHashid LocalTicket)
| LocalPersonCollectionRepoTeam ShrIdent RpIdent
| LocalPersonCollectionRepoFollowers ShrIdent RpIdent
| LocalPersonCollectionRepoProposalFollowers ShrIdent RpIdent (KeyHashid LocalTicket)
deriving (Eq, Ord)
parseLocalPersonCollection
:: Route App -> Maybe LocalPersonCollection
parseLocalPersonCollection (SharerFollowersR shr) =
Just $ LocalPersonCollectionSharerFollowers shr
parseLocalPersonCollection (SharerTicketTeamR shr talkhid) =
Just $ LocalPersonCollectionSharerTicketTeam shr talkhid
parseLocalPersonCollection (SharerTicketFollowersR shr talkhid) =
Just $ LocalPersonCollectionSharerTicketFollowers shr talkhid
parseLocalPersonCollection (SharerProposalFollowersR shr talkhid) =
Just $ LocalPersonCollectionSharerProposalFollowers shr talkhid
parseLocalPersonCollection (ProjectTeamR shr prj) =
Just $ LocalPersonCollectionProjectTeam shr prj
parseLocalPersonCollection (ProjectFollowersR shr prj) =
Just $ LocalPersonCollectionProjectFollowers shr prj
parseLocalPersonCollection (ProjectTicketTeamR shr prj num) =
Just $ LocalPersonCollectionProjectTicketTeam shr prj num
parseLocalPersonCollection (ProjectTicketParticipantsR shr prj num) =
Just $ LocalPersonCollectionProjectTicketFollowers shr prj num
parseLocalPersonCollection (RepoTeamR shr rp) =
Just $ LocalPersonCollectionRepoTeam shr rp
parseLocalPersonCollection (RepoFollowersR shr rp) =
Just $ LocalPersonCollectionRepoFollowers shr rp
parseLocalPersonCollection (RepoProposalFollowersR shr rp ltkhid) =
Just $ LocalPersonCollectionRepoProposalFollowers shr rp ltkhid
parseLocalPersonCollection _ = Nothing
renderLocalPersonCollection :: LocalPersonCollection -> Route App
renderLocalPersonCollection (LocalPersonCollectionSharerFollowers shr) = SharerFollowersR shr
renderLocalPersonCollection (LocalPersonCollectionSharerTicketTeam shr talkhid) = SharerTicketTeamR shr talkhid
renderLocalPersonCollection (LocalPersonCollectionSharerTicketFollowers shr talkhid) = SharerTicketFollowersR shr talkhid
renderLocalPersonCollection (LocalPersonCollectionSharerProposalFollowers shr talkhid) = SharerProposalFollowersR shr talkhid
renderLocalPersonCollection (LocalPersonCollectionProjectTeam shr prj) = ProjectTeamR shr prj
renderLocalPersonCollection (LocalPersonCollectionProjectFollowers shr prj) = ProjectFollowersR shr prj
renderLocalPersonCollection (LocalPersonCollectionProjectTicketTeam shr prj ltkhid) = ProjectTicketTeamR shr prj ltkhid
renderLocalPersonCollection (LocalPersonCollectionProjectTicketFollowers shr prj ltkhid) = ProjectTicketParticipantsR shr prj ltkhid
renderLocalPersonCollection (LocalPersonCollectionRepoTeam shr rp) = RepoTeamR shr rp
renderLocalPersonCollection (LocalPersonCollectionRepoFollowers shr rp) = RepoFollowersR shr rp
renderLocalPersonCollection (LocalPersonCollectionRepoProposalFollowers shr rp ltkhid) = RepoProposalFollowersR shr rp ltkhid
parseLocalRecipient
:: Route App -> Maybe (Either LocalActor LocalPersonCollection)
parseLocalRecipient r =
Left <$> parseLocalActor r <|> Right <$> parseLocalPersonCollection r
-------------------------------------------------------------------------------
-- Intermediate recipient types
--
-- These are here just to help with grouping recipients. From this
-- representation it's easy to group recipients into a form that is friendly to
-- the code that fetches the actual recipients from the DB.
-------------------------------------------------------------------------------
data LocalTicketRecipientDirect = LocalTicketTeam | LocalTicketFollowerz
deriving (Eq, Ord)
data LocalPatchRecipientDirect = LocalPatchFollowers deriving (Eq, Ord)
data LocalProjectRecipientDirect
= LocalProject
| LocalProjectTeam
| LocalProjectFollowers
deriving (Eq, Ord)
data LocalProjectRecipient
= LocalProjectDirect LocalProjectRecipientDirect
| LocalProjectTicketRelated (KeyHashid LocalTicket) LocalTicketRecipientDirect
deriving (Eq, Ord)
data LocalRepoRecipientDirect
= LocalRepo
| LocalRepoTeam
| LocalRepoFollowers
deriving (Eq, Ord)
data LocalRepoRecipient
= LocalRepoDirect LocalRepoRecipientDirect
| LocalRepoProposalRelated (KeyHashid LocalTicket) LocalPatchRecipientDirect
deriving (Eq, Ord)
data LocalSharerRecipientDirect
= LocalSharer
| LocalSharerFollowers
deriving (Eq, Ord)
data LocalSharerRecipient
= LocalSharerDirect LocalSharerRecipientDirect
| LocalSharerTicketRelated (KeyHashid TicketAuthorLocal) LocalTicketRecipientDirect
| LocalSharerProposalRelated (KeyHashid TicketAuthorLocal) LocalPatchRecipientDirect
| LocalProjectRelated PrjIdent LocalProjectRecipient
| LocalRepoRelated RpIdent LocalRepoRecipient
deriving (Eq, Ord)
data LocalGroupedRecipient = LocalSharerRelated ShrIdent LocalSharerRecipient
deriving (Eq, Ord)
groupedRecipientFromActor :: LocalActor -> LocalGroupedRecipient
groupedRecipientFromActor (LocalActorSharer shr) =
LocalSharerRelated shr $ LocalSharerDirect LocalSharer
groupedRecipientFromActor (LocalActorProject shr prj) =
LocalSharerRelated shr $ LocalProjectRelated prj $
LocalProjectDirect LocalProject
groupedRecipientFromActor (LocalActorRepo shr rp) =
LocalSharerRelated shr $ LocalRepoRelated rp $ LocalRepoDirect LocalRepo
groupedRecipientFromCollection
:: LocalPersonCollection -> LocalGroupedRecipient
groupedRecipientFromCollection
(LocalPersonCollectionSharerFollowers shr) =
LocalSharerRelated shr $ LocalSharerDirect LocalSharerFollowers
groupedRecipientFromCollection
(LocalPersonCollectionSharerTicketTeam shr talkhid) =
LocalSharerRelated shr $
LocalSharerTicketRelated talkhid LocalTicketTeam
groupedRecipientFromCollection
(LocalPersonCollectionSharerTicketFollowers shr talkhid) =
LocalSharerRelated shr $
LocalSharerTicketRelated talkhid LocalTicketFollowerz
groupedRecipientFromCollection
(LocalPersonCollectionSharerProposalFollowers shr talkhid) =
LocalSharerRelated shr $
LocalSharerProposalRelated talkhid LocalPatchFollowers
groupedRecipientFromCollection
(LocalPersonCollectionProjectTeam shr prj) =
LocalSharerRelated shr $ LocalProjectRelated prj $
LocalProjectDirect LocalProjectTeam
groupedRecipientFromCollection
(LocalPersonCollectionProjectFollowers shr prj) =
LocalSharerRelated shr $ LocalProjectRelated prj $
LocalProjectDirect LocalProjectFollowers
groupedRecipientFromCollection
(LocalPersonCollectionProjectTicketTeam shr prj num) =
LocalSharerRelated shr $ LocalProjectRelated prj $
LocalProjectTicketRelated num LocalTicketTeam
groupedRecipientFromCollection
(LocalPersonCollectionProjectTicketFollowers shr prj num) =
LocalSharerRelated shr $ LocalProjectRelated prj $
LocalProjectTicketRelated num LocalTicketFollowerz
groupedRecipientFromCollection
(LocalPersonCollectionRepoTeam shr rp) =
LocalSharerRelated shr $ LocalRepoRelated rp $
LocalRepoDirect LocalRepoTeam
groupedRecipientFromCollection
(LocalPersonCollectionRepoFollowers shr rp) =
LocalSharerRelated shr $ LocalRepoRelated rp $
LocalRepoDirect LocalRepoFollowers
groupedRecipientFromCollection
(LocalPersonCollectionRepoProposalFollowers shr rp ltkhid) =
LocalSharerRelated shr $ LocalRepoRelated rp $
LocalRepoProposalRelated ltkhid LocalPatchFollowers
-------------------------------------------------------------------------------
-- Recipient set types
--
-- These types represent a set of recipients grouped by the variable components
-- of their routes. It's convenient to use when looking for the recipients in
-- the DB, and easy to manipulate and check the recipient list in terms of app
-- logic rather than plain lists of routes.
-------------------------------------------------------------------------------
data LocalTicketDirectSet = LocalTicketDirectSet
{ localRecipTicketTeam :: Bool
, localRecipTicketFollowers :: Bool
}
deriving Eq
newtype LocalPatchDirectSet = LocalPatchDirectSet
{ localRecipPatchFollowers :: Bool
}
deriving Eq
data LocalProjectDirectSet = LocalProjectDirectSet
{ localRecipProject :: Bool
, localRecipProjectTeam :: Bool
, localRecipProjectFollowers :: Bool
}
deriving Eq
data LocalProjectRelatedSet = LocalProjectRelatedSet
{ localRecipProjectDirect
:: LocalProjectDirectSet
, localRecipProjectTicketRelated
:: [(KeyHashid LocalTicket, LocalTicketDirectSet)]
}
deriving Eq
data LocalRepoDirectSet = LocalRepoDirectSet
{ localRecipRepo :: Bool
, localRecipRepoTeam :: Bool
, localRecipRepoFollowers :: Bool
}
deriving Eq
data LocalRepoRelatedSet = LocalRepoRelatedSet
{ localRecipRepoDirect
:: LocalRepoDirectSet
, localRecipRepoProposalRelated
:: [(KeyHashid LocalTicket, LocalPatchDirectSet)]
}
deriving Eq
data LocalSharerDirectSet = LocalSharerDirectSet
{ localRecipSharer :: Bool
, localRecipSharerFollowers :: Bool
}
deriving Eq
data LocalSharerRelatedSet = LocalSharerRelatedSet
{ localRecipSharerDirect
:: LocalSharerDirectSet
, localRecipSharerTicketRelated
:: [(KeyHashid TicketAuthorLocal, LocalTicketDirectSet)]
, localRecipSharerProposalRelated
:: [(KeyHashid TicketAuthorLocal, LocalPatchDirectSet)]
, localRecipProjectRelated
:: [(PrjIdent, LocalProjectRelatedSet)]
, localRecipRepoRelated
:: [(RpIdent, LocalRepoRelatedSet)]
}
deriving Eq
type LocalRecipientSet = [(ShrIdent, LocalSharerRelatedSet)]
groupLocalRecipients :: [LocalGroupedRecipient] -> LocalRecipientSet
groupLocalRecipients
= map (second lsr2set)
. groupAllExtract
(\ (LocalSharerRelated shr _) -> shr)
(\ (LocalSharerRelated _ lsr) -> lsr)
where
lsr2set = mk . partitionLSR . NE.toList
where
partitionLSR = foldr f ([], [], [], [], [])
where
f i (ds, ts, ps, js, rs) =
case i of
LocalSharerDirect d ->
(d:ds, ts, ps, js, rs)
LocalSharerTicketRelated talkhid ltr ->
(ds, (talkhid, ltr):ts, ps, js, rs)
LocalSharerProposalRelated talkhid lpr ->
(ds, ts, (talkhid, lpr):ps, js, rs)
LocalProjectRelated prj ljr ->
(ds, ts, ps, (prj, ljr):js, rs)
LocalRepoRelated rp lrr ->
(ds, ts, ps, js, (rp, lrr):rs)
mk (ds, ts, ps, js, rs) =
LocalSharerRelatedSet
(lsrs2set ds)
(map (second ltrs2set) $ groupWithExtract fst snd ts)
(map (second lprs2set) $ groupWithExtract fst snd ps)
(map (second ljr2set) $ groupWithExtract fst snd js)
(map (second lrr2set) $ groupWithExtract fst snd rs)
where
lsrs2set = foldl' f initial
where
initial = LocalSharerDirectSet False False
f s LocalSharer =
s { localRecipSharer = True }
f s LocalSharerFollowers =
s { localRecipSharerFollowers = True }
ltrs2set = foldl' f initial
where
initial = LocalTicketDirectSet False False
f s LocalTicketTeam =
s { localRecipTicketTeam = True }
f s LocalTicketFollowerz =
s { localRecipTicketFollowers = True }
lprs2set = foldl' f initial
where
initial = LocalPatchDirectSet False
f s LocalPatchFollowers = s { localRecipPatchFollowers = True }
ljr2set = uncurry mk . partitionEithers . map ljr2e . NE.toList
where
ljr2e (LocalProjectDirect d) = Left d
ljr2e (LocalProjectTicketRelated num ltrs) = Right (num, ltrs)
mk ds ts =
LocalProjectRelatedSet
(ljrs2set ds)
(map (second ltrs2set) $ groupWithExtract fst snd ts)
where
ljrs2set = foldl' f initial
where
initial = LocalProjectDirectSet False False False
f s LocalProject =
s { localRecipProject = True }
f s LocalProjectTeam =
s { localRecipProjectTeam = True }
f s LocalProjectFollowers =
s { localRecipProjectFollowers = True }
lrr2set = uncurry mk . partitionEithers . map lrr2e . NE.toList
where
lrr2e (LocalRepoDirect d) = Left d
lrr2e (LocalRepoProposalRelated num ltrs) = Right (num, ltrs)
mk ds ps =
LocalRepoRelatedSet
(lrrs2set ds)
(map (second lprs2set) $ groupWithExtract fst snd ps)
where
lrrs2set = foldl' f initial
where
initial = LocalRepoDirectSet False False False
f s LocalRepo = s { localRecipRepo = True }
f s LocalRepoTeam = s { localRecipRepoTeam = True }
f s LocalRepoFollowers = s { localRecipRepoFollowers = True }
-------------------------------------------------------------------------------
-- Parse URIs into a grouped recipient set
-------------------------------------------------------------------------------
makeRecipientSet
:: [LocalActor] -> [LocalPersonCollection] -> LocalRecipientSet
makeRecipientSet actors collections =
groupLocalRecipients $
map groupedRecipientFromActor actors ++
map groupedRecipientFromCollection collections
parseRecipients
:: (MonadSite m, SiteEnv m ~ App)
=> NonEmpty FedURI
-> ExceptT Text m (LocalRecipientSet, [FedURI])
parseRecipients recips = do
hLocal <- asksSite siteInstanceHost
let (locals, remotes) = splitRecipients hLocal recips
(lusInvalid, routesInvalid, localsSet) = parseLocalRecipients locals
unless (null lusInvalid) $
throwE $
"Local recipients are invalid routes: " <>
T.pack (show $ map (renderObjURI . ObjURI hLocal) lusInvalid)
unless (null routesInvalid) $ do
renderUrl <- askUrlRender
throwE $
"Local recipients are non-recipient routes: " <>
T.pack (show $ map renderUrl routesInvalid)
return (localsSet, remotes)
where
splitRecipients :: Host -> NonEmpty FedURI -> ([LocalURI], [FedURI])
splitRecipients home recips =
let (local, remote) = NE.partition ((== home) . objUriAuthority) recips
in (map objUriLocal local, remote)
parseLocalRecipients
:: [LocalURI] -> ([LocalURI], [Route App], LocalRecipientSet)
parseLocalRecipients lus =
let (lusInvalid, routes) = partitionEithers $ map parseRoute lus
(routesInvalid, recips) = partitionEithers $ map parseRecip routes
(actors, collections) = partitionEithers recips
grouped =
map groupedRecipientFromActor actors ++
map groupedRecipientFromCollection collections
in (lusInvalid, routesInvalid, groupLocalRecipients grouped)
where
parseRoute lu =
case decodeRouteLocal lu of
Nothing -> Left lu
Just route -> Right route
parseRecip route =
case parseLocalRecipient route of
Nothing -> Left route
Just recip -> Right recip
data ParsedAudience u = ParsedAudience
{ paudLocalRecips :: LocalRecipientSet
, paudRemoteActors :: [(Authority u, NonEmpty LocalURI)]
, paudBlinded :: Audience u
, paudFwdHosts :: [Authority u]
}
parseAudience
:: (MonadSite m, SiteEnv m ~ App)
=> Audience URIMode
-> ExceptT Text m (Maybe (ParsedAudience URIMode))
parseAudience audience = do
let recips = concatRecipients audience
for (nonEmpty recips) $ \ recipsNE -> do
(localsSet, remotes) <- parseRecipients recipsNE
let remotesGrouped =
groupByHost $ remotes \\ audienceNonActors audience
hosts = map fst remotesGrouped
return ParsedAudience
{ paudLocalRecips = localsSet
, paudRemoteActors = remotesGrouped
, paudBlinded =
audience { audienceBto = [], audienceBcc = [] }
, paudFwdHosts =
let nonActorHosts =
LO.nubSort $
map objUriAuthority $ audienceNonActors audience
in LO.isect hosts nonActorHosts
}
where
groupByHost :: [FedURI] -> [(Host, NonEmpty LocalURI)]
groupByHost = groupAllExtract objUriAuthority objUriLocal
actorIsMember :: LocalActor -> LocalRecipientSet -> Bool
actorIsMember (LocalActorSharer shr) lrSet =
case lookup shr lrSet of
Just lsrSet -> localRecipSharer $ localRecipSharerDirect lsrSet
Nothing -> False
actorIsMember (LocalActorProject shr prj) lrSet = fromMaybe False $ do
lsrSet <- lookup shr lrSet
lprSet <- lookup prj $ localRecipProjectRelated lsrSet
return $ localRecipProject $ localRecipProjectDirect $ lprSet
actorIsMember (LocalActorRepo shr rp) lrSet = fromMaybe False $ do
lsrSet <- lookup shr lrSet
lrrSet <- lookup rp $ localRecipRepoRelated lsrSet
return $ localRecipRepo $ localRecipRepoDirect $ lrrSet
actorRecips :: LocalActor -> LocalRecipientSet
actorRecips = groupLocalRecipients . (: []) . groupedRecipientFromActor
localRecipSieve
:: LocalRecipientSet -> Bool -> LocalRecipientSet -> LocalRecipientSet
localRecipSieve sieve allowActors =
localRecipSieve' sieve allowActors allowActors
localRecipSieve'
:: LocalRecipientSet
-> Bool
-> Bool
-> LocalRecipientSet
-> LocalRecipientSet
localRecipSieve' sieve allowSharers allowOthers =
mapMaybe (uncurry applySharerRelated) . sortAlign sieve
where
onlyActorsJ (LocalProjectRelatedSet (LocalProjectDirectSet j _t _f) _ts) =
LocalProjectRelatedSet (LocalProjectDirectSet (j && allowOthers) False False) []
onlyActorsR (LocalRepoRelatedSet (LocalRepoDirectSet r _t _f) _ps) =
LocalRepoRelatedSet (LocalRepoDirectSet (r && allowOthers) False False) []
onlyActorsS (LocalSharerRelatedSet (LocalSharerDirectSet s _f) _ts _ps js rs) =
LocalSharerRelatedSet
(LocalSharerDirectSet (s && allowSharers) False)
[]
[]
(map (second onlyActorsJ) js)
(map (second onlyActorsR) rs)
applySharerRelated _ (This _) = Nothing
applySharerRelated shr (That s) =
if allowSharers || allowOthers
then Just (shr, onlyActorsS s)
else Nothing
applySharerRelated shr (These (LocalSharerRelatedSet s' t' p' j' r') (LocalSharerRelatedSet s t p j r)) =
Just
( shr
, LocalSharerRelatedSet
(applySharer s' s)
(mapMaybe (uncurry applyTicketRelated) $ sortAlign t' t)
(mapMaybe (uncurry applyPatchRelated) $ sortAlign p' p)
(mapMaybe (uncurry applyProjectRelated) $ sortAlign j' j)
(mapMaybe (uncurry applyRepoRelated) $ sortAlign r' r)
)
where
applySharer (LocalSharerDirectSet s' f') (LocalSharerDirectSet s f) =
LocalSharerDirectSet (s && (s' || allowSharers)) (f && f')
applyTicketRelated ltkhid (These t' t) = Just (ltkhid, applyTicket t' t)
where
applyTicket (LocalTicketDirectSet t' f') (LocalTicketDirectSet t f) =
LocalTicketDirectSet (t && t') (f && f')
applyTicketRelated _ _ = Nothing
applyPatchRelated ltkhid (These p' p) = Just (ltkhid, applyPatch p' p)
where
applyPatch (LocalPatchDirectSet f') (LocalPatchDirectSet f) =
LocalPatchDirectSet $ f && f'
applyPatchRelated _ _ = Nothing
applyProjectRelated _ (This _) = Nothing
applyProjectRelated prj (That j) =
if allowOthers
then Just (prj, onlyActorsJ j)
else Nothing
applyProjectRelated prj (These (LocalProjectRelatedSet j' t') (LocalProjectRelatedSet j t)) =
Just
( prj
, LocalProjectRelatedSet
(applyProject j' j)
(mapMaybe (uncurry applyTicketRelated) $ sortAlign t' t)
)
where
applyProject (LocalProjectDirectSet j' t' f') (LocalProjectDirectSet j t f) =
LocalProjectDirectSet (j && (j' || allowOthers)) (t && t') (f && f')
applyRepoRelated _ (This _) = Nothing
applyRepoRelated rp (That r) =
if allowOthers
then Just (rp, onlyActorsR r)
else Nothing
applyRepoRelated rp (These (LocalRepoRelatedSet r' p') (LocalRepoRelatedSet r p)) =
Just
( rp
, LocalRepoRelatedSet
(applyRepo r' r)
(mapMaybe (uncurry applyPatchRelated) $ sortAlign p' p)
)
where
applyRepo (LocalRepoDirectSet r' t' f') (LocalRepoDirectSet r t f) =
LocalRepoDirectSet (r && (r' || allowOthers)) (t && t') (f && f')
data Aud u
= AudLocal [LocalActor] [LocalPersonCollection]
| AudRemote (Authority u) [LocalURI] [LocalURI]
collectAudience
:: Foldable f
=> f (Aud u)
-> ( LocalRecipientSet
, [(Authority u, NonEmpty LocalURI)]
, [Authority u]
, [Route App]
, [ObjURI u]
)
collectAudience auds =
let (locals, remotes) = partitionAudience auds
(actors, collections) =
let organize = LO.nubSort . concat
in bimap organize organize $ unzip locals
groupedRemotes =
let organize = LO.nubSort . sconcat
in map (second $ bimap organize organize . NE.unzip) $
groupAllExtract fst snd remotes
in ( makeRecipientSet actors collections
, mapMaybe (\ (h, (as, _)) -> (h,) <$> nonEmpty as) groupedRemotes
, [ h | (h, (_, cs)) <- groupedRemotes, not (null cs) ]
, map renderLocalActor actors ++
map renderLocalPersonCollection collections
, concatMap (\ (h, (as, cs)) -> ObjURI h <$> as ++ cs) groupedRemotes
)
where
partitionAudience = foldl' f ([], [])
where
f (ls, rs) (AudLocal as cs) = ((as, cs) : ls, rs)
f (ls, rs) (AudRemote h as cs) = (ls , (h, (as, cs)) : rs)

399
src/Vervis/Actor.hs Normal file
View file

@ -0,0 +1,399 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor
( getInbox
, getOutbox
, getOutboxItem
, getFollowersCollection
, getActorFollowersCollection
, getFollowingCollection
)
where
import Control.Applicative ((<|>))
import Control.Concurrent.STM.TVar (readTVarIO, modifyTVar')
import Control.Exception hiding (Handler)
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger.CallStack
import Control.Monad.STM (atomically)
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Data.Aeson
import Data.Aeson.Encode.Pretty
import Data.Bifunctor
import Data.Bitraversable
import Data.Foldable (for_)
import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.Text.Lazy.Encoding (decodeUtf8)
import Data.Time.Clock
import Data.Time.Interval (TimeInterval, toTimeUnit)
import Data.Time.Units (Second)
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import Network.HTTP.Types.Status
import Text.Blaze.Html (Html, preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text
import Text.HTML.SanitizeXSS
import Text.Shakespeare.I18N (RenderMessage)
import Yesod.Core hiding (logDebug)
import Yesod.Core.Handler
import Yesod.Form.Fields
import Yesod.Form.Functions
import Yesod.Form.Types
import Yesod.Persist.Core
import qualified Data.ByteString.Char8 as BC (unpack)
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL (toStrict)
import qualified Data.Vector as V
import qualified Database.Esqueleto as E
import Database.Persist.JSON
import Network.FedURI
import Web.ActivityPub hiding (Project (..), ActorLocal (..))
import Yesod.ActivityPub
import Yesod.Auth.Unverified
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Yesod.RenderSource
import Data.Aeson.Local
import Data.Either.Local
import Data.EventTime.Local
import Data.Paginate.Local
import Data.Time.Clock.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.API
import Vervis.FedURI
import Vervis.Federation
import Vervis.Federation.Auth
import Vervis.Foundation
import Vervis.Model hiding (Ticket)
import Vervis.Model.Ident
import Vervis.Paginate
import Vervis.Recipient
import Vervis.Settings
import Vervis.Ticket
getShowTime = showTime <$> liftIO getCurrentTime
where
showTime now =
showEventTime .
intervalToEventTime .
FriendlyConvert .
diffUTCTime now
objectSummary o =
case M.lookup "summary" o of
Just (String t) | not (T.null t) -> Just t
_ -> Nothing
objectId o =
case M.lookup "id" o <|> M.lookup "@id" o of
Just (String t) | not (T.null t) -> t
_ -> error "'id' field not found"
getInbox here actor hash = do
key <- decodeKeyHashid404 hash
(total, pages, mpage) <- runDB $ do
inboxID <- do
actorID <- actor <$> get404 key
actorInbox <$> getJust actorID
getPageAndNavCount
(countItems inboxID)
(\ off lim -> map adaptItem <$> getItems inboxID off lim)
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let here' = here hash
pageUrl = encodeRoutePageLocal here'
host <- getsYesod $ appInstanceHost . appSettings
selectRep $
case mpage of
Nothing -> do
provideAP $ pure $ Doc host $ Collection
{ collectionId = encodeRouteLocal here'
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
provideRep (redirectFirstPage here' :: Handler Html)
Just (items, navModel) -> do
let current = nmCurrent navModel
provideAP $ pure $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here'
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems = map fst items
}
provideRep $ do
let pageNav = navWidget navModel
showTime <- getShowTime
defaultLayout $(widgetFile "person/inbox")
where
countItems ibid =
(+) <$> count [InboxItemLocalInbox ==. ibid]
<*> count [InboxItemRemoteInbox ==. ibid]
getItems ibid off lim =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` (ibl `E.InnerJoin` ob) `E.LeftOuterJoin` (ibr `E.InnerJoin` ract)) -> do
E.on $ ibr E.?. InboxItemRemoteActivity E.==. ract E.?. RemoteActivityId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ ibl E.?. InboxItemLocalActivity E.==. ob E.?. OutboxItemId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.orderBy [E.desc $ ib E.^. InboxItemId]
E.offset $ fromIntegral off
E.limit $ fromIntegral lim
return
( ib E.^. InboxItemId
, ob E.?. OutboxItemActivity
, ob E.?. OutboxItemPublished
, ract E.?. RemoteActivityContent
, ract E.?. RemoteActivityReceived
)
adaptItem
(E.Value ibid, E.Value mact, E.Value mpub, E.Value mobj, E.Value mrec) =
case (mact, mpub, mobj, mrec) of
(Nothing, Nothing, Nothing, Nothing) ->
error $ ibiidString ++ " neither local nor remote"
(Just _, Just _, Just _, Just _) ->
error $ ibiidString ++ " both local and remote"
(Just act, Just pub, Nothing, Nothing) ->
(persistJSONObject act, (pub, False))
(Nothing, Nothing, Just obj, Just rec) ->
(persistJSONObject obj, (rec, True))
_ -> error $ "Unexpected query result for " ++ ibiidString
where
ibiidString = "InboxItem #" ++ show (fromSqlKey ibid)
getOutbox here actor hash = do
key <- decodeKeyHashid404 hash
(total, pages, mpage) <- runDB $ do
outboxID <- do
actorID <- actor <$> get404 key
actorOutbox <$> getJust actorID
let countAllItems = count [OutboxItemOutbox ==. outboxID]
selectItems off lim = selectList [OutboxItemOutbox ==. outboxID] [Desc OutboxItemId, OffsetBy off, LimitTo lim]
getPageAndNavCount countAllItems selectItems
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let here' = here hash
pageUrl = encodeRoutePageLocal here'
host <- getsYesod $ appInstanceHost . appSettings
selectRep $
case mpage of
Nothing -> do
provideAP $ pure $ Doc host $ Collection
{ collectionId = encodeRouteLocal here'
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
provideRep (redirectFirstPage here' :: Handler Html)
Just (items, navModel) -> do
let current = nmCurrent navModel
provideAP $ pure $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here'
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems = map (persistJSONObject . outboxItemActivity . entityVal) items
}
provideRep $ do
let pageNav = navWidget navModel
showTime <- getShowTime
defaultLayout $(widgetFile "person/outbox")
getOutboxItem here actor topicHash itemHash = do
topicID <- decodeKeyHashid404 topicHash
itemID <- decodeKeyHashid404 itemHash
body <- runDB $ do
outboxID <- do
actorID <- actor <$> get404 topicID
actorOutbox <$> getJust actorID
item <- get404 itemID
unless (outboxItemOutbox item == outboxID) notFound
return $ outboxItemActivity item
let here' = here topicHash itemHash
provideHtmlAndAP'' body $ redirectToPrettyJSON here'
getLocalActors
:: [ActorId] -> ReaderT SqlBackend Handler [LocalActorBy Key]
getLocalActors actorIDs = do
localActors <-
concat <$> sequenceA
[ map LocalActorPerson <$>
selectKeysList [PersonActor <-. actorIDs] []
, map LocalActorGroup <$>
selectKeysList [GroupActor <-. actorIDs] []
, map LocalActorRepo <$>
selectKeysList [RepoActor <-. actorIDs] []
, map LocalActorDeck <$>
selectKeysList [DeckActor <-. actorIDs] []
, map LocalActorLoom <$>
selectKeysList [LoomActor <-. actorIDs] []
]
case compare (length localActors) (length actorIDs) of
LT -> error "Found actor ID not used by any specific actor"
GT -> error "Found actor ID used by multiple specific actors"
EQ -> return localActors
getFollowersCollection
:: Route App -> AppDB FollowerSetId -> Handler TypedContent
getFollowersCollection here getFsid = do
(locals, remotes, l, r) <- runDB $ do
fsid <- getFsid
(,,,) <$> do actorIDs <-
map (followActor . entityVal) <$>
selectList
[FollowTarget ==. fsid, FollowPublic ==. True]
[]
getLocalActors actorIDs
<*> do E.select $ E.from $ \ (rf `E.InnerJoin` ra `E.InnerJoin` ro `E.InnerJoin` i) -> do
E.on $ ro E.^. RemoteObjectInstance E.==. i E.^. InstanceId
E.on $ ra E.^. RemoteActorIdent E.==. ro E.^. RemoteObjectId
E.on $ rf E.^. RemoteFollowActor E.==. ra E.^. RemoteActorId
E.where_
$ rf E.^. RemoteFollowTarget E.==. E.val fsid
E.&&. rf E.^. RemoteFollowPublic E.==. E.val True
return
( i E.^. InstanceHost
, ro E.^. RemoteObjectIdent
)
<*> count [FollowTarget ==. fsid]
<*> count [RemoteFollowTarget ==. fsid]
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hashActor <- getHashLocalActor
let followersAP = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ l + r
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems =
map (encodeRouteHome . renderLocalActor . hashActor) locals ++
map (uncurry ObjURI . bimap E.unValue E.unValue) remotes
}
provideHtmlAndAP followersAP $ redirectToPrettyJSON here
getActorFollowersCollection here actor hash = do
key <- decodeKeyHashid404 hash
getFollowersCollection (here hash) (getFsid key)
where
getFsid key = do
actorID <- actor <$> get404 key
actorFollowers <$> getJust actorID
getFollowingCollection here actor hash = do
key <- decodeKeyHashid404 hash
(localTotal, localActors, workItems, remotes) <- runDB $ do
followerActorID <- actor <$> get404 key
followerSetIDs <-
map (followTarget . entityVal) <$>
selectList [FollowActor ==. followerActorID] []
actorIDs <- selectKeysList [ActorFollowers <-. followerSetIDs] []
ticketIDs <- selectKeysList [TicketFollowers <-. followerSetIDs] []
(,,,) (length followerSetIDs)
<$> getLocalActors actorIDs
<*> ((++) <$> getTickets ticketIDs <*> getCloths ticketIDs)
<*> getRemotes followerActorID
hashActor <- getHashLocalActor
workItemRoute <- askWorkItemRoute
let locals =
map (renderLocalActor . hashActor) localActors ++
map workItemRoute workItems
unless (length locals == localTotal) $
error "Bug! List length mismatch"
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let here' = here hash
followingAP = Collection
{ collectionId = encodeRouteLocal here'
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ localTotal + length remotes
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems = map encodeRouteHome locals ++ remotes
}
provideHtmlAndAP followingAP $ redirectToPrettyJSON here'
where
getTickets tids =
map workItem <$> selectList [TicketDeckTicket <-. tids] []
where
workItem (Entity t (TicketDeck _ d)) = WorkItemTicket d t
getCloths tids =
map workItem <$> selectList [TicketLoomTicket <-. tids] []
where
workItem (Entity c (TicketLoom _ l _)) = WorkItemCloth l c
getRemotes aid =
map (followRemoteTarget . entityVal) <$>
selectList [FollowRemoteActor ==. aid] []

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2018, 2019, 2020, 2022
- by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -84,6 +85,7 @@ import Yesod.Mail.Send (runMailer)
import Control.Concurrent.ResultShare
import Data.KeyFile
import Network.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Control.Concurrent.Local
@ -103,20 +105,19 @@ import Vervis.RemoteActorStore
-- Don't forget to add new modules to your cabal file!
import Vervis.Handler.Client
import Vervis.Handler.Common
import Vervis.Handler.Git
import Vervis.Handler.Cloth
import Vervis.Handler.Deck
--import Vervis.Handler.Git
import Vervis.Handler.Group
import Vervis.Handler.Home
import Vervis.Handler.Inbox
import Vervis.Handler.Key
import Vervis.Handler.Patch
--import Vervis.Handler.Key
import Vervis.Handler.Loom
import Vervis.Handler.Person
import Vervis.Handler.Project
import Vervis.Handler.Repo
import Vervis.Handler.Role
import Vervis.Handler.Sharer
--import Vervis.Handler.Role
--import Vervis.Handler.Sharer
import Vervis.Handler.Ticket
import Vervis.Handler.Wiki
import Vervis.Handler.Workflow
--import Vervis.Handler.Wiki
--import Vervis.Handler.Workflow
import Vervis.Migration (migrateDB)
import Vervis.Model
@ -230,8 +231,8 @@ makeFoundation appSettings = do
return app
where
verifyRepoDir = do
repos <- lift repoTreeFromDir
repos' <- repoTreeFromDB
repos <- lift reposFromDir
repos' <- reposFromDB
unless (repos == repos') $ liftIO $ do
putStrLn "Repo tree based on filesystem:"
printRepos repos
@ -240,31 +241,23 @@ makeFoundation appSettings = do
throwIO $ userError "Repo dir check failed!"
liftIO $ printRepos repos
where
printRepos = traverse_ $ \ (shr, rps) ->
for_ rps $ \ (rp, vcs) ->
printRepos = traverse_ $ \ (rp, vcs) ->
putStrLn $
"Found repo " ++
shr ++ " / " ++ rp ++
"Found repo " ++ rp ++
" [" ++ T.unpack (versionControlSystemName vcs) ++ "]"
repoTreeFromDir = do
reposFromDir = do
dir <- askRepoRootDir
outers <- liftIO $ sort <$> listDirectory dir
repos <- for outers $ \ outer -> do
let path = dir </> outer
checkDir path
inners <- liftIO $ sort <$> listDirectory path
inners' <- for inners $ \ inner -> do
checkDir $ path </> inner
subdirs <- liftIO $ sort <$> listDirectory dir
for subdirs $ \ subdir -> do
checkDir $ dir </> subdir
vcs <- do
mvcs <- detectVcs $ path </> inner
let ref = outer ++ "/" ++ inner
mvcs <- detectVcs $ dir </> subdir
let ref = dir ++ "/" ++ subdir
case mvcs of
Left False -> error $ "Failed to detect VCS: " ++ ref
Left True -> error $ "Detected both VCSs: " ++ ref
Right v -> return v
return (inner, vcs)
return $ (outer,) <$> nonEmpty inners'
return $ catMaybes repos
return (subdir, vcs)
where
checkDir path = liftIO $ do
isdir <- doesDirectoryExist path
@ -280,18 +273,12 @@ makeFoundation appSettings = do
(False, True) -> Right VCSGit
(False, False) -> Left False
(True, True) -> Left True
repoTreeFromDB =
fmap adapt $ E.select $ E.from $ \ (s `E.InnerJoin` r) -> do
E.on $ s E.^. SharerId E.==. r E.^. RepoSharer
E.orderBy [E.asc $ s E.^. SharerIdent, E.asc $ r E.^. RepoIdent]
return (s E.^. SharerIdent, (r E.^. RepoIdent, r E.^. RepoVcs))
reposFromDB = do
hashRepo <- getEncodeKeyHashid
sortOn fst . map (adapt hashRepo) <$> selectList [] []
where
adapt =
groupWithExtract
(lower . unShrIdent . E.unValue . fst)
(first (lower . unRpIdent) . bimap E.unValue E.unValue . snd)
where
lower = T.unpack . CI.foldedCase
adapt hashRepo (Entity repoID repo) =
(T.unpack $ keyHashidText $ hashRepo repoID, repoVcs repo)
migrate :: MonadLogger m => Text -> ReaderT b m (Either Text (Int, Int)) -> ReaderT b m ()
migrate name a = do
r <- a
@ -372,6 +359,7 @@ sshServer :: App -> IO ()
sshServer foundation =
runSsh
(appSettings foundation)
(appHashidsContext foundation)
(appConnPool foundation)
(loggingFunction foundation)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2018, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2018, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -25,14 +25,16 @@ import Yesod.Feed
import qualified Data.Text as T (concat)
import Yesod.Hashids
import Vervis.Changes
import Vervis.Foundation
import Vervis.Model.Ident
import Vervis.Model
import Development.PatchMediaType
changeEntry :: ShrIdent -> RpIdent -> LogEntry -> FeedEntry (Route App)
changeEntry shr rp le = FeedEntry
{ feedEntryLink = RepoCommitR shr rp $ leHash le
changeEntry :: KeyHashid Repo -> LogEntry -> FeedEntry (Route App)
changeEntry rp le = FeedEntry
{ feedEntryLink = RepoCommitR rp $ leHash le
, feedEntryUpdated = fst $ leTime le
, feedEntryTitle = leMessage le
, feedEntryContent = mempty
@ -40,15 +42,14 @@ changeEntry shr rp le = FeedEntry
}
changeFeed
:: ShrIdent -- ^ Sharer name
-> RpIdent -- ^ Repo name
:: KeyHashid Repo -- ^ Repo key
-> Maybe Text -- ^ Optional branch name
-> VersionControlSystem -- ^ To pick VCS specific terms
-> [LogEntry] -- ^ Changes, recent first
-> Feed (Route App)
changeFeed shr repo mbranch vcs les = Feed
changeFeed repo mbranch vcs les = Feed
{ feedTitle = T.concat
[ rp2text repo
[ keyHashidText repo
, case mbranch of
Nothing -> ""
Just b -> ":" <> b
@ -59,16 +60,16 @@ changeFeed shr repo mbranch vcs les = Feed
]
, feedLinkSelf =
case mbranch of
Nothing -> RepoHeadChangesR shr repo
Just b -> RepoChangesR shr repo b
Nothing -> RepoCommitsR repo
Just b -> RepoBranchCommitsR repo b
, feedLinkHome =
case mbranch of
Nothing -> RepoHeadChangesR shr repo
Just b -> RepoChangesR shr repo b
, feedAuthor = shr2text shr
Nothing -> RepoCommitsR repo
Just b -> RepoBranchCommitsR repo b
, feedAuthor = keyHashidText repo
, feedDescription = mempty
, feedLanguage = "en"
, feedUpdated = fst $ leTime $ head les
, feedLogo = Nothing
, feedEntries = map (changeEntry shr repo) les
, feedEntries = map (changeEntry repo) les
}

View file

@ -22,14 +22,12 @@ module Vervis.Client
, followTicket
, followRepo
, offerTicket
, createTicket
, resolve
, undoFollowSharer
, undoFollowProject
, undoFollowTicket
, undoFollowRepo
, unresolve
, createMR
, offerMR
, createDeck
)
@ -69,11 +67,11 @@ import Data.Either.Local
import Database.Persist.Local
import Vervis.ActivityPub
import Vervis.ActivityPub.Recipient
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Recipient
import Vervis.Ticket
import Vervis.WorkItem
@ -87,6 +85,8 @@ createThread
-> Route App
-> m (Either Text (Note URIMode))
createThread shrAuthor (TextPandocMarkdown msg) hDest recipsA recipsC context = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
let encodeRecipRoute = ObjURI hDest . encodeRouteLocal
contentHtml <- ExceptT . pure $ renderPandocMarkdown msg
@ -109,6 +109,7 @@ createThread shrAuthor (TextPandocMarkdown msg) hDest recipsA recipsC context =
, noteSource = msg
, noteContent = contentHtml
}
-}
createReply
:: ShrIdent
@ -120,6 +121,8 @@ createReply
-> MessageId
-> Handler (Either Text (Note URIMode))
createReply shrAuthor (TextPandocMarkdown msg) hDest recipsA recipsC context midParent = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
let encodeRecipRoute = ObjURI hDest . encodeRouteLocal
@ -159,11 +162,14 @@ createReply shrAuthor (TextPandocMarkdown msg) hDest recipsA recipsC context mid
, noteSource = msg
, noteContent = contentHtml
}
-}
follow
:: (MonadHandler m, HandlerSite m ~ App)
=> ShrIdent -> ObjURI URIMode -> ObjURI URIMode -> Bool -> m (TextHtml, Audience URIMode, AP.Follow URIMode)
follow shrAuthor uObject@(ObjURI hObject luObject) uRecip hide = do
error "Temporarily disabled"
{-
summary <-
TextHtml . TL.toStrict . renderHtml <$>
withUrlRenderer
@ -186,44 +192,59 @@ follow shrAuthor uObject@(ObjURI hObject luObject) uRecip hide = do
}
audience = Audience [uRecip] [] [] [] [] []
return (summary, audience, followAP)
-}
followSharer
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent -> ShrIdent -> Bool -> m (TextHtml, Audience URIMode, AP.Follow URIMode)
followSharer shrAuthor shrObject hide = do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
let uObject = encodeRouteHome $ SharerR shrObject
follow shrAuthor uObject uObject hide
-}
followProject
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent -> ShrIdent -> PrjIdent -> Bool -> m (TextHtml, Audience URIMode, AP.Follow URIMode)
followProject shrAuthor shrObject prjObject hide = do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
let uObject = encodeRouteHome $ ProjectR shrObject prjObject
follow shrAuthor uObject uObject hide
-}
followTicket
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent -> ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Bool -> m (TextHtml, Audience URIMode, AP.Follow URIMode)
followTicket shrAuthor shrObject prjObject numObject hide = do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
let uObject = encodeRouteHome $ ProjectTicketR shrObject prjObject numObject
uRecip = encodeRouteHome $ ProjectR shrObject prjObject
follow shrAuthor uObject uRecip hide
-}
followRepo
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent -> ShrIdent -> RpIdent -> Bool -> m (TextHtml, Audience URIMode, AP.Follow URIMode)
followRepo shrAuthor shrObject rpObject hide = do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
let uObject = encodeRouteHome $ RepoR shrObject rpObject
follow shrAuthor uObject uObject hide
-}
offerTicket
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent -> TextHtml -> TextPandocMarkdown -> ShrIdent -> PrjIdent -> m (Either Text (TextHtml, Audience URIMode, AP.Ticket URIMode, FedURI))
offerTicket shrAuthor (TextHtml title) (TextPandocMarkdown desc) shr prj = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
descHtml <- ExceptT . pure $ renderPandocMarkdown desc
@ -266,68 +287,6 @@ offerTicket shrAuthor (TextHtml title) (TextPandocMarkdown desc) shr prj = runEx
}
return (summary, audience, ticket, target)
createTicket
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent
-> TextHtml
-> TextPandocMarkdown
-> FedURI
-> FedURI
-> m (Either Text (TextHtml, Audience URIMode, Create URIMode))
createTicket shrAuthor (TextHtml title) (TextPandocMarkdown desc) target context = runExceptT $ do
summary <-
TextHtml . TL.toStrict . renderHtml <$>
withUrlRenderer
[hamlet|
<p>
<a href=@{SharerR shrAuthor}>
#{shr2text shrAuthor}
\ opened a ticket on project #
<a href="#{renderObjURI context}"}>
#{renderObjURI context}
: #{preEscapedToHtml title}.
|]
encodeRouteHome <- getEncodeRouteHome
let recipsA = [target]
recipsC =
let ObjURI h (LocalURI lu) = context
in [ ObjURI h $ LocalURI $ lu <> "/followers"
, ObjURI h $ LocalURI $ lu <> "/team"
, encodeRouteHome $ SharerFollowersR shrAuthor
]
audience = Audience
{ audienceTo = recipsA ++ recipsC
, audienceBto = []
, audienceCc = []
, audienceBcc = []
, audienceGeneral = []
, audienceNonActors = recipsC
}
encodeRouteLocal <- getEncodeRouteLocal
hLocal <- asksSite siteInstanceHost
descHtml <- ExceptT . pure $ renderPandocMarkdown desc
let ticket = AP.Ticket
{ AP.ticketLocal = Nothing
, AP.ticketAttributedTo = encodeRouteLocal $ SharerR shrAuthor
, AP.ticketPublished = Nothing
, AP.ticketUpdated = Nothing
, AP.ticketContext = Just context
, AP.ticketSummary = TextHtml title
, AP.ticketContent = TextHtml descHtml
, AP.ticketSource = TextPandocMarkdown desc
, AP.ticketAssignedTo = Nothing
, AP.ticketResolved = Nothing
, AP.ticketAttachment = Nothing
}
create = Create
{ createObject = CreateTicket hLocal ticket
, createTarget = Just target
}
return (summary, audience, create)
resolve
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent
@ -358,6 +317,7 @@ resolve shrUser uObject = runExceptT $ do
recips = map encodeRouteHome audLocal ++ audRemote
return (Nothing, Audience recips [] [] [] [] [], Resolve uObject)
-}
undoFollow
:: (MonadUnliftIO m, MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -369,6 +329,8 @@ undoFollow
-> Route App
-> m (Either Text (TextHtml, Audience URIMode, Undo URIMode))
undoFollow shrAuthor pidAuthor getFsid typ objRoute recipRoute = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
obiidFollow <- runSiteDBExcept $ do
@ -395,6 +357,7 @@ undoFollow shrAuthor pidAuthor getFsid typ objRoute recipRoute = runExceptT $ do
}
audience = Audience [encodeRouteHome recipRoute] [] [] [] [] []
return (summary, audience, undo)
-}
undoFollowSharer
:: (MonadUnliftIO m, MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -403,6 +366,8 @@ undoFollowSharer
-> ShrIdent
-> m (Either Text (TextHtml, Audience URIMode, Undo URIMode))
undoFollowSharer shrAuthor pidAuthor shrFollowee =
error "Temporarily disabled"
{-
undoFollow shrAuthor pidAuthor getFsid "sharer" objRoute objRoute
where
objRoute = SharerR shrFollowee
@ -432,6 +397,7 @@ undoFollowProject shrAuthor pidAuthor shrFollowee prjFollowee =
mj <- lift $ getValBy $ UniqueProject prjFollowee sidFollowee
j <- fromMaybeE mj "Unfollow target no such local project"
lift $ actorFollowers <$> getJust (projectActor j)
-}
undoFollowTicket
:: (MonadUnliftIO m, MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -442,6 +408,8 @@ undoFollowTicket
-> KeyHashid LocalTicket
-> m (Either Text (TextHtml, Audience URIMode, Undo URIMode))
undoFollowTicket shrAuthor pidAuthor shrFollowee prjFollowee numFollowee =
error "Temporarily disabled"
{-
undoFollow shrAuthor pidAuthor getFsid "project" objRoute recipRoute
where
objRoute = ProjectTicketR shrFollowee prjFollowee numFollowee
@ -467,6 +435,7 @@ undoFollowTicket shrAuthor pidAuthor shrFollowee prjFollowee numFollowee =
unless (ticketProjectLocalProject tpl == jid) $
throwE "Hashid doesn't match sharer/project"
return $ localTicketFollowers lt
-}
undoFollowRepo
:: (MonadUnliftIO m, MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -476,6 +445,8 @@ undoFollowRepo
-> RpIdent
-> m (Either Text (TextHtml, Audience URIMode, Undo URIMode))
undoFollowRepo shrAuthor pidAuthor shrFollowee rpFollowee =
error "Temporarily disabled"
{-
undoFollow shrAuthor pidAuthor getFsid "repo" objRoute objRoute
where
objRoute = RepoR shrFollowee rpFollowee
@ -486,6 +457,7 @@ undoFollowRepo shrAuthor pidAuthor shrFollowee rpFollowee =
mr <- lift $ getValBy $ UniqueRepo rpFollowee sidFollowee
repoFollowers <$>
fromMaybeE mr "Unfollow target no such local repo"
-}
unresolve
:: (MonadUnliftIO m, MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -493,6 +465,8 @@ unresolve
-> FedURI
-> m (Either Text (Maybe TextHtml, Audience URIMode, Undo URIMode))
unresolve shrUser uTicket = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
wiFollowers <- askWorkItemFollowers
ticket <- parseWorkItem "Ticket" uTicket
@ -550,75 +524,7 @@ unresolve shrUser uTicket = runExceptT $ do
recips = map encodeRouteHome audLocal ++ audRemote
return (Nothing, Audience recips [] [] [] [] [], Undo uResolve)
createMR
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> ShrIdent
-> TextHtml
-> TextPandocMarkdown
-> FedURI
-> Maybe FedURI
-> PatchMediaType
-> Text
-> m (Either Text (Maybe TextHtml, Audience URIMode, AP.Ticket URIMode, Maybe FedURI))
createMR shrAuthor title desc uContext muBranch typ diff = runExceptT $ do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
manager <- asksSite appHttpManager
hLocal <- asksSite siteInstanceHost
context <- parseTicketContext uContext
descHtml <-
ExceptT . pure $ renderPandocMarkdown $ unTextPandocMarkdown desc
context' <- bitraverse pure (getRemoteContextHttp "Context") context
let audAuthor =
AudLocal
[]
[LocalPersonCollectionSharerFollowers shrAuthor]
audContext = contextAudience context'
uTarget =
case context' of
Left _ -> uContext
Right (uTracker, _, _, _) -> uTracker
(_, _, _, audLocal, audRemote) =
collectAudience $ audAuthor : audContext
recips = map encodeRouteHome audLocal ++ audRemote
ObjURI hBranch luBranch = fromMaybe uContext muBranch
luAuthor = encodeRouteLocal $ SharerR shrAuthor
ticket = AP.Ticket
{ AP.ticketLocal = Nothing
, AP.ticketAttributedTo = luAuthor
, AP.ticketPublished = Nothing
, AP.ticketUpdated = Nothing
, AP.ticketContext = Just uContext
, AP.ticketSummary = title
, AP.ticketContent = TextHtml descHtml
, AP.ticketSource = desc
, AP.ticketAssignedTo = Nothing
, AP.ticketResolved = Nothing
, AP.ticketAttachment = Just
( hBranch
, MergeRequest
{ mrOrigin = Nothing
, mrTarget = luBranch
, mrBundle = Right
( hLocal
, BundleOffer Nothing $ pure AP.Patch
{ AP.patchLocal = Nothing
, AP.patchAttributedTo = luAuthor
, AP.patchPublished = Nothing
, AP.patchType = typ
, AP.patchContent = diff
}
)
}
)
}
return (Nothing, Audience recips [] [] [] [] [], ticket, Just uTarget)
-}
offerMR
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -631,6 +537,8 @@ offerMR
-> Text
-> m (Either Text (Maybe TextHtml, Audience URIMode, AP.Ticket URIMode))
offerMR shrAuthor title desc uContext muBranch typ diff = runExceptT $ do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
manager <- asksSite appHttpManager
@ -684,6 +592,7 @@ offerMR shrAuthor title desc uContext muBranch typ diff = runExceptT $ do
)
}
return (Nothing, Audience recips [] [] [] [] [], ticket)
-}
createDeck
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
@ -692,6 +601,8 @@ createDeck
-> Maybe Text
-> m (Maybe TextHtml, Audience URIMode, AP.ActorDetail, Maybe FedURI)
createDeck shrAuthor name mdesc = do
error "Temporarily disabled"
{-
encodeRouteHome <- getEncodeRouteHome
let audAuthor =
@ -709,3 +620,4 @@ createDeck shrAuthor name mdesc = do
}
return (Nothing, Audience recips [] [] [] [] [], detail, Nothing)
-}

133
src/Vervis/Cloth.hs Normal file
View file

@ -0,0 +1,133 @@
{- This file is part of Vervis.
-
- Written in 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Cloth
( getCloth
, getCloth404
)
where
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.Maybe
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import Yesod.Core
import Yesod.Hashids
import Data.Either.Local
import Database.Persist.Local
import Vervis.Foundation
import Vervis.Model
getCloth
:: MonadIO m
=> LoomId
-> TicketLoomId
-> ReaderT SqlBackend m
( Maybe
( Entity Loom
, Entity TicketLoom
, Entity Ticket
, Either (Entity TicketAuthorLocal) (Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
)
getCloth lid tlid = runMaybeT $ do
l <- MaybeT $ get lid
tl <- MaybeT $ get tlid
guard $ ticketLoomLoom tl == lid
let tid = ticketLoomTicket tl
t <- lift $ getJust tid
bnids <- lift $ do
mne <-
nonEmpty <$> selectKeysList [BundleTicket ==. tlid] [Desc BundleId]
case mne of
Nothing -> error "Found Loom Ticket without any Bundles"
Just ne -> return ne
author <-
lift $
requireEitherAlt
(getBy $ UniqueTicketAuthorLocal tid)
(getBy $ UniqueTicketAuthorRemote tid)
"MR doesn't have author"
"MR has both local and remote author"
mresolved <- lift $ getResolved tid
return (Entity lid l, Entity tlid tl, Entity tid t, author, mresolved, bnids)
where
getResolved
:: MonadIO m
=> TicketId
-> ReaderT SqlBackend m
(Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
)
getResolved tid = do
metr <- getBy $ UniqueTicketResolve tid
for metr $ \ etr@(Entity trid _) ->
(etr,) <$>
requireEitherAlt
(getBy $ UniqueTicketResolveLocal trid)
(getBy $ UniqueTicketResolveRemote trid)
"No TRX"
"Both TRL and TRR"
getCloth404
:: KeyHashid Loom
-> KeyHashid TicketLoom
-> AppDB
( Entity Loom
, Entity TicketLoom
, Entity Ticket
, Either (Entity TicketAuthorLocal) (Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
getCloth404 lkhid tlkhid = do
lid <- decodeKeyHashid404 lkhid
tlid <- decodeKeyHashid404 tlkhid
mcloth <- getCloth lid tlid
case mcloth of
Nothing -> notFound
Just cloth -> return cloth

View file

@ -15,13 +15,13 @@
-}
module Vervis.Darcs
( readSourceView
, readWikiView
, readChangesView
, lastChange
, readPatch
, writePostApplyHooks
, applyDarcsPatch
( --readSourceView
--, readWikiView
--, readChangesView
--, lastChange
--, readPatch
writePostApplyHooks
--, applyDarcsPatch
)
where
@ -45,6 +45,7 @@ import Data.Text.Encoding (encodeUtf8, decodeUtf8With, decodeUtf8)
import Data.Text.Encoding.Error (strictDecode)
import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime)
import Data.Traversable (for)
import Database.Persist
import Development.Darcs.Internal.Hash.Codec
import Development.Darcs.Internal.Hash.Types
import Development.Darcs.Internal.Inventory.Parser
@ -71,6 +72,7 @@ import qualified Development.Darcs.Internal.Patch.Parser as P
import Network.FedURI
import Yesod.ActivityPub
import Yesod.Hashids
import Yesod.MonadSite
import Darcs.Local.Repository
@ -94,8 +96,8 @@ import Vervis.Path
import Vervis.Readme
import Vervis.Settings
import Vervis.SourceTree
import Vervis.Wiki (WikiView (..))
{-
dirToAnchoredPath :: [EntryName] -> AnchoredPath
dirToAnchoredPath = AnchoredPath . map (decodeWhiteName . encodeUtf8)
@ -164,7 +166,9 @@ readSourceView path dir = do
let mitem = find expandedTree anch
for mitem $ itemToSourceView (last dir)
return $ renderSources dir <$> msv
-}
{-
readWikiView
:: (EntryName -> EntryName -> Maybe Text)
-- ^ Page name predicate. Returns 'Nothing' for a file which isn't a page.
@ -214,7 +218,9 @@ readWikiView isPage isMain path dir = do
mkview Nothing b = WikiViewRaw b
mkview (Just mt) b = WikiViewPage mt b
for mpage $ \ (load, mmtitle) -> mkview mmtitle <$> load
-}
{-
readChangesView
:: FilePath
-- ^ Repository path
@ -383,20 +389,20 @@ readPatch path hash = handle $ runExceptT $ do
mkedit' (Replace fp regex old new) = AddTextFile "Replace" 0 [T.concat ["replace ", T.pack fp, " ", regex, " ", old, " ", new]]
mkedit' (Binary fp old new) = EditBinaryFile fp (fromIntegral $ B.length old) 0 (fromIntegral $ B.length new) 0
mkedit' (Pref pref old new) = AddTextFile "Pref" 0 [T.concat ["changepref ", pref, " ", old, " ", new]]
-}
writePostApplyHooks :: WorkerDB ()
writePostApplyHooks = do
repos <- E.select $ E.from $ \ (r `E.InnerJoin` s) -> do
E.on $ r E.^. RepoSharer E.==. s E.^. SharerId
E.where_ $ r E.^. RepoVcs E.==. E.val VCSDarcs
return (s E.^. SharerIdent, r E.^. RepoIdent)
hook <- asksSite $ appPostApplyHookFile . appSettings
authority <- asksSite $ renderAuthority . siteInstanceHost
for_ repos $ \ (E.Value shr, E.Value rp) -> do
path <- askRepoDir shr rp
repos <- selectKeysList [RepoVcs ==. VCSDarcs] []
for_ repos $ \ repoID -> do
repoHash <- encodeKeyHashid repoID
path <- askRepoDir repoHash
liftIO $
writeDefaultsFile path hook authority (shr2text shr) (rp2text rp)
writeDefaultsFile path hook authority (keyHashidText repoHash)
{-
applyDarcsPatch shr rp patch = do
path <- askRepoDir shr rp
let input = BL.fromStrict $ TE.encodeUtf8 patch
@ -414,3 +420,4 @@ applyDarcsPatch shr rp patch = do
, "\nstderr: ", out2text err
]
ExitSuccess -> return ()
-}

807
src/Vervis/Delivery.hs Normal file
View file

@ -0,0 +1,807 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2021, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Delivery
( deliverHttp
, deliverHttpBL
, deliverRemoteDB_D
, deliverRemoteDB_P
, deliverRemoteDB_R
, deliverRemoteHTTP_D
, deliverRemoteHTTP_P
, deliverRemoteHTTP_R
, deliverRemoteDB'
, deliverRemoteDB''
, deliverRemoteHttp
, deliverRemoteHttp'
, deliverLocal'
, deliverLocal
, insertRemoteActivityToLocalInboxes
)
where
import Control.Applicative
import Control.Exception hiding (Handler, try)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
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.Bifunctor
import Data.Bitraversable
import Data.ByteString (ByteString)
import Data.Either
import Data.Foldable
import Data.Function
import Data.List.NonEmpty (NonEmpty (..), nonEmpty)
import Data.Maybe
import Data.Semigroup
import Data.Text (Text)
import Data.Text.Encoding
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import Network.HTTP.Client
import Network.TLS -- hiding (SHA256)
import Text.Blaze.Html (preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text
import UnliftIO.Exception (try)
import Yesod.Core hiding (logError, logWarn, logInfo, logDebug)
import Yesod.Core.Handler
import Yesod.Persist.Core
import qualified Data.ByteString.Lazy as BL
import qualified Data.CaseInsensitive as CI
import qualified Data.List.NonEmpty as NE
import qualified Data.List as L
import qualified Data.List.Ordered as LO
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Database.Esqueleto as E
import Yesod.HttpSignature
import Database.Persist.JSON
import Network.FedURI
import Network.HTTP.Digest
import Yesod.ActivityPub
import Yesod.MonadSite
import Yesod.FedURI
import Yesod.Hashids
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Data.Either.Local
import Data.List.NonEmpty.Local
import Data.Patch.Local hiding (Patch)
import Data.Tuple.Local
import Database.Persist.Local
import qualified Data.Patch.Local as P
import Vervis.ActivityPub
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Recipient
import Vervis.RemoteActorStore
import Vervis.Settings
import Vervis.Time
deliverHttp
:: (MonadSite m, SiteEnv m ~ App)
=> AP.Doc AP.Activity URIMode
-> Maybe LocalURI
-> Host
-> LocalURI
-> m (Either AP.APPostError (Response ()))
deliverHttp doc mfwd h luInbox =
deliverActivity (ObjURI h luInbox) (ObjURI h <$> mfwd) doc
deliverHttpBL
:: (MonadSite m, SiteEnv m ~ App)
=> BL.ByteString
-> Maybe LocalURI
-> Host
-> LocalURI
-> m (Either AP.APPostError (Response ()))
deliverHttpBL body mfwd h luInbox =
deliverActivityBL' (ObjURI h luInbox) (ObjURI h <$> mfwd) body
deliverRemoteDB_
:: (MonadIO m, PersistRecordBackend fwder SqlBackend)
=> (ForwardingId -> Key sender -> fwder)
-> BL.ByteString
-> RemoteActivityId
-> Key sender
-> ByteString
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend m
[((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, Key fwder))]
deliverRemoteDB_ makeFwder body ractid senderKey sig recips = do
let body' = BL.toStrict body
makeFwd (RemoteRecipient raid _ _ msince) =
Forwarding raid ractid body' sig (isNothing msince)
fetchedDeliv <- for recips $ bitraverse pure $ \ rs -> do
fwds <- insertMany' makeFwd rs
insertMany' (flip makeFwder senderKey . snd) fwds
return $ takeNoError5 fetchedDeliv
where
takeNoError noError = mapMaybe $ \ (i, rs) -> (i,) <$> nonEmpty (mapMaybe noError $ NE.toList rs)
takeNoError5 = takeNoError noError
where
noError ((RemoteRecipient ak luA luI Nothing , fwid), fwrid) = Just (ak, luA, luI, fwid, fwrid)
noError ((RemoteRecipient _ _ _ (Just _), _ ), _ ) = Nothing
deliverRemoteDB_D
:: MonadIO m
=> BL.ByteString
-> RemoteActivityId
-> DeckId
-> ByteString
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend m
[((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderDeckId))]
deliverRemoteDB_D = deliverRemoteDB_ ForwarderDeck
deliverRemoteDB_P
:: MonadIO m
=> BL.ByteString
-> RemoteActivityId
-> PersonId
-> ByteString
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend m
[((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderPersonId))]
deliverRemoteDB_P = deliverRemoteDB_ ForwarderPerson
deliverRemoteDB_R
:: MonadIO m
=> BL.ByteString
-> RemoteActivityId
-> RepoId
-> ByteString
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend m
[((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderRepoId))]
deliverRemoteDB_R = deliverRemoteDB_ ForwarderRepo
deliverRemoteHTTP'
:: (MonadSite m, SiteEnv m ~ App, PersistRecordBackend fwder SqlBackend)
=> UTCTime
-> LocalActor
-> BL.ByteString
-> ByteString
-> [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, Key fwder))]
-> m ()
deliverRemoteHTTP' now sender body sig fetched = do
let deliver h inbox =
forwardActivity (ObjURI h inbox) sig (renderLocalActor sender) body
traverse_ (fork . deliverFetched deliver now) fetched
where
fork = forkWorker "Inbox forwarding to remote members of local collections: delivery failed"
deliverFetched deliver now ((_, h), recips@(r :| rs)) = do
let (raid, _luActor, luInbox, fwid, forwarderKey) = r
e <- deliver h luInbox
let e' = case e of
Left err ->
if isInstanceErrorP err
then Nothing
else Just False
Right _resp -> Just True
case e' of
Nothing -> runSiteDB $ do
let recips' = NE.toList recips
updateWhere [RemoteActorId <-. map fst5 recips', RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
updateWhere [ForwardingId <-. map fourth5 recips'] [ForwardingRunning =. False]
Just success -> do
runSiteDB $
if success
then do
delete forwarderKey
delete fwid
else do
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
update fwid [ForwardingRunning =. False]
for_ rs $ \ (raid, _luActor, luInbox, fwid, forwarderKey) ->
fork $ do
e <- deliver h luInbox
runSiteDB $
case e of
Left _err -> do
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
update fwid [ForwardingRunning =. False]
Right _resp -> do
delete forwarderKey
delete fwid
deliverRemoteHTTP_D
:: (MonadSite m, SiteEnv m ~ App)
=> UTCTime
-> KeyHashid Deck
-> BL.ByteString
-> ByteString
-> [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderDeckId))]
-> m ()
deliverRemoteHTTP_D now dkhid =
deliverRemoteHTTP' now $ LocalActorDeck dkhid
deliverRemoteHTTP_P
:: (MonadSite m, SiteEnv m ~ App)
=> UTCTime
-> KeyHashid Person
-> BL.ByteString
-> ByteString
-> [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderPersonId))]
-> m ()
deliverRemoteHTTP_P now pkhid = deliverRemoteHTTP' now $ LocalActorPerson pkhid
deliverRemoteHTTP_R
:: (MonadSite m, SiteEnv m ~ App)
=> UTCTime
-> KeyHashid Repo
-> BL.ByteString
-> ByteString
-> [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, ForwardingId, ForwarderRepoId))]
-> m ()
deliverRemoteHTTP_R now rkhid = deliverRemoteHTTP' now $ LocalActorRepo rkhid
deliverRemoteDB'
:: Host
-> OutboxItemId
-> [(Host, NonEmpty LocalURI)]
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> AppDB
( [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, DeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
)
deliverRemoteDB' hContext = deliverRemoteDB'' [hContext]
data Recip
= RecipRA (Entity RemoteActor)
| RecipURA (Entity UnfetchedRemoteActor)
| RecipRC (Entity RemoteCollection)
deliverRemoteDB''
:: MonadIO m
=> [Host]
-> OutboxItemId
-> [(Host, NonEmpty LocalURI)]
-> [((InstanceId, Host), NonEmpty RemoteRecipient)]
-> ReaderT SqlBackend m
( [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, DeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
)
deliverRemoteDB'' hContexts obid recips known = do
recips' <- for recips $ \ (h, lus) -> do
let lus' = NE.nub lus
(iid, inew) <- idAndNew <$> insertBy' (Instance h)
if inew
then return ((iid, h), (Nothing, Nothing, Just lus'))
else do
es <- for lus' $ \ lu -> do
ma <- runMaybeT $ do
Entity roid ro <- MaybeT $ getBy $ UniqueRemoteObject iid lu
recip <- RecipRA <$> MaybeT (getBy $ UniqueRemoteActor roid)
<|> RecipURA <$> MaybeT (getBy $ UniqueUnfetchedRemoteActor roid)
<|> RecipRC <$> MaybeT (getBy $ UniqueRemoteCollection roid)
return (ro, recip)
return $
case ma of
Nothing -> Just $ Left lu
Just (ro, r) ->
case r of
RecipRA (Entity raid ra) -> Just $ Right $ Left $ RemoteRecipient raid (remoteObjectIdent ro) (remoteActorInbox ra) (remoteActorErrorSince ra)
RecipURA (Entity uraid ura) -> Just $ Right $ Right (uraid, remoteObjectIdent ro, unfetchedRemoteActorSince ura)
RecipRC _ -> Nothing
let (unknown, newKnown) = partitionEithers $ catMaybes $ NE.toList es
(fetched, unfetched) = partitionEithers newKnown
return ((iid, h), (nonEmpty fetched, nonEmpty unfetched, nonEmpty unknown))
let moreKnown = mapMaybe (\ (i, (f, _, _)) -> (i,) <$> f) recips'
unfetched = mapMaybe (\ (i, (_, uf, _)) -> (i,) <$> uf) recips'
stillUnknown = mapMaybe (\ (i, (_, _, uk)) -> (i,) <$> uk) recips'
allFetched = unionRemotes known moreKnown
fetchedDeliv <- for allFetched $ \ (i, rs) ->
let fwd = snd i `elem` hContexts
in (i,) <$> insertMany' (\ (RemoteRecipient raid _ _ msince) -> Delivery raid obid fwd $ isNothing msince) rs
unfetchedDeliv <- for unfetched $ \ (i, rs) ->
let fwd = snd i `elem` hContexts
in (i,) <$> insertMany' (\ (uraid, _, msince) -> UnlinkedDelivery uraid obid fwd $ isNothing msince) rs
unknownDeliv <- for stillUnknown $ \ (i, lus) -> do
-- TODO maybe for URA insertion we should do insertUnique?
ros <- insertMany' (\ lu -> RemoteObject (fst i) lu) lus
rs <- insertMany' (\ (_lu, roid) -> UnfetchedRemoteActor roid Nothing) ros
let fwd = snd i `elem` hContexts
(i,) <$> insertMany' (\ (_, uraid) -> UnlinkedDelivery uraid obid fwd True) rs
return
( takeNoError4 fetchedDeliv
, takeNoError3 unfetchedDeliv
, map
(second $ NE.map $ \ (((lu, _roid), ak), dlk) -> (ak, lu, dlk))
unknownDeliv
)
where
takeNoError noError = mapMaybe $ \ (i, rs) -> (i,) <$> nonEmpty (mapMaybe noError $ NE.toList rs)
takeNoError3 = takeNoError noError
where
noError ((ak, lu, Nothing), dlk) = Just (ak, lu, dlk)
noError ((_ , _ , Just _ ), _ ) = Nothing
takeNoError4 = takeNoError noError
where
noError (RemoteRecipient ak luA luI Nothing , dlk) = Just (ak, luA, luI, dlk)
noError (RemoteRecipient _ _ _ (Just _), _ ) = Nothing
deliverRemoteHttp
:: Host
-> OutboxItemId
-> AP.Doc AP.Activity URIMode
-> ( [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, DeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
)
-> Worker ()
deliverRemoteHttp hContext = deliverRemoteHttp' [hContext]
deliverRemoteHttp'
:: [Host]
-> OutboxItemId
-> AP.Doc AP.Activity URIMode
-> ( [((InstanceId, Host), NonEmpty (RemoteActorId, LocalURI, LocalURI, DeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
, [((InstanceId, Host), NonEmpty (UnfetchedRemoteActorId, LocalURI, UnlinkedDeliveryId))]
)
-> Worker ()
deliverRemoteHttp' hContexts obid doc (fetched, unfetched, unknown) = do
logDebug' "Starting"
let deliver fwd h inbox = do
let fwd' = if h `elem` hContexts then Just fwd else Nothing
(isJust fwd',) <$> deliverHttp doc fwd' h inbox
now <- liftIO getCurrentTime
logDebug' $
"Launching fetched " <> showHosts fetched
traverse_ (fork . deliverFetched deliver now) fetched
logDebug' $
"Launching unfetched " <> showHosts unfetched
traverse_ (fork . deliverUnfetched deliver now) unfetched
logDebug' $
"Launching unknown " <> showHosts unknown
traverse_ (fork . deliverUnfetched deliver now) unknown
logDebug' "Done (async delivery may still be running)"
where
showHosts = T.pack . show . map (renderAuthority . snd . fst)
logDebug' t = logDebug $ prefix <> t
where
prefix =
T.concat
[ "Outbox POST handler: deliverRemoteHttp obid#"
, T.pack $ show $ fromSqlKey obid
, ": "
]
fork = forkWorker "Outbox POST handler: HTTP delivery"
deliverFetched deliver now ((_, h), recips@(r :| rs)) = do
logDebug'' "Starting"
let (raid, luActor, luInbox, dlid) = r
(_, e) <- deliver luActor h luInbox
e' <- case e of
Left err -> do
logError $ T.concat
[ "Outbox DL delivery #", T.pack $ show dlid
, " error for <", renderObjURI $ ObjURI h luActor
, ">: ", T.pack $ displayException err
]
return $
if isInstanceErrorP err
then Nothing
else Just False
Right _resp -> return $ Just True
case e' of
Nothing -> runSiteDB $ do
let recips' = NE.toList recips
updateWhere [RemoteActorId <-. map fst4 recips', RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
updateWhere [DeliveryId <-. map fourth4 recips'] [DeliveryRunning =. False]
Just success -> do
runSiteDB $
if success
then delete dlid
else do
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
update dlid [DeliveryRunning =. False]
for_ rs $ \ (raid, luActor, luInbox, dlid) ->
fork $ do
(_, e) <- deliver luActor h luInbox
runSiteDB $
case e of
Left err -> do
logError $ T.concat
[ "Outbox DL delivery #", T.pack $ show dlid
, " error for <", renderObjURI $ ObjURI h luActor
, ">: ", T.pack $ displayException err
]
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
update dlid [DeliveryRunning =. False]
Right _resp -> delete dlid
where
logDebug'' t = logDebug' $ T.concat ["deliverFetched ", renderAuthority h, t]
deliverUnfetched deliver now ((iid, h), recips@(r :| rs)) = do
logDebug'' "Starting"
let (uraid, luActor, udlid) = r
e <- fetchRemoteActor iid h luActor
let e' = case e of
Left err -> Just Nothing
Right (Left err) ->
if isInstanceErrorG err
then Nothing
else Just Nothing
Right (Right mera) -> Just $ Just mera
case e' of
Nothing -> runSiteDB $ do
let recips' = NE.toList recips
updateWhere [UnfetchedRemoteActorId <-. map fst3 recips', UnfetchedRemoteActorSince ==. Nothing] [UnfetchedRemoteActorSince =. Just now]
updateWhere [UnlinkedDeliveryId <-. map thd3 recips'] [UnlinkedDeliveryRunning =. False]
Just mmera -> do
for_ rs $ \ (uraid, luActor, udlid) ->
fork $ do
e <- fetchRemoteActor iid h luActor
case e of
Right (Right mera) ->
case mera of
Nothing -> runSiteDB $ delete udlid
Just (Entity raid ra) -> do
(fwd, e') <- deliver luActor h $ remoteActorInbox ra
runSiteDB $
case e' of
Left _ -> do
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
delete udlid
insert_ $ Delivery raid obid fwd False
Right _ -> delete udlid
_ -> runSiteDB $ do
updateWhere [UnfetchedRemoteActorId ==. uraid, UnfetchedRemoteActorSince ==. Nothing] [UnfetchedRemoteActorSince =. Just now]
update udlid [UnlinkedDeliveryRunning =. False]
case mmera of
Nothing -> runSiteDB $ do
updateWhere [UnfetchedRemoteActorId ==. uraid, UnfetchedRemoteActorSince ==. Nothing] [UnfetchedRemoteActorSince =. Just now]
update udlid [UnlinkedDeliveryRunning =. False]
Just mera ->
case mera of
Nothing -> runSiteDB $ delete udlid
Just (Entity raid ra) -> do
(fwd, e'') <- deliver luActor h $ remoteActorInbox ra
runSiteDB $
case e'' of
Left _ -> do
updateWhere [RemoteActorId ==. raid, RemoteActorErrorSince ==. Nothing] [RemoteActorErrorSince =. Just now]
delete udlid
insert_ $ Delivery raid obid fwd False
Right _ -> delete udlid
where
logDebug'' t = logDebug' $ T.concat ["deliverUnfetched ", renderAuthority h, t]
-- | Given a list of local recipients, which may include actors and
-- collections,
--
-- * Insert activity to inboxes of actors
-- * If collections are listed, insert activity to the local members and return
-- the remote members
insertActivityToLocalInboxes
:: ( MonadSite m
, YesodHashids (SiteEnv m)
, PersistRecordBackend record SqlBackend
)
=> (InboxId -> InboxItemId -> record)
-- ^ Database record to insert as an new inbox item to each inbox
-> Bool
-- ^ Whether to deliver to collection only if owner actor is addressed
-> Maybe LocalActor
-- ^ An actor whose collections are excluded from requiring an owner, i.e.
-- even if owner is required, this actor's collections will be delivered
-- to, even if this actor isn't addressed. This is meant to be the
-- activity's author.
-> Maybe ActorId
-- ^ A un actor whose inbox to exclude from delivery, even if this actor is
-- listed in the recipient set. This is meant to be the activity's
-- author.
-> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
insertActivityToLocalInboxes makeInboxItem requireOwner mauthor maidAuthor recips = do
-- Predicate for filtering addressed stages
--allowStage <- getAllowStage
-- Unhash actor and work item hashids
people <- unhashKeys $ recipPeople recips
groups <- unhashKeys $ recipGroups recips
repos <- unhashKeys $ recipRepos recips
decksAndTickets <- do
decks <- unhashKeys $ recipDecks recips
for decks $ \ (deckID, (DeckFamilyRoutes deck tickets)) ->
(deckID,) . (deck,) <$> unhashKeys tickets
loomsAndCloths <- do
looms <- unhashKeys $ recipLooms recips
for looms $ \ (loomID, (LoomFamilyRoutes loom cloths)) ->
(loomID,) . (loom,) <$> unhashKeys cloths
-- Grab local actor sets whose stages are allowed for delivery
isAuthor <- getIsAuthor
let allowStages'
:: (famili -> routes)
-> (routes -> Bool)
-> (Key record -> LocalActorBy Key)
-> (Key record, famili)
-> Bool
allowStages' = allowStages isAuthor
peopleForStages =
filter (allowStages' id routePerson LocalActorPerson) people
groupsForStages =
filter (allowStages' id routeGroup LocalActorGroup) groups
reposForStages =
filter (allowStages' id routeRepo LocalActorRepo) repos
decksAndTicketsForStages =
filter (allowStages' fst routeDeck LocalActorDeck) decksAndTickets
loomsAndClothsForStages =
filter (allowStages' fst routeLoom LocalActorLoom) loomsAndCloths
-- Grab local actors being addressed
let personIDsForSelf =
[ key | (key, routes) <- people, routePerson routes ]
groupIDsForSelf =
[ key | (key, routes) <- groups, routeGroup routes ]
repoIDsForSelf =
[ key | (key, routes) <- repos, routeRepo routes ]
deckIDsForSelf =
[ key | (key, (routes, _)) <- decksAndTickets, routeDeck routes ]
loomIDsForSelf =
[ key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ]
-- Grab actor actors whose followers are going to be delivered to
let personIDsForFollowers =
[ key | (key, routes) <- peopleForStages, routePersonFollowers routes ]
repoIDsForFollowers =
[ key | (key, routes) <- reposForStages, routeRepoFollowers routes ]
deckIDsForFollowers =
[ key | (key, (routes, _)) <- decksAndTicketsForStages, routeDeckFollowers routes ]
loomIDsForFollowers =
[ key | (key, (routes, _)) <- loomsAndClothsForStages, routeLoomFollowers routes ]
-- Grab tickets and cloths whose followers are going to be delivered to
let ticketSetsForFollowers =
mapMaybe
(\ (deckID, (_, tickets)) -> (deckID,) <$>
NE.nonEmpty
[ ticketDeckID | (ticketDeckID, routes) <- tickets
, routeTicketFollowers routes
]
)
decksAndTicketsForStages
clothSetsForFollowers =
mapMaybe
(\ (loomID, (_, cloths)) -> (loomID,) <$>
NE.nonEmpty
[ ticketLoomID | (ticketLoomID, routes) <- cloths
, routeClothFollowers routes
]
)
loomsAndClothsForStages
-- Get addressed Actor IDs from DB
actorIDsForSelf <- orderedUnion <$> sequenceA
[ selectActorIDsOrdered personActor PersonActor personIDsForSelf
, selectActorIDsOrdered groupActor GroupActor groupIDsForSelf
, selectActorIDsOrdered repoActor RepoActor repoIDsForSelf
, selectActorIDsOrdered deckActor DeckActor deckIDsForSelf
, selectActorIDsOrdered loomActor LoomActor loomIDsForSelf
]
-- Get actor and work item FollowerSet IDs from DB
followerSetIDs <- do
actorIDs <- concat <$> sequenceA
[ selectActorIDs personActor personIDsForFollowers
, selectActorIDs repoActor repoIDsForFollowers
, selectActorIDs deckActor deckIDsForFollowers
, selectActorIDs loomActor loomIDsForFollowers
]
ticketIDs <-
concat <$>
((++)
<$> traverse
(selectTicketIDs ticketDeckTicket TicketDeckDeck)
ticketSetsForFollowers
<*> traverse
(selectTicketIDs ticketLoomTicket TicketLoomLoom)
clothSetsForFollowers
)
(++)
<$> (map (actorFollowers . entityVal) <$>
selectList [ActorId <-. actorIDs] []
)
<*> (map (ticketFollowers . entityVal) <$>
selectList [TicketId <-. ticketIDs] []
)
-- Get the local and remote followers of the follower sets from DB
localFollowers <-
map (followActor . entityVal) <$>
selectList [FollowTarget <-. followerSetIDs] [Asc FollowActor]
remoteFollowers <- getRemoteFollowers followerSetIDs
-- Insert inbox items to all local recipients, i.e. the local actors
-- directly addressed or listed in a local stage addressed
let localRecipients =
let allLocal = LO.union localFollowers actorIDsForSelf
in case maidAuthor of
Nothing -> allLocal
Just actorID -> LO.minus' allLocal [actorID]
inboxIDs <-
map (actorInbox . entityVal) <$>
selectList [ActorId <-. localRecipients] []
inboxItemIDs <- insertMany $ replicate (length inboxIDs) $ InboxItem True
insertMany_ $ zipWith makeInboxItem inboxIDs inboxItemIDs
-- Return remote followers, to whom we need to deliver via HTTP
return remoteFollowers
where
orderedUnion = foldl' LO.union []
unhashKeys
:: ( MonadSite m
, YesodHashids (SiteEnv m)
, ToBackendKey SqlBackend record
)
=> [(KeyHashid record, routes)]
-> m [(Key record, routes)]
unhashKeys actorSets = do
unhash <- decodeKeyHashidPure <$> asksSite siteHashidsContext
return $ mapMaybe (unhashKey unhash) actorSets
where
unhashKey unhash (hash, famili) = (,famili) <$> unhash hash
getIsAuthor =
case mauthor of
Nothing -> pure $ const False
Just author -> maybe (const False) (==) <$> unhashLocalActor author
allowStages
:: (LocalActorBy Key -> Bool)
-> (famili -> routes)
-> (routes -> Bool)
-> (Key record -> LocalActorBy Key)
-> (Key record, famili)
-> Bool
allowStages isAuthor familyActor routeActor makeActor (actorID, famili)
= routeActor (familyActor famili)
|| not requireOwner
|| isAuthor (makeActor actorID)
selectActorIDs
:: (MonadIO m, PersistRecordBackend record SqlBackend)
=> (record -> ActorId)
-> [Key record]
-> ReaderT SqlBackend m [ActorId]
selectActorIDs grabActor ids =
map (grabActor . entityVal) <$> selectList [persistIdField <-. ids] []
selectActorIDsOrdered
:: (MonadIO m, PersistRecordBackend record SqlBackend)
=> (record -> ActorId)
-> EntityField record ActorId
-> [Key record]
-> ReaderT SqlBackend m [ActorId]
selectActorIDsOrdered grabActor actorField ids =
map (grabActor . entityVal) <$> selectList [persistIdField <-. ids] [Asc actorField]
selectTicketIDs
:: ( MonadIO m
, PersistRecordBackend tracker SqlBackend
, PersistRecordBackend item SqlBackend
)
=> (item -> TicketId)
-> EntityField item (Key tracker)
-> (Key tracker, NonEmpty (Key item))
-> ReaderT SqlBackend m [TicketId]
selectTicketIDs grabTicket trackerField (trackerID, workItemIDs) = do
maybeTracker <- get trackerID
case maybeTracker of
Nothing -> pure []
Just _ ->
map (grabTicket . entityVal) <$>
selectList [persistIdField <-. NE.toList workItemIDs, trackerField ==. trackerID] []
getRemoteFollowers
:: MonadIO m
=> [FollowerSetId]
-> ReaderT SqlBackend m
[((InstanceId, Host), NonEmpty RemoteRecipient)]
getRemoteFollowers fsids =
fmap groupRemotes $
E.select $ E.from $ \ (rf `E.InnerJoin` ra `E.InnerJoin` ro `E.InnerJoin` i) -> do
E.on $ ro E.^. RemoteObjectInstance E.==. i E.^. InstanceId
E.on $ ra E.^. RemoteActorIdent E.==. ro E.^. RemoteObjectId
E.on $ rf E.^. RemoteFollowActor E.==. ra E.^. RemoteActorId
E.where_ $ rf E.^. RemoteFollowTarget `E.in_` E.valList fsids
E.orderBy [E.asc $ i E.^. InstanceId, E.asc $ ra E.^. RemoteActorId]
return
( i E.^. InstanceId
, i E.^. InstanceHost
, ra E.^. RemoteActorId
, ro E.^. RemoteObjectIdent
, ra E.^. RemoteActorInbox
, ra E.^. RemoteActorErrorSince
)
where
groupRemotes = groupWithExtractBy ((==) `on` fst) fst snd . map toTuples
where
toTuples (E.Value iid, E.Value h, E.Value raid, E.Value luA, E.Value luI, E.Value ms) = ((iid, h), RemoteRecipient raid luA luI ms)
-- | Given a list of local recipients, which may include actors and
-- collections,
--
-- * Insert activity to inboxes of actors
-- * If collections are listed, insert activity to the local members and return
-- the remote members
deliverLocal'
:: (MonadSite m, YesodHashids (SiteEnv m))
=> Bool -- ^ Whether to deliver to collection only if owner actor is addressed
-> LocalActor
-> ActorId
-> OutboxItemId
-> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
deliverLocal' requireOwner author aidAuthor obiid =
insertActivityToLocalInboxes makeItem requireOwner (Just author) (Just aidAuthor)
where
makeItem ibid ibiid = InboxItemLocal ibid obiid ibiid
-- | Given a list of local recipients, which may include actors and
-- collections,
--
-- * Insert activity to inboxes of actors
-- * If the author's follower collection is listed, insert activity to the
-- local members and return the remote members
-- * Ignore other collections
deliverLocal
:: KeyHashid Person
-> ActorId
-> OutboxItemId
-> RecipientRoutes
-> AppDB
[ ( (InstanceId, Host)
, NonEmpty RemoteRecipient
)
]
deliverLocal authorHash aidAuthor obiid
= deliverLocal' True (LocalActorPerson authorHash) aidAuthor obiid
. localRecipSieve sieve True
where
sieve = RecipientRoutes [(authorHash, PersonRoutes False True)] [] [] [] []
insertRemoteActivityToLocalInboxes
:: (MonadSite m, YesodHashids (SiteEnv m))
=> Bool
-> RemoteActivityId
-> RecipientRoutes
-> ReaderT SqlBackend m [((InstanceId, Host), NonEmpty RemoteRecipient)]
insertRemoteActivityToLocalInboxes requireOwner ractid =
insertActivityToLocalInboxes makeItem requireOwner Nothing Nothing
where
makeItem ibid ibiid = InboxItemRemote ibid ractid ibiid

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -18,9 +18,18 @@ module Vervis.Discussion
, MessageTreeNode (..)
, getDiscussionTree
, getRepliesCollection
, NoteTopic (..)
, NoteParent (..)
, parseNoteContext
, parseNoteParent
, getLocalParentMessageId
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Data.Graph.Inductive.Graph (mkGraph, lab')
import Data.Graph.Inductive.PatriciaTree (Gr)
import Data.Graph.Inductive.Query.DFS (dffWith)
@ -39,7 +48,9 @@ import Web.ActivityPub
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Control.Monad.Trans.Except.Local
import Data.Tree.Local (sortForestOn)
import Vervis.FedURI
@ -47,7 +58,7 @@ import Vervis.Foundation
import Vervis.Model
data MessageTreeNodeAuthor
= MessageTreeNodeLocal LocalMessageId Sharer
= MessageTreeNodeLocal LocalMessageId PersonId
| MessageTreeNodeRemote Host LocalURI LocalURI (Maybe Text)
data MessageTreeNode = MessageTreeNode
@ -59,12 +70,10 @@ data MessageTreeNode = MessageTreeNode
getMessages :: AppDB DiscussionId -> Handler [MessageTreeNode]
getMessages getdid = runDB $ do
did <- getdid
l <- select $ from $ \ (lm `InnerJoin` m `InnerJoin` p `InnerJoin` s) -> do
on $ p ^. PersonIdent ==. s ^. SharerId
on $ lm ^. LocalMessageAuthor ==. p ^. PersonId
l <- select $ from $ \ (lm `InnerJoin` m) -> do
on $ lm ^. LocalMessageRest ==. m ^. MessageId
where_ $ m ^. MessageRoot ==. val did
return (m, lm ^. LocalMessageId, s)
return (m, lm ^. LocalMessageId, lm ^. LocalMessageAuthor)
r <- select $ from $ \ (rm `InnerJoin` m `InnerJoin` ra `InnerJoin` ro `InnerJoin` i `InnerJoin` ro2) -> do
on $ rm ^. RemoteMessageIdent ==. ro2 ^. RemoteObjectId
on $ ro ^. RemoteObjectInstance ==. i ^. InstanceId
@ -81,8 +90,8 @@ getMessages getdid = runDB $ do
)
return $ map mklocal l ++ map mkremote r
where
mklocal (Entity mid m, Value lmid, Entity _ s) =
MessageTreeNode mid m $ MessageTreeNodeLocal lmid s
mklocal (Entity mid m, Value lmid, Value pid) =
MessageTreeNode mid m $ MessageTreeNodeLocal lmid pid
mkremote (Entity mid m, Value h, Value luMsg, Value luAuthor, Value name) =
MessageTreeNode mid m $ MessageTreeNodeRemote h luMsg luAuthor name
@ -120,7 +129,8 @@ getRepliesCollection here getDiscussionId404 = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeHid <- getEncodeKeyHashid
let localUri' = localUri encodeRouteHome encodeHid
hashPerson <- getEncodeKeyHashid
let localUri' = localUri hashPerson encodeRouteHome encodeHid
replies = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
@ -135,15 +145,13 @@ getRepliesCollection here getDiscussionId404 = do
where
selectLocals did =
E.select $ E.from $
\ (m `E.InnerJoin` lm `E.InnerJoin` p `E.InnerJoin` s) -> do
E.on $ p E.^. PersonIdent E.==. s E.^. SharerId
E.on $ lm E.^. LocalMessageAuthor E.==. p E.^. PersonId
\ (m `E.InnerJoin` lm) -> do
E.on $ m E.^. MessageId E.==. lm E.^. LocalMessageRest
E.where_ $
m E.^. MessageRoot E.==. E.val did E.&&.
E.isNothing (m E.^. MessageParent) E.&&.
E.isNothing (lm E.^. LocalMessageUnlinkedParent)
return (s E.^. SharerIdent, lm E.^. LocalMessageId)
return (lm E.^. LocalMessageAuthor, lm E.^. LocalMessageId)
selectRemotes did =
E.select $ E.from $
\ (m `E.InnerJoin` rm `E.InnerJoin` ro `E.InnerJoin` i) -> do
@ -155,6 +163,81 @@ getRepliesCollection here getDiscussionId404 = do
E.isNothing (m E.^. MessageParent) E.&&.
E.isNothing (rm E.^. RemoteMessageLostParent)
return (i E.^. InstanceHost, ro E.^. RemoteObjectIdent)
localUri encR encH (E.Value shrAuthor, E.Value lmid) =
encR $ MessageR shrAuthor (encH lmid)
localUri hashPerson encR encH (E.Value pid, E.Value lmid) =
encR $ PersonMessageR (hashPerson pid) (encH lmid)
remoteUri (E.Value h, E.Value lu) = ObjURI h lu
data NoteTopic
= NoteTopicTicket DeckId TicketDeckId
| NoteTopicCloth LoomId TicketLoomId
deriving Eq
parseNoteTopic (TicketR dkhid ltkhid) =
NoteTopicTicket
<$> decodeKeyHashidE dkhid "Note context invalid dkhid"
<*> decodeKeyHashidE ltkhid "Note context invalid ltkhid"
parseNoteTopic (ClothR lkhid ltkhid) =
NoteTopicCloth
<$> decodeKeyHashidE lkhid "Note context invalid lkhid"
<*> decodeKeyHashidE ltkhid "Note context invalid ltkhid"
parseNoteTopic _ = throwE "Local context isn't a ticket/cloth route"
parseNoteContext
:: (MonadSite m, SiteEnv m ~ App)
=> FedURI
-> ExceptT Text m (Either NoteTopic FedURI)
parseNoteContext uContext = do
let ObjURI hContext luContext = uContext
local <- hostIsLocal hContext
if local
then Left <$> do
route <-
fromMaybeE
(decodeRouteLocal luContext)
"Local context isn't a valid route"
parseNoteTopic route
else return $ Right uContext
data NoteParent
= NoteParentMessage PersonId LocalMessageId
| NoteParentTopic NoteTopic
deriving Eq
parseNoteParent
:: (MonadSite m, SiteEnv m ~ App)
=> FedURI
-> ExceptT Text m (Either NoteParent FedURI)
parseNoteParent uParent = do
let ObjURI hParent luParent = uParent
local <- hostIsLocal hParent
if local
then Left <$> do
route <-
fromMaybeE
(decodeRouteLocal luParent)
"Local parent isn't a valid route"
(<|>)
(uncurry NoteParentMessage <$> parseNoteID route)
(NoteParentTopic <$> parseNoteTopic route)
else return $ Right uParent
where
parseNoteID (PersonMessageR pkhid lmkhid) =
(,) <$> decodeKeyHashidE pkhid
"Local parent has non-existent person hashid"
<*> decodeKeyHashidE lmkhid
"Local parent has non-existent message hashid"
parseNoteID _ = throwE "Local parent isn't a message route"
getLocalParentMessageId :: DiscussionId -> PersonId -> LocalMessageId -> ExceptT Text AppDB MessageId
getLocalParentMessageId did pid lmid = do
mp <- lift $ get pid
_ <- fromMaybeE mp "Local parent: no such pid"
mlm <- lift $ get lmid
lm <- fromMaybeE mlm "Local parent: no such lmid"
unless (localMessageAuthor lm == pid) $ throwE "Local parent: No such message, lmid mismatches pid"
let mid = localMessageRest lm
m <- lift $ getJust mid
unless (messageRoot m == did) $
throwE "Local parent belongs to a different discussion"
return mid

View file

@ -13,11 +13,21 @@
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- These are for Barbie-related generated instances for ForwarderBy
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
--{-# LANGUAGE StandaloneDeriving #-}
--{-# LANGUAGE UndecidableInstances #-}
module Vervis.Federation
( handleSharerInbox
, handleProjectInbox
(
{-
handlePersonInbox
, handleDeckInbox
, handleLoomInbox
, handleRepoInbox
, fixRunningDeliveries
-}
fixRunningDeliveries
, retryOutboxDelivery
)
where
@ -33,6 +43,7 @@ import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Crypto.Hash
import Data.Aeson
import Data.Barbie
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.Either
@ -50,6 +61,7 @@ import Data.Traversable
import Data.Tuple
import Database.Persist hiding (deleteBy)
import Database.Persist.Sql hiding (deleteBy)
import GHC.Generics
import Network.HTTP.Client
import Network.HTTP.Types.Header
import Network.HTTP.Types.URI
@ -95,45 +107,22 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActivityPub.Recipient
import Vervis.ActorKey
import Vervis.Delivery
import Vervis.Federation.Auth
import Vervis.Federation.Discussion
import Vervis.Federation.Offer
import Vervis.Federation.Push
import Vervis.Federation.Ticket
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Recipient
import Vervis.RemoteActorStore
import Vervis.Settings
prependError :: Monad m => Text -> ExceptT Text m a -> ExceptT Text m a
prependError t a = do
r <- lift $ runExceptT a
case r of
Left e -> throwE $ t <> ": " <> e
Right x -> return x
parseTicket :: Monad m => (ShrIdent, PrjIdent) -> LocalURI -> ExceptT Text m (KeyHashid LocalTicket)
parseTicket project luContext = do
route <- case decodeRouteLocal luContext of
Nothing -> throwE "Local context isn't a valid route"
Just r -> return r
case route of
ProjectTicketR shr prj num ->
if (shr, prj) == project
then return num
else throwE "Local context ticket doesn't belong to the recipient project"
_ -> throwE "Local context isn't a ticket route"
handleSharerInbox
:: ShrIdent
-> UTCTime
{-
handlePersonInbox
:: KeyHashid Person
-> ActivityAuthentication
-> ActivityBody
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handleSharerInbox shrRecip _now (ActivityAuthLocal (ActivityAuthLocalPerson pidAuthor)) body = (,Nothing) <$> do
handlePersonInbox recipHash (ActivityAuthLocal (LocalActorPerson pidAuthor)) body = (,Nothing) <$> do
(shrActivity, obiid) <- do
luAct <-
fromMaybeE
@ -274,7 +263,7 @@ handleSharerInbox shrRecip now (ActivityAuthRemote author) body = do
localRecips <- do
mrecips <- parseAudience $ activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
msig <- checkForward $ LocalActorSharer shrRecip
msig <- checkForwarding $ LocalActorSharer shrRecip
let mfwd = (localRecips,) <$> msig
case activitySpecific $ actbActivity body of
AcceptActivity accept ->
@ -327,7 +316,58 @@ handleProjectInbox shrRecip prjRecip now auth body = do
localRecips <- do
mrecips <- parseAudience $ activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
msig <- checkForward $ LocalActorProject shrRecip prjRecip
msig <- checkForwarding $ LocalActorProject shrRecip prjRecip
let mfwd = (localRecips,) <$> msig
case activitySpecific $ actbActivity body of
CreateActivity (Create obj mtarget) ->
case obj of
CreateNote _ note ->
(,Nothing) <$> projectCreateNoteF now shrRecip prjRecip remoteAuthor body mfwd luActivity note
CreateTicket _ ticket ->
(,Nothing) <$> projectCreateTicketF now shrRecip prjRecip remoteAuthor body mfwd luActivity ticket mtarget
_ -> error "Unsupported create object type for projects"
FollowActivity follow ->
(,Nothing) <$> projectFollowF shrRecip prjRecip now remoteAuthor body mfwd luActivity follow
OfferActivity (Offer obj target) ->
case obj of
OfferTicket ticket ->
(,Nothing) <$> projectOfferTicketF now shrRecip prjRecip remoteAuthor body mfwd luActivity ticket target
OfferDep dep ->
projectOfferDepF now shrRecip prjRecip remoteAuthor body mfwd luActivity dep target
_ -> return ("Unsupported offer object type for projects", Nothing)
ResolveActivity resolve ->
(,Nothing) <$> projectResolveF now shrRecip prjRecip remoteAuthor body mfwd luActivity resolve
UndoActivity undo ->
(,Nothing) <$> projectUndoF shrRecip prjRecip now remoteAuthor body mfwd luActivity undo
_ -> return ("Unsupported activity type for projects", Nothing)
where
errorLocalForwarded (ActivityAuthLocalPerson pid) =
"Project inbox got local forwarded activity by pid#" <>
T.pack (show $ fromSqlKey pid)
errorLocalForwarded (ActivityAuthLocalProject jid) =
"Project inbox got local forwarded activity by jid#" <>
T.pack (show $ fromSqlKey jid)
errorLocalForwarded (ActivityAuthLocalRepo rid) =
"Project inbox got local forwarded activity by rid#" <>
T.pack (show $ fromSqlKey rid)
handleDeckInbox
:: KeyHashid Project
-> UTCTime
-> ActivityAuthentication
-> ActivityBody
-> ExceptT Text Handler (Text, Maybe (ExceptT Text Worker Text))
handleDeckInbox dkkhid now auth body = do
remoteAuthor <-
case auth of
ActivityAuthLocal local -> throwE $ errorLocalForwarded local
ActivityAuthRemote ra -> return ra
luActivity <-
fromMaybeE (activityId $ actbActivity body) "Activity without 'id'"
localRecips <- do
mrecips <- parseAudience $ activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
msig <- checkForwarding $ LocalActorProject shrRecip prjRecip
let mfwd = (localRecips,) <$> msig
case activitySpecific $ actbActivity body of
CreateActivity (Create obj mtarget) ->
@ -379,7 +419,7 @@ handleRepoInbox shrRecip rpRecip now auth body = do
localRecips <- do
mrecips <- parseAudience $ activityAudience $ actbActivity body
paudLocalRecips <$> fromMaybeE mrecips "Activity with no recipients"
msig <- checkForward $ LocalActorRepo shrRecip rpRecip
msig <- checkForwarding $ LocalActorRepo shrRecip rpRecip
let mfwd = (localRecips,) <$> msig
case activitySpecific $ actbActivity body of
ApplyActivity (AP.Apply uObject uTarget) ->
@ -420,6 +460,7 @@ handleRepoInbox shrRecip rpRecip now auth body = do
errorLocalForwarded (ActivityAuthLocalRepo rid) =
"Repo inbox got local forwarded activity by rid#" <>
T.pack (show $ fromSqlKey rid)
-}
fixRunningDeliveries :: (MonadIO m, MonadLogger m, IsSqlBackend backend) => ReaderT backend m ()
fixRunningDeliveries = do
@ -442,23 +483,38 @@ fixRunningDeliveries = do
, " forwarding deliveries"
]
data Fwder
= FwderProject ForwarderProjectId
| FwderSharer ForwarderSharerId
| FwderRepo ForwarderRepoId
data ForwarderBy f
= FwderPerson (f ForwarderPerson)
| FwderGroup (f ForwarderGroup)
| FwderRepo (f ForwarderRepo)
| FwderDeck (f ForwarderDeck)
| FwderLoom (f ForwarderLoom)
deriving (Generic, FunctorB, ConstraintsB)
partitionFwders :: [Fwder] -> ([ForwarderProjectId], [ForwarderSharerId], [ForwarderRepoId])
partitionFwders = foldl' f ([], [], [])
partitionFwders
:: [ForwarderBy f]
-> ( [f ForwarderPerson]
, [f ForwarderGroup]
, [f ForwarderRepo]
, [f ForwarderDeck]
, [f ForwarderLoom]
)
partitionFwders = foldl' f ([], [], [], [], [])
where
f (js, ss, rs) (FwderProject j) = (j : js, ss , rs)
f (js, ss, rs) (FwderSharer s) = (js , s : ss, rs)
f (js, ss, rs) (FwderRepo r) = (js , ss , r : rs)
f (ps, gs, rs, ds, ls) = \ fwder ->
case fwder of
FwderPerson p -> (p : ps, gs, rs, ds, ls)
FwderGroup g -> (ps, g : gs, rs, ds, ls)
FwderRepo r -> (ps, gs, r : rs, ds, ls)
FwderDeck d -> (ps, gs, rs, d : ds, ls)
FwderLoom l -> (ps, gs, rs, ds, l : ls)
retryOutboxDelivery :: Worker ()
retryOutboxDelivery = do
logInfo "Periodic delivery starting"
now <- liftIO $ getCurrentTime
(udls, dls, fws) <- runSiteDB $ do
(unlinkedHttp, linkedHttp, forwardingHttp) <- runSiteDB $ do
-- Get all unlinked deliveries which aren't running already in outbox
-- post handlers
unlinked' <- E.select $ E.from $ \ (udl `E.InnerJoin` ob `E.InnerJoin` ura `E.InnerJoin` ro `E.InnerJoin` i `E.LeftOuterJoin` ra `E.LeftOuterJoin` rc) -> do
@ -483,21 +539,27 @@ retryOutboxDelivery = do
, ra E.?. RemoteActorId
, rc E.?. RemoteCollectionId
)
-- Strip the E.Value wrappers and organize the records for the
-- filtering and grouping we'll need to do
let unlinked = map adaptUnlinked unlinked'
-- Split into found (recipient has been reached) and lonely (recipient
-- hasn't been reached
(found, lonely) = partitionMaybes unlinked
-- Turn the found ones into linked deliveries
deleteWhere [UnlinkedDeliveryId <-. map (unlinkedID . snd) found]
insertMany_ $ mapMaybe toLinked found
-- We're left with the lonely ones. We'll check which actors have been
-- unreachable for too long, and we'll delete deliveries for them. The
-- rest of the actors we'll try to reach by HTTP.
dropAfter <- lift $ asksSite $ appDropDeliveryAfter . appSettings
let (lonelyOld, lonelyNew) = partitionEithers $ map (decideBySinceUDL dropAfter now) lonely
let (lonelyOld, lonelyNew) =
partitionEithers $ map (decideBySinceUDL dropAfter now) lonely
deleteWhere [UnlinkedDeliveryId <-. lonelyOld]
-- Now let's grab the linked deliveries, and similarly delete old ones
-- and return the rest for HTTP delivery.
linked <- E.select $ E.from $ \ (dl `E.InnerJoin` ra `E.InnerJoin` ro `E.InnerJoin` i `E.InnerJoin` ob) -> do
@ -518,73 +580,68 @@ retryOutboxDelivery = do
, dl E.^. DeliveryForwarding
, ob E.^. OutboxItemActivity
)
let (linkedOld, linkedNew) = partitionEithers $ map (decideBySinceDL dropAfter now . adaptLinked) linked
let (linkedOld, linkedNew) =
partitionEithers $
map (decideBySinceDL dropAfter now . adaptLinked) linked
deleteWhere [DeliveryId <-. linkedOld]
-- Same for forwarding deliveries, which are always linked
forwarding <- E.select $ E.from $ \ (fw `E.InnerJoin` ra `E.InnerJoin` ro `E.InnerJoin` i `E.LeftOuterJoin` (fwj `E.InnerJoin` j `E.InnerJoin` s) `E.LeftOuterJoin` (fws `E.InnerJoin` s2) `E.LeftOuterJoin` (fwr `E.InnerJoin` r `E.InnerJoin` s3)) -> do
E.on $ r E.?. RepoSharer E.==. s3 E.?. SharerId
E.on $ fwr E.?. ForwarderRepoSender E.==. r E.?. RepoId
forwarding <- E.select $ E.from $
\ (fw `E.InnerJoin` ra `E.InnerJoin` ro `E.InnerJoin` i
`E.LeftOuterJoin` fwp
`E.LeftOuterJoin` fwg
`E.LeftOuterJoin` fwr
`E.LeftOuterJoin` fwd
`E.LeftOuterJoin` fwl
) -> do
E.on $ E.just (fw E.^. ForwardingId) E.==. fwl E.?. ForwarderLoomTask
E.on $ E.just (fw E.^. ForwardingId) E.==. fwd E.?. ForwarderDeckTask
E.on $ E.just (fw E.^. ForwardingId) E.==. fwr E.?. ForwarderRepoTask
E.on $ fws E.?. ForwarderSharerSender E.==. s2 E.?. SharerId
E.on $ E.just (fw E.^. ForwardingId) E.==. fws E.?. ForwarderSharerTask
E.on $ j E.?. ProjectSharer E.==. s E.?. SharerId
E.on $ fwj E.?. ForwarderProjectSender E.==. j E.?. ProjectId
E.on $ E.just (fw E.^. ForwardingId) E.==. fwj E.?. ForwarderProjectTask
E.on $ E.just (fw E.^. ForwardingId) E.==. fwg E.?. ForwarderGroupTask
E.on $ E.just (fw E.^. ForwardingId) E.==. fwp E.?. ForwarderPersonTask
E.on $ ro E.^. RemoteObjectInstance E.==. i E.^. InstanceId
E.on $ ra E.^. RemoteActorIdent E.==. ro E.^. RemoteObjectId
E.on $ fw E.^. ForwardingRecipient E.==. ra E.^. RemoteActorId
E.where_ $ fw E.^. ForwardingRunning E.==. E.val False
E.orderBy [E.asc $ ro E.^. RemoteObjectInstance, E.asc $ ra E.^. RemoteActorId]
return
( i E.^. InstanceId
, i E.^. InstanceHost
, ra E.^. RemoteActorId
, ra E.^. RemoteActorInbox
, ra E.^. RemoteActorErrorSince
, fw E.^. ForwardingId
, fw E.^. ForwardingActivityRaw
, fwj E.?. ForwarderProjectId
, s E.?. SharerIdent
, j E.?. ProjectIdent
, fws E.?. ForwarderSharerId
, s2 E.?. SharerIdent
, fwr E.?. ForwarderRepoId
, s3 E.?. SharerIdent
, r E.?. RepoIdent
, fw E.^. ForwardingSignature
)
let (forwardingOld, forwardingNew) = partitionEithers $ map (decideBySinceFW dropAfter now . adaptForwarding) forwarding
return (i, ra, fw, fwp, fwg, fwr, fwd, fwl)
let (forwardingOld, forwardingNew) =
partitionEithers $
map (decideBySinceFW dropAfter now . adaptForwarding)
forwarding
(fwidsOld, fwdersOld) = unzip forwardingOld
(fwjidsOld, fwsidsOld, fwridsOld) = partitionFwders fwdersOld
deleteWhere [ForwarderProjectId <-. fwjidsOld]
deleteWhere [ForwarderSharerId <-. fwsidsOld]
(fwpidsOld, fwgidsOld, fwridsOld, fwdidsOld, fwlidsOld) =
partitionFwders fwdersOld
deleteWhere [ForwarderPersonId <-. fwpidsOld]
deleteWhere [ForwarderGroupId <-. fwgidsOld]
deleteWhere [ForwarderRepoId <-. fwridsOld]
deleteWhere [ForwarderDeckId <-. fwdidsOld]
deleteWhere [ForwarderLoomId <-. fwlidsOld]
deleteWhere [ForwardingId <-. fwidsOld]
return (groupUnlinked lonelyNew, groupLinked linkedNew, groupForwarding forwardingNew)
return
( groupUnlinked lonelyNew
, groupLinked linkedNew
, groupForwarding forwardingNew
)
let deliver = deliverHttpBL
logInfo "Periodic delivery prepared DB, starting async HTTP POSTs"
logDebug $
"Periodic delivery forking linked " <>
T.pack (show $ map (renderAuthority . snd . fst) dls)
waitsDL <- traverse (fork . deliverLinked deliver now) dls
T.pack (show $ map (renderAuthority . snd . fst) linkedHttp)
waitsDL <- traverse (fork . deliverLinked deliver now) linkedHttp
logDebug $
"Periodic delivery forking forwarding " <>
T.pack (show $ map (renderAuthority . snd . fst) fws)
waitsFW <- traverse (fork . deliverForwarding now) fws
T.pack (show $ map (renderAuthority . snd . fst) forwardingHttp)
waitsFW <- traverse (fork . deliverForwarding now) forwardingHttp
logDebug $
"Periodic delivery forking unlinked " <>
T.pack (show $ map (renderAuthority . snd . fst) udls)
waitsUDL <- traverse (fork . deliverUnlinked deliver now) udls
T.pack (show $ map (renderAuthority . snd . fst) unlinkedHttp)
waitsUDL <- traverse (fork . deliverUnlinked deliver now) unlinkedHttp
logDebug $
T.concat
@ -621,10 +678,14 @@ retryOutboxDelivery = do
, since
)
)
unlinkedID ((_, (_, (udlid, _, _, _))), _) = udlid
toLinked (Left raid, ((_, (_, (_, fwd, obid, _))), _)) = Just $ Delivery raid obid fwd False
toLinked (Right _ , _ ) = Nothing
relevant dropAfter now since = addUTCTime dropAfter since > now
decideBySinceUDL dropAfter now (udl@(_, (_, (udlid, _, _, _))), msince) =
case msince of
Nothing -> Right udl
@ -632,9 +693,7 @@ retryOutboxDelivery = do
if relevant dropAfter now since
then Right udl
else Left udlid
groupUnlinked
= map (second $ groupWithExtractBy1 ((==) `on` fst) fst snd)
. groupWithExtractBy ((==) `on` fst) fst snd
adaptLinked
(E.Value iid, E.Value h, E.Value raid, E.Value ident, E.Value inbox, E.Value since, E.Value dlid, E.Value fwd, E.Value act) =
( ( (iid, h)
@ -642,6 +701,7 @@ retryOutboxDelivery = do
)
, since
)
decideBySinceDL dropAfter now (dl@(_, (_, (dlid, _, _))), msince) =
case msince of
Nothing -> Right dl
@ -649,56 +709,58 @@ retryOutboxDelivery = do
if relevant dropAfter now since
then Right dl
else Left dlid
groupLinked
= map (second $ groupWithExtractBy1 ((==) `on` fst) fst snd)
. groupWithExtractBy ((==) `on` fst) fst snd
adaptForwarding
( E.Value iid, E.Value h, E.Value raid, E.Value inbox, E.Value since
, E.Value fwid, E.Value body
, E.Value mfwjid, E.Value mprj, E.Value mshr
, E.Value mfwsid, E.Value mshr2
, E.Value mfwrid, E.Value mrp, E.Value mshr3
, E.Value sig
( Entity iid (Instance h)
, Entity raid (RemoteActor _ _ inbox _ since)
, Entity fwid (Forwarding _ _ body sig _)
, mfwp, mfwg, mfwr, mfwd, mfwl
) =
( ( (iid, h)
, ( (raid, inbox)
, ( fwid
, BL.fromStrict body
, let project = together3 mfwjid mprj mshr
sharer = together2 mfwsid mshr2
repo = together3 mfwrid mrp mshr3
in case (project, sharer, repo) of
(Just (fwjid, shr, prj), Nothing, Nothing) ->
(FwderProject fwjid, ProjectR shr prj)
(Nothing, Just (fwsid, shr), Nothing) ->
(FwderSharer fwsid, SharerR shr)
(Nothing, Nothing, Just (fwrid, shr, rp)) ->
(FwderRepo fwrid, RepoR shr rp)
_ -> error $ "Non-single fwder for fw#" ++ show fwid
, case (mfwp, mfwg, mfwr, mfwd, mfwl) of
(Nothing, Nothing, Nothing, Nothing, Nothing) ->
error "Found fwid without a Forwarder* record"
(Just fwp, Nothing, Nothing, Nothing, Nothing) ->
FwderPerson fwp
(Nothing, Just fwg, Nothing, Nothing, Nothing) ->
FwderGroup fwg
(Nothing, Nothing, Just fwr, Nothing, Nothing) ->
FwderRepo fwr
(Nothing, Nothing, Nothing, Just fwd, Nothing) ->
FwderDeck fwd
(Nothing, Nothing, Nothing, Nothing, Just fwl) ->
FwderLoom fwl
_ -> error "Found fwid with multiple forwarders"
, sig
)
)
)
, since
)
where
together2 (Just x) (Just y) = Just (x, y)
together2 Nothing Nothing = Nothing
together2 _ _ = error $ "Got weird forwarder for fw#" ++ show fwid
together3 :: Maybe a -> Maybe b -> Maybe c -> Maybe (a, b, c)
together3 (Just x) (Just y) (Just z) = Just (x, y, z)
together3 Nothing Nothing Nothing = Nothing
together3 _ _ _ = error $ "Got weird forwarder for fw#" ++ show fwid
decideBySinceFW dropAfter now (fw@(_, (_, (fwid, _, (fwder, _), _))), msince) =
decideBySinceFW dropAfter now (fw@(_, (_, (fwid, _, fwder, _))), msince) =
case msince of
Nothing -> Right fw
Just since ->
if relevant dropAfter now since
then Right fw
else Left (fwid, fwder)
else Left (fwid, bmap entityKey fwder)
groupUnlinked
= map (second $ groupWithExtractBy1 ((==) `on` fst) fst snd)
. groupWithExtractBy ((==) `on` fst) fst snd
groupLinked
= map (second $ groupWithExtractBy1 ((==) `on` fst) fst snd)
. groupWithExtractBy ((==) `on` fst) fst snd
groupForwarding
= map (second $ groupWithExtractBy1 ((==) `on` fst) fst snd)
. groupWithExtractBy ((==) `on` fst) fst snd
fork action = do
wait <- asyncWorker action
return $ do
@ -708,6 +770,7 @@ retryOutboxDelivery = do
logError $ "Periodic delivery error! " <> T.pack (displayException e)
return False
Right success -> return success
deliverLinked deliver now ((_, h), recips) = do
logDebug $ "Periodic deliver starting linked for host " <> renderAuthority h
waitsR <- for recips $ \ ((raid, (ident, inbox)), delivs) -> fork $ do
@ -740,6 +803,7 @@ retryOutboxDelivery = do
unless (and results) $
logError $ "Periodic DL delivery error for host " <> renderAuthority h
return True
deliverUnlinked deliver now ((iid, h), recips) = do
logDebug $ "Periodic deliver starting unlinked for host " <> renderAuthority h
waitsR <- for recips $ \ ((uraid, luRecip), delivs) -> fork $ do
@ -777,22 +841,27 @@ retryOutboxDelivery = do
unless (and results) $
logError $ "Periodic UDL delivery error for host " <> renderAuthority h
return True
deliverForwarding now ((_, h), recips) = do
logDebug $ "Periodic deliver starting forwarding for host " <> renderAuthority h
waitsR <- for recips $ \ ((raid, inbox), delivs) -> fork $ do
logDebug $
"Periodic deliver starting forwarding for inbox " <>
renderObjURI (ObjURI h inbox)
waitsD <- for delivs $ \ (fwid, body, (fwder, sender), sig) -> fork $ do
waitsD <- for delivs $ \ (fwid, body, fwderE, sig) -> fork $ do
let (fwderK, senderK) = splitForwarder fwderE
sender <- renderLocalActor <$> hashLocalActor senderK
e <- forwardActivity (ObjURI h inbox) sig sender body
case e of
Left _err -> return False
Right _resp -> do
runSiteDB $ do
case fwder of
FwderProject k -> delete k
FwderSharer k -> delete k
case fwderK of
FwderPerson k -> delete k
FwderGroup k -> delete k
FwderRepo k -> delete k
FwderDeck k -> delete k
FwderLoom k -> delete k
delete fwid
return True
results <- sequence waitsD
@ -807,3 +876,14 @@ retryOutboxDelivery = do
unless (and results) $
logError $ "Periodic FW delivery error for host " <> renderAuthority h
return True
where
splitForwarder (FwderPerson (Entity f (ForwarderPerson _ p))) =
(FwderPerson f, LocalActorPerson p)
splitForwarder (FwderGroup (Entity f (ForwarderGroup _ g))) =
(FwderGroup f, LocalActorGroup g)
splitForwarder (FwderRepo (Entity f (ForwarderRepo _ r))) =
(FwderRepo f, LocalActorRepo r)
splitForwarder (FwderDeck (Entity f (ForwarderDeck _ d))) =
(FwderDeck f, LocalActorDeck d)
splitForwarder (FwderLoom (Entity f (ForwarderLoom _ l))) =
(FwderLoom f, LocalActorLoom l)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -15,10 +15,10 @@
module Vervis.Federation.Auth
( RemoteAuthor (..)
, ActivityAuthenticationLocal (..)
, ActivityAuthentication (..)
, ActivityBody (..)
, authenticateActivity
, checkForwarding
)
where
@ -94,12 +94,12 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActivityPub.Recipient
import Vervis.ActorKey
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Recipient
import Vervis.RemoteActorStore
import Vervis.Settings
@ -109,13 +109,8 @@ data RemoteAuthor = RemoteAuthor
, remoteAuthorId :: RemoteActorId
}
data ActivityAuthenticationLocal
= ActivityAuthLocalPerson PersonId
| ActivityAuthLocalProject ProjectId
| ActivityAuthLocalRepo RepoId
data ActivityAuthentication
= ActivityAuthLocal ActivityAuthenticationLocal
= ActivityAuthLocal (LocalActorBy Key)
| ActivityAuthRemote RemoteAuthor
data ActivityBody = ActivityBody
@ -271,7 +266,7 @@ verifySelfSig
-> LocalRefURI
-> ByteString
-> Signature
-> ExceptT String Handler ActivityAuthenticationLocal
-> ExceptT String Handler (LocalActorBy Key)
verifySelfSig luAuthor (LocalRefURI lruKey) input (Signature sig) = do
author <- do
route <-
@ -299,22 +294,25 @@ verifySelfSig luAuthor (LocalRefURI lruKey) input (Signature sig) = do
ExceptT . pure $ verifySignature (actorKeyPublicBin akey) input sig
unless valid $
throwE "Self sig verification says not valid"
ExceptT $ runDB $ do
mauthorId <- runMaybeT $ getLocalActor author
return $
case mauthorId of
Nothing -> Left "Local author: No such user/project"
Just id_ -> Right id_
localAuth <- unhashLocalActorE author "No such actor"
withExceptT T.unpack $ runDBExcept $ findLocalAuthInDB localAuth
return localAuth
where
getLocalActor (LocalActorSharer shr) = do
sid <- MaybeT $ getKeyBy $ UniqueSharer shr
ActivityAuthLocalPerson <$> MaybeT (getKeyBy $ UniquePersonIdent sid)
getLocalActor (LocalActorProject shr prj) = do
sid <- MaybeT $ getKeyBy $ UniqueSharer shr
ActivityAuthLocalProject <$> MaybeT (getKeyBy $ UniqueProject prj sid)
getLocalActor (LocalActorRepo shr rp) = do
sid <- MaybeT $ getKeyBy $ UniqueSharer shr
ActivityAuthLocalRepo <$> MaybeT (getKeyBy $ UniqueRepo rp sid)
findLocalAuthInDB (LocalActorPerson pid) = do
mp <- lift $ get pid
when (isNothing mp) $ throwE "No such person"
findLocalAuthInDB (LocalActorGroup gid) = do
mg <- lift $ get gid
when (isNothing mg) $ throwE "No such group"
findLocalAuthInDB (LocalActorRepo rid) = do
mr <- lift $ get rid
when (isNothing mr) $ throwE "No such repo"
findLocalAuthInDB (LocalActorDeck did) = do
md <- lift $ get did
when (isNothing md) $ throwE "No such deck"
findLocalAuthInDB (LocalActorLoom lid) = do
ml <- lift $ get lid
when (isNothing ml) $ throwE "No such loom"
verifyForwardedSig
:: Host
@ -413,3 +411,31 @@ authenticateActivity now = do
case parseObjURI =<< (first displayException . decodeUtf8') fwd of
Left e -> throwE $ "ActivityPub-Forwarder isn't a valid FedURI: " <> T.pack e
Right u -> return u
checkForwarding recip = join <$> do
let hSig = hForwardingSignature
msig <- maybeHeader hSig
for msig $ \ sig -> do
_proof <- withExceptT (T.pack . displayException) $ ExceptT $
let requires = [hDigest, hActivityPubForwarder]
in prepareToVerifyHttpSigWith hSig False requires [] Nothing
forwarder <- requireHeader hActivityPubForwarder
renderUrl <- getUrlRender
return $
if forwarder == encodeUtf8 (renderUrl $ renderLocalActor recip)
then Just sig
else Nothing
where
maybeHeader n = do
let n' = decodeUtf8 $ CI.original n
hs <- lookupHeaders n
case hs of
[] -> return Nothing
[h] -> return $ Just h
_ -> throwE $ n' <> " multiple headers found"
requireHeader n = do
let n' = decodeUtf8 $ CI.original n
mh <- maybeHeader n
case mh of
Nothing -> throwE $ n' <> " header not found"
Just h -> return h

View file

@ -65,13 +65,13 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActivityPub.Recipient
import Vervis.FedURI
import Vervis.Federation.Auth
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Recipient
import Vervis.Settings
import Vervis.Ticket
import Vervis.Patch
@ -209,14 +209,20 @@ updateOrphans author luNote did mid = do
sharerCreateNoteF
:: UTCTime
-> ShrIdent
-> PersonId
-> RemoteAuthor
-> ActivityBody
-> Maybe (LocalRecipientSet, ByteString)
-> LocalURI
-> Note URIMode
-> ExceptT Text Handler Text
sharerCreateNoteF now shrRecip author body mfwd luCreate note = do
sharerCreateNoteF now pidRecip author body mfwd luCreate note = do
error "sharerCreateF temporarily disabled"
{-
(luNote, published, context, mparent, source, content) <- checkNote note
case context of
Right uContext -> runDBExcept $ do
@ -338,18 +344,24 @@ sharerCreateNoteF now shrRecip author body mfwd luCreate note = do
did <- fromMaybeE mdid "Remote parent known, but no context RemoteDiscussion"
unless (messageRoot m == did) $
throwE "Remote parent belongs to a different discussion"
-}
projectCreateNoteF
:: UTCTime
-> ShrIdent
-> PrjIdent
-> KeyHashid Project
-> RemoteAuthor
-> ActivityBody
-> Maybe (LocalRecipientSet, ByteString)
-> LocalURI
-> Note URIMode
-> ExceptT Text Handler Text
projectCreateNoteF now shrRecip prjRecip author body mfwd luCreate note = do
projectCreateNoteF now deckRecip author body mfwd luCreate note = do
error "projectCreateNoteF temporarily disabled"
{-
(luNote, published, context, mparent, source, content) <- checkNote note
case context of
Right _ -> return "Not using; context isn't local"
@ -436,18 +448,24 @@ projectCreateNoteF now shrRecip prjRecip author body mfwd luCreate note = do
Entity jid j <- getBy404 $ UniqueProject prjRecip sid
a <- getJust $ projectActor j
return (jid, actorInbox a)
-}
repoCreateNoteF
:: UTCTime
-> ShrIdent
-> RpIdent
-> KeyHashid Repo
-> RemoteAuthor
-> ActivityBody
-> Maybe (LocalRecipientSet, ByteString)
-> LocalURI
-> Note URIMode
-> ExceptT Text Handler Text
repoCreateNoteF now shrRecip rpRecip author body mfwd luCreate note = do
repoCreateNoteF now repoRecip author body mfwd luCreate note = do
error "repoCreateNoteF temporarily disabled"
{-
(luNote, published, context, mparent, source, content) <- checkNote note
case context of
Right _ -> return "Not using; context isn't local"
@ -535,3 +553,4 @@ repoCreateNoteF now shrRecip rpRecip author body mfwd luCreate note = do
sid <- getKeyBy404 $ UniqueSharer shrRecip
Entity rid r <- getBy404 $ UniqueRepo rpRecip sid
return (rid, repoInbox r)
-}

View file

@ -89,7 +89,7 @@ import Vervis.Patch
import Vervis.Ticket
sharerAcceptF
:: ShrIdent
:: KeyHashid Person
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -97,7 +97,12 @@ sharerAcceptF
-> LocalURI
-> Accept URIMode
-> ExceptT Text Handler Text
sharerAcceptF shr now author body mfwd luAccept (Accept (ObjURI hOffer luOffer) mresult) = do
sharerAcceptF recipHash now author body mfwd luAccept (Accept (ObjURI hOffer luOffer) mresult) = do
error "sharerAcceptF temporarily disabled"
{-
mres <- lift $ runDB $ do
Entity pidRecip recip <- do
sid <- getKeyBy404 $ UniqueSharer shr
@ -231,9 +236,10 @@ sharerAcceptF shr now author body mfwd luAccept (Accept (ObjURI hOffer luOffer)
( "Inserted remote reverse ticket dep"
, (,collections) <$> msig
)
-}
sharerRejectF
:: ShrIdent
:: KeyHashid Person
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -241,7 +247,14 @@ sharerRejectF
-> LocalURI
-> Reject URIMode
-> ExceptT Text Handler Text
sharerRejectF shr now author body mfwd luReject (Reject (ObjURI hOffer luOffer)) = do
sharerRejectF recipHash now author body mfwd luReject (Reject (ObjURI hOffer luOffer)) = do
error "sharerRejectF temporarily disabled"
{-
lift $ runDB $ do
Entity pidRecip recip <- do
sid <- getKeyBy404 $ UniqueSharer shr
@ -277,7 +290,9 @@ sharerRejectF shr now author body mfwd luReject (Reject (ObjURI hOffer luOffer))
Just u -> u
guard $ originalRecip == remoteAuthorURI author
lift $ delete frrid
-}
{-
followF
:: (Route App -> Maybe a)
-> Route App
@ -402,9 +417,10 @@ followF
doc = accept $ Just luAct
update obiid [OutboxItemActivity =. persistJSONObjectFromDoc doc]
return (obiid, doc)
-}
sharerFollowF
:: ShrIdent
:: KeyHashid Person
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -412,7 +428,13 @@ sharerFollowF
-> LocalURI
-> AP.Follow URIMode
-> ExceptT Text Handler Text
sharerFollowF shr =
sharerFollowF recipHash =
error "sharerFollowF temporarily disabled"
{-
followF
objRoute
(SharerR shr)
@ -450,10 +472,10 @@ sharerFollowF shr =
followers (p, Nothing) = personFollowers p
followers (_, Just lt) = localTicketFollowers lt
-}
projectFollowF
:: ShrIdent
-> PrjIdent
:: KeyHashid Project
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -461,7 +483,12 @@ projectFollowF
-> LocalURI
-> AP.Follow URIMode
-> ExceptT Text Handler Text
projectFollowF shr prj =
projectFollowF deckHash =
error "projectFollowF temporarily disabled"
{-
followF
objRoute
(ProjectR shr prj)
@ -493,10 +520,10 @@ projectFollowF shr prj =
followers (a, Nothing) = actorFollowers a
followers (_, Just lt) = localTicketFollowers lt
-}
repoFollowF
:: ShrIdent
-> RpIdent
:: KeyHashid Repo
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -504,7 +531,13 @@ repoFollowF
-> LocalURI
-> AP.Follow URIMode
-> ExceptT Text Handler Text
repoFollowF shr rp =
repoFollowF repoHash =
error "repoFollowF temporarily disabled"
{-
followF
objRoute
(RepoR shr rp)
@ -535,6 +568,7 @@ repoFollowF shr rp =
followers (r, Nothing) = repoFollowers r
followers (_, Just lt) = localTicketFollowers lt
-}
getFollow (Left _) = return Nothing
getFollow (Right ractid) = getBy $ UniqueRemoteFollowFollow ractid
@ -612,7 +646,7 @@ insertAcceptOnUndo actor author luUndo obiid auds = do
actorOutboxItem (LocalActorRepo shr rp) = RepoOutboxItemR shr rp
sharerUndoF
:: ShrIdent
:: KeyHashid Person
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -620,7 +654,13 @@ sharerUndoF
-> LocalURI
-> Undo URIMode
-> ExceptT Text Handler Text
sharerUndoF shrRecip now author body mfwd luUndo (Undo uObj) = do
sharerUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
error "sharerUndoF temporarily disabled"
{-
object <- parseActivity uObj
mmmhttp <- runDBExcept $ do
p <- lift $ do
@ -702,10 +742,10 @@ sharerUndoF shrRecip now author body mfwd luUndo (Undo uObj) = do
audTicket =
AudLocal [] [ticketFollowers]
return ([ticketFollowers], [audAuthor, audTicket])
-}
projectUndoF
:: ShrIdent
-> PrjIdent
:: KeyHashid Project
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -713,7 +753,17 @@ projectUndoF
-> LocalURI
-> Undo URIMode
-> ExceptT Text Handler Text
projectUndoF shrRecip prjRecip now author body mfwd luUndo (Undo uObj) = do
projectUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
error "projectUndoF temporarily disabled"
{-
object <- parseActivity uObj
mmmhttp <- runDBExcept $ do
(Entity jid j, a) <- lift $ do
@ -794,10 +844,10 @@ projectUndoF shrRecip prjRecip now author body mfwd luUndo (Undo uObj) = do
audTicket =
AudLocal [] [ticketFollowers]
return ([ticketFollowers], [audAuthor, audTicket])
-}
repoUndoF
:: ShrIdent
-> RpIdent
:: KeyHashid Repo
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -805,7 +855,14 @@ repoUndoF
-> LocalURI
-> Undo URIMode
-> ExceptT Text Handler Text
repoUndoF shrRecip rpRecip now author body mfwd luUndo (Undo uObj) = do
repoUndoF recipHash now author body mfwd luUndo (Undo uObj) = do
error "repoUndoF temporarily disabled"
{-
object <- parseActivity uObj
mmmhttp <- runDBExcept $ do
Entity rid r <- lift $ do
@ -885,3 +942,4 @@ repoUndoF shrRecip rpRecip now author body mfwd luUndo (Undo uObj) = do
audTicket =
AudLocal [] [ticketFollowers]
return ([ticketFollowers], [audAuthor, audTicket])
-}

View file

@ -69,7 +69,7 @@ import Vervis.Model
import Vervis.Model.Ident
sharerPushF
:: ShrIdent
:: KeyHashid Person
-> UTCTime
-> RemoteAuthor
-> ActivityBody
@ -77,7 +77,13 @@ sharerPushF
-> LocalURI
-> Push URIMode
-> ExceptT Text Handler Text
sharerPushF shr now author body mfwd luPush push = do
sharerPushF recipHash now author body mfwd luPush push = do
error "sharerPushF temporarily disabled"
{-
lift $ runDB $ do
Entity pidRecip recip <- do
sid <- getKeyBy404 $ UniqueSharer shr
@ -113,3 +119,4 @@ sharerPushF shr now author body mfwd luPush push = do
delete ibiid
return Nothing
Just _ -> return $ Just ractid
-}

File diff suppressed because it is too large Load diff

View file

@ -72,12 +72,13 @@ import qualified Network.HTTP.Signature as S (Algorithm (..))
import Crypto.PublicVerifKey
import Network.FedURI
import Web.ActivityAccess
import Web.ActivityPub hiding (Ticket, TicketDependency, Bundle, Patch)
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Text.Email.Local
import Text.Jasmine.Local (discardm)
import Yesod.Paginate.Local
@ -126,15 +127,20 @@ data App = App
-- Aliases for the routes file, because it doesn't like spaces in path piece
-- type names.
type PersonKeyHashid = KeyHashid Person
type GroupKeyHashid = KeyHashid Group
type RepoKeyHashid = KeyHashid Repo
type OutboxItemKeyHashid = KeyHashid OutboxItem
type SshKeyKeyHashid = KeyHashid SshKey
type MessageKeyHashid = KeyHashid Message
type LocalMessageKeyHashid = KeyHashid LocalMessage
type LocalTicketKeyHashid = KeyHashid LocalTicket
type TicketAuthorLocalKeyHashid = KeyHashid TicketAuthorLocal
type TicketDepKeyHashid = KeyHashid LocalTicketDependency
type BundleKeyHashid = KeyHashid Bundle
type PatchKeyHashid = KeyHashid Patch
type DeckKeyHashid = KeyHashid Deck
type LoomKeyHashid = KeyHashid Loom
type TicketDeckKeyHashid = KeyHashid TicketDeck
type TicketLoomKeyHashid = KeyHashid TicketLoom
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
@ -205,11 +211,13 @@ instance Yesod App where
(getCurrentRoute >>= \ mr -> case mr of
Nothing -> return False
Just PostReceiveR -> return False
Just (SharerOutboxR _) -> return False
Just (SharerInboxR _) -> return False
Just (ProjectInboxR _ _) -> return False
Just (RepoInboxR _ _) -> return False
Just (GitUploadRequestR _ _) -> return False
Just (PersonOutboxR _) -> return False
Just (PersonInboxR _) -> return False
Just (GroupInboxR _) -> return False
Just (RepoInboxR _) -> return False
Just (DeckInboxR _) -> return False
Just (LoomInboxR _) -> return False
Just (GitUploadRequestR _) -> return False
Just (DvaraR _) -> return False
Just r -> isWriteRequest r
)
@ -245,13 +253,14 @@ instance Yesod App where
mperson <- do
mperson' <- maybeAuthAllowUnverified
for mperson' $ \ (p@(Entity pid person), verified) -> runDB $ do
sharer <- getJust $ personIdent person
inboxID <- actorInbox <$> getJust (personActor person)
unread <- do
vs <- countUnread $ personInbox person
vs <- countUnread inboxID
case vs :: [E.Value Int] of
[E.Value i] -> return i
_ -> error $ "countUnread returned " ++ show vs
return (p, verified, sharer, unread)
hash <- encodeKeyHashid pid
return (p, hash, verified, unread)
(title, bcs) <- breadcrumbs
-- We break up the default layout into two components:
@ -291,24 +300,34 @@ instance Yesod App where
-- Who can access which pages.
isAuthorized r w = case (r, w) of
-- Authentication
(AuthR a , True)
| a == resendVerifyR -> personFromResendForm
(AuthR (PluginR "account" ["verify", u, _]), False) -> personUnver u
-- Client
(NotificationsR, _ ) -> personAny
(PublishR , True) -> personAny
(SharerInboxR shr , False) -> person shr
(NotificationsR shr , _ ) -> person shr
(SharerOutboxR shr , True) -> person shr
(SharerFollowR shr , True) -> personAny
(SharerUnfollowR shr , True) -> personAny
-- Person
(PersonInboxR p , False) -> person p
(PersonOutboxR p , True) -> person p
-- Group
{-
(GroupsR , True) -> personAny
(GroupNewR , _ ) -> personAny
(GroupMembersR grp , True) -> groupAdmin grp
(GroupMemberNewR grp , _ ) -> groupAdmin grp
(GroupMemberR grp _memb , True) -> groupAdmin grp
-}
{-
(KeysR , _ ) -> personAny
(KeyR _key , _ ) -> personAny
(KeyNewR , _ ) -> personAny
@ -320,31 +339,33 @@ instance Yesod App where
(ProjectRoleR shr _rl , _ ) -> personOrGroupAdmin shr
(ProjectRoleOpsR shr _rl , _ ) -> personOrGroupAdmin shr
(ProjectRoleOpNewR shr _rl , _ ) -> personOrGroupAdmin shr
-}
-- Repo
(RepoInboxR _ , False) -> personAny
-- Deck
(DeckInboxR _ , False) -> personAny
-- Loom
(LoomInboxR _ , False) -> personAny
(ReposR shr , True) -> personOrGroupAdmin shr
(RepoNewR shr , _ ) -> personOrGroupAdmin shr
(RepoR shar _ , True) -> person shar
(RepoEditR shr _rp , _ ) -> person shr
(RepoFollowR _shr _rp , True) -> personAny
(RepoUnfollowR _shr _rp , True) -> personAny
(RepoDevsR shr _rp , _ ) -> person shr
(RepoDevNewR shr _rp , _ ) -> person shr
(RepoDevR shr _rp _dev , _ ) -> person shr
(ProjectsR shr , True) -> personOrGroupAdmin shr
(ProjectNewR shr , _ ) -> personOrGroupAdmin shr
(ProjectR shr _prj , True) -> person shr
(ProjectEditR shr _prj , _ ) -> person shr
(ProjectFollowR _shr _prj , _ ) -> personAny
(ProjectUnfollowR _shr _prj , _ ) -> personAny
(ProjectDevsR shr _prj , _ ) -> person shr
(ProjectDevNewR shr _prj , _ ) -> person shr
(ProjectDevR shr _prj _dev , _ ) -> person shr
-- (GlobalWorkflowsR , _ ) -> serverAdmin
-- (GlobalWorkflowNewR , _ ) -> serverAdmin
-- (GlobalWorkflowR _wfl , _ ) -> serverAdmin
{-
(WorkflowsR shr , _ ) -> personOrGroupAdmin shr
(WorkflowNewR shr , _ ) -> personOrGroupAdmin shr
(WorkflowR shr _wfl , _ ) -> personOrGroupAdmin shr
@ -357,7 +378,9 @@ instance Yesod App where
(WorkflowEnumCtorsR shr _ _ , _ ) -> personOrGroupAdmin shr
(WorkflowEnumCtorNewR shr _ _ , _ ) -> personOrGroupAdmin shr
(WorkflowEnumCtorR shr _ _ _ , _ ) -> personOrGroupAdmin shr
-}
{-
(ProjectTicketsR s j , True) -> projOp ProjOpOpenTicket s j
(ProjectTicketNewR s j , _ ) -> projOp ProjOpOpenTicket s j
(ProjectTicketR user _ _ , True) -> person user
@ -380,6 +403,8 @@ instance Yesod App where
(ProjectTicketDepsR s j _ , True) -> projOp ProjOpAddTicketDep s j
(ProjectTicketDepNewR s j _ , _ ) -> projOp ProjOpAddTicketDep s j
(TicketDepOldR s j _ _ , True) -> projOp ProjOpRemoveTicketDep s j
-}
_ -> return Authorized
where
nobody :: Handler AuthResult
@ -412,11 +437,10 @@ instance Yesod App where
personAny :: Handler AuthResult
personAny = personAnd $ \ _p -> return Authorized
person :: ShrIdent -> Handler AuthResult
person ident = personAnd $ \ (Entity _ p) -> do
let sid = personIdent p
sharer <- runDB $ getJust sid
return $ if ident == sharerIdent sharer
person :: KeyHashid Person -> Handler AuthResult
person hash = personAnd $ \ (Entity pid _) -> do
hash' <- encodeKeyHashid pid
return $ if hash == hash'
then Authorized
else Unauthorized "No access to this operation"
@ -454,6 +478,7 @@ instance Yesod App where
return $
Unauthorized "Requesting resend for invalid username"
{-
groupRole :: (GroupRole -> Bool) -> ShrIdent -> Handler AuthResult
groupRole role grp = personAnd $ \ (Entity pid _p) -> runDB $ do
Entity sid _s <- getBy404 $ UniqueSharer grp
@ -507,6 +532,7 @@ instance Yesod App where
_ ->
Unauthorized
"You need a project role with that operation enabled"
-}
-- This function creates static content files in the static folder
-- and names them based on a hash of their content. This allows
@ -605,23 +631,21 @@ instance AccountDB AccountPersistDB' where
addNewUser name email key pwd = AccountPersistDB' $ runDB $ do
now <- liftIO getCurrentTime
let sharer = Sharer
{ sharerIdent = text2shr name
, sharerName = Nothing
, sharerCreated = now
}
msid <- insertBy sharer
case msid of
Left _ -> do
mr <- getMessageRender
return $ Left $ mr $ MsgUsernameExists name
Right sid -> do
ibid <- insert Inbox
obid <- insert Outbox
fsid <- insert FollowerSet
let actor = Actor
{ actorName = name
, actorDesc = ""
, actorCreatedAt = now
, actorInbox = ibid
, actorOutbox = obid
, actorFollowers = fsid
}
aid <- insert actor
let defTime = UTCTime (ModifiedJulianDay 0) 0
person = Person
{ personIdent = sid
{ personUsername = text2username $ name
, personLogin = name
, personPassphraseHash = pwd
, personEmail = email
@ -630,13 +654,19 @@ instance AccountDB AccountPersistDB' where
, personVerifiedKeyCreated = now
, personResetPassKey = ""
, personResetPassKeyCreated = defTime
, personAbout = ""
, personInbox = ibid
, personOutbox = obid
, personFollowers = fsid
, personActor = aid
-- , personReviewFollow = True
}
pid <- insert person
return $ Right $ Entity pid person
mpid <- insertBy person
case mpid of
Left _ -> do
delete aid
delete ibid
delete obid
delete fsid
mr <- getMessageRender
return $ Left $ mr $ MsgUsernameExists name
Right pid -> return $ Right $ Entity pid person
verifyAccount = morphAPDB . verifyAccount
setVerifyKey = (morphAPDB .) . setVerifyKey
@ -744,7 +774,7 @@ instance YesodRemoteActorStore App where
instance YesodActivityPub App where
siteInstanceHost = appInstanceHost . appSettings
sitePostSignedHeaders _ =
hRequestTarget :| [hHost, hDate, hDigest, hActivityPubActor]
hRequestTarget :| [hHost, hDate, hDigest, AP.hActivityPubActor]
siteGetHttpSign = do
(akey1, akey2, new1) <- liftIO . readTVarIO =<< asksSite appActorKeys
renderUrl <- askUrlRender
@ -759,6 +789,7 @@ instance YesodPaginate App where
instance YesodBreadcrumbs App where
breadcrumb route = return $ case route of
{-
StaticR _ -> ("", Nothing)
FaviconSvgR -> ("", Nothing)
FaviconPngR -> ("", Nothing)
@ -985,5 +1016,6 @@ instance YesodBreadcrumbs App where
)
WikiPageR shr prj _page -> ("Wiki", Just $ ProjectR shr prj)
-}
_ -> ("PAGE TITLE HERE", Just HomeR)

View file

@ -15,13 +15,16 @@
-}
module Vervis.Git
( readSourceView
(
{-
readSourceView
, readChangesView
, listRefs
, readPatch
, lastCommitTime
, writePostReceiveHooks
, applyGitPatches
-}
writePostReceiveHooks
--, applyGitPatches
)
where
@ -53,6 +56,7 @@ import Data.Time.Clock (UTCTime (..))
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Traversable (for)
import Data.Word (Word32)
import Database.Persist
import System.Exit
import System.Hourglass (timeCurrent)
import System.Process.Typed
@ -73,6 +77,7 @@ import qualified Database.Esqueleto as E
import Network.FedURI
import Yesod.ActivityPub
import Yesod.Hashids
import Yesod.MonadSite
import Data.ByteString.Char8.Local (takeLine)
@ -95,6 +100,7 @@ import Vervis.Readme
import Vervis.Settings
import Vervis.SourceTree
{-
matchReadme :: (ModePerm, ObjId, Text, EntObjType) -> Bool
matchReadme (_, _, name, EntObjBlob) = isReadme name
matchReadme _ = False
@ -340,19 +346,19 @@ lastCommitTime repo =
utc (Elapsed (Seconds i)) = posixSecondsToUTCTime $ fromIntegral i
utc0 = UTCTime (ModifiedJulianDay 0) 0
foldlM' i l f = foldlM f i l
-}
writePostReceiveHooks :: WorkerDB ()
writePostReceiveHooks = do
repos <- E.select $ E.from $ \ (r `E.InnerJoin` s) -> do
E.on $ r E.^. RepoSharer E.==. s E.^. SharerId
E.where_ $ r E.^. RepoVcs E.==. E.val VCSGit
return (s E.^. SharerIdent, r E.^. RepoIdent)
hook <- asksSite $ appPostReceiveHookFile . appSettings
authority <- asksSite $ renderAuthority . siteInstanceHost
for_ repos $ \ (E.Value shr, E.Value rp) -> do
path <- askRepoDir shr rp
liftIO $ writeHookFile path hook authority (shr2text shr) (rp2text rp)
repos <- selectKeysList [RepoVcs ==. VCSGit] []
for_ repos $ \ repoID -> do
repoHash <- encodeKeyHashid repoID
path <- askRepoDir repoHash
liftIO $ writeHookFile path hook authority (keyHashidText repoHash)
{-
applyGitPatches shr rp branch patches = do
path <- askRepoDir shr rp
let input = BL.concat $ NE.toList $ NE.map (BL.fromStrict . TE.encodeUtf8) patches
@ -373,3 +379,4 @@ applyGitPatches shr rp branch patches = do
ExitSuccess -> return ()
where
out2text = TU.decodeLenient . BL.toStrict
-}

View file

@ -15,36 +15,27 @@
-}
module Vervis.Handler.Client
( getPublishR
, postSharerOutboxR
, postPublishR
( getResendVerifyEmailR
, getActorKey1R
, getActorKey2R
, getHomeR
, getBrowseR
, postSharerFollowR
, postProjectFollowR
, postProjectTicketFollowR
, postRepoFollowR
, postSharerUnfollowR
, postProjectUnfollowR
, postProjectTicketUnfollowR
, postRepoUnfollowR
, getNotificationsR
, postNotificationsR
, postProjectTicketsR
, postProjectTicketCloseR
, postProjectTicketOpenR
, getPublishR
, postPublishR
, getInboxDebugR
)
where
import Control.Applicative
import Control.Concurrent.STM.TVar
import Control.Exception hiding (Handler)
import Control.Monad
import Control.Monad.Trans.Except
import Data.Bitraversable
import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.Time.Clock
@ -53,22 +44,26 @@ import Database.Persist
import Text.Blaze.Html (preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text
import Text.HTML.SanitizeXSS
import Yesod.Auth
import Yesod.Auth.Account
import Yesod.Auth.Account.Message
import Yesod.Core
import Yesod.Core.Widget
import Yesod.Form
import Yesod.Persist.Core
import qualified Data.ByteString.Char8 as BC
import qualified Data.HashMap.Strict as M
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TLE
import qualified Database.Esqueleto as E
import Dvara
import Database.Persist.JSON
import Network.FedURI
import Web.ActivityPub hiding (Ticket)
import Yesod.ActivityPub
import Yesod.Auth.Unverified
import Yesod.FedURI
@ -84,10 +79,9 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.ActorKey
import Vervis.API
import Vervis.Client
import Vervis.FedURI
import Vervis.Form.Ticket
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
@ -96,10 +90,111 @@ import Vervis.Path
import Vervis.Settings
import Vervis.Ticket
import qualified Vervis.Client as C
import qualified Vervis.Darcs as D
import qualified Vervis.Git as G
-- | Account verification email resend form
getResendVerifyEmailR :: Handler Html
getResendVerifyEmailR = do
person <- requireUnverifiedAuth
defaultLayout $ do
setTitleI MsgEmailUnverified
[whamlet|
<p>_{MsgEmailUnverified}
^{resendVerifyEmailWidget (username person) AuthR}
|]
getActorKey
:: ((ActorKey, ActorKey, Bool) -> ActorKey)
-> Route App
-> Handler TypedContent
getActorKey choose route = do
actorKey <-
liftIO . fmap (actorKeyPublicBin . choose) . readTVarIO =<<
getsYesod appActorKeys
encodeRouteLocal <- getEncodeRouteLocal
let key = AP.PublicKey
{ AP.publicKeyId = LocalRefURI $ Left $ encodeRouteLocal route
, AP.publicKeyExpires = Nothing
, AP.publicKeyOwner = AP.OwnerInstance
, AP.publicKeyMaterial = actorKey
}
provideHtmlAndAP key $ redirectToPrettyJSON route
getActorKey1R :: Handler TypedContent
getActorKey1R = getActorKey (\ (k1, _, _) -> k1) ActorKey1R
getActorKey2R :: Handler TypedContent
getActorKey2R = getActorKey (\ (_, k2, _) -> k2) ActorKey2R
getHomeR :: Handler Html
getHomeR = do
mp <- maybeAuth
case mp of
Just p -> personalOverview p
Nothing -> redirect BrowseR
where
personalOverview :: Entity Person -> Handler Html
personalOverview (Entity _pid _person) =
defaultLayout $ do
setTitle "Vervis > Overview"
$(widgetFile "personal-overview")
getBrowseR :: Handler Html
getBrowseR = do
(people, groups, repos, decks, looms) <- runDB $
(,,,,)
<$> (E.select $ E.from $ \ (person `E.InnerJoin` actor) -> do
E.on $ person E.^. PersonActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ person E.^. PersonId]
return (person, actor)
)
<*> (E.select $ E.from $ \ (group `E.InnerJoin` actor) -> do
E.on $ group E.^. GroupActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ group E.^. GroupId]
return (group, actor)
)
<*> (E.select $ E.from $ \ (repo `E.InnerJoin` actor) -> do
E.on $ repo E.^. RepoActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ repo E.^. RepoId]
return (repo, actor)
)
<*> (E.select $ E.from $ \ (deck `E.InnerJoin` actor) -> do
E.on $ deck E.^. DeckActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ deck E.^. DeckId]
return (deck, actor)
)
<*> (E.select $ E.from $ \ (loom `E.InnerJoin` actor) -> do
E.on $ loom E.^. LoomActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ loom E.^. LoomId]
return (loom, actor)
)
{-
now <- liftIO getCurrentTime
repoRows <- forM repos $
\ (E.Value sharer, E.Value mproj, E.Value repo, E.Value vcs) -> do
path <- askRepoDir sharer repo
mlast <- case vcs of
VCSDarcs -> liftIO $ D.lastChange path now
VCSGit -> do
mt <- liftIO $ G.lastCommitTime path
return $ Just $ case mt of
Nothing -> Never
Just t ->
intervalToEventTime $
FriendlyConvert $
now `diffUTCTime` t
return (sharer, mproj, repo, vcs, mlast)
-}
hashPerson <- getEncodeKeyHashid
hashGroup <- getEncodeKeyHashid
hashRepo <- getEncodeKeyHashid
hashDeck <- getEncodeKeyHashid
hashLoom <- getEncodeKeyHashid
defaultLayout $ do
setTitle "Welcome to Vervis!"
$(widgetFile "browse")
getShowTime = showTime <$> liftIO getCurrentTime
where
showTime now =
@ -108,6 +203,16 @@ getShowTime = showTime <$> liftIO getCurrentTime
FriendlyConvert .
diffUTCTime now
notificationForm :: Maybe (Maybe (InboxItemId, Bool)) -> Form (Maybe (InboxItemId, Bool))
notificationForm defs = renderDivs $ mk
<$> aopt hiddenField (name "Inbox Item ID#") (fmap fst <$> defs)
<*> aopt hiddenField (name "New unread flag") (fmap snd <$> defs)
where
name t = FieldSettings "" Nothing Nothing (Just t) []
mk Nothing Nothing = Nothing
mk (Just ibid) (Just unread) = Just (ibid, unread)
mk _ _ = error "Missing hidden field?"
objectSummary o =
case M.lookup "summary" o of
Just (String t) | not (T.null t) -> Just t
@ -118,6 +223,166 @@ objectId o =
Just (String t) | not (T.null t) -> t
_ -> error "'id' field not found"
getNotificationsR :: Handler Html
getNotificationsR = do
Entity _ viewer <- requireVerifiedAuth
items <- runDB $ do
inboxID <- actorInbox <$> getJust (personActor viewer)
map adaptItem <$> getItems inboxID
notifications <- for items $ \ (ibiid, activity) -> do
((_result, widget), enctype) <-
runFormPost $ notificationForm $ Just $ Just (ibiid, False)
return (activity, widget, enctype)
((_result, widgetAll), enctypeAll) <-
runFormPost $ notificationForm $ Just Nothing
showTime <- getShowTime
defaultLayout $(widgetFile "person/notifications")
where
getItems ibid =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` (ibl `E.InnerJoin` ob) `E.LeftOuterJoin` (ibr `E.InnerJoin` ract)) -> do
E.on $ ibr E.?. InboxItemRemoteActivity E.==. ract E.?. RemoteActivityId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ ibl E.?. InboxItemLocalActivity E.==. ob E.?. OutboxItemId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.&&.
ib E.^. InboxItemUnread E.==. E.val True
E.orderBy [E.desc $ ib E.^. InboxItemId]
return
( ib E.^. InboxItemId
, ob E.?. OutboxItemActivity
, ob E.?. OutboxItemPublished
, ract E.?. RemoteActivityContent
, ract E.?. RemoteActivityReceived
)
adaptItem
(E.Value ibid, E.Value mact, E.Value mpub, E.Value mobj, E.Value mrec) =
case (mact, mpub, mobj, mrec) of
(Nothing, Nothing, Nothing, Nothing) ->
error $ ibiidString ++ " neither local nor remote"
(Just _, Just _, Just _, Just _) ->
error $ ibiidString ++ " both local and remote"
(Just act, Just pub, Nothing, Nothing) ->
(ibid, (persistJSONObject act, (pub, False)))
(Nothing, Nothing, Just obj, Just rec) ->
(ibid, (persistJSONObject obj, (rec, True)))
_ -> error $ "Unexpected query result for " ++ ibiidString
where
ibiidString = "InboxItem #" ++ show (E.fromSqlKey ibid)
postNotificationsR :: Handler Html
postNotificationsR = do
Entity _ poster <- requireVerifiedAuth
((result, _widget), _enctype) <- runFormPost $ notificationForm Nothing
case result of
FormMissing -> setMessage "Field(s) missing"
FormFailure l ->
setMessage $ toHtml $ "Marking as read failed:" <> T.pack (show l)
FormSuccess mitem -> do
(multi, markedUnread) <- runDB $ do
inboxID <- actorInbox <$> getJust (personActor poster)
case mitem of
Nothing -> do
ibiids <- map E.unValue <$> getItems inboxID
updateWhere
[InboxItemId <-. ibiids]
[InboxItemUnread =. False]
return (True, False)
Just (ibiid, unread) -> do
mib <-
requireEitherAlt
(getValBy $ UniqueInboxItemLocalItem ibiid)
(getValBy $ UniqueInboxItemRemoteItem ibiid)
"Unused InboxItem"
"InboxItem used more than once"
let samePid =
case mib of
Left ibl ->
inboxItemLocalInbox ibl == inboxID
Right ibr ->
inboxItemRemoteInbox ibr == inboxID
if samePid
then do
update ibiid [InboxItemUnread =. unread]
return (False, unread)
else
permissionDenied
"Notification belongs to different user"
setMessage $
if multi
then "Items marked as read."
else if markedUnread
then "Item marked as unread."
else "Item marked as read."
redirect NotificationsR
where
getItems ibid =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` ibl `E.LeftOuterJoin` ibr) -> do
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.&&.
ib E.^. InboxItemUnread E.==. E.val True
return $ ib E.^. InboxItemId
getPublishR :: Handler Html
getPublishR = do
error "Temporarily disabled"
postPublishR :: Handler Html
postPublishR = do
error "Temporarily disabled"
getInboxDebugR :: Handler Html
getInboxDebugR = do
acts <-
liftIO . readTVarIO . snd =<< maybe notFound return =<< getsYesod appActivities
defaultLayout
[whamlet|
<p>
Welcome to the ActivityPub inbox test page! Activities received
by this Vervis instance are listed here for testing and
debugging. To test, go to another Vervis instance and publish
something that supports federation, either through the regular UI
or via the /publish page, and then come back here to see the
result. Activities that aren't understood or their processing
fails get listed here too, with a report of what exactly
happened.
<p>Last 10 activities posted:
<ul>
$forall ActivityReport time msg ctypes body <- acts
<li>
<div>#{show time}
<div>#{msg}
<div><code>#{intercalate " | " $ map BC.unpack ctypes}
<div><pre>#{TLE.decodeUtf8 body}
|]
{-
fedUriField
:: (Monad m, RenderMessage (HandlerSite m) FormMessage) => Field m FedURI
fedUriField = Field
@ -348,63 +613,6 @@ getPublishR = do
widget7 enctype7
widget8 enctype8
postSharerOutboxR :: ShrIdent -> Handler Text
postSharerOutboxR shr = do
federation <- getsYesod $ appFederation . appSettings
unless federation badMethod
(ep@(Entity pid person), sharer) <- runDB $ do
Entity sid s <- getBy404 $ UniqueSharer shr
(,s) <$> getBy404 (UniquePersonIdent sid)
(_app, mpid, _scopes) <- maybe notAuthenticated return =<< getDvaraAuth
pid' <-
maybe (permissionDenied "Not authorized to post as a user") return mpid
unless (pid == pid') $
permissionDenied "Can't post as other users"
verifyContentTypeAP
Doc h activity <- requireInsecureJsonBody
hl <- hostIsLocal h
unless hl $ invalidArgs ["Activity host isn't the instance host"]
result <- runExceptT $ handle ep sharer activity
case result of
Left err -> invalidArgs [err]
Right obiid -> do
obikhid <- encodeKeyHashid obiid
sendResponseCreated $ SharerOutboxItemR shr obikhid
where
handle eperson sharer (Activity _mid actor mcap summary audience specific) = do
case decodeRouteLocal actor of
Just (SharerR shr') | shr' == shr -> return ()
_ -> throwE "Can't post activity sttributed to someone else"
case specific of
AddActivity (AP.Add obj target) ->
case obj of
Right (AddBundle patches) ->
addBundleC eperson sharer summary audience patches target
_ -> throwE "Unsupported Add 'object' type"
ApplyActivity apply ->
applyC eperson sharer summary audience mcap apply
CreateActivity (Create obj mtarget) ->
case obj of
CreateNote _ note ->
createNoteC eperson sharer summary audience note mtarget
CreateTicket _ ticket ->
createTicketC eperson sharer summary audience ticket mtarget
_ -> throwE "Unsupported Create 'object' type"
FollowActivity follow ->
followC shr summary audience follow
OfferActivity (Offer obj target) ->
case obj of
OfferTicket ticket ->
offerTicketC eperson sharer summary audience ticket target
OfferDep dep ->
offerDepC eperson sharer summary audience dep target
_ -> throwE "Unsupported Offer 'object' type"
ResolveActivity resolve ->
resolveC eperson sharer summary audience resolve
UndoActivity undo ->
undoC eperson sharer summary audience undo
_ -> throwE "Unsupported activity type"
data Result
= ResultPublishComment ((Host, ShrIdent, PrjIdent, KeyHashid LocalTicket), Maybe FedURI, Text)
| ResultCreateTicket (FedURI, FedURI, TextHtml, TextPandocMarkdown)
@ -587,54 +795,6 @@ postPublishR = do
C.follow shrAuthor uObject uRecip False
followC shrAuthor (Just summary) audience followAP
getBrowseR :: Handler Html
getBrowseR = do
(rowsRepo, rowsProject) <- do
(repos, projects) <- runDB $ do
rs <- E.select $ E.from $
\ (repo `E.LeftOuterJoin` project `E.InnerJoin` sharer) -> do
E.on $ repo E.^. RepoSharer E.==. sharer E.^. SharerId
E.on $ repo E.^. RepoProject E.==. project E.?. ProjectId
E.orderBy
[ E.asc $ sharer E.^. SharerIdent
, E.asc $ project E.?. ProjectIdent
, E.asc $ repo E.^. RepoIdent
]
return
( sharer E.^. SharerIdent
, project E.?. ProjectIdent
, repo E.^. RepoIdent
, repo E.^. RepoVcs
)
js <- E.select $ E.from $ \ (j `E.InnerJoin` s `E.LeftOuterJoin` r) -> do
E.on $ E.just (j E.^. ProjectId) E.==. E.joinV (r E.?. RepoProject)
E.on $ j E.^. ProjectSharer E.==. s E.^. SharerId
E.where_ $ E.isNothing $ r E.?. RepoId
return
( s E.^. SharerIdent
, j E.^. ProjectIdent
)
return (rs, js)
now <- liftIO getCurrentTime
repoRows <- forM repos $
\ (E.Value sharer, E.Value mproj, E.Value repo, E.Value vcs) -> do
path <- askRepoDir sharer repo
mlast <- case vcs of
VCSDarcs -> liftIO $ D.lastChange path now
VCSGit -> do
mt <- liftIO $ G.lastCommitTime path
return $ Just $ case mt of
Nothing -> Never
Just t ->
intervalToEventTime $
FriendlyConvert $
now `diffUTCTime` t
return (sharer, mproj, repo, vcs, mlast)
return (repoRows, projects)
defaultLayout $ do
setTitle "Welcome to Vervis!"
$(widgetFile "homepage")
setFollowMessage :: ShrIdent -> Either Text OutboxItemId -> Handler ()
setFollowMessage _ (Left err) = setMessage $ toHtml err
setFollowMessage shr (Right obiid) = do
@ -733,146 +893,6 @@ postRepoUnfollowR shrFollowee rpFollowee = do
setUnfollowMessage shrAuthor eid
redirect $ RepoR shrFollowee rpFollowee
notificationForm :: Maybe (Maybe (InboxItemId, Bool)) -> Form (Maybe (InboxItemId, Bool))
notificationForm defs = renderDivs $ mk
<$> aopt hiddenField (name "Inbox Item ID#") (fmap fst <$> defs)
<*> aopt hiddenField (name "New unread flag") (fmap snd <$> defs)
where
name t = FieldSettings "" Nothing Nothing (Just t) []
mk Nothing Nothing = Nothing
mk (Just ibid) (Just unread) = Just (ibid, unread)
mk _ _ = error "Missing hidden field?"
getNotificationsR :: ShrIdent -> Handler Html
getNotificationsR shr = do
items <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
p <- getValBy404 $ UniquePersonIdent sid
let ibid = personInbox p
map adaptItem <$> getItems ibid
notifications <- for items $ \ (ibiid, activity) -> do
((_result, widget), enctype) <-
runFormPost $ notificationForm $ Just $ Just (ibiid, False)
return (activity, widget, enctype)
((_result, widgetAll), enctypeAll) <-
runFormPost $ notificationForm $ Just Nothing
showTime <- getShowTime
defaultLayout $(widgetFile "person/notifications")
where
getItems ibid =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` (ibl `E.InnerJoin` ob) `E.LeftOuterJoin` (ibr `E.InnerJoin` ract)) -> do
E.on $ ibr E.?. InboxItemRemoteActivity E.==. ract E.?. RemoteActivityId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ ibl E.?. InboxItemLocalActivity E.==. ob E.?. OutboxItemId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.&&.
ib E.^. InboxItemUnread E.==. E.val True
E.orderBy [E.desc $ ib E.^. InboxItemId]
return
( ib E.^. InboxItemId
, ob E.?. OutboxItemActivity
, ob E.?. OutboxItemPublished
, ract E.?. RemoteActivityContent
, ract E.?. RemoteActivityReceived
)
adaptItem
(E.Value ibid, E.Value mact, E.Value mpub, E.Value mobj, E.Value mrec) =
case (mact, mpub, mobj, mrec) of
(Nothing, Nothing, Nothing, Nothing) ->
error $ ibiidString ++ " neither local nor remote"
(Just _, Just _, Just _, Just _) ->
error $ ibiidString ++ " both local and remote"
(Just act, Just pub, Nothing, Nothing) ->
(ibid, (persistJSONObject act, (pub, False)))
(Nothing, Nothing, Just obj, Just rec) ->
(ibid, (persistJSONObject obj, (rec, True)))
_ -> error $ "Unexpected query result for " ++ ibiidString
where
ibiidString = "InboxItem #" ++ show (E.fromSqlKey ibid)
postNotificationsR :: ShrIdent -> Handler Html
postNotificationsR shr = do
((result, _widget), _enctype) <- runFormPost $ notificationForm Nothing
case result of
FormSuccess mitem -> do
(multi, markedUnread) <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
p <- getValBy404 $ UniquePersonIdent sid
let ibid = personInbox p
case mitem of
Nothing -> do
ibiids <- map E.unValue <$> getItems ibid
updateWhere
[InboxItemId <-. ibiids]
[InboxItemUnread =. False]
return (True, False)
Just (ibiid, unread) -> do
mibl <- getValBy $ UniqueInboxItemLocalItem ibiid
mibr <- getValBy $ UniqueInboxItemRemoteItem ibiid
mib <-
requireEitherM
mibl
mibr
"Unused InboxItem"
"InboxItem used more than once"
let samePid =
case mib of
Left ibl ->
inboxItemLocalInbox ibl == ibid
Right ibr ->
inboxItemRemoteInbox ibr == ibid
if samePid
then do
update ibiid [InboxItemUnread =. unread]
return (False, unread)
else
permissionDenied
"Notification belongs to different user"
setMessage $
if multi
then "Items marked as read."
else if markedUnread
then "Item marked as unread."
else "Item marked as read."
FormMissing -> do
setMessage "Field(s) missing"
FormFailure l -> do
setMessage $ toHtml $ "Marking as read failed:" <> T.pack (show l)
redirect $ NotificationsR shr
where
getItems ibid =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` ibl `E.LeftOuterJoin` ibr) -> do
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.&&.
ib E.^. InboxItemUnread E.==. E.val True
return $ ib E.^. InboxItemId
-- TODO copied from Vervis.Federation, put this in 1 place
requireEitherM
:: MonadIO m => Maybe a -> Maybe b -> String -> String -> m (Either a b)
requireEitherM mx my f t =
case requireEither mx my of
Left b -> liftIO $ throwIO $ userError $ if b then t else f
Right exy -> return exy
postProjectTicketsR :: ShrIdent -> PrjIdent -> Handler Html
postProjectTicketsR shr prj = do
wid <- runDB $ do
@ -989,3 +1009,4 @@ postProjectTicketOpenR shr prj ltkhid = do
Left e -> setMessage $ toHtml $ "Error: " <> e
Right _obiid -> setMessage "Ticket reopened"
redirect $ ProjectTicketR shr prj ltkhid
-}

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -13,8 +13,28 @@
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Handler.Patch
( getSharerProposalsR
module Vervis.Handler.Cloth
( getClothR
, getClothDiscussionR
, getClothEventsR
, getClothFollowersR
, getClothDepsR
, getClothReverseDepsR
, getBundleR
, getPatchR
, getClothDepR
{-
, getSharerProposalsR
, getSharerProposalR
, getSharerProposalDiscussionR
, getSharerProposalDepsR
@ -33,6 +53,7 @@ module Vervis.Handler.Patch
, getRepoProposalEventsR
, getRepoProposalBundleR
, getRepoProposalBundlePatchR
-}
)
where
@ -51,6 +72,7 @@ import qualified Data.List.NonEmpty as NE
import qualified Data.List.Ordered as LO
import qualified Database.Esqueleto as E
import Development.PatchMediaType
import Network.FedURI
import Web.ActivityPub hiding (Ticket (..), Patch (..), Bundle (..), Repo (..))
import Yesod.ActivityPub
@ -60,21 +82,432 @@ import Yesod.Hashids
import qualified Web.ActivityPub as AP
import Data.Paginate.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.Actor
import Vervis.API
import Vervis.Cloth
import Vervis.Discussion
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Model.Ticket
import Vervis.Paginate
import Vervis.Patch
import Vervis.Ticket
getClothR :: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothR loomHash clothHash = do
(repoID, mbranch, ticket, author, resolve, bundleID) <- runDB $ do
(Entity _ loom, Entity _ cloth, Entity _ ticket', author', resolve', bundleID' :| _) <-
getCloth404 loomHash clothHash
(,,,,,)
(loomRepo loom)
(ticketLoomBranch cloth)
ticket'
<$> (case author' of
Left (Entity _ tal) ->
return $ Left $ ticketAuthorLocalAuthor tal
Right (Entity _ tar) -> Right <$> do
ra <- getJust $ ticketAuthorRemoteAuthor tar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (i, ro)
)
<*> (for resolve' $ \ (_, etrx) ->
bitraverse
(\ (Entity _ trl) -> do
let obiid = ticketResolveLocalActivity trl
obid <- outboxItemOutbox <$> getJust obiid
actorID <- do
maybeActorID <- getKeyBy $ UniqueActorOutbox obid
case maybeActorID of
Nothing -> error "Found outbox not used by any actor"
Just a -> return a
actor <- getLocalActor actorID
return (actor, obiid)
)
(\ (Entity _ trr) -> do
roid <-
remoteActivityIdent <$>
getJust (ticketResolveRemoteActivity trr)
ro <- getJust roid
i <- getJust $ remoteObjectInstance ro
return (i, ro)
)
etrx
)
<*> pure bundleID'
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hashPerson <- getEncodeKeyHashid
hashItem <- getEncodeKeyHashid
hLocal <- getsYesod siteInstanceHost
repoHash <- encodeKeyHashid repoID
bundleHash <- encodeKeyHashid bundleID
let route mk = encodeRouteLocal $ mk loomHash clothHash
authorHost =
case author of
Left _ -> hLocal
Right (i, _) -> instanceHost i
ticketLocalAP = AP.TicketLocal
{ AP.ticketId = route ClothR
, AP.ticketReplies = route ClothDiscussionR
, AP.ticketParticipants = route ClothFollowersR
, AP.ticketTeam = Nothing
, AP.ticketEvents = route ClothEventsR
, AP.ticketDeps = route ClothDepsR
, AP.ticketReverseDeps = route ClothReverseDepsR
}
mergeRequestAP = AP.MergeRequest
{ AP.mrOrigin = Nothing
, AP.mrTarget =
case mbranch of
Nothing -> Left $ encodeRouteLocal $ RepoR repoHash
Just b -> Right AP.Branch
{ AP.branchName = b
, AP.branchRef = "refs/heads/" <> b
, AP.branchRepo = encodeRouteLocal $ RepoR repoHash
}
, AP.mrBundle =
Left $ encodeRouteHome $ BundleR loomHash clothHash bundleHash
}
ticketAP = AP.Ticket
{ AP.ticketLocal = Just (hLocal, ticketLocalAP)
, AP.ticketAttributedTo =
case author of
Left authorID ->
encodeRouteLocal $ PersonR $ hashPerson authorID
Right (_instance, object) ->
remoteObjectIdent object
, AP.ticketPublished = Just $ ticketCreated ticket
, AP.ticketUpdated = Nothing
, AP.ticketContext = Just $ encodeRouteHome $ LoomR loomHash
-- , AP.ticketName = Just $ "#" <> T.pack (show num)
, AP.ticketSummary = TextHtml $ ticketTitle ticket
, AP.ticketContent = TextHtml $ ticketDescription ticket
, AP.ticketSource = TextPandocMarkdown $ ticketSource ticket
, AP.ticketAssignedTo = Nothing
, AP.ticketResolved =
let u (Left (actor, obiid)) =
encodeRouteHome $
outboxItemRoute actor $ hashItem obiid
u (Right (i, ro)) =
ObjURI (instanceHost i) (remoteObjectIdent ro)
in (,Nothing) . Just . u <$> resolve
, AP.ticketAttachment = Just (hLocal, mergeRequestAP)
}
provideHtmlAndAP' authorHost ticketAP $ redirectToPrettyJSON here
where
here = ClothR loomHash clothHash
{-
mpid <- maybeAuthId
( wshr, wfl,
author, massignee, mresolved, cloth, lcloth, tparams, eparams, cparams) <-
runDB $ do
(Entity sid sharer, Entity jid project, Entity tid cloth, Entity _ lcloth, _etcl, _etpl, author, resolved) <- getProjectCloth404 shar proj ltkhid
tparams <- getClothTextParams tid wid
eparams <- getClothEnumParams tid wid
cparams <- getClothClasses tid wid
return
( wshr, wfl
, author', massignee, mresolved, cloth, lcloth
, tparams, eparams, cparams
)
let desc :: Widget
desc = toWidget $ preEscapedToMarkup $ clothDescription cloth
discuss =
discussionW
(return $ localClothDiscuss lcloth)
(ProjectClothTopReplyR shar proj ltkhid)
(ProjectClothReplyR shar proj ltkhid . encodeHid)
cRelevant <- newIdent
cIrrelevant <- newIdent
let relevant filt =
bool cIrrelevant cRelevant $
case clothStatus cloth of
TSNew -> wffNew filt
TSTodo -> wffTodo filt
TSClosed -> wffClosed filt
provideHtmlAndAP' host clothAP $
let followButton =
followW
(ProjectClothFollowR shar proj ltkhid)
(ProjectClothUnfollowR shar proj ltkhid)
(return $ localClothFollowers lcloth)
in $(widgetFile "cloth/one")
-}
getClothDiscussionR
:: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothDiscussionR _ _ = do
error "Temporarily disabled"
{-
encodeHid <- getEncodeKeyHashid
getDiscussion
(ProjectClothReplyR shar proj ltkhid . encodeHid)
(ProjectClothTopReplyR shar proj ltkhid)
(selectDiscussionId shar proj ltkhid)
-}
getClothEventsR
:: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothEventsR _ _ = do
error "Not implemented yet"
getClothFollowersR
:: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothFollowersR loomHash clothHash = getFollowersCollection here getFsid
where
here = ClothFollowersR loomHash clothHash
getFsid = do
(_, _, Entity _ t, _, _, _) <- getCloth404 loomHash clothHash
return $ ticketFollowers t
getClothDepsR
:: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothDepsR loomHash clothHash =
error "Temporarily disabled"
{-
getDependencyCollection here dep getLocalClothId404
where
here = ClothDepsR loomHash clothHash
dep = ClothDepR loomHash clothHash
getLocalClothId404 = do
(_, _, Entity ltid _, _, _, _, _) <- getCloth404 dkhid ltkhid
return ltid
-}
getClothReverseDepsR
:: KeyHashid Loom -> KeyHashid TicketLoom -> Handler TypedContent
getClothReverseDepsR loomHash clothHash =
error "Temporarily disabled"
{-
getReverseDependencyCollection here getLocalClothId404
where
here = ClothReverseDepsR loomhash clothHash
getLocalClothId404 = do
(_, _, _, Entity ltid _, _, _, _, _) <- getCloth404 loomHash clothHash
return ltid
-}
getBundleR
:: KeyHashid Loom
-> KeyHashid TicketLoom
-> KeyHashid Bundle
-> Handler TypedContent
getBundleR loomHash clothHash bundleHash = do
(patchIDs, previousBundles, maybeCurrentBundle) <- runDB $ do
(_, Entity clothID _, _, _, _, latest :| prevs) <-
getCloth404 loomHash clothHash
bundleID <- decodeKeyHashid404 bundleHash
bundle <- get404 bundleID
unless (bundleTicket bundle == clothID) notFound
patches <- do
ids <- selectKeysList [PatchBundle ==. bundleID] [Desc PatchId]
case nonEmpty ids of
Nothing -> error "Bundle without any Patches in DB"
Just ne -> return ne
let (prevs, mcurr) =
if bundleID == latest
then (prevs, Nothing)
else ([] , Just latest)
return (patches, prevs, mcurr)
encodeRouteLocal <- getEncodeRouteLocal
hashBundle <- getEncodeKeyHashid
hashPatch <- getEncodeKeyHashid
let versionRoute = BundleR loomHash clothHash . hashBundle
bundleLocalAP = AP.BundleLocal
{ AP.bundleId = encodeRouteLocal here
, AP.bundleContext =
encodeRouteLocal $ ClothR loomHash clothHash
, AP.bundlePrevVersions =
map (encodeRouteLocal . versionRoute) previousBundles
, AP.bundleCurrentVersion =
encodeRouteLocal . versionRoute <$> maybeCurrentBundle
}
bundleAP =
AP.BundleHosted
(Just bundleLocalAP)
(NE.map
( encodeRouteLocal
. PatchR loomHash clothHash bundleHash
. hashPatch
)
patchIDs
)
provideHtmlAndAP bundleAP $ redirectToPrettyJSON here
where
here = BundleR loomHash clothHash bundleHash
getPatchR
:: KeyHashid Loom
-> KeyHashid TicketLoom
-> KeyHashid Bundle
-> KeyHashid Patch
-> Handler TypedContent
getPatchR loomHash clothHash bundleHash patchHash = do
(patch, author) <- runDB $ do
(_, _, _, author', _, versions) <- getCloth404 loomHash clothHash
(,) <$> do bundleID <- decodeKeyHashid404 bundleHash
unless (bundleID `elem` versions) notFound
patchID <- decodeKeyHashid404 patchHash
patch' <- get404 patchID
unless (patchBundle patch' == bundleID) notFound
return patch'
<*> bitraverse
(\ (Entity _ tal) -> return $ ticketAuthorLocalAuthor tal)
(\ (Entity _ tar) -> do
ra <- getJust $ ticketAuthorRemoteAuthor tar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (i, ro)
)
author'
encodeRouteLocal <- getEncodeRouteLocal
hashPerson <- getEncodeKeyHashid
hLocal <- getsYesod siteInstanceHost
let host =
case author of
Left _ -> hLocal
Right (i, _) -> instanceHost i
patchLocalAP = AP.PatchLocal
{ AP.patchId = encodeRouteLocal here
, AP.patchContext =
encodeRouteLocal $ BundleR loomHash clothHash bundleHash
}
patchAP = AP.Patch
{ AP.patchLocal = Just (hLocal, patchLocalAP)
, AP.patchAttributedTo =
case author of
Left authorID ->
encodeRouteLocal $ PersonR $ hashPerson authorID
Right (_, object) -> remoteObjectIdent object
, AP.patchPublished = Just $ patchCreated patch
, AP.patchType = patchType patch
, AP.patchContent = patchContent patch
}
provideHtmlAndAP' host patchAP $ redirectToPrettyJSON here
where
here = PatchR loomHash clothHash bundleHash patchHash
getClothDepR
:: KeyHashid Loom
-> KeyHashid TicketLoom
-> KeyHashid LocalTicketDependency
-> Handler TypedContent
getClothDepR _ _ _ = do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
wiRoute <- askWorkItemRoute
hLocal <- asksSite siteInstanceHost
tdid <- decodeKeyHashid404 tdkhid
(td, author, parent, child) <- runDB $ do
td <- get404 tdid
(td,,,)
<$> getAuthor tdid
<*> getWorkItem ( localTicketDependencyParent td)
<*> getChild tdid
let host =
case author of
Left _ -> hLocal
Right (h, _) -> h
tdepAP = AP.TicketDependency
{ ticketDepId = Just $ encodeRouteHome here
, ticketDepParent = encodeRouteHome $ wiRoute parent
, ticketDepChild =
case child of
Left wi -> encodeRouteHome $ wiRoute wi
Right (h, lu) -> ObjURI h lu
, ticketDepAttributedTo =
case author of
Left shr -> encodeRouteLocal $ SharerR shr
Right (_h, lu) -> lu
, ticketDepPublished = Just $ localTicketDependencyCreated td
, ticketDepUpdated = Nothing
}
provideHtmlAndAP' host tdepAP $ redirectToPrettyJSON here
where
here = TicketDepR tdkhid
getAuthor tdid = do
tda <- requireEitherAlt
(getValBy $ UniqueTicketDependencyAuthorLocal tdid)
(getValBy $ UniqueTicketDependencyAuthorRemote tdid)
"No TDA"
"Both TDAL and TDAR"
bitraverse
(\ tdal -> do
p <- getJust $ ticketDependencyAuthorLocalAuthor tdal
s <- getJust $ personIdent p
return $ sharerIdent s
)
(\ tdar -> do
ra <- getJust $ ticketDependencyAuthorRemoteAuthor tdar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tda
getChild tdid = do
tdc <- requireEitherAlt
(getValBy $ UniqueTicketDependencyChildLocal tdid)
(getValBy $ UniqueTicketDependencyChildRemote tdid)
"No TDC"
"Both TDCL and TDCR"
bitraverse
(getWorkItem . ticketDependencyChildLocalChild)
(\ tdcr -> do
ro <- getJust $ ticketDependencyChildRemoteChild tdcr
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tdc
-}
{-
getSharerProposalsR :: ShrIdent -> Handler TypedContent
getSharerProposalsR =
getSharerWorkItems SharerProposalsR SharerProposalR countPatches selectPatches
@ -595,112 +1028,4 @@ getRepoProposalEventsR shr rp ltkhid = do
provideEmptyCollection
CollectionTypeOrdered
(RepoProposalEventsR shr rp ltkhid)
getRepoProposalBundleR
:: ShrIdent
-> RpIdent
-> KeyHashid LocalTicket
-> KeyHashid Bundle
-> Handler TypedContent
getRepoProposalBundleR shr rp ltkhid bnkhid = do
(ptids, prevs, mcurr) <- runDB $ do
(_, _, Entity tid _, _, _, _, _, _, v :| vs) <- getRepoProposal404 shr rp ltkhid
bnid <- decodeKeyHashid404 bnkhid
bn <- get404 bnid
unless (bundleTicket bn == tid) notFound
ptids <- selectKeysList [PatchBundle ==. bnid] [Desc PatchId]
ptidsNE <-
case nonEmpty ptids of
Nothing -> error "Bundle without any Patches in DB"
Just ne -> return ne
let (prevs, mcurr) =
if bnid == v
then (vs, Nothing)
else ([], Just v)
return (ptidsNE, prevs, mcurr)
encodeRouteLocal <- getEncodeRouteLocal
encodeBNID <- getEncodeKeyHashid
encodePTID <- getEncodeKeyHashid
let versionRoute = RepoProposalBundleR shr rp ltkhid . encodeBNID
local = BundleLocal
{ bundleId = encodeRouteLocal here
, bundleContext =
encodeRouteLocal $ RepoProposalR shr rp ltkhid
, bundlePrevVersions =
map (encodeRouteLocal . versionRoute) prevs
, bundleCurrentVersion = encodeRouteLocal . versionRoute <$> mcurr
}
bundleAP =
AP.BundleHosted
(Just local)
(NE.map
( encodeRouteLocal
. RepoProposalBundlePatchR shr rp ltkhid bnkhid
. encodePTID
)
ptids
)
provideHtmlAndAP bundleAP $ redirectToPrettyJSON here
where
here = RepoProposalBundleR shr rp ltkhid bnkhid
getRepoProposalBundlePatchR
:: ShrIdent
-> RpIdent
-> KeyHashid LocalTicket
-> KeyHashid Bundle
-> KeyHashid Patch
-> Handler TypedContent
getRepoProposalBundlePatchR shr rp ltkhid bnkhid ptkhid = do
(patch, author) <- runDB $ do
(_, _, _, _, _, _, ta, _, vers) <- getRepoProposal404 shr rp ltkhid
(,) <$> do bnid <- decodeKeyHashid404 bnkhid
unless (bnid `elem` vers) notFound
ptid <- decodeKeyHashid404 ptkhid
pt <- get404 ptid
unless (patchBundle pt == bnid) notFound
return pt
<*> bitraverse
(\ (Entity _ tal, _) -> do
p <- getJust $ ticketAuthorLocalAuthor tal
getJust $ personIdent p
)
(\ (Entity _ tar) -> do
ra <- getJust $ ticketAuthorRemoteAuthor tar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (i, ro)
)
ta
encodeRouteLocal <- getEncodeRouteLocal
hLocal <- getsYesod siteInstanceHost
let host =
case author of
Left _ -> hLocal
Right (i, _) -> instanceHost i
patchAP = AP.Patch
{ AP.patchLocal = Just
( hLocal
, AP.PatchLocal
{ AP.patchId = encodeRouteLocal here
, AP.patchContext =
encodeRouteLocal $
RepoProposalBundleR shr rp ltkhid bnkhid
}
)
, AP.patchAttributedTo =
case author of
Left sharer ->
encodeRouteLocal $ SharerR $ sharerIdent sharer
Right (_, object) -> remoteObjectIdent object
, AP.patchPublished = Just $ patchCreated patch
, AP.patchType = patchType patch
, AP.patchContent = patchContent patch
}
provideHtmlAndAP' host patchAP $ redirectToPrettyJSON here
where
here = RepoProposalBundlePatchR shr rp ltkhid bnkhid ptkhid
-}

View file

@ -13,14 +13,35 @@
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Handler.Project
( getProjectsR
, postProjectsR
, getProjectNewR
module Vervis.Handler.Deck
( getDeckR
, getDeckInboxR
, postDeckInboxR
, getDeckOutboxR
, getDeckOutboxItemR
, getDeckFollowersR
, getDeckTicketsR
, getDeckTreeR
, getDeckNewR
, postDeckNewR
, postDeckDeleteR
, getDeckEditR
, postDeckEditR
, postDeckFollowR
, postDeckUnfollowR
{-
, getProjectsR
, getProjectR
, putProjectR
, postProjectR
, getProjectEditR
, getProjectDevsR
, postProjectDevsR
, getProjectDevNewR
@ -28,19 +49,19 @@ module Vervis.Handler.Project
, deleteProjectDevR
, postProjectDevR
, getProjectTeamR
, getProjectFollowersR
-}
)
where
import Control.Monad
import Control.Monad.Trans.Except
import Data.Aeson
import Data.Foldable
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Database.Esqueleto hiding (delete, (%), (==.))
import Text.Blaze.Html (Html)
import Yesod.Auth (requireAuth)
import Yesod.Core
@ -49,47 +70,198 @@ import Yesod.Form.Functions (runFormPost)
import Yesod.Form.Types (FormResult (..))
import Yesod.Persist.Core (runDB, get404, getBy404)
import qualified Data.ByteString.Lazy as BL
import qualified Database.Esqueleto as E
import Database.Persist.JSON
import Development.PatchMediaType
import Network.FedURI
import Web.ActivityPub hiding (Project (..), Repo (..), Actor (..), ActorDetail (..), ActorLocal (..))
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Data.Either.Local
import Data.Paginate.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.Actor
import Vervis.API
import Vervis.Client
import Vervis.Federation
import Vervis.Form.Project
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Paginate
import Vervis.Settings
import Vervis.Widget.Project
import Vervis.Widget.Sharer
import Vervis.Widget.Workflow
import Vervis.Widget.Person
getProjectsR :: ShrIdent -> Handler Html
getProjectsR ident = do
projects <- runDB $ select $ from $ \ (sharer, project) -> do
where_ $
sharer ^. SharerIdent E.==. val ident &&.
sharer ^. SharerId E.==. project ^. ProjectSharer
orderBy [asc $ project ^. ProjectIdent]
return $ project ^. ProjectIdent
defaultLayout $(widgetFile "project/list")
getDeckR :: KeyHashid Deck -> Handler TypedContent
getDeckR deckHash = do
deckID <- decodeKeyHashid404 deckHash
(deck, repoIDs, actor) <- runDB $ do
d <- get404 deckID
rs <- selectKeysList [RepoProject ==. Just deckID] [Asc RepoId]
(d,rs,) <$> getJust (deckActor d)
postProjectsR :: ShrIdent -> Handler Html
postProjectsR shr = do
encodeRouteLocal <- getEncodeRouteLocal
let deckAP = AP.TicketTracker
{ AP.ticketTrackerActor = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = encodeRouteLocal $ DeckR deckHash
, AP.actorInbox = encodeRouteLocal $ DeckInboxR deckHash
, AP.actorOutbox =
Just $ encodeRouteLocal $ DeckOutboxR deckHash
, AP.actorFollowers =
Just $ encodeRouteLocal $ DeckFollowersR deckHash
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = ActorTypeTicketTracker
, AP.actorUsername = Nothing
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
, AP.ticketTrackerTeam = Nothing
}
followButton =
followW
(DeckFollowR deckHash)
(DeckUnfollowR deckHash)
(actorFollowers actor)
provideHtmlAndAP deckAP $ redirectToPrettyJSON $ DeckR deckHash
getDeckInboxR :: KeyHashid Deck -> Handler TypedContent
getDeckInboxR = getInbox DeckInboxR deckActor
postDeckInboxR :: KeyHashid Deck -> Handler TypedContent
postDeckInboxR _ = error "Temporarily disabled"
getDeckOutboxR :: KeyHashid Deck -> Handler TypedContent
getDeckOutboxR = getOutbox DeckOutboxR deckActor
getDeckOutboxItemR
:: KeyHashid Deck -> KeyHashid OutboxItem -> Handler TypedContent
getDeckOutboxItemR = getOutboxItem DeckOutboxItemR deckActor
getDeckFollowersR :: KeyHashid Deck -> Handler TypedContent
getDeckFollowersR = getActorFollowersCollection DeckFollowersR deckActor
getDeckTicketsR :: KeyHashid Deck -> Handler TypedContent
getDeckTicketsR deckHash = selectRep $ do
{-
provideRep $ do
((filtResult, filtWidget), filtEnctype) <- runFormGet ticketFilterForm
let tf =
case filtResult of
FormSuccess filt -> filt
FormMissing -> def
FormFailure l ->
error $ "Ticket filter form failed: " ++ show l
(total, pages, mpage) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
let countAllTickets = count [TicketProjectLocalProject ==. jid]
selectTickets off lim =
getTicketSummaries
(filterTickets tf)
(Just $ \ t -> [E.asc $ t E.^. TicketId])
(Just (off, lim))
jid
getPageAndNavCount countAllTickets selectTickets
case mpage of
Nothing -> redirectFirstPage here
Just (rows, navModel) ->
let pageNav = navWidget navModel
in defaultLayout $(widgetFile "ticket/list")
-}
provideAP' $ do
deckID <- decodeKeyHashid404 deckHash
(total, pages, mpage) <- runDB $ do
let countAllTickets = count [TicketDeckDeck ==. deckID]
selectTickets off lim =
selectKeysList
[TicketDeckDeck ==. deckID]
[OffsetBy off, LimitTo lim, Desc TicketDeckTicket]
getPageAndNavCount countAllTickets selectTickets
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
hashTicket <- getEncodeKeyHashid
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- asksSite siteInstanceHost
return $
case mpage of
Nothing -> encodeStrict $ Doc host $ Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
Just (tickets, navModel) ->
let current = nmCurrent navModel
in encodeStrict $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems =
encodeRouteHome . TicketR deckHash . hashTicket <$> tickets
}
where
here = DeckTicketsR deckHash
encodeStrict = BL.toStrict . encode
getDeckTreeR :: KeyHashid Deck -> Handler Html
getDeckTreeR _ = error "Temporarily disabled"
{-
(summaries, deps) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
(,) <$> getTicketSummaries Nothing Nothing Nothing jid
<*> getTicketDepEdges jid
defaultLayout $ ticketTreeDW shr prj summaries deps
-}
getDeckNewR :: Handler Html
getDeckNewR = do
error "Temporarily disabled"
{-
Entity sid _ <- runDB $ getBy404 $ UniqueSharer shr
((_result, widget), enctype) <- runFormPost $ newProjectForm sid
defaultLayout $(widgetFile "project/new")
-}
postDeckNewR :: Handler Html
postDeckNewR = do
error "Temporarily disabled"
{-
ep@(Entity _ p) <- requireAuth
Entity sid s <- runDB $ do
_ <- getBy404 $ UniqueSharer shr
@ -115,64 +287,27 @@ postProjectsR shr = do
Right prj -> do
setMessage "Project created!"
redirect $ ProjectR shr prj
-}
getProjectNewR :: ShrIdent -> Handler Html
getProjectNewR shr = do
Entity sid _ <- runDB $ getBy404 $ UniqueSharer shr
((_result, widget), enctype) <- runFormPost $ newProjectForm sid
defaultLayout $(widgetFile "project/new")
postDeckDeleteR :: KeyHashid Deck -> Handler Html
postDeckDeleteR _ = error "Temporarily disabled"
getProjectR :: ShrIdent -> PrjIdent -> Handler TypedContent
getProjectR shar proj = do
(actor, project, workflow, wsharer, repos) <- runDB $ do
Entity sid s <- getBy404 $ UniqueSharer shar
Entity pid p <- getBy404 $ UniqueProject proj sid
w <- get404 $ projectWorkflow p
sw <-
if workflowSharer w == sid
then return s
else get404 $ workflowSharer w
rs <- selectList [RepoProject ==. Just pid] [Asc RepoIdent]
a <- getJust $ projectActor p
return (a, p, w, sw, rs)
getDeckEditR :: KeyHashid Deck -> Handler Html
getDeckEditR _ = do
error "Temporarily disabled"
{-
(sid, ep) <- runDB $ do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
ep <- getBy404 $ UniqueProject prj sid
return (sid, ep)
((_result, widget), enctype) <- runFormPost $ editProjectForm sid ep
defaultLayout $(widgetFile "project/edit")
-}
route2fed <- getEncodeRouteHome
route2local <- getEncodeRouteLocal
let projectAP = AP.TicketTracker
{ AP.ticketTrackerActor = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = route2local $ ProjectR shar proj
, AP.actorInbox = route2local $ ProjectInboxR shar proj
, AP.actorOutbox =
Just $ route2local $ ProjectOutboxR shar proj
, AP.actorFollowers =
Just $ route2local $ ProjectFollowersR shar proj
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
[ Left $ route2local ActorKey1R
, Left $ route2local ActorKey2R
]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = ActorTypeTicketTracker
, AP.actorUsername = Nothing
, AP.actorName =
Just $ fromMaybe (prj2text proj) $ projectName project
, AP.actorSummary = projectDesc project
}
}
, AP.ticketTrackerTeam = route2local $ ProjectTeamR shar proj
}
followButton =
followW
(ProjectFollowR shar proj)
(ProjectUnfollowR shar proj)
(return $ actorFollowers actor)
provideHtmlAndAP projectAP $(widgetFile "project/one")
putProjectR :: ShrIdent -> PrjIdent -> Handler Html
putProjectR shr prj = do
postDeckEditR :: KeyHashid Deck -> Handler Html
postDeckEditR _ = do
error "Temporarily disabled"
{-
(sid, ep@(Entity jid _)) <- runDB $ do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
eproj <- getBy404 $ UniqueProject prj sid
@ -189,22 +324,50 @@ putProjectR shr prj = do
FormFailure _l -> do
setMessage "Project update failed, see errors below."
defaultLayout $(widgetFile "project/edit")
-}
postProjectR :: ShrIdent -> PrjIdent -> Handler Html
postProjectR shr prj = do
mmethod <- lookupPostParam "_method"
case mmethod of
Just "PUT" -> putProjectR shr prj
_ -> notFound
postDeckFollowR :: KeyHashid Deck -> Handler ()
postDeckFollowR _ = error "Temporarily disabled"
getProjectEditR :: ShrIdent -> PrjIdent -> Handler Html
getProjectEditR shr prj = do
(sid, ep) <- runDB $ do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
ep <- getBy404 $ UniqueProject prj sid
return (sid, ep)
((_result, widget), enctype) <- runFormPost $ editProjectForm sid ep
defaultLayout $(widgetFile "project/edit")
postDeckUnfollowR :: KeyHashid Deck -> Handler ()
postDeckUnfollowR _ = error "Temporarily disabled"
{-
getProjectsR :: ShrIdent -> Handler Html
getProjectsR ident = do
projects <- runDB $ select $ from $ \ (sharer, project) -> do
where_ $
sharer ^. SharerIdent E.==. val ident &&.
sharer ^. SharerId E.==. project ^. ProjectSharer
orderBy [asc $ project ^. ProjectIdent]
return $ project ^. ProjectIdent
defaultLayout $(widgetFile "project/list")
getProjectDevsR :: ShrIdent -> PrjIdent -> Handler Html
getProjectDevsR shr prj = do
@ -371,13 +534,4 @@ getProjectTeamR shr prj = do
, collectionItems = map (encodeRouteHome . SharerR) memberShrs
}
provideHtmlAndAP team $ redirect (here, [("prettyjson", "true")])
getProjectFollowersR :: ShrIdent -> PrjIdent -> Handler TypedContent
getProjectFollowersR shr prj = getFollowersCollection here getFsid
where
here = ProjectFollowersR shr prj
getFsid = do
sid <- getKeyBy404 $ UniqueSharer shr
j <- getValBy404 $ UniqueProject prj sid
a <- getJust $ projectActor j
return $ actorFollowers a
-}

View file

@ -15,11 +15,10 @@
module Vervis.Handler.Discussion
( getDiscussion
, getDiscussionMessage
, getTopReply
, postTopReply
, getReply
, postReply
--, getTopReply
--, postTopReply
--, getReply
--, postReply
)
where
@ -57,7 +56,6 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.API
import Vervis.Client
import Vervis.Discussion
import Vervis.Federation
import Vervis.FedURI
@ -69,8 +67,6 @@ import Yesod.RenderSource
import Vervis.Settings
import Vervis.Widget.Discussion
import qualified Vervis.Client as C
getDiscussion
:: (MessageId -> Route App)
-> Route App
@ -79,6 +75,7 @@ getDiscussion
getDiscussion reply topic getdid =
defaultLayout $ discussionW getdid topic reply
{-
getNode :: AppDB DiscussionId -> MessageId -> AppDB MessageTreeNode
getNode getdid mid = do
did <- getdid
@ -119,83 +116,6 @@ getNodeL getdid lmid = do
return $ MessageTreeNode mid m $ MessageTreeNodeLocal lmid s
-}
getDiscussionMessage :: ShrIdent -> LocalMessageId -> Handler TypedContent
getDiscussionMessage shr lmid = do
doc <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
pid <- getKeyBy404 $ UniquePersonIdent sid
lm <- get404 lmid
unless (localMessageAuthor lm == pid) notFound
m <- getJust $ localMessageRest lm
route2fed <- getEncodeRouteHome
uContext <- do
let did = messageRoot m
mlt <- getBy $ UniqueLocalTicketDiscussion did
mrd <- getValBy $ UniqueRemoteDiscussion did
case (mlt, mrd) of
(Nothing, Nothing) -> fail $ "DiscussionId #" ++ show did ++ " has no context"
(Just _, Just _) -> fail $ "DiscussionId #" ++ show did ++ " has both ticket and remote contexts"
(Just (Entity ltid lt), Nothing) -> do
tpl <- do
mtpl <- runMaybeT $ do
tclid <- MaybeT $ getKeyBy $ UniqueTicketContextLocal $ localTicketTicket lt
MaybeT $ getValBy $ UniqueTicketProjectLocal tclid
case mtpl of
Nothing -> error "No TPL"
Just v -> return v
j <- getJust $ ticketProjectLocalProject tpl
s <- getJust $ projectSharer j
let shr = sharerIdent s
prj = projectIdent j
ltkhid <- encodeKeyHashid ltid
return $ route2fed $ ProjectTicketR shr prj ltkhid
(Nothing, Just rd) -> do
ro <- getJust $ remoteDiscussionIdent rd
i <- getJust $ remoteObjectInstance ro
return $ ObjURI (instanceHost i) (remoteObjectIdent ro)
muParent <- for (messageParent m) $ \ midParent -> do
mlocal <- getBy $ UniqueLocalMessage midParent
mremote <- getValBy $ UniqueRemoteMessage midParent
case (mlocal, mremote) of
(Nothing, Nothing) -> fail "Message with no author"
(Just _, Just _) -> fail "Message used as both local and remote"
(Just (Entity lmidParent lmParent), Nothing) -> do
p <- getJust $ localMessageAuthor lmParent
s <- getJust $ personIdent p
lmhidParent <- encodeKeyHashid lmidParent
return $ route2fed $ MessageR (sharerIdent s) lmhidParent
(Nothing, Just rmParent) -> do
rs <- getJust $ remoteMessageAuthor rmParent
ro <- getJust $ remoteActorIdent rs
i <- getJust $ remoteObjectInstance ro
return $ ObjURI (instanceHost i) (remoteObjectIdent ro)
--ob <- getJust $ localMessageCreate lm
--let activity = docValue $ persistJSONValue $ outboxItemActivity ob
host <- getsYesod $ appInstanceHost . appSettings
route2local <- getEncodeRouteLocal
lmhid <- encodeKeyHashid lmid
return $ Doc host Note
{ noteId = Just $ route2local $ MessageR shr lmhid
, noteAttrib = route2local $ SharerR shr
, noteAudience = Audience [] [] [] [] [] []
--case activitySpecific activity of
-- CreateActivity (Create note) -> noteAudience note
-- _ -> error $ "lmid#" ++ show (fromSqlKey lmid) ++ "'s create isn't a Create activity!"
, noteReplyTo = Just $ fromMaybe uContext muParent
, noteContext = Just uContext
, notePublished = Just $ messageCreated m
, noteSource = messageSource m
, noteContent = messageContent m
}
selectRep $ do
provideAP $ pure doc
provideRep $
defaultLayout
[whamlet|
<div><pre>#{encodePrettyToLazyText doc}
|]
getTopReply :: Route App -> Handler Html
getTopReply replyP = do
((_result, widget), enctype) <- runFormPost newMessageForm
@ -305,3 +225,4 @@ postReply hDest recipsA recipsC context recipF replyG replyP after getdid midPar
case mlmid of
Nothing -> error "noteC succeeded but no lmid found for obiid"
Just lmid -> redirect $ after lmid
-}

View file

@ -14,8 +14,7 @@
-}
module Vervis.Handler.Git
( getGitRefDiscoverR
, postGitUploadRequestR
(
)
where
@ -50,42 +49,6 @@ import Vervis.Foundation (Handler)
import Vervis.Model.Ident
import Vervis.Path (askRepoDir)
getGitRefDiscoverR :: ShrIdent -> RpIdent -> Handler TypedContent
getGitRefDiscoverR shr rp = do
let typ = "application/x-git-upload-pack-advertisement"
path <- askRepoDir shr rp
let pathG = fromString path
seemsThere <- liftIO $ isRepo pathG
if seemsThere
then do
rq <- getRequest
case reqGetParams rq of
[("service", serv)] ->
if serv == "git-upload-pack"
then do
let settings =
( proc "git"
[ "upload-pack"
, "--stateless-rpc"
, "--advertise-refs"
, path
]
)
{ std_out = CreatePipe
}
(_, mh, _, _) <-
liftIO $ createProcess settings
let h = fromJust mh
refs <- liftIO $ B.hGetContents h
let content = runPut $ do
putService UploadPack
putByteString refs
setHeader "Cache-Control" "no-cache"
return $ TypedContent typ $ toContent content
else permissionDenied "Service not supported"
_ -> notFound
else notFound
{-
getGitRefDiscoverR :: ShrIdent -> RpIdent -> Handler GitRefDiscovery
getGitRefDiscoverR shar repo = do
@ -108,39 +71,6 @@ getGitRefDiscoverR shar repo = do
else notFound
-}
postGitUploadRequestR :: ShrIdent -> RpIdent -> Handler TypedContent
postGitUploadRequestR shr rp = do
let typ = "application/x-git-upload-pack-result"
path <- askRepoDir shr rp
let pathG = fromString path
seemsThere <- liftIO $ isRepo pathG
if seemsThere
then do
getBody <- strictRequestBody <$> waiRequest
body <- liftIO getBody
let settings =
( proc "git"
[ "upload-pack"
, "--stateless-rpc"
, path
]
)
{ std_in = CreatePipe
, std_out = CreatePipe
}
(mhin, mhout, _, _) <- liftIO $ createProcess settings
let hin = fromJust mhin
hout = fromJust mhout
liftIO $ BL.hPut hin body >> hClose hin
setHeader "Cache-Control" "no-cache"
let loop = do
b <- liftIO $ B.hGet hout BLI.defaultChunkSize
unless (B.null b) $ do
sendChunkBS b
loop
respondSource typ loop
else notFound
{- This is commented out for now because it doesn't work. The 'collectObjIds'
- function file descriptor exhaustion. I don't know whether and how I can fix
- that. Maybe dive deep into what happens under the hood in 'hit', or make a

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -14,16 +14,32 @@
-}
module Vervis.Handler.Group
( getGroupsR
( getGroupR
, getGroupInboxR
, postGroupInboxR
, getGroupOutboxR
, getGroupOutboxItemR
, getGroupFollowersR
{-
, getGroupsR
, postGroupsR
, getGroupNewR
, getGroup
, getGroupMembersR
, postGroupMembersR
, getGroupMemberNewR
, getGroupMemberR
, deleteGroupMemberR
, postGroupMemberR
-}
)
where
@ -39,19 +55,95 @@ import Yesod.Core.Content (TypedContent)
import Yesod.Core.Handler
import Yesod.Form.Functions (runFormPost)
import Yesod.Form.Types (FormResult (..))
import Yesod.Persist.Core (runDB, getBy404)
import Yesod.Persist.Core
import qualified Database.Esqueleto as E
import Vervis.Form.Group
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import qualified Web.ActivityPub as AP
import Vervis.Actor
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Group
import Vervis.Model.Ident (ShrIdent, shr2text)
import Vervis.Settings (widgetFile)
import Vervis.Time (showDate)
import Vervis.Widget.Sharer
getGroupR :: KeyHashid Group -> Handler TypedContent
getGroupR groupHash = do
groupID <- decodeKeyHashid404 groupHash
(group, actor) <- runDB $ do
g <- get404 groupID
(g,) <$> getJust (groupActor g)
encodeRouteLocal <- getEncodeRouteLocal
let route mk = encodeRouteLocal $ mk groupHash
groupAP = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = route GroupR
, AP.actorInbox = route GroupInboxR
, AP.actorOutbox = Just $ route GroupOutboxR
, AP.actorFollowers = Just $ route GroupFollowersR
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeOther "Group"
, AP.actorUsername = Nothing
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
provideHtmlAndAP groupAP $ redirectToPrettyJSON here
where
here = GroupR groupHash
getGroupInboxR :: KeyHashid Group -> Handler TypedContent
getGroupInboxR = getInbox GroupInboxR groupActor
postGroupInboxR :: KeyHashid Group -> Handler TypedContent
postGroupInboxR _ = error "Temporarily disabled"
getGroupOutboxR :: KeyHashid Group -> Handler TypedContent
getGroupOutboxR = getOutbox GroupOutboxR groupActor
getGroupOutboxItemR
:: KeyHashid Group -> KeyHashid OutboxItem -> Handler TypedContent
getGroupOutboxItemR = getOutboxItem GroupOutboxItemR groupActor
getGroupFollowersR :: KeyHashid Group -> Handler TypedContent
getGroupFollowersR = getActorFollowersCollection GroupFollowersR groupActor
{-
getGroupsR :: Handler Html
getGroupsR = do
groups <- runDB $ select $ from $ \ (sharer, group) -> do
@ -98,10 +190,6 @@ getGroupNewR = do
((_result, widget), enctype) <- runFormPost newGroupForm
defaultLayout $(widgetFile "group/new")
getGroup :: ShrIdent -> Group -> Handler TypedContent
getGroup shar group = selectRep $ provideRep $
defaultLayout $(widgetFile "group/one")
getGroupMembersR :: ShrIdent -> Handler Html
getGroupMembersR shar = do
(group, members) <- runDB $ do
@ -211,3 +299,4 @@ postGroupMemberR grp memb = do
case mmethod of
Just "DELETE" -> deleteGroupMemberR grp memb
_ -> notFound
-}

View file

@ -1,70 +0,0 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Handler.Home
( getHomeR
)
where
import Database.Esqueleto hiding ((==.))
import Yesod.Auth.Account (newAccountR)
import Data.Time.Clock
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Traversable
import Database.Persist
import Time.Types (Elapsed (..), Seconds (..))
import Yesod.Auth
import Yesod.Core
import Yesod.Persist.Core
import qualified Database.Esqueleto as E ((==.))
import Data.EventTime.Local
import Vervis.Darcs
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Path
import Vervis.Settings
import qualified Vervis.Git as G
import qualified Vervis.Darcs as D
personalOverview :: Entity Person -> Handler Html
personalOverview (Entity _pid person) = do
(ident, projects, repos) <- runDB $ do
let sid = personIdent person
sharer <- get404 sid
prjs <-
map (projectIdent . entityVal) <$>
selectList [ProjectSharer ==. sid] [Asc ProjectIdent]
rps <-
map (repoIdent . entityVal) <$>
selectList
[RepoSharer ==. sid, RepoProject ==. Nothing]
[Asc RepoIdent]
return (sharerIdent sharer, prjs, rps)
defaultLayout $ do
setTitle "Vervis > Overview"
$(widgetFile "personal-overview")
getHomeR :: Handler Html
getHomeR = do
mp <- maybeAuth
case mp of
Just p -> personalOverview p
Nothing -> redirect BrowseR

View file

@ -14,21 +14,22 @@
-}
module Vervis.Handler.Inbox
( getInboxDebugR
, getSharerInboxR
( getSharerInboxR
, getProjectInboxR
, getDeckInboxR
, getRepoInboxR
, postSharerInboxR
, postProjectInboxR
, postDeckInboxR
, postRepoInboxR
, getSharerOutboxR
, getSharerOutboxItemR
, getProjectOutboxR
, getProjectOutboxItemR
, getDeckOutboxR
, getDeckOutboxItemR
, getRepoOutboxR
, getRepoOutboxItemR
, getActorKey1R
, getActorKey2R
)
where
@ -103,8 +104,6 @@ import Vervis.Model.Ident
import Vervis.Paginate
import Vervis.Settings
import qualified Vervis.Client as C
getShowTime = showTime <$> liftIO getCurrentTime
where
showTime now =
@ -123,124 +122,6 @@ objectId o =
Just (String t) | not (T.null t) -> t
_ -> error "'id' field not found"
getInboxDebugR :: Handler Html
getInboxDebugR = do
acts <-
liftIO . readTVarIO . snd =<< maybe notFound return =<< getsYesod appActivities
defaultLayout
[whamlet|
<p>
Welcome to the ActivityPub inbox test page! Activities received
by this Vervis instance are listed here for testing and
debugging. To test, go to another Vervis instance and publish
something that supports federation (currently, only ticket
comments), either through the regular UI or via the /publish
page, and then come back here to see the result. Activities that
aren't understood or their processing fails get listed here too,
with a report of what exactly happened.
<p>Last 10 activities posted:
<ul>
$forall ActivityReport time msg ctypes body <- acts
<li>
<div>#{show time}
<div>#{msg}
<div><code>#{intercalate " | " $ map BC.unpack ctypes}
<div><pre>#{decodeUtf8 body}
|]
getInbox :: Route App -> AppDB InboxId -> Handler TypedContent
getInbox here getInboxId = do
(total, pages, mpage) <- runDB $ do
ibid <- getInboxId
getPageAndNavCount
(countItems ibid)
(\ off lim -> map adaptItem <$> getItems ibid off lim)
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- getsYesod $ appInstanceHost . appSettings
selectRep $
case mpage of
Nothing -> do
provideAP $ pure $ Doc host $ Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
provideRep (redirectFirstPage here :: Handler Html)
Just (items, navModel) -> do
let current = nmCurrent navModel
provideAP $ pure $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems = map fst items
}
provideRep $ do
let pageNav = navWidget navModel
showTime <- getShowTime
defaultLayout $(widgetFile "person/inbox")
where
countItems ibid =
(+) <$> count [InboxItemLocalInbox ==. ibid]
<*> count [InboxItemRemoteInbox ==. ibid]
getItems ibid off lim =
E.select $ E.from $
\ (ib `E.LeftOuterJoin` (ibl `E.InnerJoin` ob) `E.LeftOuterJoin` (ibr `E.InnerJoin` ract)) -> do
E.on $ ibr E.?. InboxItemRemoteActivity E.==. ract E.?. RemoteActivityId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibr E.?. InboxItemRemoteItem
E.on $ ibl E.?. InboxItemLocalActivity E.==. ob E.?. OutboxItemId
E.on $ E.just (ib E.^. InboxItemId) E.==. ibl E.?. InboxItemLocalItem
E.where_
$ ( E.isNothing (ibr E.?. InboxItemRemoteInbox) E.||.
ibr E.?. InboxItemRemoteInbox E.==. E.just (E.val ibid)
)
E.&&.
( E.isNothing (ibl E.?. InboxItemLocalInbox) E.||.
ibl E.?. InboxItemLocalInbox E.==. E.just (E.val ibid)
)
E.orderBy [E.desc $ ib E.^. InboxItemId]
E.offset $ fromIntegral off
E.limit $ fromIntegral lim
return
( ib E.^. InboxItemId
, ob E.?. OutboxItemActivity
, ob E.?. OutboxItemPublished
, ract E.?. RemoteActivityContent
, ract E.?. RemoteActivityReceived
)
adaptItem
(E.Value ibid, E.Value mact, E.Value mpub, E.Value mobj, E.Value mrec) =
case (mact, mpub, mobj, mrec) of
(Nothing, Nothing, Nothing, Nothing) ->
error $ ibiidString ++ " neither local nor remote"
(Just _, Just _, Just _, Just _) ->
error $ ibiidString ++ " both local and remote"
(Just act, Just pub, Nothing, Nothing) ->
(persistJSONObject act, (pub, False))
(Nothing, Nothing, Just obj, Just rec) ->
(persistJSONObject obj, (rec, True))
_ -> error $ "Unexpected query result for " ++ ibiidString
where
ibiidString = "InboxItem #" ++ show (fromSqlKey ibid)
getSharerInboxR :: ShrIdent -> Handler TypedContent
getSharerInboxR shr = getInbox here getInboxId
where
@ -260,6 +141,16 @@ getProjectInboxR shr prj = getInbox here getInboxId
a <- getJust $ projectActor j
return $ actorInbox a
getDeckInboxR :: KeyHashid Project -> Handler TypedContent
getDeckInboxR dkkhid = do
dkid <- decodeKeyHashid404 dkkhid
getInbox here (getInboxId dkid)
where
here = ProjectInboxR dkkhid
getInboxId dkid = do
dk <- get404 dkid
actorInbox <$> getJust (projectActor dk)
getRepoInboxR :: ShrIdent -> RpIdent -> Handler TypedContent
getRepoInboxR shr rp = getInbox here getInboxId
where
@ -330,6 +221,9 @@ postSharerInboxR shrRecip = handleInbox $ handleSharerInbox shrRecip
postProjectInboxR :: ShrIdent -> PrjIdent -> Handler ()
postProjectInboxR shr prj = handleInbox $ handleProjectInbox shr prj
postDeckInboxR :: KeyHashid Project -> Handler ()
postDeckInboxR dkkhid = handleInbox $ handleDeckInbox dkkhid
postRepoInboxR :: ShrIdent -> RpIdent -> Handler ()
postRepoInboxR shr rp = handleInbox $ handleRepoInbox shr rp
@ -342,70 +236,6 @@ jsonField = checkMMap fromTextarea toTextarea textareaField
-}
getOutbox :: Route App -> AppDB OutboxId -> Handler TypedContent
getOutbox here getObid = do
(total, pages, mpage) <- runDB $ do
obid <- getObid
let countAllItems = count [OutboxItemOutbox ==. obid]
selectItems off lim = selectList [OutboxItemOutbox ==. obid] [Desc OutboxItemId, OffsetBy off, LimitTo lim]
getPageAndNavCount countAllItems selectItems
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- getsYesod $ appInstanceHost . appSettings
selectRep $
case mpage of
Nothing -> do
provideAP $ pure $ Doc host $ Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
provideRep (redirectFirstPage here :: Handler Html)
Just (items, navModel) -> do
let current = nmCurrent navModel
provideAP $ pure $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems = map (persistJSONObject . outboxItemActivity . entityVal) items
}
provideRep $ do
let pageNav = navWidget navModel
showTime <- getShowTime
defaultLayout $(widgetFile "person/outbox")
getOutboxItem
:: Route App
-> AppDB OutboxId
-> KeyHashid OutboxItem
-> Handler TypedContent
getOutboxItem here getObid obikhid = do
obiid <- decodeKeyHashid404 obikhid
body <- runDB $ do
obid <- getObid
obi <- get404 obiid
unless (outboxItemOutbox obi == obid) notFound
return $ outboxItemActivity obi
provideHtmlAndAP'' body $ redirect (here, [("prettyjson", "true")])
getSharerOutboxR :: ShrIdent -> Handler TypedContent
getSharerOutboxR shr = getOutbox here getObid
where
@ -445,6 +275,27 @@ getProjectOutboxItemR shr prj obikhid = getOutboxItem here getObid obikhid
a <- getJust $ projectActor j
return $ actorOutbox a
getDeckOutboxR :: KeyHashid Project -> Handler TypedContent
getDeckOutboxR dkkhid = do
dkid <- decodeKeyHashid404 dkkhid
getOutbox here (getObid dkid)
where
here = DeckOutboxR dkkhid
getObid dkid = do
dk <- get404 dkid
actorOutbox <$> getJust (projectActor dk)
getDeckOutboxItemR
:: KeyHashid Project -> KeyHashid OutboxItem -> Handler TypedContent
getDeckOutboxItemR dkkhid obikhid = do
dkid <- decodeKeyHashid404 dkkhid
getOutboxItem here (getObid dkid) obikhid
where
here = DeckOutboxItemR dkkhid obikhid
getObid dkid = do
dk <- get404 dkid
actorOutbox <$> getJust (projectActor dk)
getRepoOutboxR :: ShrIdent -> RpIdent -> Handler TypedContent
getRepoOutboxR shr rp = getOutbox here getObid
where
@ -463,23 +314,3 @@ getRepoOutboxItemR shr rp obikhid = getOutboxItem here getObid obikhid
sid <- getKeyBy404 $ UniqueSharer shr
r <- getValBy404 $ UniqueRepo rp sid
return $ repoOutbox r
getActorKey :: ((ActorKey, ActorKey, Bool) -> ActorKey) -> Route App -> Handler TypedContent
getActorKey choose route = do
actorKey <-
liftIO . fmap (actorKeyPublicBin . choose) . readTVarIO =<<
getsYesod appActorKeys
encodeRouteLocal <- getEncodeRouteLocal
let key = PublicKey
{ publicKeyId = LocalRefURI $ Left $ encodeRouteLocal route
, publicKeyExpires = Nothing
, publicKeyOwner = OwnerInstance
, publicKeyMaterial = actorKey
}
provideHtmlAndAP key $ redirect (route, [("prettyjson", "true")])
getActorKey1R :: Handler TypedContent
getActorKey1R = getActorKey (\ (k1, _, _) -> k1) ActorKey1R
getActorKey2R :: Handler TypedContent
getActorKey2R = getActorKey (\ (_, k2, _) -> k2) ActorKey2R

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -18,7 +18,6 @@ module Vervis.Handler.Key
, postKeysR
, getKeyNewR
, getKeyR
, getSshKeyR
, deleteKeyR
, postKeyR
)
@ -55,6 +54,7 @@ import Vervis.Model.Ident
import Vervis.Settings
import Vervis.Widget (buttonW)
{-
getKeysR :: Handler Html
getKeysR = do
pid <- requireAuthId
@ -92,30 +92,9 @@ getKeyR tag = do
let toText = decodeUtf8With lenientDecode
content = toText $ encode $ sshKeyContent key
defaultLayout $(widgetFile "key/one")
-}
getSshKeyR :: ShrIdent -> KeyHashid SshKey -> Handler TypedContent
getSshKeyR shr skkhid = do
skid <- decodeKeyHashid404 skkhid
key <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
pid <- getKeyBy404 $ UniquePersonIdent sid
sk <- get404 skid
unless (sshKeyPerson sk == pid) notFound
return sk
encodeRouteLocal <- getEncodeRouteLocal
let here = SshKeyR shr skkhid
keyAP = SshPublicKey
{ sshPublicKeyId = encodeRouteLocal here
, sshPublicKeyExpires = Nothing
, sshPublicKeyOwner = encodeRouteLocal $ SharerR shr
, sshPublicKeyAlgorithm =
case sshKeyAlgo key of
"ssh-rsa" -> SshKeyAlgorithmRSA
_ -> error "Unexpected sshKeyAlgo in DB"
, sshPublicKeyMaterial = sshKeyContent key
}
provideHtmlAndAP keyAP $ redirectToPrettyJSON here
{-
deleteKeyR :: KyIdent -> Handler Html
deleteKeyR tag = do
pid <- requireAuthId
@ -131,3 +110,4 @@ postKeyR tag = do
case mmethod of
Just "DELETE" -> deleteKeyR tag
_ -> notFound
-}

200
src/Vervis/Handler/Loom.hs Normal file
View file

@ -0,0 +1,200 @@
{- This file is part of Vervis.
-
- Written in 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Handler.Loom
( getLoomR
, getLoomInboxR
, postLoomInboxR
, getLoomOutboxR
, getLoomOutboxItemR
, getLoomFollowersR
, getLoomClothsR
)
where
import Control.Monad
import Control.Monad.Trans.Except
import Data.Aeson
import Data.Foldable
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Text.Blaze.Html (Html)
import Yesod.Auth (requireAuth)
import Yesod.Core
import Yesod.Core.Handler (redirect, setMessage, lookupPostParam, notFound)
import Yesod.Form.Functions (runFormPost)
import Yesod.Form.Types (FormResult (..))
import Yesod.Persist.Core (runDB, get404, getBy404)
import qualified Data.ByteString.Lazy as BL
import qualified Database.Esqueleto as E
import Database.Persist.JSON
import Development.PatchMediaType
import Network.FedURI
import Yesod.ActivityPub
import Yesod.Hashids
import Yesod.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Data.Either.Local
import Data.Paginate.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.Actor
import Vervis.API
import Vervis.Federation
import Vervis.Foundation
import Vervis.Model
import Vervis.Paginate
import Vervis.Settings
getLoomR :: KeyHashid Loom -> Handler TypedContent
getLoomR loomHash = do
loomID <- decodeKeyHashid404 loomHash
(loom, actor) <- runDB $ do
l <- get404 loomID
(l,) <$> getJust (loomActor l)
encodeRouteLocal <- getEncodeRouteLocal
let route mk = encodeRouteLocal $ mk loomHash
loomAP = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = route LoomR
, AP.actorInbox = route LoomInboxR
, AP.actorOutbox = Just $ route LoomOutboxR
, AP.actorFollowers = Just $ route LoomFollowersR
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeOther "PatchTracker"
, AP.actorUsername = Nothing
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
provideHtmlAndAP loomAP $ redirectToPrettyJSON here
where
here = LoomR loomHash
getLoomInboxR :: KeyHashid Loom -> Handler TypedContent
getLoomInboxR = getInbox LoomInboxR loomActor
postLoomInboxR :: KeyHashid Loom -> Handler TypedContent
postLoomInboxR _ = error "Temporarily disabled"
getLoomOutboxR :: KeyHashid Loom -> Handler TypedContent
getLoomOutboxR = getOutbox LoomOutboxR loomActor
getLoomOutboxItemR
:: KeyHashid Loom -> KeyHashid OutboxItem -> Handler TypedContent
getLoomOutboxItemR = getOutboxItem LoomOutboxItemR loomActor
getLoomFollowersR :: KeyHashid Loom -> Handler TypedContent
getLoomFollowersR = getActorFollowersCollection LoomFollowersR loomActor
getLoomClothsR :: KeyHashid Loom -> Handler TypedContent
getLoomClothsR loomHash = selectRep $ do
{-
provideRep $ do
((filtResult, filtWidget), filtEnctype) <- runFormGet ticketFilterForm
let tf =
case filtResult of
FormSuccess filt -> filt
FormMissing -> def
FormFailure l ->
error $ "Ticket filter form failed: " ++ show l
(total, pages, mpage) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
let countAllTickets = count [TicketProjectLocalProject ==. jid]
selectTickets off lim =
getTicketSummaries
(filterTickets tf)
(Just $ \ t -> [E.asc $ t E.^. TicketId])
(Just (off, lim))
jid
getPageAndNavCount countAllTickets selectTickets
case mpage of
Nothing -> redirectFirstPage here
Just (rows, navModel) ->
let pageNav = navWidget navModel
in defaultLayout $(widgetFile "ticket/list")
-}
AP.provideAP' $ do
loomID <- decodeKeyHashid404 loomHash
(total, pages, mpage) <- runDB $ do
let countAllTickets = count [TicketLoomLoom ==. loomID]
selectTickets off lim =
selectKeysList
[TicketLoomLoom ==. loomID]
[OffsetBy off, LimitTo lim, Desc TicketLoomTicket]
getPageAndNavCount countAllTickets selectTickets
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
hashTicket <- getEncodeKeyHashid
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- asksSite siteInstanceHost
return $
case mpage of
Nothing -> encodeStrict $ AP.Doc host $ AP.Collection
{ AP.collectionId = encodeRouteLocal here
, AP.collectionType = AP.CollectionTypeOrdered
, AP.collectionTotalItems = Just total
, AP.collectionCurrent = Nothing
, AP.collectionFirst = Just $ pageUrl 1
, AP.collectionLast = Just $ pageUrl pages
, AP.collectionItems = [] :: [Text]
}
Just (tickets, navModel) ->
let current = nmCurrent navModel
in encodeStrict $ AP.Doc host $ AP.CollectionPage
{ AP.collectionPageId = pageUrl current
, AP.collectionPageType = AP.CollectionPageTypeOrdered
, AP.collectionPageTotalItems = Nothing
, AP.collectionPageCurrent = Just $ pageUrl current
, AP.collectionPageFirst = Just $ pageUrl 1
, AP.collectionPageLast = Just $ pageUrl pages
, AP.collectionPagePartOf = encodeRouteLocal here
, AP.collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, AP.collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, AP.collectionPageStartIndex = Nothing
, AP.collectionPageItems =
encodeRouteHome . ClothR loomHash . hashTicket <$> tickets
}
where
here = LoomClothsR loomHash
encodeStrict = BL.toStrict . encode

View file

@ -14,13 +14,28 @@
-}
module Vervis.Handler.Person
( getResendVerifyEmailR
, getPeopleR
, getPerson
( getPersonR
, getPersonInboxR
, postPersonInboxR
, getPersonOutboxR
, postPersonOutboxR
, getPersonOutboxItemR
, getPersonFollowersR
, getPersonFollowingR
, getSshKeyR
, getPersonMessageR
, postPersonFollowR
, postPersonUnfollowR
)
where
import Database.Esqueleto hiding (isNothing, count)
import Control.Monad
import Control.Monad.Trans.Except
import Data.Maybe
import Data.Traversable
import Database.Persist
import Dvara
import Text.Blaze.Html (toHtml)
import Yesod.Core
import Yesod.Auth.Account (newAccountR, resendVerifyEmailWidget, username)
@ -28,138 +43,254 @@ import Yesod.Auth.Account.Message (AccountMsg (MsgEmailUnverified))
import Yesod.Persist.Core
import qualified Data.Text as T (unpack)
import qualified Database.Persist as P
import Yesod.Auth.Unverified (requireUnverifiedAuth)
import Text.Email.Local
import Network.FedURI
import Web.ActivityPub
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import qualified Web.ActivityPub as AP
import Data.Either.Local
import Database.Persist.Local
import Vervis.ActivityPub
import Vervis.Actor
import Vervis.ActorKey
import Vervis.Foundation
import Vervis.Model hiding (Actor (..))
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Secure
import Vervis.Settings
import Vervis.Widget (avatarW)
import Vervis.Widget.Sharer
import Vervis.Ticket
import Vervis.Widget
import Vervis.Widget.Person
-- | Account verification email resend form
getResendVerifyEmailR :: Handler Html
getResendVerifyEmailR = do
person <- requireUnverifiedAuth
defaultLayout $ do
setTitleI MsgEmailUnverified
[whamlet|
<p>_{MsgEmailUnverified}
^{resendVerifyEmailWidget (username person) AuthR}
|]
-- | Get list of users
getPeopleR :: Handler Html
getPeopleR = do
people <- runDB $ select $ from $ \ (sharer, person) -> do
where_ $ sharer ^. SharerId ==. person ^. PersonIdent
orderBy [asc $ sharer ^. SharerIdent]
return $ sharer ^. SharerIdent
defaultLayout $(widgetFile "people")
{-
-- | Create new user
postPeopleR :: Handler Html
postPeopleR = redirect $ AuthR newAccountR
settings <- getsYesod appSettings
if appRegister settings
then do
room <- case appAccounts settings of
Nothing -> return True
Just cap -> do
current <- runDB $ count ([] :: [Filter Person])
return $ current < cap
if room
then do
((result, widget), enctype) <- runFormPost newPersonForm
case result of
FormSuccess np -> do
now <- liftIO getCurrentTime
runDB $ do
let sharer = Sharer
{ sharerIdent = npLogin np
, sharerName = npName np
, sharerCreated = now
}
sid <- insert sharer
let person = Person
{ personIdent = sid
, personLogin = shr2text $ npLogin np
, personHash = Nothing
, personEmail = npEmail np
}
person' <- setPassword (npPass np) person
insert_ person'
redirectUltDest HomeR
FormMissing -> do
setMessage "Field(s) missing"
defaultLayout $(widgetFile "person-new")
FormFailure _l -> do
setMessage
"User registration failed, see errors below"
defaultLayout $(widgetFile "person-new")
else do
setMessage "Maximal number of registered users reached"
redirect PeopleR
else do
setMessage "User registration disabled"
redirect PeopleR
-}
{-
getPersonNewR :: Handler Html
getPersonNewR = redirect $ AuthR newAccountR
regEnabled <- getsYesod $ appRegister . appSettings
if regEnabled
then do
((_result, widget), enctype) <- runFormPost newPersonForm
defaultLayout $(widgetFile "person-new")
else notFound
-}
getPerson :: ShrIdent -> Sharer -> Entity Person -> Handler TypedContent
getPerson shr sharer (Entity pid person) = do
getPersonR :: KeyHashid Person -> Handler TypedContent
getPersonR personHash = do
personID <- decodeKeyHashid404 personHash
(person, actor, sshKeyIDs) <- runDB $ do
p <- get404 personID
a <- getJust $ personActor p
ks <- selectKeysList [SshKeyPerson ==. personID] [Asc SshKeyId]
return (p, a, ks)
encodeRouteLocal <- getEncodeRouteLocal
encodeKeyHashid <- getEncodeKeyHashid
skids <- runDB $ P.selectKeysList [SshKeyPerson P.==. pid] [P.Asc SshKeyId]
let personAP = Actor
{ actorLocal = ActorLocal
{ actorId = encodeRouteLocal $ SharerR shr
, actorInbox = encodeRouteLocal $ SharerInboxR shr
, actorOutbox = Just $ encodeRouteLocal $ SharerOutboxR shr
, actorFollowers = Just $ encodeRouteLocal $ SharerFollowersR shr
, actorFollowing = Just $ encodeRouteLocal $ SharerFollowingR shr
, actorPublicKeys =
hashSshKey <- getEncodeKeyHashid
let personAP = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = encodeRouteLocal $ PersonR personHash
, AP.actorInbox = encodeRouteLocal $ PersonInboxR personHash
, AP.actorOutbox = Just $ encodeRouteLocal $ PersonOutboxR personHash
, AP.actorFollowers = Just $ encodeRouteLocal $ PersonFollowersR personHash
, AP.actorFollowing = Just $ encodeRouteLocal $ PersonFollowingR personHash
, AP.actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, actorSshKeys =
map (encodeRouteLocal . SshKeyR shr . encodeKeyHashid) skids
, AP.actorSshKeys =
map (encodeRouteLocal . SshKeyR personHash . hashSshKey) sshKeyIDs
}
, actorDetail = ActorDetail
{ actorType = ActorTypePerson
, actorUsername = Just $ shr2text shr
, actorName = sharerName sharer
, actorSummary = Nothing
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypePerson
, AP.actorUsername = Just $ username2text $ personUsername person
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
secure <- getSecure
provideHtmlAndAP personAP $(widgetFile "person")
where
followButton =
followW
(SharerFollowR shr)
(SharerUnfollowR shr)
(return $ personFollowers person)
(PersonFollowR personHash)
(PersonUnfollowR personHash)
(actorFollowers actor)
let ep = Entity personID person
secure <- getSecure
provideHtmlAndAP personAP $(widgetFile "person")
getPersonInboxR :: KeyHashid Person -> Handler TypedContent
getPersonInboxR = getInbox PersonInboxR personActor
postPersonInboxR :: KeyHashid Person -> Handler TypedContent
postPersonInboxR _ = error "Temporarily disabled"
getPersonOutboxR :: KeyHashid Person -> Handler TypedContent
getPersonOutboxR = getOutbox PersonOutboxR personActor
postPersonOutboxR :: KeyHashid Person -> Handler TypedContent
postPersonOutboxR personHash = do
federation <- getsYesod $ appFederation . appSettings
unless federation badMethod
personID <- decodeKeyHashid404 personHash
person <- runDB $ get404 personID
verifyPermission personID
verifyContentTypeAP
AP.Doc h activity <- requireInsecureJsonBody
hl <- hostIsLocal h
unless hl $ invalidArgs ["Activity host isn't the instance host"]
result <- runExceptT $ do
verifyAttribution $ AP.activityActor activity
handle (Entity personID person) activity
case result of
Left err -> invalidArgs [err]
Right outboxItemID -> do
outboxItemHash <- encodeKeyHashid outboxItemID
sendResponseCreated $ PersonOutboxItemR personHash outboxItemHash
where
verifyPermission recipientID = do
(_app, mpid, _scopes) <- maybe notAuthenticated return =<< getDvaraAuth
senderID <-
maybe (permissionDenied "Not authorized to post as a user") return mpid
unless (recipientID == senderID) $
permissionDenied "Can't post as other users"
verifyAttribution actor =
case decodeRouteLocal actor of
Just (PersonR actorHash) | actorHash == personHash -> return ()
_ -> throwE "Can't post activity attributed to someone else"
handle eperson (AP.Activity _mid actor mcap summary audience specific) =
case specific of
{-
AddActivity (AP.Add obj target) ->
case obj of
Right (AddBundle patches) ->
addBundleC eperson sharer summary audience patches target
_ -> throwE "Unsupported Add 'object' type"
ApplyActivity apply ->
applyC eperson sharer summary audience mcap apply
CreateActivity (Create obj mtarget) ->
case obj of
CreateNote _ note ->
createNoteC eperson sharer summary audience note mtarget
CreateTicket _ ticket ->
createTicketC eperson sharer summary audience ticket mtarget
_ -> throwE "Unsupported Create 'object' type"
FollowActivity follow ->
followC shr summary audience follow
OfferActivity (Offer obj target) ->
case obj of
OfferTicket ticket ->
offerTicketC eperson sharer summary audience ticket target
OfferDep dep ->
offerDepC eperson sharer summary audience dep target
_ -> throwE "Unsupported Offer 'object' type"
ResolveActivity resolve ->
resolveC eperson sharer summary audience resolve
UndoActivity undo ->
undoC eperson sharer summary audience undo
-}
_ -> throwE "Unsupported activity type"
getPersonOutboxItemR
:: KeyHashid Person -> KeyHashid OutboxItem -> Handler TypedContent
getPersonOutboxItemR = getOutboxItem PersonOutboxItemR personActor
getPersonFollowersR :: KeyHashid Person -> Handler TypedContent
getPersonFollowersR = getActorFollowersCollection PersonFollowersR personActor
getPersonFollowingR :: KeyHashid Person -> Handler TypedContent
getPersonFollowingR = getFollowingCollection PersonFollowingR personActor
getSshKeyR :: KeyHashid Person -> KeyHashid SshKey -> Handler TypedContent
getSshKeyR personHash keyHash = do
personID <- decodeKeyHashid404 personHash
keyID <- decodeKeyHashid404 keyHash
key <- runDB $ do
_ <- get404 personID
k <- get404 keyID
unless (sshKeyPerson k == personID) notFound
return k
encodeRouteLocal <- getEncodeRouteLocal
let here = SshKeyR personHash keyHash
keyAP = AP.SshPublicKey
{ AP.sshPublicKeyId = encodeRouteLocal here
, AP.sshPublicKeyExpires = Nothing
, AP.sshPublicKeyOwner = encodeRouteLocal $ PersonR personHash
, AP.sshPublicKeyAlgorithm =
case sshKeyAlgo key of
"ssh-rsa" -> AP.SshKeyAlgorithmRSA
_ -> error "Unexpected sshKeyAlgo in DB"
, AP.sshPublicKeyMaterial = sshKeyContent key
}
provideHtmlAndAP keyAP $ redirectToPrettyJSON here
getPersonMessageR
:: KeyHashid Person -> KeyHashid LocalMessage -> Handler TypedContent
getPersonMessageR personHash localMessageHash = do
personID <- decodeKeyHashid404 personHash
localMessageID <- decodeKeyHashid404 localMessageHash
encodeRouteHome <- getEncodeRouteHome
workItemRoute <- askWorkItemRoute
note <- runDB $ do
_ <- get404 personID
localMessage <- get404 localMessageID
unless (localMessageAuthor localMessage == personID) notFound
message <- getJust $ localMessageRest localMessage
uContext <- do
let discussionID = messageRoot message
topic <-
requireEitherAlt
(getKeyBy $ UniqueTicketDiscuss discussionID)
(getValBy $ UniqueRemoteDiscussion discussionID)
"Neither T nor RD found"
"Both T and RD found"
case topic of
Left ticketID ->
encodeRouteHome . workItemRoute <$> getWorkItem ticketID
Right rd -> do
ro <- getJust $ remoteDiscussionIdent rd
i <- getJust $ remoteObjectInstance ro
return $ ObjURI (instanceHost i) (remoteObjectIdent ro)
muParent <- for (messageParent message) $ \ parentID -> do
parent <-
requireEitherAlt
(getBy $ UniqueLocalMessage parentID)
(getValBy $ UniqueRemoteMessage parentID)
"Message with no author"
"Message used as both local and remote"
case parent of
Left (Entity localParentID localParent) -> do
authorHash <-
encodeKeyHashid $ localMessageAuthor localParent
localParentHash <- encodeKeyHashid localParentID
return $ encodeRouteHome $
PersonMessageR authorHash localParentHash
Right remoteParent -> do
rs <- getJust $ remoteMessageAuthor remoteParent
ro <- getJust $ remoteActorIdent rs
i <- getJust $ remoteObjectInstance ro
return $ ObjURI (instanceHost i) (remoteObjectIdent ro)
encodeRouteLocal <- getEncodeRouteLocal
return AP.Note
{ AP.noteId = Just $ encodeRouteLocal here
, AP.noteAttrib = encodeRouteLocal $ PersonR personHash
, AP.noteAudience = AP.Audience [] [] [] [] [] []
, AP.noteReplyTo = Just $ fromMaybe uContext muParent
, AP.noteContext = Just uContext
, AP.notePublished = Just $ messageCreated message
, AP.noteSource = messageSource message
, AP.noteContent = messageContent message
}
provideHtmlAndAP note $ redirectToPrettyJSON here
where
here = PersonMessageR personHash localMessageHash
postPersonFollowR :: KeyHashid Person -> Handler ()
postPersonFollowR _ = error "Temporarily disabled"
postPersonUnfollowR :: KeyHashid Person -> Handler ()
postPersonUnfollowR _ = error "Temporarily disabled"

View file

@ -15,42 +15,67 @@
-}
module Vervis.Handler.Repo
( getReposR
, postReposR
, getRepoNewR
, getRepoR
, putRepoR
, deleteRepoR
, postRepoR
, getRepoEditR
( getRepoR
, getRepoInboxR
, postRepoInboxR
, getRepoOutboxR
, getRepoOutboxItemR
, getRepoFollowersR
, getDarcsDownloadR
, getGitRefDiscoverR
, postGitUploadRequestR
, getRepoSourceR
, getRepoHeadChangesR
, getRepoBranchR
, getRepoChangesR
, getRepoBranchSourceR
, getRepoCommitsR
, getRepoBranchCommitsR
, getRepoCommitR
, getRepoNewR
, postRepoNewR
, postRepoDeleteR
, getRepoEditR
, postRepoEditR
, postRepoFollowR
, postRepoUnfollowR
, postPostReceiveR
{-
, getReposR
, putRepoR
, postRepoR
, getRepoBranchR
, getRepoDevsR
, postRepoDevsR
, getRepoDevNewR
, getRepoDevR
, deleteRepoDevR
, postRepoDevR
, getDarcsDownloadR
, getRepoTeamR
, getRepoFollowersR
, getHighlightStyleR
, postPostReceiveR
-}
)
where
import Control.Exception hiding (Handler)
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger (logWarn)
import Control.Monad.Trans.Except
import Data.Bifunctor
import Data.Binary.Put
import Data.Foldable
import Data.Git.Graph
import Data.Git.Harder
import Data.Git.Harder.Pack
import Data.Git.Named (RefName (..))
import Data.Git.Ref (toHex)
import Data.Git.Repository
@ -60,6 +85,8 @@ import Data.Git.Types (Blob (..), Person (..), entName)
import Data.Graph.Inductive.Graph (noNodes)
import Data.Graph.Inductive.Query.Topsort
import Data.List (inits)
import Data.Maybe
import Data.String
import Data.Text (Text, unpack)
import Data.Text.Encoding
import Data.Text.Encoding.Error (lenientDecode)
@ -69,19 +96,28 @@ import Database.Persist
import Database.Persist.Sql
import Data.Hourglass (timeConvert)
import Formatting (sformat, stext, (%))
import Network.Git.Transport.HTTP.Fetch.RefDiscovery
import Network.Git.Transport.HTTP.Fetch.UploadRequest
import Network.Git.Types
import Network.Wai (strictRequestBody)
import System.Directory
import System.FilePath
import System.Hourglass (dateCurrent)
import System.IO
import System.Process
import Text.Blaze.Html (Html)
import Text.Pandoc.Highlighting
import Yesod.Auth (requireAuthId)
import Yesod.Core
import Yesod.Core hiding (joinPath)
import Yesod.Core.Content
import Yesod.Core.Handler (lookupPostParam, redirect, notFound)
import Yesod.Form.Functions (runFormPost)
import Yesod.Form.Types (FormResult (..))
import Yesod.Persist.Core
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Internal as BLI
import qualified Data.CaseInsensitive as CI (foldedCase)
import qualified Data.DList as D
import qualified Data.Set as S (member)
@ -91,8 +127,8 @@ import qualified Database.Esqueleto as E
import Data.MediaType
import Database.Persist.JSON
import Development.PatchMediaType
import Network.FedURI
import Web.ActivityPub hiding (Repo (..), Project)
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
@ -111,38 +147,220 @@ import Yesod.Persist.Local
import qualified Data.Git.Local as G (createRepo)
import qualified Darcs.Local.Repository as D (createRepo)
import Vervis.Actor
import Vervis.API
import Vervis.Form.Repo
import Vervis.Foundation
import Vervis.Handler.Repo.Darcs
import Vervis.Handler.Repo.Git
import Vervis.Path
import Vervis.Model hiding (Actor (..))
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Paginate
import Vervis.Readme
import Vervis.Settings
import Vervis.SourceTree
import Vervis.Style
import Vervis.Widget.Repo
import Vervis.Widget.Sharer
import qualified Vervis.Formatting as F
import qualified Vervis.Hook as H
getReposR :: ShrIdent -> Handler Html
getReposR user = do
repos <- runDB $ E.select $ E.from $ \ (sharer, repo) -> do
E.where_ $
sharer E.^. SharerIdent E.==. E.val user E.&&.
sharer E.^. SharerId E.==. repo E.^. RepoSharer
E.orderBy [E.asc $ repo E.^. RepoIdent]
return $ repo E.^. RepoIdent
defaultLayout $(widgetFile "repo/list")
getRepoR :: KeyHashid Repo -> Handler TypedContent
getRepoR repoHash = do
repoID <- decodeKeyHashid404 repoHash
(repo, actor) <- runDB $ do
r <- get404 repoID
(r,) <$> getJust (repoActor r)
postReposR :: ShrIdent -> Handler Html
postReposR user = do
encodeRouteLocal <- getEncodeRouteLocal
let repoAP = AP.Repo
{ AP.repoActor = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = encodeRouteLocal $ RepoR repoHash
, AP.actorInbox = encodeRouteLocal $ RepoInboxR repoHash
, AP.actorOutbox =
Just $ encodeRouteLocal $ RepoOutboxR repoHash
, AP.actorFollowers =
Just $ encodeRouteLocal $ RepoFollowersR repoHash
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeRepo
, AP.actorUsername = Nothing
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
, AP.repoTeam = Nothing
, AP.repoVcs = repoVcs repo
}
next =
case repoVcs repo of
VCSDarcs -> RepoSourceR repoHash
VCSGit -> RepoBranchSourceR repoHash $ repoMainBranch repo
provideHtmlAndAP repoAP $ redirect $ next []
getRepoInboxR :: KeyHashid Repo -> Handler TypedContent
getRepoInboxR = getInbox RepoInboxR repoActor
postRepoInboxR :: KeyHashid Repo -> Handler TypedContent
postRepoInboxR _ = error "Temporarily disabled"
getRepoOutboxR :: KeyHashid Repo -> Handler TypedContent
getRepoOutboxR = getOutbox RepoOutboxR repoActor
getRepoOutboxItemR
:: KeyHashid Repo -> KeyHashid OutboxItem -> Handler TypedContent
getRepoOutboxItemR = getOutboxItem RepoOutboxItemR repoActor
getRepoFollowersR :: KeyHashid Repo -> Handler TypedContent
getRepoFollowersR = getActorFollowersCollection RepoFollowersR repoActor
getDarcsDownloadR :: KeyHashid Repo -> [Text] -> Handler TypedContent
getDarcsDownloadR repoHash dir = do
repoPath <- askRepoDir repoHash
let filePath = repoPath </> "_darcs" </> joinPath (map T.unpack dir)
exists <- liftIO $ doesFileExist filePath
if exists
then sendFile typeOctet filePath
else notFound
getGitRefDiscoverR :: KeyHashid Repo -> Handler TypedContent
getGitRefDiscoverR repoHash = do
let typ = "application/x-git-upload-pack-advertisement"
path <- askRepoDir repoHash
let pathG = fromString path
seemsThere <- liftIO $ isRepo pathG
if seemsThere
then do
rq <- getRequest
case reqGetParams rq of
[("service", serv)] ->
if serv == "git-upload-pack"
then do
let settings =
( proc "git"
[ "upload-pack"
, "--stateless-rpc"
, "--advertise-refs"
, path
]
)
{ std_out = CreatePipe
}
(_, mh, _, _) <-
liftIO $ createProcess settings
let h = fromJust mh
refs <- liftIO $ B.hGetContents h
let content = runPut $ do
putService UploadPack
putByteString refs
setHeader "Cache-Control" "no-cache"
return $ TypedContent typ $ toContent content
else permissionDenied "Service not supported"
_ -> notFound
else notFound
postGitUploadRequestR :: KeyHashid Repo -> Handler TypedContent
postGitUploadRequestR repoHash = do
let typ = "application/x-git-upload-pack-result"
path <- askRepoDir repoHash
let pathG = fromString path
seemsThere <- liftIO $ isRepo pathG
if seemsThere
then do
getBody <- strictRequestBody <$> waiRequest
body <- liftIO getBody
let settings =
( proc "git"
[ "upload-pack"
, "--stateless-rpc"
, path
]
)
{ std_in = CreatePipe
, std_out = CreatePipe
}
(mhin, mhout, _, _) <- liftIO $ createProcess settings
let hin = fromJust mhin
hout = fromJust mhout
liftIO $ BL.hPut hin body >> hClose hin
setHeader "Cache-Control" "no-cache"
let loop = do
b <- liftIO $ B.hGet hout BLI.defaultChunkSize
unless (B.null b) $ do
sendChunkBS b
loop
respondSource typ loop
else notFound
getRepoSourceR :: KeyHashid Repo -> [Text] -> Handler Html
getRepoSourceR repoHash path = do
repoID <- decodeKeyHashid404 repoHash
repo <- runDB $ get404 repoID
case repoVcs repo of
VCSDarcs -> error "Temporarily disabled"
--getDarcsRepoSource repo repoHash path
VCSGit -> notFound
getRepoBranchSourceR :: KeyHashid Repo -> Text -> [Text] -> Handler Html
getRepoBranchSourceR repoHash branch path = do
repoID <- decodeKeyHashid404 repoHash
repo <- runDB $ get404 repoID
case repoVcs repo of
VCSDarcs -> notFound
VCSGit -> error "Temporarily disabled"
--getGitRepoSource repo repoHash branch dir
getRepoCommitsR :: KeyHashid Repo -> Handler TypedContent
getRepoCommitsR repoHash = do
repoID <- decodeKeyHashid404 repoHash
repo <- runDB $ get404 repoID
case repoVcs repo of
VCSDarcs ->
error "Temporarily disabled"
--getDarcsRepoHeadChanges repoHash
VCSGit ->
error "Temporarily disabled"
--getGitRepoHeadChanges repo repoHash
getRepoBranchCommitsR :: KeyHashid Repo -> Text -> Handler TypedContent
getRepoBranchCommitsR repoHash branch = do
repoID <- decodeKeyHashid404 repoHash
repo <- runDB $ get404 repoID
case repoVcs repo of
VCSDarcs ->
error "Temporarily disabled"
--getDarcsRepoChanges repoHash branch
VCSGit ->
error "Temporarily disabled"
--getGitRepoChanges repoHash branch
getRepoCommitR :: KeyHashid Repo -> Text -> Handler TypedContent
getRepoCommitR repoHash ref = do
error "Temporarily disabled"
{-
repoID <- decodeKeyHashid404 repoHash
repo <- runDB $ get404 repoID
case repoVcs repo of
VCSDarcs -> getDarcsPatch repoHash ref
VCSGit -> getGitPatch repoHash ref
-}
getRepoNewR :: Handler Html
getRepoNewR = do
error "Temporarily disabled"
--Entity sid _sharer <- runDB $ getBy404 $ UniqueSharer user
--((_result, widget), enctype) <- runFormPost $ newRepoForm sid Nothing
--defaultLayout $(widgetFile "repo/new")
postRepoNewR :: Handler Html
postRepoNewR = do
error "Temporarily disabled"
{-
Entity sid _sharer <- runDB $ getBy404 $ UniqueSharer user
((result, widget), enctype) <- runFormPost $ newRepoForm sid Nothing
case result of
@ -213,63 +431,46 @@ postReposR user = do
FormFailure _l -> do
setMessage "Repo creation failed, see errors below"
defaultLayout $(widgetFile "repo/new")
-}
getRepoNewR :: ShrIdent -> Handler Html
getRepoNewR user = do
Entity sid _sharer <- runDB $ getBy404 $ UniqueSharer user
((_result, widget), enctype) <- runFormPost $ newRepoForm sid Nothing
defaultLayout $(widgetFile "repo/new")
selectRepo :: ShrIdent -> RpIdent -> AppDB (Maybe (Sharer, Project, Workflow, Sharer), Repo)
selectRepo shar repo = do
postRepoDeleteR :: KeyHashid Repo -> Handler Html
postRepoDeleteR repoHash = do
error "Temporarily disabled"
{-
runDB $ do
Entity sid _s <- getBy404 $ UniqueSharer shar
Entity _rid r <- getBy404 $ UniqueRepo repo sid
mj <- for (repoProject r) $ \ jid -> do
j <- get404 jid
s <- get404 $ projectSharer j
w <- get404 $ projectWorkflow j
sw <- get404 $ workflowSharer w
return (s, j, w, sw)
return (mj, r)
Entity rid _r <- getBy404 $ UniqueRepo repo sid
delete rid
path <- askRepoDir shar repo
exists <- liftIO $ doesDirectoryExist path
if exists
then liftIO $ removeDirectoryRecursive path
else
$logWarn $ sformat
( "Deleted repo " % F.sharer % "/" % F.repo
% " from DB but repo dir doesn't exist"
)
shar repo
setMessage "Repo deleted."
redirect HomeR
-}
getRepoR :: ShrIdent -> RpIdent -> Handler TypedContent
getRepoR shr rp = do
(_, repo) <- runDB $ selectRepo shr rp
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let repoAP = AP.Repo
{ AP.repoActor = Actor
{ actorLocal = ActorLocal
{ actorId = encodeRouteLocal $ RepoR shr rp
, actorInbox = encodeRouteLocal $ RepoInboxR shr rp
, actorOutbox =
Just $ encodeRouteLocal $ RepoOutboxR shr rp
, actorFollowers =
Just $ encodeRouteLocal $ RepoFollowersR shr rp
, actorFollowing = Nothing
, actorPublicKeys =
[ Left $ encodeRouteLocal ActorKey1R
, Left $ encodeRouteLocal ActorKey2R
]
, actorSshKeys = []
}
, actorDetail = ActorDetail
{ actorType = ActorTypeRepo
, actorUsername = Nothing
, actorName = Just $ rp2text rp
, actorSummary = repoDesc repo
}
}
, AP.repoTeam = encodeRouteLocal $ RepoTeamR shr rp
, AP.repoVcs = repoVcs repo
}
dir = case repoVcs repo of
VCSDarcs -> []
VCSGit -> [repoMainBranch repo]
provideHtmlAndAP repoAP $ redirect $ RepoSourceR shr rp dir
getRepoEditR :: KeyHashid Repo -> Handler Html
getRepoEditR repoHash = do
error "Temporarily disabled"
{-
(sid, er) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
er <- getBy404 $ UniqueRepo rp sid
return (sid, er)
((_result, widget), enctype) <- runFormPost $ editRepoForm sid er
defaultLayout $(widgetFile "repo/edit")
-}
putRepoR :: ShrIdent -> RpIdent -> Handler Html
putRepoR shr rp = do
postRepoEditR :: KeyHashid Repo -> Handler Html
postRepoEditR repoHash = do
error "Temporarily disabled"
{-
mer <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
er@(Entity rid r) <- getBy404 $ UniqueRepo rp sid
@ -296,58 +497,184 @@ putRepoR shr rp = do
FormFailure _l -> do
setMessage "Repository update failed, see errors below."
defaultLayout $(widgetFile "repo/edit")
-}
deleteRepoR :: ShrIdent -> RpIdent -> Handler Html
deleteRepoR shar repo = do
runDB $ do
Entity sid _s <- getBy404 $ UniqueSharer shar
Entity rid _r <- getBy404 $ UniqueRepo repo sid
delete rid
path <- askRepoDir shar repo
exists <- liftIO $ doesDirectoryExist path
if exists
then liftIO $ removeDirectoryRecursive path
else
$logWarn $ sformat
( "Deleted repo " % F.sharer % "/" % F.repo
% " from DB but repo dir doesn't exist"
postRepoFollowR :: KeyHashid Repo -> Handler ()
postRepoFollowR _ = error "Temporarily disabled"
postRepoUnfollowR :: KeyHashid Repo -> Handler ()
postRepoUnfollowR _ = error "Temporarily disabled"
postPostReceiveR :: Handler Text
postPostReceiveR = do
error "Temporarily disabled"
{-
push <- requireCheckJsonBody
(pushAP, shr, rp) <- push2ap push
user <- runDB $ do
p <- getJustEntity $ toSqlKey $ H.pushUser push
s <- getJust $ personIdent $ entityVal p
return (p, s)
let shrUser = sharerIdent $ snd user
summary <- do
let mbranch = H.pushBranch push
total = pushCommitsTotal pushAP
lasts = pushCommitsLast pushAP
rest firsts = total - length firsts - length lasts
hashText (Hash b) = decodeUtf8 b
commitW c =
[hamlet|
<a href=@{RepoCommitR shr rp $ hashText $ commitHash c}>
#{commitTitle c}
|]
withUrlRenderer
[hamlet|
<p>
<a href=@{SharerR shrUser}>#{shr2text shrUser}
\ pushed #{total} #
\ #{commitsText mbranch total} to repo #
<a href=@{RepoR shr rp}>#{rp2text rp}</a>^{branchText shr rp mbranch}:
<ul>
$maybe firsts <- pushCommitsFirst pushAP
$forall c <- firsts
<li>^{commitW c}
<li>#{rest firsts}
$forall c <- lasts
<li>^{commitW c}
|]
eid <- runExceptT $ pushCommitsC user summary pushAP shr rp
case eid of
Left e -> liftIO $ throwIO $ userError $ T.unpack e
Right obiid -> do
renderUrl <- askUrlRender
obikhid <- encodeKeyHashid obiid
return $
"Push activity published: " <>
renderUrl (SharerOutboxItemR shrUser obikhid)
where
push2ap (H.Push secret _ sharer repo mbranch mbefore after early mlate) = do
encodeRouteLocal <- getEncodeRouteLocal
let shr = text2shr sharer
rp = text2rp repo
commit2ap' = commit2ap shr rp
(commitsLast, commitsFirst) <-
runDB $ case mlate of
Nothing -> (,) <$> traverse commit2ap' early <*> pure Nothing
Just (_omitted, late) ->
(,) <$> traverse commit2ap' late
<*> (Just <$> traverse commit2ap' early)
return
( Push
{ pushCommitsLast = commitsLast
, pushCommitsFirst = commitsFirst
, pushCommitsTotal =
case mlate of
Nothing -> length early
Just (omitted, late) ->
length early + omitted + length late
, pushTarget =
encodeRouteLocal $
case mbranch of
Nothing -> RepoR shr rp
Just b -> RepoBranchR shr rp b
, pushContext = encodeRouteLocal $ RepoR shr rp
, pushHashBefore = mbefore
, pushHashAfter = after
}
, shr
, rp
)
shar repo
setMessage "Repo deleted."
redirect HomeR
where
commit2ap shr rp (H.Commit (wauthor, wtime) mcommitted hash title desc) = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
author <- authorByEmail wauthor
mcommitter <- traverse (authorByEmail . fst) mcommitted
return Commit
{ commitId = encodeRouteLocal $ RepoCommitR shr rp hash
, commitRepository = encodeRouteLocal $ RepoR shr rp
, commitAuthor = second (encodeRouteHome . SharerR) author
, commitCommitter =
second (encodeRouteHome . SharerR) <$> mcommitter
, commitTitle = title
, commitHash = Hash $ encodeUtf8 hash
, commitDescription =
if T.null desc
then Nothing
else Just desc
, commitWritten = wtime
, commitCommitted = snd <$> mcommitted
}
where
authorByEmail (H.Author name email) = do
mperson <- getValBy $ UniquePersonEmail email
case mperson of
Nothing -> return $ Left $ Author name email
Just person ->
Right . sharerIdent <$> getJust (personIdent person)
commitsText :: Maybe a -> Int -> Text
commitsText Nothing n =
if n > 1
then "patches"
else "patch"
commitsText (Just _) n =
if n > 1
then "commits"
else "commit"
--branchText :: ShrIdent -> RpIdent -> Maybe Text -> HtmlUrl (Route App)
branchText _ _ Nothing = const mempty
branchText shr rp (Just branch) =
[hamlet|
, branch #
<a href=@{RepoBranchR shr rp branch}>#{branch}
|]
-}
postRepoR :: ShrIdent -> RpIdent -> Handler Html
postRepoR shar repo = do
mmethod <- lookupPostParam "_method"
case mmethod of
Just "PUT" -> putRepoR shar repo
Just "DELETE" -> deleteRepoR shar repo
_ -> notFound
getRepoEditR :: ShrIdent -> RpIdent -> Handler Html
getRepoEditR shr rp = do
(sid, er) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
er <- getBy404 $ UniqueRepo rp sid
return (sid, er)
((_result, widget), enctype) <- runFormPost $ editRepoForm sid er
defaultLayout $(widgetFile "repo/edit")
getRepoSourceR :: ShrIdent -> RpIdent -> [Text] -> Handler Html
getRepoSourceR shar repo refdir = do
repository <- runDB $ selectRepo shar repo
case repoVcs $ snd repository of
VCSDarcs -> getDarcsRepoSource repository shar repo refdir
VCSGit -> case refdir of
[] -> notFound
(ref:dir) -> getGitRepoSource repository shar repo ref dir
getRepoHeadChangesR :: ShrIdent -> RpIdent -> Handler TypedContent
getRepoHeadChangesR user repo = do
(_, repository) <- runDB $ selectRepo user repo
case repoVcs repository of
VCSDarcs -> getDarcsRepoHeadChanges user repo
VCSGit -> getGitRepoHeadChanges repository user repo
{-
getReposR :: ShrIdent -> Handler Html
getReposR user = do
repos <- runDB $ E.select $ E.from $ \ (sharer, repo) -> do
E.where_ $
sharer E.^. SharerIdent E.==. E.val user E.&&.
sharer E.^. SharerId E.==. repo E.^. RepoSharer
E.orderBy [E.asc $ repo E.^. RepoIdent]
return $ repo E.^. RepoIdent
defaultLayout $(widgetFile "repo/list")
selectRepo :: ShrIdent -> RpIdent -> AppDB (Maybe (Sharer, Project, Workflow, Sharer), Repo)
selectRepo shar repo = do
Entity sid _s <- getBy404 $ UniqueSharer shar
Entity _rid r <- getBy404 $ UniqueRepo repo sid
mj <- for (repoProject r) $ \ jid -> do
j <- get404 jid
s <- get404 $ projectSharer j
w <- get404 $ projectWorkflow j
sw <- get404 $ workflowSharer w
return (s, j, w, sw)
return (mj, r)
getRepoBranchR :: ShrIdent -> RpIdent -> Text -> Handler TypedContent
getRepoBranchR shar repo ref = do
@ -356,20 +683,6 @@ getRepoBranchR shar repo ref = do
VCSDarcs -> notFound
VCSGit -> getGitRepoBranch shar repo ref
getRepoChangesR :: ShrIdent -> RpIdent -> Text -> Handler TypedContent
getRepoChangesR shar repo ref = do
(_, repository) <- runDB $ selectRepo shar repo
case repoVcs repository of
VCSDarcs -> getDarcsRepoChanges shar repo ref
VCSGit -> getGitRepoChanges shar repo ref
getRepoCommitR :: ShrIdent -> RpIdent -> Text -> Handler TypedContent
getRepoCommitR shr rp ref = do
(_, repository) <- runDB $ selectRepo shr rp
case repoVcs repository of
VCSDarcs -> getDarcsPatch shr rp ref
VCSGit -> getGitPatch shr rp ref
getRepoDevsR :: ShrIdent -> RpIdent -> Handler Html
getRepoDevsR shr rp = do
devs <- runDB $ do
@ -551,125 +864,4 @@ getHighlightStyleR styleName =
Nothing -> notFound
Just style ->
return $ TypedContent typeCss $ toContent $ styleToCss style
postPostReceiveR :: Handler Text
postPostReceiveR = do
push <- requireCheckJsonBody
(pushAP, shr, rp) <- push2ap push
user <- runDB $ do
p <- getJustEntity $ toSqlKey $ H.pushUser push
s <- getJust $ personIdent $ entityVal p
return (p, s)
let shrUser = sharerIdent $ snd user
summary <- do
let mbranch = H.pushBranch push
total = pushCommitsTotal pushAP
lasts = pushCommitsLast pushAP
rest firsts = total - length firsts - length lasts
hashText (Hash b) = decodeUtf8 b
commitW c =
[hamlet|
<a href=@{RepoCommitR shr rp $ hashText $ commitHash c}>
#{commitTitle c}
|]
withUrlRenderer
[hamlet|
<p>
<a href=@{SharerR shrUser}>#{shr2text shrUser}
\ pushed #{total} #
\ #{commitsText mbranch total} to repo #
<a href=@{RepoR shr rp}>#{rp2text rp}</a>^{branchText shr rp mbranch}:
<ul>
$maybe firsts <- pushCommitsFirst pushAP
$forall c <- firsts
<li>^{commitW c}
<li>#{rest firsts}
$forall c <- lasts
<li>^{commitW c}
|]
eid <- runExceptT $ pushCommitsC user summary pushAP shr rp
case eid of
Left e -> liftIO $ throwIO $ userError $ T.unpack e
Right obiid -> do
renderUrl <- askUrlRender
obikhid <- encodeKeyHashid obiid
return $
"Push activity published: " <>
renderUrl (SharerOutboxItemR shrUser obikhid)
where
push2ap (H.Push secret _ sharer repo mbranch mbefore after early mlate) = do
encodeRouteLocal <- getEncodeRouteLocal
let shr = text2shr sharer
rp = text2rp repo
commit2ap' = commit2ap shr rp
(commitsLast, commitsFirst) <-
runDB $ case mlate of
Nothing -> (,) <$> traverse commit2ap' early <*> pure Nothing
Just (_omitted, late) ->
(,) <$> traverse commit2ap' late
<*> (Just <$> traverse commit2ap' early)
return
( Push
{ pushCommitsLast = commitsLast
, pushCommitsFirst = commitsFirst
, pushCommitsTotal =
case mlate of
Nothing -> length early
Just (omitted, late) ->
length early + omitted + length late
, pushTarget =
encodeRouteLocal $
case mbranch of
Nothing -> RepoR shr rp
Just b -> RepoBranchR shr rp b
, pushContext = encodeRouteLocal $ RepoR shr rp
, pushHashBefore = mbefore
, pushHashAfter = after
}
, shr
, rp
)
where
commit2ap shr rp (H.Commit (wauthor, wtime) mcommitted hash title desc) = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
author <- authorByEmail wauthor
mcommitter <- traverse (authorByEmail . fst) mcommitted
return Commit
{ commitId = encodeRouteLocal $ RepoCommitR shr rp hash
, commitRepository = encodeRouteLocal $ RepoR shr rp
, commitAuthor = second (encodeRouteHome . SharerR) author
, commitCommitter =
second (encodeRouteHome . SharerR) <$> mcommitter
, commitTitle = title
, commitHash = Hash $ encodeUtf8 hash
, commitDescription =
if T.null desc
then Nothing
else Just desc
, commitWritten = wtime
, commitCommitted = snd <$> mcommitted
}
where
authorByEmail (H.Author name email) = do
mperson <- getValBy $ UniquePersonEmail email
case mperson of
Nothing -> return $ Left $ Author name email
Just person ->
Right . sharerIdent <$> getJust (personIdent person)
commitsText :: Maybe a -> Int -> Text
commitsText Nothing n =
if n > 1
then "patches"
else "patch"
commitsText (Just _) n =
if n > 1
then "commits"
else "commit"
--branchText :: ShrIdent -> RpIdent -> Maybe Text -> HtmlUrl (Route App)
branchText _ _ Nothing = const mempty
branchText shr rp (Just branch) =
[hamlet|
, branch #
<a href=@{RepoBranchR shr rp branch}>#{branch}
|]
-}

View file

@ -17,7 +17,6 @@ module Vervis.Handler.Repo.Darcs
( getDarcsRepoSource
, getDarcsRepoHeadChanges
, getDarcsRepoChanges
, getDarcsDownloadR
, getDarcsPatch
)
where
@ -61,7 +60,6 @@ import Text.FilePath.Local (breakExt)
import Vervis.ActivityPub
import Vervis.ChangeFeed (changeFeed)
import Vervis.Changes
import Vervis.Form.Repo
import Vervis.Foundation
import Vervis.Path
import Vervis.Model
@ -73,10 +71,6 @@ import Vervis.Settings
import Vervis.SourceTree
import Vervis.Style
import Vervis.Time
import Vervis.Widget (buttonW)
import Vervis.Widget.Project
import Vervis.Widget.Repo
import Vervis.Widget.Sharer
import qualified Vervis.Darcs as D (readSourceView, readChangesView, readPatch)
@ -163,16 +157,6 @@ getDarcsRepoHeadChanges shar repo = do
getDarcsRepoChanges :: ShrIdent -> RpIdent -> Text -> Handler TypedContent
getDarcsRepoChanges shar repo tag = notFound
getDarcsDownloadR :: ShrIdent -> RpIdent -> [Text] -> Handler TypedContent
getDarcsDownloadR shar repo dir = do
path <- askRepoDir shar repo
let darcsDir = path </> "_darcs"
filePath = darcsDir </> joinPath (map T.unpack dir)
exists <- liftIO $ doesFileExist filePath
if exists
then sendFile typeOctet filePath
else notFound
getDarcsPatch :: ShrIdent -> RpIdent -> Text -> Handler TypedContent
getDarcsPatch shr rp ref = do
path <- askRepoDir shr rp

View file

@ -75,7 +75,6 @@ import Text.FilePath.Local (breakExt)
import Vervis.ActivityPub
import Vervis.ChangeFeed (changeFeed)
import Vervis.Changes
import Vervis.Form.Repo
import Vervis.Foundation
import Vervis.Path
import Vervis.Model
@ -87,10 +86,6 @@ import Vervis.Settings
import Vervis.SourceTree
import Vervis.Style
import Vervis.Time (showDate)
import Vervis.Widget (buttonW)
import Vervis.Widget.Project
import Vervis.Widget.Repo
import Vervis.Widget.Sharer
import qualified Data.ByteString.Lazy as BL (ByteString)
import qualified Vervis.Git as G (readSourceView, readChangesView, listRefs, readPatch)

View file

@ -15,7 +15,6 @@
module Vervis.Handler.Sharer
( getSharersR
, getSharerR
, getSharerFollowersR
, getSharerFollowingR
)
@ -65,22 +64,6 @@ getSharersR = do
let pageNav = navWidget navModel
defaultLayout $(widgetFile "sharer/list")
getSharerR :: ShrIdent -> Handler TypedContent
getSharerR shr = do
ment <- runDB $ do
Entity sid sharer <- getBy404 $ UniqueSharer shr
runMaybeT . fmap (sharer,)
$ Left <$> MaybeT (getBy $ UniquePersonIdent sid)
<|> Right <$> MaybeT (getBy $ UniqueGroup sid)
case ment of
Nothing -> do
$logWarn $ "Found non-person non-group sharer: " <> shr2text shr
notFound
Just (s, ent) ->
case ent of
Left ep -> getPerson shr s ep
Right (Entity _ g) -> getGroup shr g
getSharerFollowersR :: ShrIdent -> Handler TypedContent
getSharerFollowersR shr = getFollowersCollection here getFsid
where
@ -98,84 +81,3 @@ getSharerFollowersR shr = getFollowersCollection here getFsid
case val of
Left person -> return $ personFollowers person
Right _group -> notFound
getSharerFollowingR :: ShrIdent -> Handler TypedContent
getSharerFollowingR shr = do
(localTotal, sharers, projects, tickets, repos, remotes) <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
pid <- getKeyBy404 $ UniquePersonIdent sid
fsids <-
map (followTarget . entityVal) <$>
selectList [FollowPerson ==. pid] []
(,,,,,) (length fsids)
<$> getSharers fsids
<*> getProjects fsids
<*> getTickets fsids
<*> getRepos fsids
<*> getRemotes pid
let locals = sharers ++ projects ++ tickets ++ repos
unless (length locals == localTotal) $
liftIO $ throwIO $ userError "Bug! List length mismatch"
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let here = SharerFollowingR shr
followingAP = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ localTotal + length remotes
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems = map encodeRouteHome locals ++ remotes
}
provideHtmlAndAP followingAP $ redirectToPrettyJSON here
where
getSharers fsids = do
sids <-
map (personIdent . entityVal) <$>
selectList [PersonFollowers <-. fsids] []
map (SharerR . sharerIdent . entityVal) <$>
selectList [SharerId <-. sids] []
getProjects fsids =
fmap (map $ \ (E.Value shr, E.Value prj) -> ProjectR shr prj) $
E.select $ E.from $ \ (a `E.InnerJoin` j `E.InnerJoin` s) -> do
E.on $ j E.^. ProjectSharer E.==. s E.^. SharerId
E.on $ a E.^. ActorId E.==. j E.^. ProjectActor
E.where_ $ a E.^. ActorFollowers `E.in_` E.valList fsids
return (s E.^. SharerIdent, j E.^. ProjectIdent)
getTickets fsids = do
ltids <- selectKeysList [LocalTicketFollowers <-. fsids] []
triples <-
E.select $ E.from $
\ (lt `E.InnerJoin`
t `E.InnerJoin`
tcl `E.InnerJoin`
tpl `E.InnerJoin`
j `E.InnerJoin`
s) -> do
E.on $ j E.^. ProjectSharer E.==. s E.^. SharerId
E.on $ tpl E.^. TicketProjectLocalProject E.==. j E.^. ProjectId
E.on $ tcl E.^. TicketContextLocalId E.==. tpl E.^. TicketProjectLocalContext
E.on $ t E.^. TicketId E.==. tcl E.^. TicketContextLocalTicket
E.on $ lt E.^. LocalTicketTicket E.==. t E.^. TicketId
E.where_ $ lt E.^. LocalTicketId `E.in_` E.valList ltids
return
( s E.^. SharerIdent
, j E.^. ProjectIdent
, lt E.^. LocalTicketId
)
encodeHid <- getEncodeKeyHashid
return $
map (\ (E.Value shr, E.Value prj, E.Value tid) -> ProjectTicketR shr prj $ encodeHid tid)
triples
getRepos fsids = do
rids <- selectKeysList [RepoFollowers <-. fsids] []
pairs <- E.select $ E.from $ \ (r `E.InnerJoin` s) -> do
E.on $ r E.^. RepoSharer E.==. s E.^. SharerId
E.where_ $ r E.^. RepoId `E.in_` E.valList rids
return (s E.^. SharerIdent, r E.^. RepoIdent)
return $ map (\ (E.Value shr, E.Value rp) -> RepoR shr rp) pairs
getRemotes pid =
map (followRemoteTarget . entityVal) <$>
selectList [FollowRemotePerson ==. pid] []

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2018, 2019, 2020, 2022
- by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -14,10 +15,25 @@
-}
module Vervis.Handler.Ticket
( getProjectTicketsR
( getTicketR
, getTicketDiscussionR
, getTicketEventsR
, getTicketFollowersR
, getTicketDepsR
, getTicketReverseDepsR
, getTicketDepR
{-
, getProjectTicketsR
, getProjectTicketTreeR
, getProjectTicketNewR
, getProjectTicketR
, putProjectTicketR
, deleteProjectTicketR
, postProjectTicketR
@ -33,31 +49,26 @@ module Vervis.Handler.Ticket
, getClaimRequestsTicketR
, postClaimRequestsTicketR
, getClaimRequestNewR
, getProjectTicketDiscussionR
, postProjectTicketDiscussionR
, getMessageR
, postProjectTicketMessageR
, getProjectTicketTopReplyR
, getProjectTicketReplyR
, getProjectTicketDepsR
, postProjectTicketDepsR
, getProjectTicketDepNewR
, postTicketDepOldR
, deleteTicketDepOldR
, getProjectTicketReverseDepsR
, getTicketDepR
, getProjectTicketParticipantsR
, getProjectTicketTeamR
, getProjectTicketEventsR
, getSharerTicketsR
, getSharerTicketR
, getSharerTicketDiscussionR
, getSharerTicketDepsR
, getSharerTicketReverseDepsR
, getSharerTicketFollowersR
, getSharerTicketTeamR
, getSharerTicketEventsR
-}
)
where
@ -120,11 +131,11 @@ import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub
import Vervis.Actor
import Vervis.API
import Vervis.Discussion
import Vervis.Federation
import Vervis.FedURI
import Vervis.Form.Ticket
import Vervis.Foundation
import Vervis.Handler.Discussion
--import Vervis.GraphProxy (ticketDepGraph)
@ -138,200 +149,33 @@ import Vervis.Style
import Vervis.Ticket
import Vervis.TicketFilter (filterTickets)
import Vervis.Time (showDate)
import Vervis.Widget (buttonW)
import Vervis.Widget.Discussion (discussionW)
import Vervis.Widget.Sharer
import Vervis.Widget.Ticket
getProjectTicketsR :: ShrIdent -> PrjIdent -> Handler TypedContent
getProjectTicketsR shr prj = selectRep $ do
provideRep $ do
((filtResult, filtWidget), filtEnctype) <- runFormGet ticketFilterForm
let tf =
case filtResult of
FormSuccess filt -> filt
FormMissing -> def
FormFailure l ->
error $ "Ticket filter form failed: " ++ show l
(total, pages, mpage) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
let countAllTickets = count [TicketProjectLocalProject ==. jid]
selectTickets off lim =
getTicketSummaries
(filterTickets tf)
(Just $ \ t -> [E.asc $ t E.^. TicketId])
(Just (off, lim))
jid
getPageAndNavCount countAllTickets selectTickets
case mpage of
Nothing -> redirectFirstPage here
Just (rows, navModel) ->
let pageNav = navWidget navModel
in defaultLayout $(widgetFile "ticket/list")
provideAP' $ do
(total, pages, mpage) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
let countAllTickets = count [TicketProjectLocalProject ==. jid]
selectTickets off lim = do
tids <- E.select $ E.from $ \ (tcl `E.InnerJoin` tpl) -> do
E.on $ tcl E.^. TicketContextLocalId E.==. tpl E.^. TicketProjectLocalContext
E.where_ $ tpl E.^. TicketProjectLocalProject E.==. E.val jid
E.orderBy [E.desc $ tcl E.^. TicketContextLocalTicket]
E.offset $ fromIntegral off
E.limit $ fromIntegral lim
return $ tcl E.^. TicketContextLocalTicket
let tids' = map E.unValue tids
locals <- E.select $ E.from $ \ (lt `E.LeftOuterJoin` (tal `E.InnerJoin` p `E.InnerJoin` s `E.LeftOuterJoin` tup)) -> do
E.on $ tal E.?. TicketAuthorLocalId E.==. tup E.?. TicketUnderProjectAuthor
E.on $ p E.?. PersonIdent E.==. s E.?. SharerId
E.on $ tal E.?. TicketAuthorLocalAuthor E.==. p E.?. PersonId
E.on $ E.just (lt E.^. LocalTicketId) E.==. tal E.?. TicketAuthorLocalTicket
E.where_ $ lt E.^. LocalTicketTicket `E.in_` E.valList tids'
E.orderBy [E.desc $ lt E.^. LocalTicketTicket]
return
( lt E.^. LocalTicketTicket
, ( lt E.^. LocalTicketId
, tal E.?. TicketAuthorLocalId
, s E.?. SharerIdent
, tup E.?. TicketUnderProjectId
)
)
remotes <- E.select $ E.from $ \ (tcl `E.InnerJoin` tar `E.InnerJoin` rt `E.InnerJoin` ro `E.InnerJoin` i) -> do
E.on $ ro E.^. RemoteObjectInstance E.==. i E.^. InstanceId
E.on $ rt E.^. RemoteTicketIdent E.==. ro E.^. RemoteObjectId
E.on $ tar E.^. TicketAuthorRemoteId E.==. rt E.^. RemoteTicketTicket
E.on $ tcl E.^. TicketContextLocalId E.==. tar E.^. TicketAuthorRemoteTicket
E.where_ $ tcl E.^. TicketContextLocalTicket `E.in_` E.valList tids'
E.orderBy [E.desc $ tcl E.^. TicketContextLocalTicket]
return
( tcl E.^. TicketContextLocalTicket
, ( i E.^. InstanceHost
, ro E.^. RemoteObjectIdent
)
)
return $
map snd $
LO.mergeBy
(flip compare `on` fst)
(map (second Left) locals)
(map (second Right) remotes)
getPageAndNavCount countAllTickets selectTickets
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- asksSite siteInstanceHost
encodeLT <- getEncodeKeyHashid
encodeTAL <- getEncodeKeyHashid
return $
case mpage of
Nothing -> encodeStrict $ Doc host $ Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
Just (tickets, navModel) ->
let current = nmCurrent navModel
in encodeStrict $ Doc host $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems =
map (ticketRoute encodeRouteHome encodeLT encodeTAL)
tickets
}
where
here = ProjectTicketsR shr prj
encodeStrict = BL.toStrict . encode
ticketRoute encodeRoute encodeLT encodeTAL (Left (E.Value ltid, E.Value mtalid, E.Value mshr, E.Value mtupid)) =
encodeRoute $
case (mtalid, mshr, mtupid) of
(Nothing, Nothing, Nothing) -> ProjectTicketR shr prj $ encodeLT ltid
(Just talid, Just shrA, Nothing) -> SharerTicketR shrA $ encodeTAL talid
(Just _, Just _, Just _) -> ProjectTicketR shr prj $ encodeLT ltid
_ -> error "Impossible"
ticketRoute _ _ _ (Right (E.Value h, E.Value lu)) = ObjURI h lu
getProjectTicketTreeR :: ShrIdent -> PrjIdent -> Handler Html
getProjectTicketTreeR _shr _prj = error "Ticket tree view disabled for now"
{-
(summaries, deps) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
(,) <$> getTicketSummaries Nothing Nothing Nothing jid
<*> getTicketDepEdges jid
defaultLayout $ ticketTreeDW shr prj summaries deps
-}
getProjectTicketNewR :: ShrIdent -> PrjIdent -> Handler Html
getProjectTicketNewR shr prj = do
wid <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity _ j <- getBy404 $ UniqueProject prj sid
return $ projectWorkflow j
((_result, widget), enctype) <- runFormPost $ newTicketForm wid
defaultLayout $(widgetFile "ticket/new")
getProjectTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getProjectTicketR shar proj ltkhid = do
mpid <- maybeAuthId
( wshr, wfl,
author, massignee, mresolved, ticket, lticket, tparams, eparams, cparams) <-
runDB $ do
(Entity sid sharer, Entity jid project, Entity tid ticket, Entity _ lticket, _etcl, _etpl, author, resolved) <- getProjectTicket404 shar proj ltkhid
(wshr, wid, wfl) <- do
w <- get404 $ projectWorkflow project
wsharer <-
if workflowSharer w == sid
then return sharer
else get404 $ workflowSharer w
return
( sharerIdent wsharer
, projectWorkflow project
, workflowIdent w
)
author' <-
case author of
Left (Entity _ tal, _) -> Left <$> do
p <- getJust $ ticketAuthorLocalAuthor tal
getJust $ personIdent p
getTicketR :: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketR deckHash ticketHash = do
(ticket, author, resolve) <- runDB $ do
(_, _, Entity _ ticket', author', resolve') <-
getTicket404 deckHash ticketHash
(,,) ticket'
<$> (case author' of
Left (Entity _ tal) ->
return $ Left $ ticketAuthorLocalAuthor tal
Right (Entity _ tar) -> Right <$> do
ra <- getJust $ ticketAuthorRemoteAuthor tar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (i, ro, ra)
massignee <- for (ticketAssignee ticket) $ \ apid -> do
person <- get404 apid
sharer <- get404 $ personIdent person
return (sharer, fromMaybe False $ (== apid) <$> mpid)
mresolved <- for resolved $ \ (_, etrx) ->
return (i, ro)
)
<*> (for resolve' $ \ (_, etrx) ->
bitraverse
(\ (Entity _ trl) -> do
let obiid = ticketResolveLocalActivity trl
obid <- outboxItemOutbox <$> getJust obiid
ent <- getOutboxActorEntity obid
actor <- actorEntityPath ent
actorID <- do
maybeActorID <- getKeyBy $ UniqueActorOutbox obid
case maybeActorID of
Nothing -> error "Found outbox not used by any actor"
Just a -> return a
actor <- getLocalActor actorID
return (actor, obiid)
)
(\ (Entity _ trr) -> do
@ -343,6 +187,63 @@ getProjectTicketR shar proj ltkhid = do
return (i, ro)
)
etrx
)
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
hashPerson <- getEncodeKeyHashid
hashItem <- getEncodeKeyHashid
hLocal <- getsYesod siteInstanceHost
let route mk = encodeRouteLocal $ mk deckHash ticketHash
authorHost =
case author of
Left _ -> hLocal
Right (i, _) -> instanceHost i
ticketLocalAP = AP.TicketLocal
{ AP.ticketId = route TicketR
, AP.ticketReplies = route TicketDiscussionR
, AP.ticketParticipants = route TicketFollowersR
, AP.ticketTeam = Nothing
, AP.ticketEvents = route TicketEventsR
, AP.ticketDeps = route TicketDepsR
, AP.ticketReverseDeps = route TicketReverseDepsR
}
ticketAP = AP.Ticket
{ AP.ticketLocal = Just (hLocal, ticketLocalAP)
, AP.ticketAttributedTo =
case author of
Left authorID ->
encodeRouteLocal $ PersonR $ hashPerson authorID
Right (_instance, object) ->
remoteObjectIdent object
, AP.ticketPublished = Just $ ticketCreated ticket
, AP.ticketUpdated = Nothing
, AP.ticketContext = Just $ encodeRouteHome $ DeckR deckHash
-- , AP.ticketName = Just $ "#" <> T.pack (show num)
, AP.ticketSummary = TextHtml $ ticketTitle ticket
, AP.ticketContent = TextHtml $ ticketDescription ticket
, AP.ticketSource = TextPandocMarkdown $ ticketSource ticket
, AP.ticketAssignedTo = Nothing
, AP.ticketResolved =
let u (Left (actor, obiid)) =
encodeRouteHome $
outboxItemRoute actor $ hashItem obiid
u (Right (i, ro)) =
ObjURI (instanceHost i) (remoteObjectIdent ro)
in (,Nothing) . Just . u <$> resolve
, AP.ticketAttachment = Nothing
}
provideHtmlAndAP' authorHost ticketAP $ redirectToPrettyJSON here
where
here = TicketR deckHash ticketHash
{-
mpid <- maybeAuthId
( wshr, wfl,
author, massignee, mresolved, ticket, lticket, tparams, eparams, cparams) <-
runDB $ do
(Entity sid sharer, Entity jid project, Entity tid ticket, Entity _ lticket, _etcl, _etpl, author, resolved) <- getProjectTicket404 shar proj ltkhid
tparams <- getTicketTextParams tid wid
eparams <- getTicketEnumParams tid wid
cparams <- getTicketClasses tid wid
@ -351,7 +252,6 @@ getProjectTicketR shar proj ltkhid = do
, author', massignee, mresolved, ticket, lticket
, tparams, eparams, cparams
)
encodeHid <- getEncodeKeyHashid
let desc :: Widget
desc = toWidget $ preEscapedToMarkup $ ticketDescription ticket
discuss =
@ -367,60 +267,6 @@ getProjectTicketR shar proj ltkhid = do
TSNew -> wffNew filt
TSTodo -> wffTodo filt
TSClosed -> wffClosed filt
hLocal <- getsYesod siteInstanceHost
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeKeyHashid <- getEncodeKeyHashid
let host =
case author of
Left _ -> hLocal
Right (i, _, _) -> instanceHost i
ticketAP = AP.Ticket
{ AP.ticketLocal = Just
( hLocal
, AP.TicketLocal
{ AP.ticketId =
encodeRouteLocal $ ProjectTicketR shar proj ltkhid
, AP.ticketReplies =
encodeRouteLocal $ ProjectTicketDiscussionR shar proj ltkhid
, AP.ticketParticipants =
encodeRouteLocal $ ProjectTicketParticipantsR shar proj ltkhid
, AP.ticketTeam =
Just $ encodeRouteLocal $ ProjectTicketTeamR shar proj ltkhid
, AP.ticketEvents =
encodeRouteLocal $ ProjectTicketEventsR shar proj ltkhid
, AP.ticketDeps =
encodeRouteLocal $ ProjectTicketDepsR shar proj ltkhid
, AP.ticketReverseDeps =
encodeRouteLocal $ ProjectTicketReverseDepsR shar proj ltkhid
}
)
, AP.ticketAttributedTo =
case author of
Left sharer ->
encodeRouteLocal $ SharerR $ sharerIdent sharer
Right (_inztance, object, _actor) ->
remoteObjectIdent object
, AP.ticketPublished = Just $ ticketCreated ticket
, AP.ticketUpdated = Nothing
, AP.ticketContext =
Just $ encodeRouteHome $ ProjectR shar proj
-- , AP.ticketName = Just $ "#" <> T.pack (show num)
, AP.ticketSummary = TextHtml $ ticketTitle ticket
, AP.ticketContent = TextHtml $ ticketDescription ticket
, AP.ticketSource = TextPandocMarkdown $ ticketSource ticket
, AP.ticketAssignedTo =
encodeRouteHome . SharerR . sharerIdent . fst <$> massignee
, AP.ticketResolved =
let u (Left (actor, obiid)) =
encodeRouteHome $
outboxItemRoute actor $ encodeKeyHashid obiid
u (Right (i, ro)) =
ObjURI (instanceHost i) (remoteObjectIdent ro)
in (,Nothing) . Just . u <$> mresolved
, AP.ticketAttachment = Nothing
}
provideHtmlAndAP' host ticketAP $
let followButton =
followW
@ -428,6 +274,174 @@ getProjectTicketR shar proj ltkhid = do
(ProjectTicketUnfollowR shar proj ltkhid)
(return $ localTicketFollowers lticket)
in $(widgetFile "ticket/one")
-}
getTicketDiscussionR
:: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketDiscussionR _ _ = do
error "Temporarily disabled"
{-
encodeHid <- getEncodeKeyHashid
getDiscussion
(ProjectTicketReplyR shar proj ltkhid . encodeHid)
(ProjectTicketTopReplyR shar proj ltkhid)
(selectDiscussionId shar proj ltkhid)
-}
getTicketEventsR
:: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketEventsR _ _ = do
error "Not implemented yet"
getTicketFollowersR
:: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketFollowersR deckHash ticketHash = getFollowersCollection here getFsid
where
here = TicketFollowersR deckHash ticketHash
getFsid = do
(_, _, Entity _ t, _, _) <- getTicket404 deckHash ticketHash
return $ ticketFollowers t
getTicketDepsR
:: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketDepsR deckHash ticketHash =
error "Temporarily disabled"
{-
getDependencyCollection here dep getLocalTicketId404
where
here = TicketDepsR deckHash ticketHash
dep = TicketDepR deckHash ticketHash
getLocalTicketId404 = do
(_, _, Entity ltid _, _, _, _, _) <- getTicket404 dkhid ltkhid
return ltid
-}
getTicketReverseDepsR
:: KeyHashid Deck -> KeyHashid TicketDeck -> Handler TypedContent
getTicketReverseDepsR deckHash ticketHash =
error "Temporarily disabled"
{-
getReverseDependencyCollection here getLocalTicketId404
where
here = TicketReverseDepsR deckhash ticketHash
getLocalTicketId404 = do
(_, _, _, Entity ltid _, _, _, _, _) <- getTicket404 deckHash ticketHash
return ltid
-}
getTicketDepR
:: KeyHashid Deck
-> KeyHashid TicketDeck
-> KeyHashid LocalTicketDependency
-> Handler TypedContent
getTicketDepR _ _ _ = do
error "Temporarily disabled"
{-
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
wiRoute <- askWorkItemRoute
hLocal <- asksSite siteInstanceHost
tdid <- decodeKeyHashid404 tdkhid
(td, author, parent, child) <- runDB $ do
td <- get404 tdid
(td,,,)
<$> getAuthor tdid
<*> getWorkItem ( localTicketDependencyParent td)
<*> getChild tdid
let host =
case author of
Left _ -> hLocal
Right (h, _) -> h
tdepAP = AP.TicketDependency
{ ticketDepId = Just $ encodeRouteHome here
, ticketDepParent = encodeRouteHome $ wiRoute parent
, ticketDepChild =
case child of
Left wi -> encodeRouteHome $ wiRoute wi
Right (h, lu) -> ObjURI h lu
, ticketDepAttributedTo =
case author of
Left shr -> encodeRouteLocal $ SharerR shr
Right (_h, lu) -> lu
, ticketDepPublished = Just $ localTicketDependencyCreated td
, ticketDepUpdated = Nothing
}
provideHtmlAndAP' host tdepAP $ redirectToPrettyJSON here
where
here = TicketDepR tdkhid
getAuthor tdid = do
tda <- requireEitherAlt
(getValBy $ UniqueTicketDependencyAuthorLocal tdid)
(getValBy $ UniqueTicketDependencyAuthorRemote tdid)
"No TDA"
"Both TDAL and TDAR"
bitraverse
(\ tdal -> do
p <- getJust $ ticketDependencyAuthorLocalAuthor tdal
s <- getJust $ personIdent p
return $ sharerIdent s
)
(\ tdar -> do
ra <- getJust $ ticketDependencyAuthorRemoteAuthor tdar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tda
getChild tdid = do
tdc <- requireEitherAlt
(getValBy $ UniqueTicketDependencyChildLocal tdid)
(getValBy $ UniqueTicketDependencyChildRemote tdid)
"No TDC"
"Both TDCL and TDCR"
bitraverse
(getWorkItem . ticketDependencyChildLocalChild)
(\ tdcr -> do
ro <- getJust $ ticketDependencyChildRemoteChild tdcr
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tdc
-}
{-
getProjectTicketNewR :: ShrIdent -> PrjIdent -> Handler Html
getProjectTicketNewR shr prj = do
wid <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity _ j <- getBy404 $ UniqueProject prj sid
return $ projectWorkflow j
((_result, widget), enctype) <- runFormPost $ newTicketForm wid
defaultLayout $(widgetFile "ticket/new")
putProjectTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
putProjectTicketR shr prj ltkhid = do
@ -757,15 +771,6 @@ selectDiscussionId shr prj ltkhid = do
(_es, _ej, _et, Entity _ lticket, _etcl, _etpl, _author, _) <- getProjectTicket404 shr prj ltkhid
return $ localTicketDiscuss lticket
getProjectTicketDiscussionR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getProjectTicketDiscussionR shar proj ltkhid = do
encodeHid <- getEncodeKeyHashid
getDiscussion
(ProjectTicketReplyR shar proj ltkhid . encodeHid)
(ProjectTicketTopReplyR shar proj ltkhid)
(selectDiscussionId shar proj ltkhid)
postProjectTicketDiscussionR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postProjectTicketDiscussionR shr prj ltkhid = do
@ -828,16 +833,6 @@ getProjectTicketReplyR shr prj ltkhid mkhid = do
(selectDiscussionId shr prj ltkhid)
mid
getProjectTicketDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getProjectTicketDepsR shr prj ltkhid =
getDependencyCollection here getLocalTicketId404
where
here = ProjectTicketDepsR shr prj ltkhid
getLocalTicketId404 = do
(_, _, _, Entity ltid _, _, _, _, _) <- getProjectTicket404 shr prj ltkhid
return ltid
postProjectTicketDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postProjectTicketDepsR _shr _prj _ltkhid = error "Temporarily disabled"
@ -908,85 +903,6 @@ deleteTicketDepOldR _shr _prj _pnum _cnum = error "Dep deletion disabled for now
redirect $ ProjectTicketDepsR shr prj pnum
-}
getProjectTicketReverseDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getProjectTicketReverseDepsR shr prj ltkhid =
getReverseDependencyCollection here getLocalTicketId404
where
here = ProjectTicketReverseDepsR shr prj ltkhid
getLocalTicketId404 = do
(_, _, _, Entity ltid _, _, _, _, _) <- getProjectTicket404 shr prj ltkhid
return ltid
getTicketDepR :: KeyHashid LocalTicketDependency -> Handler TypedContent
getTicketDepR tdkhid = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
wiRoute <- askWorkItemRoute
hLocal <- asksSite siteInstanceHost
tdid <- decodeKeyHashid404 tdkhid
(td, author, parent, child) <- runDB $ do
td <- get404 tdid
(td,,,)
<$> getAuthor tdid
<*> getWorkItem ( localTicketDependencyParent td)
<*> getChild tdid
let host =
case author of
Left _ -> hLocal
Right (h, _) -> h
tdepAP = AP.TicketDependency
{ ticketDepId = Just $ encodeRouteHome here
, ticketDepParent = encodeRouteHome $ wiRoute parent
, ticketDepChild =
case child of
Left wi -> encodeRouteHome $ wiRoute wi
Right (h, lu) -> ObjURI h lu
, ticketDepAttributedTo =
case author of
Left shr -> encodeRouteLocal $ SharerR shr
Right (_h, lu) -> lu
, ticketDepPublished = Just $ localTicketDependencyCreated td
, ticketDepUpdated = Nothing
}
provideHtmlAndAP' host tdepAP $ redirectToPrettyJSON here
where
here = TicketDepR tdkhid
getAuthor tdid = do
tda <- requireEitherAlt
(getValBy $ UniqueTicketDependencyAuthorLocal tdid)
(getValBy $ UniqueTicketDependencyAuthorRemote tdid)
"No TDA"
"Both TDAL and TDAR"
bitraverse
(\ tdal -> do
p <- getJust $ ticketDependencyAuthorLocalAuthor tdal
s <- getJust $ personIdent p
return $ sharerIdent s
)
(\ tdar -> do
ra <- getJust $ ticketDependencyAuthorRemoteAuthor tdar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tda
getChild tdid = do
tdc <- requireEitherAlt
(getValBy $ UniqueTicketDependencyChildLocal tdid)
(getValBy $ UniqueTicketDependencyChildRemote tdid)
"No TDC"
"Both TDCL and TDCR"
bitraverse
(getWorkItem . ticketDependencyChildLocalChild)
(\ tdcr -> do
ro <- getJust $ ticketDependencyChildRemoteChild tdcr
i <- getJust $ remoteObjectInstance ro
return (instanceHost i, remoteObjectIdent ro)
)
tdc
getProjectTicketParticipantsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getProjectTicketParticipantsR shr prj ltkhid = getFollowersCollection here getFsid
@ -1034,10 +950,6 @@ getProjectTicketTeamR shr prj ltkhid = do
}
provideHtmlAndAP team $ redirectToPrettyJSON here
getProjectTicketEventsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getProjectTicketEventsR _shr _prj _ltkhid = error "TODO not implemented"
getSharerTicketsR :: ShrIdent -> Handler TypedContent
getSharerTicketsR =
getSharerWorkItems SharerTicketsR SharerTicketR countTickets selectTickets
@ -1197,15 +1109,6 @@ getSharerTicketReverseDepsR shr talkhid =
(_, Entity ltid _, _, _, _) <- getSharerTicket404 shr talkhid
return ltid
getSharerTicketFollowersR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketFollowersR shr talkhid = getFollowersCollection here getFsid
where
here = SharerTicketFollowersR shr talkhid
getFsid = do
(_, Entity _ lt, _, _, _) <- getSharerTicket404 shr talkhid
return $ localTicketFollowers lt
getSharerTicketTeamR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketTeamR shr talkhid = do
@ -1221,3 +1124,4 @@ getSharerTicketEventsR shr talkhid = do
provideEmptyCollection
CollectionTypeOrdered
(SharerTicketEventsR shr talkhid)
-}

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -135,7 +135,6 @@ instance ToJSON Commit
data Push = Push
{ pushSecret :: Text
, pushUser :: Int64
, pushSharer :: Text
, pushRepo :: Text
, pushBranch :: Maybe Text
, pushBefore :: Maybe Text
@ -200,8 +199,8 @@ sendPush config manager push = do
adaptErr = T.pack . displayException
consHeader n b r = r { requestHeaders = (n, b) : requestHeaders r }
reportNewCommits :: Config -> Text -> Text -> IO ()
reportNewCommits config sharer repo = do
reportNewCommits :: Config -> Text -> IO ()
reportNewCommits config repo = do
user <- read <$> getEnv "VERVIS_SSH_USER"
manager <- newManager defaultManagerSettings
withRepo "." $ loop user manager
@ -251,7 +250,6 @@ reportNewCommits config sharer repo = do
let push = Push
{ pushSecret = configSecret config
, pushUser = user
, pushSharer = sharer
, pushRepo = repo
, pushBranch = Just branch
, pushBefore = old <$ moldRef
@ -306,10 +304,10 @@ reportNewCommits config sharer repo = do
postReceive :: IO ()
postReceive = do
(host, sharer, repo) <- do
(host, repo) <- do
args <- getArgs
case args of
[h, s, r] -> return (h, T.pack s, T.pack r)
[h, r] -> return (h, T.pack r)
_ -> die "Unexpected number of arguments"
cachePath <- getVervisCachePath host
config <- do
@ -317,10 +315,10 @@ postReceive = do
case mc of
Nothing -> die "Parsing hook config failed"
Just c -> return c
reportNewCommits config sharer repo
reportNewCommits config repo
reportNewPatches :: Config -> Text -> Text -> IO ()
reportNewPatches config sharer repo = do
reportNewPatches :: Config -> Text -> IO ()
reportNewPatches config repo = do
user <- read <$> getEnv "VERVIS_SSH_USER"
manager <- newManager defaultManagerSettings
melem <- parseXMLDoc <$> getEnv "DARCS_PATCHES_XML"
@ -333,7 +331,6 @@ reportNewPatches config sharer repo = do
return Push
{ pushSecret = configSecret config
, pushUser = user
, pushSharer = sharer
, pushRepo = repo
, pushBranch = Nothing
, pushBefore = Nothing
@ -416,10 +413,10 @@ reportNewPatches config sharer repo = do
postApply :: IO ()
postApply = do
(host, sharer, repo) <- do
(host, repo) <- do
args <- getArgs
case args of
[h, s, r] -> return (h, T.pack s, T.pack r)
[h, r] -> return (h, T.pack r)
_ -> die "Unexpected number of arguments"
cachePath <- getVervisCachePath host
config <- do
@ -427,4 +424,4 @@ postApply = do
case mc of
Nothing -> die "Parsing hook config failed"
Just c -> return c
reportNewPatches config sharer repo
reportNewPatches config repo

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
-}
module Vervis.Migration.Model
{-
( EntityField (..)
, Unique (..)
, model_2016_08_04
@ -282,25 +283,30 @@ module Vervis.Migration.Model
, Repo300Generic (..)
, CollabFulfillsLocalTopicCreation300Generic (..)
)
-}
where
import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.Time (UTCTime)
import Database.Persist.Class (EntityField, Unique)
import Database.Persist.EmailAddress ()
import Database.Persist.Schema.Types (Entity)
import Database.Persist.Schema.SQL ()
import Database.Persist.Schema.TH (makeEntitiesMigration)
import Database.Persist.Sql (SqlBackend)
import Text.Email.Validate (EmailAddress)
import Development.PatchMediaType
import Development.PatchMediaType.Persist
import Vervis.FedURI
import Vervis.Migration.TH (schema)
import Vervis.Model (SharerId)
import Vervis.Model.Group
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Model.Role
import Vervis.Model.TH
import Vervis.Model.Ticket
import Vervis.Model.Workflow
-- For migrations 77, 114
@ -538,3 +544,82 @@ model_2022_07_24 = $(schema "2022_07_24_collab_fulfills")
makeEntitiesMigration "300"
$(modelFile "migrations/2022_07_25_collab_fulfills_mig.model")
makeEntitiesMigration "303"
$(modelFile "migrations/303_2022-08-04_username.model")
makeEntitiesMigration "308"
$(modelFile "migrations/308_2022-08-04_remove_tcr.model")
makeEntitiesMigration "310"
$(modelFile "migrations/310_2022-08-04_move_ticket_discuss.model")
makeEntitiesMigration "312"
$(modelFile "migrations/312_2022-08-04_move_ticket_followers.model")
makeEntitiesMigration "316"
$(modelFile "migrations/316_2022-08-04_move_ticket_accept.model")
makeEntitiesMigration "318"
$(modelFile "migrations/318_2022-08-04_tal_ticket.model")
makeEntitiesMigration "323"
$(modelFile "migrations/323_2022-08-04_tar_ticket.model")
makeEntitiesMigration "328"
$(modelFile "migrations/328_2022-08-04_tjl_ticket.model")
makeEntitiesMigration "332"
$(modelFile "migrations/332_2022-08-04_trl_ticket.model")
makeEntitiesMigration "338"
$(modelFile "migrations/338_2022-08-04_rtd_child.model")
makeEntitiesMigration "342"
$(modelFile "migrations/342_2022-08-04_ltd_parent.model")
makeEntitiesMigration "345"
$(modelFile "migrations/345_2022-08-04_tdcl_child.model")
makeEntitiesMigration "348"
$(modelFile "migrations/348_2022-08-04_tr_ticket.model")
makeEntitiesMigration "356"
$(modelFile "migrations/356_2022-08-04_person_actor.model")
makeEntitiesMigration "365"
$(modelFile "migrations/365_2022-08-04_group_actor.model")
makeEntitiesMigration "367"
$(modelFile "migrations/367_2022-08-04_repo_actor.model")
model_384_loom :: [Entity SqlBackend]
model_384_loom = $(schema "384_2022-08-04_loom")
model_386_assignee :: [Entity SqlBackend]
model_386_assignee = $(schema "386_2022-08-04_assignee")
makeEntitiesMigration "388"
$(modelFile "migrations/388_2022-08-04_ticket_loom.model")
makeEntitiesMigration "396"
$(modelFile "migrations/396_2022-08-04_repo_dir.model")
model_399_fwder :: [Entity SqlBackend]
model_399_fwder = $(schema "399_2022-08-04_fwder")
model_408_collab_loom :: [Entity SqlBackend]
model_408_collab_loom = $(schema "408_2022-08-04_collab_loom")
makeEntitiesMigration "409"
$(modelFile "migrations/409_2022-08-05_repo_create.model")
makeEntitiesMigration "414"
$(modelFile "migrations/414_2022-08-05_followremote_actor.model")
makeEntitiesMigration "418"
$(modelFile "migrations/418_2022-08-06_follow_actor.model")

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -16,7 +16,11 @@
-- | Dedicated identifier name types for type safety. For use in routes, models
-- and handlers.
module Vervis.Model.Ident
( ShrIdent (..)
( Username (..)
, username2text
, text2username
, ShrIdent (..)
, shr2text
, text2shr
, KyIdent (..)
@ -57,6 +61,16 @@ import Database.Persist.Class.Local ()
import Database.Persist.Sql.Local ()
import Web.PathPieces.Local ()
newtype Username = Username { unUsername :: CI Text }
deriving
(Eq, Ord, Show, Read, PersistField, PersistFieldSql, SqlString, PathPiece)
username2text :: Username -> Text
username2text = CI.original . unUsername
text2username :: Text -> Username
text2username = Username . CI.mk
newtype ShrIdent = ShrIdent { unShrIdent :: CI Text }
deriving
(Eq, Ord, Show, Read, PersistField, PersistFieldSql, SqlString, PathPiece)

View file

@ -1,232 +0,0 @@
{- This file is part of Vervis.
-
- Written in 2020 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.Patch
( getSharerProposal
, getSharerProposal404
, getRepoProposal
, getRepoProposal404
)
where
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.Maybe
import Data.Traversable
import Database.Persist
import Database.Persist.Sql
import Yesod.Core
import Yesod.Hashids
import Data.Either.Local
import Database.Persist.Local
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
getResolved
:: MonadIO m
=> LocalTicketId
-> ReaderT SqlBackend m
(Maybe
( Entity TicketResolve
, Either (Entity TicketResolveLocal) (Entity TicketResolveRemote)
)
)
getResolved ltid = do
metr <- getBy $ UniqueTicketResolve ltid
for metr $ \ etr@(Entity trid _) ->
(etr,) <$>
requireEitherAlt
(getBy $ UniqueTicketResolveLocal trid)
(getBy $ UniqueTicketResolveRemote trid)
"No TRX"
"Both TRL and TRR"
getSharerProposal
:: MonadIO m
=> ShrIdent
-> TicketAuthorLocalId
-> ReaderT SqlBackend m
( Maybe
( Entity TicketAuthorLocal
, Entity LocalTicket
, Entity Ticket
, Either
( Entity TicketContextLocal
, Entity TicketRepoLocal
)
( Entity TicketProjectRemote
, Maybe (Entity TicketProjectRemoteAccept)
)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
)
getSharerProposal shr talid = runMaybeT $ do
pid <- do
sid <- MaybeT $ getKeyBy $ UniqueSharer shr
MaybeT $ getKeyBy $ UniquePersonIdent sid
tal <- MaybeT $ get talid
guard $ ticketAuthorLocalAuthor tal == pid
let ltid = ticketAuthorLocalTicket tal
lt <- lift $ getJust ltid
let tid = localTicketTicket lt
t <- lift $ getJust tid
bnids <-
MaybeT $
nonEmpty <$> selectKeysList [BundleTicket ==. tid] [Desc BundleId]
repo <-
requireEitherAlt
(do mtcl <- lift $ getBy $ UniqueTicketContextLocal tid
for mtcl $ \ etcl@(Entity tclid _) -> do
etrl <- MaybeT $ getBy $ UniqueTicketRepoLocal tclid
mtup1 <- lift $ getBy $ UniqueTicketUnderProjectProject tclid
mtup2 <- lift $ getBy $ UniqueTicketUnderProjectAuthor talid
unless (isJust mtup1 == isJust mtup2) $
error "TUP points to unrelated TAL and TCL!"
guard $ not $ isJust mtup1
return (etcl, etrl)
)
(do mtpr <- lift $ getBy $ UniqueTicketProjectRemote talid
lift $ for mtpr $ \ etpr@(Entity tprid _) ->
(etpr,) <$> getBy (UniqueTicketProjectRemoteAccept tprid)
)
"MR doesn't have context"
"MR has both local and remote context"
mresolved <- lift $ getResolved ltid
return (Entity talid tal, Entity ltid lt, Entity tid t, repo, mresolved, bnids)
getSharerProposal404
:: ShrIdent
-> KeyHashid TicketAuthorLocal
-> AppDB
( Entity TicketAuthorLocal
, Entity LocalTicket
, Entity Ticket
, Either
( Entity TicketContextLocal
, Entity TicketRepoLocal
)
( Entity TicketProjectRemote
, Maybe (Entity TicketProjectRemoteAccept)
)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
getSharerProposal404 shr talkhid = do
talid <- decodeKeyHashid404 talkhid
mpatch <- getSharerProposal shr talid
case mpatch of
Nothing -> notFound
Just patch -> return patch
getRepoProposal
:: MonadIO m
=> ShrIdent
-> RpIdent
-> LocalTicketId
-> ReaderT SqlBackend m
( Maybe
( Entity Sharer
, Entity Repo
, Entity Ticket
, Entity LocalTicket
, Entity TicketContextLocal
, Entity TicketRepoLocal
, Either
(Entity TicketAuthorLocal, Entity TicketUnderProject)
(Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
)
getRepoProposal shr rp ltid = runMaybeT $ do
es@(Entity sid _) <- MaybeT $ getBy $ UniqueSharer shr
er@(Entity rid _) <- MaybeT $ getBy $ UniqueRepo rp sid
lt <- MaybeT $ get ltid
let tid = localTicketTicket lt
t <- MaybeT $ get tid
etcl@(Entity tclid _) <- MaybeT $ getBy $ UniqueTicketContextLocal tid
etrl@(Entity _ trl) <- MaybeT $ getBy $ UniqueTicketRepoLocal tclid
guard $ ticketRepoLocalRepo trl == rid
bnids <-
MaybeT $
nonEmpty <$> selectKeysList [BundleTicket ==. tid] [Desc BundleId]
author <-
requireEitherAlt
(do mtal <- lift $ getBy $ UniqueTicketAuthorLocal ltid
for mtal $ \ tal@(Entity talid _) -> do
tupid1 <- MaybeT $ getKeyBy $ UniqueTicketUnderProjectProject tclid
tup@(Entity tupid2 _) <- MaybeT $ getBy $ UniqueTicketUnderProjectAuthor talid
unless (tupid1 == tupid2) $
error "TAL and TPL used by different TUPs!"
return (tal, tup)
)
(lift $ getBy $ UniqueTicketAuthorRemote tclid)
"MR doesn't have author"
"MR has both local and remote author"
mresolved <- lift $ getResolved ltid
return (es, er, Entity tid t, Entity ltid lt, etcl, etrl, author, mresolved, bnids)
getRepoProposal404
:: ShrIdent
-> RpIdent
-> KeyHashid LocalTicket
-> AppDB
( Entity Sharer
, Entity Repo
, Entity Ticket
, Entity LocalTicket
, Entity TicketContextLocal
, Entity TicketRepoLocal
, Either
(Entity TicketAuthorLocal, Entity TicketUnderProject)
(Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
, NonEmpty BundleId
)
getRepoProposal404 shr rp ltkhid = do
ltid <- decodeKeyHashid404 ltkhid
mpatch <- getRepoProposal shr rp ltid
case mpatch of
Nothing -> notFound
Just patch -> return patch

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -15,8 +15,6 @@
module Vervis.Path
( askRepoRootDir
, sharerDir
, askSharerDir
, repoDir
, askRepoDir
)
@ -28,30 +26,21 @@ import System.FilePath ((</>))
import qualified Data.CaseInsensitive as CI (foldedCase)
import qualified Data.Text as T (unpack)
import Yesod.Hashids
import Yesod.MonadSite
import Vervis.Foundation
import Vervis.Model.Ident
import Vervis.Model
import Vervis.Settings
askRepoRootDir :: (MonadSite m, SiteEnv m ~ App) => m FilePath
askRepoRootDir = asksSite $ appRepoDir . appSettings
sharerDir :: FilePath -> ShrIdent -> FilePath
sharerDir root sharer =
root </> (T.unpack $ CI.foldedCase $ unShrIdent sharer)
askSharerDir :: (MonadSite m, SiteEnv m ~ App) => ShrIdent -> m FilePath
askSharerDir sharer = do
root <- askRepoRootDir
return $ sharerDir root sharer
repoDir :: FilePath -> ShrIdent -> RpIdent -> FilePath
repoDir root sharer repo =
sharerDir root sharer </> (T.unpack $ CI.foldedCase $ unRpIdent repo)
repoDir :: FilePath -> KeyHashid Repo -> FilePath
repoDir root repo = root </> (T.unpack $ keyHashidText repo)
askRepoDir
:: (MonadSite m, SiteEnv m ~ App) => ShrIdent -> RpIdent -> m FilePath
askRepoDir sharer repo = do
:: (MonadSite m, SiteEnv m ~ App) => KeyHashid Repo -> m FilePath
askRepoDir repo = do
root <- askRepoRootDir
return $ repoDir root sharer repo
return $ repoDir root repo

905
src/Vervis/Recipient.hs Normal file
View file

@ -0,0 +1,905 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- These are for the Barbie-based generated Eq and Ord instances
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Vervis.Recipient
( -- * Local actors
LocalActorBy (..)
, LocalActor
, parseLocalActor
, renderLocalActor
-- * Local collections of (local and remote) actors
, LocalStageBy (..)
, LocalStage
, renderLocalStage
-- * Converting between KeyHashid, Key, Identity and Entity
, hashLocalActorPure
, getHashLocalActor
, hashLocalActor
, unhashLocalActorPure
, unhashLocalActor
, unhashLocalActorF
, unhashLocalActorM
, unhashLocalActorE
, unhashLocalActor404
, hashLocalStagePure
, getHashLocalStage
, hashLocalStage
, unhashLocalStagePure
, unhashLocalStage
, unhashLocalStageF
, unhashLocalStageM
, unhashLocalStageE
, unhashLocalStage404
-- * Local recipient set
-- ** Types
, TicketRoutes (..)
, ClothRoutes (..)
, PersonRoutes (..)
, GroupRoutes (..)
, RepoRoutes (..)
, DeckRoutes (..)
, LoomRoutes (..)
, DeckFamilyRoutes (..)
, LoomFamilyRoutes (..)
, RecipientRoutes (..)
-- ** Creating
, makeRecipientSet
, actorRecips
-- * Filtering
, localRecipSieve
, localRecipSieve'
-- * Parsing audience from a received activity
, ParsedAudience (..)
, concatRecipients
, parseAudience
-- * Creating a recipient set, supporting both local and remote recips
, Aud (..)
, collectAudience
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Data.Barbie
import Data.Bifunctor
import Data.Either
import Data.Foldable
import Data.Functor.Classes
import Data.List ((\\))
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.Maybe
import Data.Semigroup
import Data.Text (Text)
import Data.These
import Data.Traversable
import GHC.Generics
import Web.Hashids
import Yesod.Core
import qualified Control.Monad.Fail as F
import qualified Data.List.NonEmpty as NE
import qualified Data.List.Ordered as LO
import qualified Data.Text as T
import Network.FedURI
import Yesod.ActivityPub
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Data.List.Local
import Data.List.NonEmpty.Local
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
-------------------------------------------------------------------------------
-- Actor and collection-of-actors types
--
-- These are the 2 kinds of local recipients. This is the starting point for
-- grouping and checking recipient lists: First parse recipient URIs into these
-- 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
(==) (LocalActorPerson p) (LocalActorPerson p') = p == p'
(==) (LocalActorGroup g) (LocalActorGroup g') = g == g'
(==) (LocalActorRepo r) (LocalActorRepo r') = r == r'
(==) (LocalActorDeck d) (LocalActorDeck d') = d == d'
(==) (LocalActorLoom l) (LocalActorLoom l') = l == l'
(==) _ _ = False
instance (Ord (f Person), Ord (f Group), Ord (f Repo), Ord (f Deck), Ord (f Loom)) => Ord (LocalActorBy f) where
(<=) (LocalActorPerson p) (LocalActorPerson p') = p <= p'
(<=) (LocalActorPerson _) _ = True
(<=) (LocalActorGroup _) (LocalActorPerson _) = False
(<=) (LocalActorGroup g) (LocalActorGroup g') = g <= g'
(<=) (LocalActorGroup _) _ = True
(<=) (LocalActorGroup _) (LocalActorPerson _) = False
(<=) (LocalActorGroup g) (LocalActorGroup g') = g <= g'
(<=) (LocalActorGroup _) _ = True
-}
type LocalActor = LocalActorBy KeyHashid
parseLocalActor :: Route App -> Maybe LocalActor
parseLocalActor (PersonR pkhid) = Just $ LocalActorPerson pkhid
parseLocalActor (GroupR gkhid) = Just $ LocalActorGroup gkhid
parseLocalActor (RepoR rkhid) = Just $ LocalActorRepo rkhid
parseLocalActor (DeckR dkhid) = Just $ LocalActorDeck dkhid
parseLocalActor (LoomR lkhid) = Just $ LocalActorLoom lkhid
parseLocalActor _ = Nothing
renderLocalActor :: LocalActor -> Route App
renderLocalActor (LocalActorPerson pkhid) = PersonR pkhid
renderLocalActor (LocalActorGroup gkhid) = GroupR gkhid
renderLocalActor (LocalActorRepo rkhid) = RepoR rkhid
renderLocalActor (LocalActorDeck dkhid) = DeckR dkhid
renderLocalActor (LocalActorLoom lkhid) = LoomR lkhid
data LocalStageBy f
= LocalStagePersonFollowers (f Person)
| LocalStageRepoFollowers (f Repo)
| LocalStageDeckFollowers (f Deck)
| LocalStageTicketFollowers (f Deck) (f TicketDeck)
| LocalStageLoomFollowers (f Loom)
| LocalStageClothFollowers (f Loom) (f TicketLoom)
deriving (Generic, FunctorB, ConstraintsB)
deriving instance AllBF Eq f LocalStageBy => Eq (LocalStageBy f)
deriving instance AllBF Ord f LocalStageBy => Ord (LocalStageBy f)
type LocalStage = LocalStageBy KeyHashid
parseLocalStage :: Route App -> Maybe LocalStage
parseLocalStage (PersonFollowersR pkhid) =
Just $ LocalStagePersonFollowers pkhid
parseLocalStage (RepoFollowersR rkhid) =
Just $ LocalStageRepoFollowers rkhid
parseLocalStage (DeckFollowersR dkhid) =
Just $ LocalStageDeckFollowers dkhid
parseLocalStage (TicketFollowersR dkhid ltkhid) =
Just $ LocalStageTicketFollowers dkhid ltkhid
parseLocalStage (LoomFollowersR lkhid) =
Just $ LocalStageLoomFollowers lkhid
parseLocalStage (ClothFollowersR lkhid ltkhid) =
Just $ LocalStageClothFollowers lkhid ltkhid
parseLocalStage _ = Nothing
renderLocalStage :: LocalStage -> Route App
renderLocalStage (LocalStagePersonFollowers pkhid) =
PersonFollowersR pkhid
renderLocalStage (LocalStageRepoFollowers rkhid) =
RepoFollowersR rkhid
renderLocalStage (LocalStageDeckFollowers dkhid) =
DeckFollowersR dkhid
renderLocalStage (LocalStageTicketFollowers dkhid ltkhid) =
TicketFollowersR dkhid ltkhid
renderLocalStage (LocalStageLoomFollowers lkhid) =
LoomFollowersR lkhid
renderLocalStage (LocalStageClothFollowers lkhid ltkhid) =
ClothFollowersR lkhid ltkhid
parseLocalRecipient :: Route App -> Maybe (Either LocalActor LocalStage)
parseLocalRecipient r =
Left <$> parseLocalActor r <|> Right <$> parseLocalStage r
-------------------------------------------------------------------------------
-- Converting between KeyHashid, Key, Identity and Entity
-------------------------------------------------------------------------------
hashLocalActorPure
:: HashidsContext -> LocalActorBy Key -> LocalActorBy KeyHashid
hashLocalActorPure ctx = f
where
f (LocalActorPerson p) = LocalActorPerson $ encodeKeyHashidPure ctx p
f (LocalActorGroup g) = LocalActorGroup $ encodeKeyHashidPure ctx g
f (LocalActorRepo r) = LocalActorRepo $ encodeKeyHashidPure ctx r
f (LocalActorDeck d) = LocalActorDeck $ encodeKeyHashidPure ctx d
f (LocalActorLoom l) = LocalActorLoom $ encodeKeyHashidPure ctx l
getHashLocalActor
:: (MonadSite m, YesodHashids (SiteEnv m))
=> m (LocalActorBy Key -> LocalActorBy KeyHashid)
getHashLocalActor = do
ctx <- asksSite siteHashidsContext
return $ hashLocalActorPure ctx
hashLocalActor
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy Key -> m (LocalActorBy KeyHashid)
hashLocalActor actor = do
hash <- getHashLocalActor
return $ hash actor
unhashLocalActorPure
:: HashidsContext -> LocalActorBy KeyHashid -> Maybe (LocalActorBy Key)
unhashLocalActorPure ctx = f
where
f (LocalActorPerson p) = LocalActorPerson <$> decodeKeyHashidPure ctx p
f (LocalActorGroup g) = LocalActorGroup <$> decodeKeyHashidPure ctx g
f (LocalActorRepo r) = LocalActorRepo <$> decodeKeyHashidPure ctx r
f (LocalActorDeck d) = LocalActorDeck <$> decodeKeyHashidPure ctx d
f (LocalActorLoom l) = LocalActorLoom <$> decodeKeyHashidPure ctx l
unhashLocalActor
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy KeyHashid -> m (Maybe (LocalActorBy Key))
unhashLocalActor actor = do
ctx <- asksSite siteHashidsContext
return $ unhashLocalActorPure ctx actor
unhashLocalActorF
:: (F.MonadFail m, MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy KeyHashid -> String -> m (LocalActorBy Key)
unhashLocalActorF actor e = maybe (F.fail e) return =<< unhashLocalActor actor
unhashLocalActorM
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy KeyHashid -> MaybeT m (LocalActorBy Key)
unhashLocalActorM = MaybeT . unhashLocalActor
unhashLocalActorE
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalActorBy KeyHashid -> e -> ExceptT e m (LocalActorBy Key)
unhashLocalActorE actor e =
ExceptT $ maybe (Left e) Right <$> unhashLocalActor actor
unhashLocalActor404
:: ( MonadSite m
, MonadHandler m
, HandlerSite m ~ SiteEnv m
, YesodHashids (HandlerSite m)
)
=> LocalActorBy KeyHashid
-> m (LocalActorBy Key)
unhashLocalActor404 actor = maybe notFound return =<< unhashLocalActor actor
hashLocalStagePure
:: HashidsContext -> LocalStageBy Key -> LocalStageBy KeyHashid
hashLocalStagePure ctx = f
where
f (LocalStagePersonFollowers p) =
LocalStagePersonFollowers $ encodeKeyHashidPure ctx p
f (LocalStageRepoFollowers r) =
LocalStageRepoFollowers $ encodeKeyHashidPure ctx r
f (LocalStageDeckFollowers d) =
LocalStageDeckFollowers $ encodeKeyHashidPure ctx d
f (LocalStageTicketFollowers d t) =
LocalStageTicketFollowers
(encodeKeyHashidPure ctx d)
(encodeKeyHashidPure ctx t)
f (LocalStageLoomFollowers l) =
LocalStageLoomFollowers $ encodeKeyHashidPure ctx l
f (LocalStageClothFollowers l c) =
LocalStageClothFollowers
(encodeKeyHashidPure ctx l)
(encodeKeyHashidPure ctx c)
getHashLocalStage
:: (MonadSite m, YesodHashids (SiteEnv m))
=> m (LocalStageBy Key -> LocalStageBy KeyHashid)
getHashLocalStage = do
ctx <- asksSite siteHashidsContext
return $ hashLocalStagePure ctx
hashLocalStage
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalStageBy Key -> m (LocalStageBy KeyHashid)
hashLocalStage stage = do
hash <- getHashLocalStage
return $ hash stage
unhashLocalStagePure
:: HashidsContext -> LocalStageBy KeyHashid -> Maybe (LocalStageBy Key)
unhashLocalStagePure ctx = f
where
f (LocalStagePersonFollowers p) =
LocalStagePersonFollowers <$> decodeKeyHashidPure ctx p
f (LocalStageRepoFollowers r) =
LocalStageRepoFollowers <$> decodeKeyHashidPure ctx r
f (LocalStageDeckFollowers d) =
LocalStageDeckFollowers <$> decodeKeyHashidPure ctx d
f (LocalStageTicketFollowers d t) =
LocalStageTicketFollowers
<$> decodeKeyHashidPure ctx d
<*> decodeKeyHashidPure ctx t
f (LocalStageLoomFollowers l) =
LocalStageLoomFollowers <$> decodeKeyHashidPure ctx l
f (LocalStageClothFollowers l c) =
LocalStageClothFollowers
<$> decodeKeyHashidPure ctx l
<*> decodeKeyHashidPure ctx c
unhashLocalStage
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalStageBy KeyHashid -> m (Maybe (LocalStageBy Key))
unhashLocalStage stage = do
ctx <- asksSite siteHashidsContext
return $ unhashLocalStagePure ctx stage
unhashLocalStageF
:: (F.MonadFail m, MonadSite m, YesodHashids (SiteEnv m))
=> LocalStageBy KeyHashid -> String -> m (LocalStageBy Key)
unhashLocalStageF stage e = maybe (F.fail e) return =<< unhashLocalStage stage
unhashLocalStageM
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalStageBy KeyHashid -> MaybeT m (LocalStageBy Key)
unhashLocalStageM = MaybeT . unhashLocalStage
unhashLocalStageE
:: (MonadSite m, YesodHashids (SiteEnv m))
=> LocalStageBy KeyHashid -> e -> ExceptT e m (LocalStageBy Key)
unhashLocalStageE stage e =
ExceptT $ maybe (Left e) Right <$> unhashLocalStage stage
unhashLocalStage404
:: ( MonadSite m
, MonadHandler m
, HandlerSite m ~ SiteEnv m
, YesodHashids (HandlerSite m)
)
=> LocalStageBy KeyHashid
-> m (LocalStageBy Key)
unhashLocalStage404 stage = maybe notFound return =<< unhashLocalStage stage
-------------------------------------------------------------------------------
-- Intermediate recipient types
--
-- These are here just to help with grouping recipients. From this
-- representation it's easy to group recipients into a form that is friendly to
-- the code that fetches the actual recipients from the DB.
-------------------------------------------------------------------------------
data LeafTicket = LeafTicketFollowers deriving (Eq, Ord)
data LeafCloth = LeafClothFollowers deriving (Eq, Ord)
data LeafPerson = LeafPerson | LeafPersonFollowers deriving (Eq, Ord)
data LeafGroup = LeafGroup deriving (Eq, Ord)
data LeafRepo = LeafRepo | LeafRepoFollowers deriving (Eq, Ord)
data LeafDeck = LeafDeck | LeafDeckFollowers deriving (Eq, Ord)
data LeafLoom = LeafLoom | LeafLoomFollowers deriving (Eq, Ord)
data PieceDeck
= PieceDeck LeafDeck
| PieceTicket (KeyHashid TicketDeck) LeafTicket
deriving (Eq, Ord)
data PieceLoom
= PieceLoom LeafLoom
| PieceCloth (KeyHashid TicketLoom) LeafCloth
deriving (Eq, Ord)
data LocalRecipient
= RecipPerson (KeyHashid Person) LeafPerson
| RecipGroup (KeyHashid Group) LeafGroup
| RecipRepo (KeyHashid Repo) LeafRepo
| RecipDeck (KeyHashid Deck) PieceDeck
| RecipLoom (KeyHashid Loom) PieceLoom
deriving (Eq, Ord)
recipientFromActor :: LocalActor -> LocalRecipient
recipientFromActor (LocalActorPerson pkhid) =
RecipPerson pkhid LeafPerson
recipientFromActor (LocalActorGroup gkhid) =
RecipGroup gkhid LeafGroup
recipientFromActor (LocalActorRepo rkhid) =
RecipRepo rkhid LeafRepo
recipientFromActor (LocalActorDeck dkhid) =
RecipDeck dkhid $ PieceDeck LeafDeck
recipientFromActor (LocalActorLoom lkhid) =
RecipLoom lkhid $ PieceLoom LeafLoom
recipientFromStage :: LocalStage -> LocalRecipient
recipientFromStage (LocalStagePersonFollowers pkhid) =
RecipPerson pkhid LeafPersonFollowers
recipientFromStage (LocalStageRepoFollowers rkhid) =
RecipRepo rkhid LeafRepoFollowers
recipientFromStage (LocalStageDeckFollowers dkhid) =
RecipDeck dkhid $ PieceDeck LeafDeckFollowers
recipientFromStage (LocalStageTicketFollowers dkhid ltkhid) =
RecipDeck dkhid $ PieceTicket ltkhid LeafTicketFollowers
recipientFromStage (LocalStageLoomFollowers lkhid) =
RecipLoom lkhid $ PieceLoom LeafLoomFollowers
recipientFromStage (LocalStageClothFollowers lkhid ltkhid) =
RecipLoom lkhid $ PieceCloth ltkhid LeafClothFollowers
-------------------------------------------------------------------------------
-- Recipient set types
--
-- These types represent a set of recipients grouped by the variable components
-- of their routes. It's convenient to use when looking for the recipients in
-- the DB, and easy to manipulate and check the recipient list in terms of app
-- 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
}
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 = organize . partitionByActor
where
partitionByActor
:: [LocalRecipient]
-> ( [(KeyHashid Person, LeafPerson)]
, [(KeyHashid Group, LeafGroup)]
, [(KeyHashid Repo, LeafRepo)]
, [(KeyHashid Deck, PieceDeck)]
, [(KeyHashid Loom, PieceLoom)]
)
partitionByActor = foldl' f ([], [], [], [], [])
where
f (p, g, r, d, l) (RecipPerson pkhid pleaf) =
((pkhid, pleaf) : p, g, r, d, l)
f (p, g, r, d, l) (RecipGroup gkhid gleaf) =
(p, (gkhid, gleaf) : g, r, d, l)
f (p, g, r, d, l) (RecipRepo rkhid rleaf) =
(p, g, (rkhid, rleaf) : r, d, l)
f (p, g, r, d, l) (RecipDeck dkhid dpiece) =
(p, g, r, (dkhid, dpiece) : d, l)
f (p, g, r, d, l) (RecipLoom lkhid lpiece) =
(p, g, r, d, (lkhid, lpiece) : l)
organize
:: ( [(KeyHashid Person, LeafPerson)]
, [(KeyHashid Group, LeafGroup)]
, [(KeyHashid Repo, LeafRepo)]
, [(KeyHashid Deck, PieceDeck)]
, [(KeyHashid Loom, PieceLoom)]
)
-> RecipientRoutes
organize (p, g, r, d, l) = RecipientRoutes
{ recipPeople =
map (second $ foldr orLP $ PersonRoutes False False) $ groupByKeySort p
, recipGroups =
map (second $ foldr orLG $ GroupRoutes False) $ groupByKeySort g
, recipRepos =
map (second $ foldr orLR $ RepoRoutes False False) $ groupByKeySort r
, recipDecks =
map (second
$ uncurry DeckFamilyRoutes
. bimap
(foldr orLD $ DeckRoutes False False)
( map (second $ foldr orLT $ TicketRoutes False)
. groupByKey
)
. partitionEithers . NE.toList . NE.map pd2either
) $
groupByKeySort d
, recipLooms =
map (second
$ uncurry LoomFamilyRoutes
. bimap
(foldr orLL $ LoomRoutes False False)
( map (second $ foldr orLC $ ClothRoutes False)
. groupByKey
)
. partitionEithers . NE.toList . NE.map pl2either
) $
groupByKeySort l
}
where
groupByKey :: (Foldable f, Eq a) => f (a, b) -> [(a, NonEmpty b)]
groupByKey = groupWithExtract fst snd
groupByKeySort :: Ord a => [(a, b)] -> [(a, NonEmpty b)]
groupByKeySort = groupAllExtract fst snd
orLP :: LeafPerson -> PersonRoutes -> PersonRoutes
orLP _ pr@(PersonRoutes True True) = pr
orLP LeafPerson pr@(PersonRoutes _ _) = pr { routePerson = True }
orLP LeafPersonFollowers pr@(PersonRoutes _ _) = pr { routePersonFollowers = True }
orLG :: LeafGroup -> GroupRoutes -> GroupRoutes
orLG _ gr@(GroupRoutes True) = gr
orLG LeafGroup gr@(GroupRoutes _) = gr { routeGroup = True }
orLR :: LeafRepo -> RepoRoutes -> RepoRoutes
orLR _ rr@(RepoRoutes True True) = rr
orLR LeafRepo rr@(RepoRoutes _ _) = rr { routeRepo = True }
orLR LeafRepoFollowers rr@(RepoRoutes _ _) = rr { routeRepoFollowers = True }
orLD :: LeafDeck -> DeckRoutes -> DeckRoutes
orLD _ dr@(DeckRoutes True True) = dr
orLD LeafDeck dr@(DeckRoutes _ _) = dr { routeDeck = True }
orLD LeafDeckFollowers dr@(DeckRoutes _ _) = dr { routeDeckFollowers = True }
orLL :: LeafLoom -> LoomRoutes -> LoomRoutes
orLL _ lr@(LoomRoutes True True) = lr
orLL LeafLoom lr@(LoomRoutes _ _) = lr { routeLoom = True }
orLL LeafLoomFollowers lr@(LoomRoutes _ _) = lr { routeLoomFollowers = True }
orLT :: LeafTicket -> TicketRoutes -> TicketRoutes
orLT _ tr@(TicketRoutes True) = tr
orLT LeafTicketFollowers tr@(TicketRoutes _) = tr { routeTicketFollowers = True }
orLC :: LeafCloth -> ClothRoutes -> ClothRoutes
orLC _ cr@(ClothRoutes True) = cr
orLC LeafClothFollowers cr@(ClothRoutes _) = cr { routeClothFollowers = True }
pd2either :: PieceDeck -> Either LeafDeck (KeyHashid TicketDeck, LeafTicket)
pd2either (PieceDeck ld) = Left ld
pd2either (PieceTicket ltkhid lt) = Right (ltkhid, lt)
pl2either :: PieceLoom -> Either LeafLoom (KeyHashid TicketLoom, LeafCloth)
pl2either (PieceLoom ll) = Left ll
pl2either (PieceCloth ltkhid ll) = Right (ltkhid, ll)
-------------------------------------------------------------------------------
-- Parse URIs into a grouped recipient set
-------------------------------------------------------------------------------
makeRecipientSet :: [LocalActor] -> [LocalStage] -> RecipientRoutes
makeRecipientSet actors stages =
groupLocalRecipients $
map recipientFromActor actors ++ map recipientFromStage stages
actorIsMember :: LocalActor -> RecipientRoutes -> Bool
actorIsMember (LocalActorPerson pkhid) routes =
case lookup pkhid $ recipPeople routes of
Just p -> routePerson p
Nothing -> False
actorIsMember (LocalActorGroup gkhid) routes =
case lookup gkhid $ recipGroups routes of
Just g -> routeGroup g
Nothing -> False
actorIsMember (LocalActorRepo rkhid) routes =
case lookup rkhid $ recipRepos routes of
Just r -> routeRepo r
Nothing -> False
actorIsMember (LocalActorDeck dkhid) routes =
case lookup dkhid $ recipDecks routes of
Just d -> routeDeck $ familyDeck d
Nothing -> False
actorIsMember (LocalActorLoom lkhid) routes =
case lookup lkhid $ recipLooms routes of
Just l -> routeLoom $ familyLoom l
Nothing -> False
actorRecips :: LocalActor -> RecipientRoutes
actorRecips = groupLocalRecipients . (: []) . recipientFromActor
localRecipSieve
:: RecipientRoutes -> Bool -> RecipientRoutes -> RecipientRoutes
localRecipSieve sieve allowActors =
localRecipSieve' sieve allowActors allowActors
localRecipSieve'
:: RecipientRoutes
-> Bool
-> Bool
-> RecipientRoutes
-> RecipientRoutes
localRecipSieve' sieve allowPeople allowOthers routes = RecipientRoutes
{ recipPeople = applySieve' applyPerson recipPeople
, recipGroups = applySieve' applyGroup recipGroups
, recipRepos = applySieve' applyRepo recipRepos
, recipDecks = applySieve' applyDeck recipDecks
, recipLooms = applySieve' applyLoom recipLooms
}
where
applySieve
:: ( KeyHashid record
-> These routes routes
-> Maybe (KeyHashid record, routes)
)
-> [(KeyHashid record, routes)]
-> [(KeyHashid record, routes)]
-> [(KeyHashid record, routes)]
applySieve merge sieveList routeList =
mapMaybe (uncurry merge) $ sortAlign sieveList routeList
applySieve'
:: ( KeyHashid record
-> These routes routes
-> Maybe (KeyHashid record, routes)
)
-> (RecipientRoutes -> [(KeyHashid record, routes)])
-> [(KeyHashid record, routes)]
applySieve' merge field = applySieve merge (field sieve) (field routes)
applyPerson _ (This _) = Nothing
applyPerson pkhid (That p) =
if allowPeople && routePerson p
then Just (pkhid, PersonRoutes True False)
else Nothing
applyPerson pkhid (These (PersonRoutes p' pf') (PersonRoutes p pf)) =
let merged = PersonRoutes (p && (p' || allowPeople)) (pf && pf')
in if merged == PersonRoutes False False
then Nothing
else Just (pkhid, merged)
applyGroup _ (This _) = Nothing
applyGroup gkhid (That g) =
if allowOthers && routeGroup g
then Just (gkhid, GroupRoutes True)
else Nothing
applyGroup gkhid (These (GroupRoutes g') (GroupRoutes g)) =
let merged = GroupRoutes (g && (g' || allowOthers))
in if merged == GroupRoutes False
then Nothing
else Just (gkhid, merged)
applyRepo _ (This _) = Nothing
applyRepo rkhid (That r) =
if allowOthers && routeRepo r
then Just (rkhid, RepoRoutes True False)
else Nothing
applyRepo rkhid (These (RepoRoutes r' rf') (RepoRoutes r rf)) =
let merged = RepoRoutes (r && (r' || allowOthers)) (rf && rf')
in if merged == RepoRoutes False False
then Nothing
else Just (rkhid, merged)
applyDeck _ (This _) = Nothing
applyDeck dkhid (That d) =
if allowOthers && routeDeck (familyDeck d)
then Just (dkhid, DeckFamilyRoutes (DeckRoutes True False) [])
else Nothing
applyDeck
dkhid
(These
(DeckFamilyRoutes (DeckRoutes d' df') t')
(DeckFamilyRoutes (DeckRoutes d df) t)
) =
let deck = DeckRoutes (d && (d' || allowOthers)) (df && df')
tickets = applySieve applyTicket t' t
where
applyTicket ltkhid (These (TicketRoutes tf') (TicketRoutes tf)) =
let merged = TicketRoutes (tf && tf')
in if merged == TicketRoutes False
then Nothing
else Just (ltkhid, merged)
applyTicket _ _ = Nothing
in if deck == DeckRoutes False False && null tickets
then Nothing
else Just (dkhid, DeckFamilyRoutes deck tickets)
applyLoom _ (This _) = Nothing
applyLoom lkhid (That d) =
if allowOthers && routeLoom (familyLoom d)
then Just (lkhid, LoomFamilyRoutes (LoomRoutes True False) [])
else Nothing
applyLoom
lkhid
(These
(LoomFamilyRoutes (LoomRoutes l' lf') c')
(LoomFamilyRoutes (LoomRoutes l lf) c)
) =
let loom = LoomRoutes (l && (l' || allowOthers)) (lf && lf')
cloths = applySieve applyCloth c' c
where
applyCloth ltkhid (These (ClothRoutes cf') (ClothRoutes cf)) =
let merged = ClothRoutes (cf && cf')
in if merged == ClothRoutes False
then Nothing
else Just (ltkhid, merged)
applyCloth _ _ = Nothing
in if loom == LoomRoutes False False && null cloths
then Nothing
else Just (lkhid, LoomFamilyRoutes loom cloths)
data ParsedAudience u = ParsedAudience
{ paudLocalRecips :: RecipientRoutes
, paudRemoteActors :: [(Authority u, NonEmpty LocalURI)]
, paudBlinded :: AP.Audience u
, paudFwdHosts :: [Authority u]
}
concatRecipients :: AP.Audience u -> [ObjURI u]
concatRecipients (AP.Audience to bto cc bcc gen _) =
concat [to, bto, cc, bcc, gen]
parseRecipients
:: (MonadSite m, SiteEnv m ~ App)
=> NonEmpty FedURI
-> ExceptT Text m (RecipientRoutes, [FedURI])
parseRecipients recips = do
hLocal <- asksSite siteInstanceHost
let (locals, remotes) = splitRecipients hLocal recips
(lusInvalid, routesInvalid, localsSet) = parseLocalRecipients locals
unless (null lusInvalid) $
throwE $
"Local recipients are invalid routes: " <>
T.pack (show $ map (renderObjURI . ObjURI hLocal) lusInvalid)
unless (null routesInvalid) $ do
renderUrl <- askUrlRender
throwE $
"Local recipients are non-recipient routes: " <>
T.pack (show $ map renderUrl routesInvalid)
return (localsSet, remotes)
where
splitRecipients :: Host -> NonEmpty FedURI -> ([LocalURI], [FedURI])
splitRecipients home recips =
let (local, remote) = NE.partition ((== home) . objUriAuthority) recips
in (map objUriLocal local, remote)
parseLocalRecipients
:: [LocalURI] -> ([LocalURI], [Route App], RecipientRoutes)
parseLocalRecipients lus =
let (lusInvalid, routes) = partitionEithers $ map parseRoute lus
(routesInvalid, recips) = partitionEithers $ map parseRecip routes
(actors, stages) = partitionEithers recips
grouped =
map recipientFromActor actors ++ map recipientFromStage stages
in (lusInvalid, routesInvalid, groupLocalRecipients grouped)
where
parseRoute lu =
case decodeRouteLocal lu of
Nothing -> Left lu
Just route -> Right route
parseRecip route =
case parseLocalRecipient route of
Nothing -> Left route
Just recip -> Right recip
parseAudience
:: (MonadSite m, SiteEnv m ~ App)
=> AP.Audience URIMode
-> ExceptT Text m (Maybe (ParsedAudience URIMode))
parseAudience audience = do
let recips = concatRecipients audience
for (nonEmpty recips) $ \ recipsNE -> do
(localsSet, remotes) <- parseRecipients recipsNE
let remotesGrouped =
groupByHost $ remotes \\ AP.audienceNonActors audience
hosts = map fst remotesGrouped
return ParsedAudience
{ paudLocalRecips = localsSet
, paudRemoteActors = remotesGrouped
, paudBlinded =
audience { AP.audienceBto = [], AP.audienceBcc = [] }
, paudFwdHosts =
let nonActorHosts =
LO.nubSort $
map objUriAuthority $ AP.audienceNonActors audience
in LO.isect hosts nonActorHosts
}
where
groupByHost :: [FedURI] -> [(Host, NonEmpty LocalURI)]
groupByHost = groupAllExtract objUriAuthority objUriLocal
data Aud u
= AudLocal [LocalActor] [LocalStage]
| AudRemote (Authority u) [LocalURI] [LocalURI]
collectAudience
:: Foldable f
=> f (Aud u)
-> ( RecipientRoutes
, [(Authority u, NonEmpty LocalURI)]
, [Authority u]
, [Route App]
, [ObjURI u]
)
collectAudience auds =
let (locals, remotes) = partitionAudience auds
(actors, stages) =
let organize = LO.nubSort . concat
in bimap organize organize $ unzip locals
groupedRemotes =
let organize = LO.nubSort . sconcat
in map (second $ bimap organize organize . NE.unzip) $
groupAllExtract fst snd remotes
in ( makeRecipientSet actors stages
, mapMaybe (\ (h, (as, _)) -> (h,) <$> nonEmpty as) groupedRemotes
, [ h | (h, (_, cs)) <- groupedRemotes, not (null cs) ]
, map renderLocalActor actors ++ map renderLocalStage stages
, concatMap (\ (h, (as, cs)) -> ObjURI h <$> as ++ cs) groupedRemotes
)
where
partitionAudience = foldl' f ([], [])
where
f (ls, rs) (AudLocal as cs) = ((as, cs) : ls, rs)
f (ls, rs) (AudRemote h as cs) = (ls , (h, (as, cs)) : rs)

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -45,10 +45,14 @@ import System.Directory (doesFileExist, doesDirectoryExist)
import System.Environment
import System.FilePath ((</>))
import System.Process (CreateProcess (..), StdStream (..), createProcess, proc)
import Web.Hashids
import Yesod.Core.Dispatch
import qualified Data.Text as T
import qualified Formatting as F
import Yesod.Hashids
import Vervis.Access
import Vervis.Model
import Vervis.Model.Ident
@ -69,16 +73,11 @@ type Session = SessionT SessionBase UserAuthId ChannelBase
type SshChanDB = SqlPersistT Channel
type SshSessDB = SqlPersistT Session
data RepoSpec
= SpecUserRepo ShrIdent RpIdent
| SpecRepo RpIdent
deriving Show
data Action
= DarcsTransferMode RepoSpec
| DarcsApply RepoSpec
| GitUploadPack RepoSpec
| GitReceivePack RepoSpec
= DarcsTransferMode (KeyHashid Repo)
| DarcsApply (KeyHashid Repo)
| GitUploadPack (KeyHashid Repo)
| GitReceivePack (KeyHashid Repo)
deriving Show
-- | Result of running an action on the server side as a response to an SSH
@ -139,24 +138,23 @@ authorize (PublicKey name key) = do
-- Actions
-------------------------------------------------------------------------------
toKeyHashid t =
case fromPathPiece t of
Nothing -> fail "Can't parse keyhashid"
Just kh -> return kh
--TOD TODO TODO check paths for safety... no /./ or /../ and so on
darcsRepoSpecP :: Parser RepoSpec
darcsRepoSpecP = f <$>
part <*>
optional (char '/' *> optional (part <* optional (char '/')))
darcsRepoSpecP :: Parser (KeyHashid Repo)
darcsRepoSpecP = toKeyHashid =<< (part <* optional (char '/'))
where
f sharer (Just (Just repo)) = SpecUserRepo (text2shr sharer) (text2rp repo)
f repo _ = SpecRepo (text2rp repo)
part = takeWhile1 $ \ c -> c /= '/' && c /= '\''
gitRepoSpecP :: Parser RepoSpec
gitRepoSpecP = f <$> (msh *> part) <*> optional (char '/' *> part)
gitRepoSpecP :: Parser (KeyHashid Repo)
gitRepoSpecP = toKeyHashid =<< (msh *> part)
where
f repo Nothing = SpecRepo (text2rp repo)
f sharer (Just repo) = SpecUserRepo (text2shr sharer) (text2rp repo)
part = takeWhile1 $ \ c -> c /= '/' && c /= '\''
msh = optional (satisfy $ \ c -> c == '/' || c == '~')
msh = optional $ satisfy $ \ c -> c == '/' || c == '~'
actionP :: Parser Action
actionP = DarcsTransferMode <$>
@ -178,17 +176,6 @@ detectAction (Execute s) =
Right action -> Right action
detectAction _ = Left "Unsupported channel request"
resolveSpec :: RepoSpec -> Channel (ShrIdent, RpIdent)
resolveSpec (SpecUserRepo u r) = return (u, r)
resolveSpec (SpecRepo r) = do
u <- text2shr . T.pack . authUser <$> askAuthDetails
return (u, r)
resolveSpec' :: FilePath -> RepoSpec -> Channel (ShrIdent, RpIdent, FilePath)
resolveSpec' root spec = do
(u, r) <- resolveSpec spec
return (u, r, repoDir root u r)
execute :: FilePath -> [String] -> Channel ()
execute cmd args = do
lift $ $logDebugS src $
@ -229,26 +216,34 @@ whenGitRepoExists
:: Bool -> FilePath -> Channel ActionResult -> Channel ActionResult
whenGitRepoExists = whenRepoExists "Git" $ isRepo . fromString
canPushTo :: ShrIdent -> RpIdent -> Channel Bool
canPushTo shr rp = do
canPushTo :: RepoId -> Channel Bool
canPushTo repoID = do
pid <- authId <$> askAuthDetails
oas <- runChanDB $ checkRepoAccess (Just pid) ProjOpPush shr rp
oas <- runChanDB $ checkRepoAccess' (Just pid) ProjOpPush repoID
return $
case oas of
ObjectAccessAllowed -> True
_ -> False
runAction :: FilePath -> Bool -> Action -> Channel ActionResult
runAction repoDir _wantReply action =
runAction
:: (KeyHashid Repo -> Maybe RepoId)
-> FilePath
-> Bool
-> Action
-> Channel ActionResult
runAction decodeRepoHash root _wantReply action =
case action of
DarcsTransferMode spec -> do
(_sharer, _repo, repoPath) <- resolveSpec' repoDir spec
DarcsTransferMode repoHash -> do
let repoPath = repoDir root repoHash
whenDarcsRepoExists False repoPath $ do
execute "darcs" ["transfer-mode", "--repodir", repoPath]
return ARProcess
DarcsApply spec -> do
(sharer, repo, repoPath) <- resolveSpec' repoDir spec
can <- canPushTo sharer repo
DarcsApply repoHash -> do
let repoPath = repoDir root repoHash
can <-
case decodeRepoHash repoHash of
Nothing -> return False
Just repoID -> canPushTo repoID
if can
then whenDarcsRepoExists True repoPath $ do
pid <- authId <$> askAuthDetails
@ -256,14 +251,17 @@ runAction repoDir _wantReply action =
execute "darcs" ["apply", "--all", "--repodir", repoPath]
return ARProcess
else return $ ARFail "You can't push to this repository"
GitUploadPack spec -> do
(_sharer, _repo, repoPath) <- resolveSpec' repoDir spec
GitUploadPack repoHash -> do
let repoPath = repoDir root repoHash
whenGitRepoExists False repoPath $ do
execute "git-upload-pack" [repoPath]
return ARProcess
GitReceivePack spec -> do
(sharer, repo, repoPath) <- resolveSpec' repoDir spec
can <- canPushTo sharer repo
GitReceivePack repoHash -> do
let repoPath = repoDir root repoHash
can <-
case decodeRepoHash repoHash of
Nothing -> return False
Just repoID -> canPushTo repoID
if can
then whenGitRepoExists True repoPath $ do
pid <- authId <$> askAuthDetails
@ -272,8 +270,13 @@ runAction repoDir _wantReply action =
return ARProcess
else return $ ARFail "You can't push to this repository"
handle :: FilePath -> Bool -> ChannelRequest -> Channel ()
handle repoDir wantReply request = do
handle
:: (KeyHashid Repo -> Maybe RepoId)
-> FilePath
-> Bool
-> ChannelRequest
-> Channel ()
handle decodeRepoHash repoDir wantReply request = do
lift $ $logDebugS src $ T.pack $ show request
case detectAction request of
Left e -> do
@ -282,7 +285,7 @@ handle repoDir wantReply request = do
when wantReply channelFail
Right act -> do
lift $ $logDebugS src $ T.pack $ show act
res <- runAction repoDir wantReply act
res <- runAction decodeRepoHash repoDir wantReply act
case res of
ARDone msg -> do
lift $ $logDebugS src $ "Action done: " <> msg
@ -307,10 +310,11 @@ ready = runLoggingT $ $logInfoS src "SSH server component starting"
mkConfig
:: AppSettings
-> HashidsContext
-> ConnectionPool
-> LogFunc
-> IO (Config SessionBase ChannelBase UserAuthId)
mkConfig settings pool logFunc = do
mkConfig settings ctx pool logFunc = do
keyPair <- keyPairFromFile $ appSshKeyFile settings
return $ Config
{ cSession = SessionConfig
@ -321,7 +325,7 @@ mkConfig settings pool logFunc = do
flip runReaderT pool . flip runLoggingT logFunc
}
, cChannel = ChannelConfig
{ ccRequestHandler = handle $ appRepoDir settings
{ ccRequestHandler = handle (decodeKeyHashidPure ctx) (appRepoDir settings)
, ccRunBaseMonad =
flip runReaderT pool . flip runLoggingT logFunc
}
@ -329,7 +333,7 @@ mkConfig settings pool logFunc = do
, cReadyAction = ready logFunc
}
runSsh :: AppSettings -> ConnectionPool -> LogFunc -> IO ()
runSsh settings pool logFunc = do
config <- mkConfig settings pool logFunc
runSsh :: AppSettings -> HashidsContext -> ConnectionPool -> LogFunc -> IO ()
runSsh settings ctx pool logFunc = do
config <- mkConfig settings ctx pool logFunc
startConfig config

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2018, 2019, 2020, 2021
- Written in 2016, 2018, 2019, 2020, 2021, 2022
- by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
@ -15,7 +15,9 @@
-}
module Vervis.Ticket
( getTicketSummaries
(
{-
getTicketSummaries
--, getTicketDepEdges
, WorkflowFieldFilter (..)
, WorkflowFieldSummary (..)
@ -28,14 +30,13 @@ module Vervis.Ticket
, getTicketEnumParams
, TicketClassParam (..)
, getTicketClasses
, getSharerTicket
, getSharerTicket404
, getProjectTicket
, getProjectTicket404
-}
, getSharerWorkItems
, getDependencyCollection
, getReverseDependencyCollection
getTicket
, getTicket404
--, getDependencyCollection
--, getReverseDependencyCollection
, WorkItem (..)
, getWorkItemRoute
@ -43,7 +44,6 @@ module Vervis.Ticket
, getWorkItem
, parseWorkItem
, parseProposalBundle
, getRemoteTicketByURI
, checkDepAndTarget
)
@ -81,15 +81,15 @@ import Data.Paginate.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.ActivityPub.Recipient
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Model.Workflow
import Vervis.Paginate
import Vervis.Widget.Ticket (TicketSummary (..))
import Vervis.Recipient
{-
-- | Get summaries of all the tickets in the given project.
getTicketSummaries
:: Maybe (E.SqlExpr (Entity Ticket) -> E.SqlExpr (E.Value Bool))
@ -464,23 +464,18 @@ getTicketClasses tid wid = fmap (map toCParam) $
, f E.^. WorkflowFieldFilterClosed
, p E.?. TicketParamClassId
)
-}
getSharerTicket
getTicket
:: MonadIO m
=> ShrIdent
-> TicketAuthorLocalId
=> DeckId
-> TicketDeckId
-> ReaderT SqlBackend m
( Maybe
( Entity TicketAuthorLocal
, Entity LocalTicket
( Entity Deck
, Entity TicketDeck
, Entity Ticket
, Either
( Entity TicketContextLocal
, Entity TicketProjectLocal
)
( Entity TicketProjectRemote
, Maybe (Entity TicketProjectRemoteAccept)
)
, Either (Entity TicketAuthorLocal) (Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
@ -489,78 +484,41 @@ getSharerTicket
)
)
)
getSharerTicket shr talid = runMaybeT $ do
pid <- do
sid <- MaybeT $ getKeyBy $ UniqueSharer shr
MaybeT $ getKeyBy $ UniquePersonIdent sid
tal <- MaybeT $ get talid
guard $ ticketAuthorLocalAuthor tal == pid
let ltid = ticketAuthorLocalTicket tal
lt <- lift $ getJust ltid
let tid = localTicketTicket lt
getTicket did tdid = runMaybeT $ do
d <- MaybeT $ get did
td <- MaybeT $ get tdid
guard $ ticketDeckDeck td == did
let tid = ticketDeckTicket td
t <- lift $ getJust tid
mbn <- lift $ selectFirst [BundleTicket ==. tid] []
guard $ isNothing mbn
project <-
author <-
lift $
requireEitherAlt
(do mtcl <- lift $ getBy $ UniqueTicketContextLocal tid
for mtcl $ \ etcl@(Entity tclid _) -> do
etpl <- MaybeT $ getBy $ UniqueTicketProjectLocal tclid
mtup1 <- lift $ getBy $ UniqueTicketUnderProjectProject tclid
mtup2 <- lift $ getBy $ UniqueTicketUnderProjectAuthor talid
unless (isJust mtup1 == isJust mtup2) $
error "TUP points to unrelated TAL and TCL!"
guard $ not $ isJust mtup1
return (etcl, etpl)
)
(do mtpr <- lift $ getBy $ UniqueTicketProjectRemote talid
lift $ for mtpr $ \ etpr@(Entity tprid _) ->
(etpr,) <$> getBy (UniqueTicketProjectRemoteAccept tprid)
)
"Ticket doesn't have project"
"Ticket has both local and remote project"
mresolved <- lift $ getResolved ltid
return (Entity talid tal, Entity ltid lt, Entity tid t, project, mresolved)
(getBy $ UniqueTicketAuthorLocal tid)
(getBy $ UniqueTicketAuthorRemote tid)
"Ticket doesn't have author"
"Ticket has both local and remote author"
getSharerTicket404
:: ShrIdent
-> KeyHashid TicketAuthorLocal
-> AppDB
( Entity TicketAuthorLocal
, Entity LocalTicket
, Entity Ticket
, Either
( Entity TicketContextLocal
, Entity TicketProjectLocal
)
( Entity TicketProjectRemote
, Maybe (Entity TicketProjectRemoteAccept)
)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
)
getSharerTicket404 shr talkhid = do
talid <- decodeKeyHashid404 talkhid
mticket <- getSharerTicket shr talid
case mticket of
Nothing -> notFound
Just ticket -> return ticket
mresolved <- lift $ getResolved tid
getResolved
return (Entity did d, Entity tdid td, Entity tid t, author, mresolved)
where
getResolved
:: MonadIO m
=> LocalTicketId
=> TicketId
-> ReaderT SqlBackend m
(Maybe
( Entity TicketResolve
, Either (Entity TicketResolveLocal) (Entity TicketResolveRemote)
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
)
getResolved ltid = do
metr <- getBy $ UniqueTicketResolve ltid
getResolved tid = do
metr <- getBy $ UniqueTicketResolve tid
for metr $ \ etr@(Entity trid _) ->
(etr,) <$>
requireEitherAlt
@ -569,71 +527,14 @@ getResolved ltid = do
"No TRX"
"Both TRL and TRR"
getProjectTicket
:: MonadIO m
=> ShrIdent
-> PrjIdent
-> LocalTicketId
-> ReaderT SqlBackend m
( Maybe
( Entity Sharer
, Entity Project
, Entity Ticket
, Entity LocalTicket
, Entity TicketContextLocal
, Entity TicketProjectLocal
, Either
(Entity TicketAuthorLocal, Entity TicketUnderProject)
(Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
(Entity TicketResolveLocal)
(Entity TicketResolveRemote)
)
)
)
getProjectTicket shr prj ltid = runMaybeT $ do
es@(Entity sid _) <- MaybeT $ getBy $ UniqueSharer shr
ej@(Entity jid _) <- MaybeT $ getBy $ UniqueProject prj sid
lt <- MaybeT $ get ltid
let tid = localTicketTicket lt
t <- MaybeT $ get tid
etcl@(Entity tclid _) <- MaybeT $ getBy $ UniqueTicketContextLocal tid
etpl@(Entity _ tpl) <- MaybeT $ getBy $ UniqueTicketProjectLocal tclid
guard $ ticketProjectLocalProject tpl == jid
mbn <- lift $ selectFirst [BundleTicket ==. tid] []
guard $ isNothing mbn
author <-
requireEitherAlt
(do mtal <- lift $ getBy $ UniqueTicketAuthorLocal ltid
for mtal $ \ tal@(Entity talid _) -> do
tupid1 <- MaybeT $ getKeyBy $ UniqueTicketUnderProjectProject tclid
tup@(Entity tupid2 _) <- MaybeT $ getBy $ UniqueTicketUnderProjectAuthor talid
unless (tupid1 == tupid2) $
error "TAL and TPL used by different TUPs!"
return (tal, tup)
)
(lift $ getBy $ UniqueTicketAuthorRemote tclid)
"Ticket doesn't have author"
"Ticket has both local and remote author"
mresolved <- lift $ getResolved ltid
return (es, ej, Entity tid t, Entity ltid lt, etcl, etpl, author, mresolved)
getProjectTicket404
:: ShrIdent
-> PrjIdent
-> KeyHashid LocalTicket
getTicket404
:: KeyHashid Deck
-> KeyHashid TicketDeck
-> AppDB
( Entity Sharer
, Entity Project
( Entity Deck
, Entity TicketDeck
, Entity Ticket
, Entity LocalTicket
, Entity TicketContextLocal
, Entity TicketProjectLocal
, Either
(Entity TicketAuthorLocal, Entity TicketUnderProject)
(Entity TicketAuthorRemote)
, Either (Entity TicketAuthorLocal) (Entity TicketAuthorRemote)
, Maybe
( Entity TicketResolve
, Either
@ -641,73 +542,21 @@ getProjectTicket404
(Entity TicketResolveRemote)
)
)
getProjectTicket404 shr prj ltkhid = do
ltid <- decodeKeyHashid404 ltkhid
mticket <- getProjectTicket shr prj ltid
getTicket404 dkhid tdkhid = do
did <- decodeKeyHashid404 dkhid
tdid <- decodeKeyHashid404 tdkhid
mticket <- getTicket did tdid
case mticket of
Nothing -> notFound
Just ticket -> return ticket
getSharerWorkItems
:: ToBackendKey SqlBackend record
=> (ShrIdent -> Route App)
-> (ShrIdent -> KeyHashid record -> Route App)
-> (PersonId -> AppDB Int)
-> (PersonId -> Int -> Int -> AppDB [E.Value (Key record)])
-> ShrIdent
-> Handler TypedContent
getSharerWorkItems mkhere itemRoute countItems selectItems shr = do
(total, pages, mpage) <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
pid <- getKeyBy404 $ UniquePersonIdent sid
getPageAndNavCount (countItems pid) (selectItems pid)
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let here = mkhere shr
pageUrl = encodeRoutePageLocal here
encodeTicketKey <- getEncodeKeyHashid
let ticketUrl = itemRoute shr . encodeTicketKey
case mpage of
Nothing -> provide here $ Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just total
, collectionCurrent = Nothing
, collectionFirst = Just $ pageUrl 1
, collectionLast = Just $ pageUrl pages
, collectionItems = [] :: [Text]
}
Just (tickets, navModel) ->
let current = nmCurrent navModel
in provide here $ CollectionPage
{ collectionPageId = pageUrl current
, collectionPageType = CollectionPageTypeOrdered
, collectionPageTotalItems = Nothing
, collectionPageCurrent = Just $ pageUrl current
, collectionPageFirst = Just $ pageUrl 1
, collectionPageLast = Just $ pageUrl pages
, collectionPagePartOf = encodeRouteLocal here
, collectionPagePrev =
if current > 1
then Just $ pageUrl $ current - 1
else Nothing
, collectionPageNext =
if current < pages
then Just $ pageUrl $ current + 1
else Nothing
, collectionPageStartIndex = Nothing
, collectionPageItems =
map (encodeRouteHome . ticketUrl . E.unValue) tickets
}
where
provide :: ActivityPub a => Route App -> a URIMode -> Handler TypedContent
provide here a = provideHtmlAndAP a $ redirectToPrettyJSON here
{-
getDependencyCollection
:: Route App -> AppDB LocalTicketId -> Handler TypedContent
getDependencyCollection here getLocalTicketId404 = do
:: Route App
-> (KeyHashid LocalTicket -> Route App)
-> AppDB LocalTicketId
-> Handler TypedContent
getDependencyCollection here depRoute getLocalTicketId404 = do
tdids <- runDB $ do
ltid <- getLocalTicketId404
selectKeysList
@ -724,7 +573,7 @@ getDependencyCollection here getLocalTicketId404 = do
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems =
map (encodeRouteHome . TicketDepR . encodeHid) tdids
map (encodeRouteHome . depRoute . encodeHid) tdids
}
provideHtmlAndAP deps $ redirectToPrettyJSON here
@ -759,11 +608,11 @@ getReverseDependencyCollection here getLocalTicketId404 = do
E.on $ rtd E.^. RemoteTicketDependencyIdent E.==. ro E.^. RemoteObjectId
E.where_ $ rtd E.^. RemoteTicketDependencyChild E.==. E.val ltid
return (i E.^. InstanceHost, ro E.^. RemoteObjectIdent)
-}
data WorkItem
= WorkItemSharerTicket ShrIdent TicketAuthorLocalId Bool
| WorkItemProjectTicket ShrIdent PrjIdent LocalTicketId
| WorkItemRepoProposal ShrIdent RpIdent LocalTicketId
= WorkItemTicket DeckId TicketDeckId
| WorkItemCloth LoomId TicketLoomId
deriving Eq
getWorkItemRoute
@ -773,99 +622,26 @@ getWorkItemRoute wi = ($ wi) <$> askWorkItemRoute
askWorkItemRoute
:: (MonadSite m, YesodHashids (SiteEnv m)) => m (WorkItem -> Route App)
askWorkItemRoute = do
hashTALID <- getEncodeKeyHashid
hashLTID <- getEncodeKeyHashid
let route (WorkItemSharerTicket shr talid False) = SharerTicketR shr (hashTALID talid)
route (WorkItemSharerTicket shr talid True) = SharerProposalR shr (hashTALID talid)
route (WorkItemProjectTicket shr prj ltid) = ProjectTicketR shr prj (hashLTID ltid)
route (WorkItemRepoProposal shr rp ltid) = RepoProposalR shr rp (hashLTID ltid)
hashDID <- getEncodeKeyHashid
hashLID <- getEncodeKeyHashid
hashTDID <- getEncodeKeyHashid
hashTLID <- getEncodeKeyHashid
let route (WorkItemTicket did tdid) = TicketR (hashDID did) (hashTDID tdid)
route (WorkItemCloth lid tlid) = ClothR (hashLID lid) (hashTLID tlid)
return route
getWorkItem :: MonadIO m => LocalTicketId -> ReaderT SqlBackend m WorkItem
getWorkItem ltid = (either error return =<<) $ runExceptT $ do
lt <- lift $ getJust ltid
let tid = localTicketTicket lt
metal <- lift $ getBy $ UniqueTicketAuthorLocal ltid
mremoteContext <-
case metal of
Nothing -> return Nothing
Just (Entity talid _) -> lift $ do
metcr <- getBy (UniqueTicketProjectRemote talid)
for metcr $ \ etcr ->
(etcr,) . (> 0) <$> count [BundleTicket ==. tid]
mlocalContext <- do
metcl <- lift $ getBy $ UniqueTicketContextLocal tid
for metcl $ \ etcl@(Entity tclid _) -> do
mbn <- lift $ selectFirst [BundleTicket ==. tid] []
metpl <- lift $ getBy $ UniqueTicketProjectLocal tclid
metrl <- lift $ getBy $ UniqueTicketRepoLocal tclid
case (metpl, metrl) of
(Nothing, Nothing) -> throwE "TCL but no TPL and no TRL"
(Just etpl, Nothing) -> do
when (isJust mbn) $ throwE "TPL but patches attached"
return (etcl, Left etpl)
(Nothing, Just etrl) -> do
when (isNothing mbn) $ throwE "TRL but no patches attached"
return (etcl, Right etrl)
(Just _, Just _) -> throwE "Both TPL and TRL"
metar <-
case mlocalContext of
Nothing -> return Nothing
Just (Entity tclid _, _) ->
lift $ getBy $ UniqueTicketAuthorRemote tclid
mert <-
case metar of
Nothing -> return Nothing
Just (Entity tarid _) -> lift $ getBy $ UniqueRemoteTicket tarid
metuc <-
case (metal, mlocalContext) of
(Nothing, Nothing) -> return Nothing
(Just (Entity talid _), Nothing) -> do
mtuc <- lift $ getBy $ UniqueTicketUnderProjectAuthor talid
for mtuc $ \ _ -> throwE "No TCL, but TUC exists for TAL"
(Nothing, Just (Entity tclid _, _)) -> do
mtuc <- lift $ getBy $ UniqueTicketUnderProjectProject tclid
for mtuc $ \ _ -> throwE "No TAL, but TUC exists for TCL"
(Just (Entity talid _), Just (Entity tclid _, _)) -> do
metuc1 <- lift $ getBy $ UniqueTicketUnderProjectAuthor talid
mtucid2 <- lift $ getKeyBy $ UniqueTicketUnderProjectProject tclid
case (metuc1, mtucid2) of
(Nothing, Nothing) -> return Nothing
(Just _, Nothing) -> throwE "TAL has TUC, TCL doesn't"
(Nothing, Just _) -> throwE "TCL has TUC, TAL doesn't"
(Just etuc, Just tucid) ->
if entityKey etuc == tucid
then return $ Just etuc
else throwE "TAL and TCL have different TUCs"
verifyNothingE mert "Ticket has both LT and RT"
case (mremoteContext, metal, mlocalContext, metar) of
(Nothing, Just etal, Just (_, ctx), Nothing) ->
lift $
case metuc of
Nothing -> authorHosted etal (isRight ctx)
Just _ -> contextHosted ctx
(Nothing, Nothing, Just (_, ctx), Just _) -> lift $ contextHosted ctx
(Just (_, patch), Just etal, Nothing, Nothing) ->
lift $ authorHosted etal patch
_ -> throwE "Invalid/unexpected context/author situation"
where
contextHosted (Left (Entity _ tpl)) = do
j <- getJust $ ticketProjectLocalProject tpl
s <- getJust $ projectSharer j
return $ WorkItemProjectTicket (sharerIdent s) (projectIdent j) ltid
contextHosted (Right (Entity _ trl)) = do
r <- getJust $ ticketRepoLocalRepo trl
s <- getJust $ repoSharer r
return $ WorkItemRepoProposal (sharerIdent s) (repoIdent r) ltid
authorHosted (Entity talid tal) patch = do
p <- getJust $ ticketAuthorLocalAuthor tal
s <- getJust $ personIdent p
return $ WorkItemSharerTicket (sharerIdent s) talid patch
getWorkItem :: MonadIO m => TicketId -> ReaderT SqlBackend m WorkItem
getWorkItem tid = do
tracker <-
requireEitherAlt
(getBy $ UniqueTicketDeck tid)
(getBy $ UniqueTicketLoom tid)
"Neither TD nor TD found"
"Both TD and TL found"
return $
case tracker of
Left (Entity tdid td) -> WorkItemTicket (ticketDeckDeck td) tdid
Right (Entity tlid tl) -> WorkItemCloth (ticketLoomLoom tl) tlid
parseWorkItem name u@(ObjURI h lu) = do
hl <- hostIsLocal h
@ -875,18 +651,14 @@ parseWorkItem name u@(ObjURI h lu) = do
fromMaybeE (decodeRouteLocal lu) $
name <> ": Not a valid route"
case route of
SharerTicketR shr talkhid -> do
talid <- decodeKeyHashidE talkhid $ name <> ": Invalid talkhid"
return $ WorkItemSharerTicket shr talid False
SharerProposalR shr talkhid -> do
talid <- decodeKeyHashidE talkhid $ name <> ": Invalid talkhid"
return $ WorkItemSharerTicket shr talid True
ProjectTicketR shr prj ltkhid -> do
ltid <- decodeKeyHashidE ltkhid $ name <> ": Invalid ltkhid"
return $ WorkItemProjectTicket shr prj ltid
RepoProposalR shr rp ltkhid -> do
ltid <- decodeKeyHashidE ltkhid $ name <> ": Invalid ltkhid"
return $ WorkItemRepoProposal shr rp ltid
TicketR deck ticket ->
WorkItemTicket
<$> decodeKeyHashidE deck (name <> ": Invalid dkhid")
<*> decodeKeyHashidE ticket (name <> ": Invalid tdkhid")
ClothR loom ticket ->
WorkItemCloth
<$> decodeKeyHashidE loom (name <> ": Invalid lkhid")
<*> decodeKeyHashidE ticket (name <> ": Invalid tlkhid")
_ -> throwE $ name <> ": not a work item route"
else return $ Right u
@ -898,63 +670,14 @@ parseProposalBundle name u@(ObjURI h lu) = do
fromMaybeE (decodeRouteLocal lu) $
name <> ": Not a valid route"
case route of
SharerProposalBundleR shr talkhid bnkhid-> do
talid <- decodeKeyHashidE talkhid $ name <> ": Invalid talkhid"
bnid <- decodeKeyHashidE bnkhid $ name <> ": Invalid bnkhid"
return $ Left (shr, talid, bnid)
RepoProposalBundleR shr rp ltkhid bnkhid -> do
ltid <- decodeKeyHashidE ltkhid $ name <> ": Invalid ltkhid"
bnid <- decodeKeyHashidE bnkhid $ name <> ": Invalid bnkhid"
return $ Right (shr, rp, ltid, bnid)
BundleR loom ticket bundle ->
(,,)
<$> decodeKeyHashidE loom (name <> ": Invalid lkhid")
<*> decodeKeyHashidE ticket (name <> ": Invalid tlkhid")
<*> decodeKeyHashidE bundle (name <> ": Invalid bnkhid")
_ -> throwE $ name <> ": not a bundle route"
else return $ Right u
getRemoteTicketByURI
:: MonadIO m
=> ObjURI URIMode
-> ExceptT Text (ReaderT SqlBackend m)
(Either
Text
( Entity Instance
, Entity RemoteObject
, Entity RemoteTicket
, Entity TicketAuthorRemote
, Entity TicketContextLocal
, Either (Entity TicketProjectLocal) (Entity TicketRepoLocal)
)
)
getRemoteTicketByURI (ObjURI h lu) = adapt $ do
ei@(Entity iid _) <- do
mei <- lift $ getBy $ UniqueInstance h
fromMaybeE mei $ Right "Instance not known"
ero@(Entity roid _) <- do
mero <- lift $ getBy $ UniqueRemoteObject iid lu
fromMaybeE mero $ Right "Remote object not known"
ert@(Entity _ rt) <- do
mert <- lift $ getBy $ UniqueRemoteTicketIdent roid
fromMaybeE mert $ Right "Not a known RemoteTicket"
etar@(Entity _ tar) <- do
metar <- lift $ getEntity $ remoteTicketTicket rt
fromMaybeE metar $ Left "RT's TAR not found in DB"
etcl@(Entity tclid _) <- do
metcl <- lift $ getEntity $ ticketAuthorRemoteTicket tar
fromMaybeE metcl $ Left "TAR's TCL not found in DB"
ctx <- do
metjl <- lift $ getBy $ UniqueTicketProjectLocal tclid
metrl <- lift $ getBy $ UniqueTicketRepoLocal tclid
case (metjl, metrl) of
(Nothing, Nothing) -> throwE $ Left "TCL has neither TJL nor TRL"
(Just j, Nothing) -> return $ Left j
(Nothing, Just r) -> return $ Right r
(Just _, Just _) -> throwE $ Left "TCL has both TJL and TRL"
return (ei, ero, ert, etar, etcl, ctx)
where
adapt m = ExceptT $ adapt' <$> runExceptT m
where
adapt' (Left (Left e)) = Left e
adapt' (Left (Right e)) = Right $ Left e
adapt' (Right x) = Right $ Right x
checkDepAndTarget
:: (MonadSite m, SiteEnv m ~ App)
=> TicketDependency URIMode
@ -985,13 +708,13 @@ checkDepAndTarget
(parseLocalActor route)
"Offer local target isn't an actor route"
else return $ Right u
checkParentAndTarget (Left wi) (Left la) =
unless (workItemActor wi == la) $
checkParentAndTarget (Left wi) (Left la) = do
la' <-
case wi of
WorkItemTicket did _ -> LocalActorDeck <$> encodeKeyHashid did
WorkItemCloth lid _ -> LocalActorLoom <$> encodeKeyHashid lid
unless (la' == la) $
throwE "Parent and target mismatch"
where
workItemActor (WorkItemSharerTicket shr _ _) = LocalActorSharer shr
workItemActor (WorkItemProjectTicket shr prj _) = LocalActorProject shr prj
workItemActor (WorkItemRepoProposal shr rp _) = LocalActorRepo shr rp
checkParentAndTarget (Left _) (Right _) = throwE "Local parent but remote target"
checkParentAndTarget (Right _) (Left _) = throwE "Local target but remote parent"
checkParentAndTarget (Right _) (Right _) = return ()

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -43,16 +43,19 @@ import Vervis.Model
import Vervis.Model.Ident
import Yesod.RenderSource
import Vervis.Settings (widgetFile)
import Vervis.Widget.Sharer
import Vervis.Widget.Person
actorLinkW :: MessageTreeNodeAuthor -> Widget
actorLinkW actor = $(widgetFile "widget/actor-link")
actorLinkW actor = do
hashPerson <- getEncodeKeyHashid
$(widgetFile "widget/actor-link")
where
shortURI h (LocalURI p) = renderAuthority h <> p
messageW
:: UTCTime -> MessageTreeNode -> (MessageId -> Route App) -> Widget
messageW now (MessageTreeNode msgid msg author) reply = do
hashPerson <- getEncodeKeyHashid
encodeHid <- getEncodeKeyHashid
let showTime =
showEventTime .

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -13,9 +13,9 @@
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Widget.Sharer
( sharerLinkW
, sharerLinkFedW
module Vervis.Widget.Person
( personLinkW
, personLinkFedW
, followW
, personNavW
)
@ -29,6 +29,7 @@ import Yesod.Persist.Core
import Network.FedURI
import Yesod.Auth.Unverified
import Yesod.Hashids
import Database.Persist.Local
@ -38,19 +39,19 @@ import Vervis.Model.Ident
import Vervis.Settings
import Vervis.Widget
sharerLinkW :: Sharer -> Widget
sharerLinkW sharer =
personLinkW :: Entity Person -> Actor -> Widget
personLinkW (Entity personID person) actor = do
personHash <- encodeKeyHashid personID
[whamlet|
<a href=@{SharerR $ sharerIdent sharer}>
$maybe name <- sharerName sharer
#{name}
$nothing
#{shr2text $ sharerIdent sharer}
<a href=@{PersonR personHash}>
#{actorName actor} ~#{username2text $ personUsername person}
|]
sharerLinkFedW :: Either Sharer (Instance, RemoteObject, RemoteActor) -> Widget
sharerLinkFedW (Left sharer) = sharerLinkW sharer
sharerLinkFedW (Right (inztance, object, actor)) =
personLinkFedW
:: Either (Entity Person, Actor) (Instance, RemoteObject, RemoteActor)
-> Widget
personLinkFedW (Left (ep, a)) = personLinkW ep a
personLinkFedW (Right (inztance, object, actor)) =
[whamlet|
<a href="#{renderObjURI uActor}">
$maybe name <- remoteActorName actor
@ -61,16 +62,18 @@ sharerLinkFedW (Right (inztance, object, actor)) =
where
uActor = ObjURI (instanceHost inztance) (remoteObjectIdent object)
followW :: Route App -> Route App -> AppDB FollowerSetId -> Widget
followW followRoute unfollowRoute getFsid = do
mpid <- maybeVerifiedAuthId
for_ mpid $ \ pid -> do
mfollow <- handlerToWidget $ runDB $ do
fsid <- getFsid
getValBy $ UniqueFollow pid fsid
followW :: Route App -> Route App -> FollowerSetId -> Widget
followW followRoute unfollowRoute fsid = do
maybeUser <- maybeVerifiedAuth
for_ maybeUser $ \ (Entity _ user) -> do
mfollow <-
handlerToWidget $ runDB $
getBy $ UniqueFollow (personActor user) fsid
case mfollow of
Nothing -> buttonW POST "Follow" followRoute
Just _ -> buttonW POST "Unfollow" unfollowRoute
personNavW :: ShrIdent -> Widget
personNavW shr = $(widgetFile "person/widget/nav")
personNavW :: Entity Person -> Widget
personNavW (Entity personID person) = do
personHash <- encodeKeyHashid personID
$(widgetFile "person/widget/nav")

View file

@ -31,19 +31,22 @@ import qualified Data.List.NonEmpty as N
import qualified Data.Text as T (take)
import qualified Data.Vector as V
import Yesod.Hashids
import Data.Patch.Local (Hunk (..))
import Vervis.Changes
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Settings (widgetFile, appDiffContextLines)
import Vervis.Style
refSelectW :: ShrIdent -> RpIdent -> Set Text -> Set Text -> Widget
refSelectW shar repo branches tags = $(widgetFile "repo/widget/ref-select")
refSelectW :: KeyHashid Repo -> Set Text -> Set Text -> Widget
refSelectW hash branches tags = $(widgetFile "repo/widget/ref-select")
changesW :: Foldable f => ShrIdent -> RpIdent -> f LogEntry -> Widget
changesW shr rp entries = $(widgetFile "repo/widget/changes")
changesW :: Foldable f => KeyHashid Repo -> f LogEntry -> Widget
changesW hash entries = $(widgetFile "repo/widget/changes")
numberHunk :: Int -> Int -> Hunk -> (Int, Int, [(Bool, Int, Text)])
numberHunk startOld startNew hunk = j $ i ((startOld, startNew), []) hunk

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -14,15 +14,16 @@
-}
module Vervis.WorkItem
( WorkItemDetail (..)
, getWorkItemAuthorDetail
, askWorkItemFollowers
, contextAudience
, authorAudience
, parseTicketContext
, getRemoteContextHttp
, getWorkItemDetail
, WorkItemTarget (..)
(
-- WorkItemDetail (..)
--, getWorkItemAuthorDetail
askWorkItemFollowers
--, contextAudience
--, authorAudience
--, parseTicketContext
--, getRemoteContextHttp
--, getWorkItemDetail
--, WorkItemTarget (..)
)
where
@ -41,6 +42,7 @@ import Database.Persist.Sql
import qualified Data.Text as T
import Development.PatchMediaType
import Network.FedURI
import Web.ActivityPub
import Yesod.ActivityPub
@ -52,15 +54,15 @@ import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Vervis.ActivityPub.Recipient
import Vervis.Cloth
import Vervis.FedURI
import Vervis.Foundation
import Vervis.Model
import Vervis.Model.Ident
import Development.PatchMediaType
import Vervis.Patch
import Vervis.Recipient
import Vervis.Ticket
{-
data WorkItemDetail = WorkItemDetail
{ widIdent :: Either (WorkItem, LocalTicketId) (FedURI, LocalURI)
, widContext :: Either (Either (ShrIdent, PrjIdent) (ShrIdent, RpIdent)) (FedURI, Host, Maybe LocalURI, Maybe LocalURI)
@ -85,19 +87,22 @@ getWorkItemAuthorDetail =
i <- getJust $ remoteObjectInstance ro
return (i, ro)
)
-}
askWorkItemFollowers
:: (MonadSite m, YesodHashids (SiteEnv m))
=> m (WorkItem -> LocalPersonCollection)
:: (MonadSite m, YesodHashids (SiteEnv m)) => m (WorkItem -> LocalStage)
askWorkItemFollowers = do
hashTALID <- getEncodeKeyHashid
hashLTID <- getEncodeKeyHashid
let workItemFollowers (WorkItemSharerTicket shr talid False) = LocalPersonCollectionSharerTicketFollowers shr $ hashTALID talid
workItemFollowers (WorkItemSharerTicket shr talid True) = LocalPersonCollectionSharerProposalFollowers shr $ hashTALID talid
workItemFollowers (WorkItemProjectTicket shr prj ltid) = LocalPersonCollectionProjectTicketFollowers shr prj $ hashLTID ltid
workItemFollowers (WorkItemRepoProposal shr rp ltid) = LocalPersonCollectionRepoProposalFollowers shr rp $ hashLTID ltid
hashDeck <- getEncodeKeyHashid
hashLoom <- getEncodeKeyHashid
hashTicket <- getEncodeKeyHashid
hashCloth <- getEncodeKeyHashid
let workItemFollowers (WorkItemTicket deck ticket) =
LocalStageTicketFollowers (hashDeck deck) (hashTicket ticket)
workItemFollowers (WorkItemCloth loom cloth) =
LocalStageClothFollowers (hashLoom loom) (hashCloth cloth)
return workItemFollowers
{-
contextAudience
:: Either
(Either (ShrIdent, PrjIdent) (ShrIdent, RpIdent))
@ -249,3 +254,4 @@ getWorkItemDetail name v = do
data WorkItemTarget
= WITProject ShrIdent PrjIdent
| WITRepo ShrIdent RpIdent (Maybe Text) PatchMediaType (NonEmpty Text)
-}

View file

@ -451,7 +451,7 @@ instance ActivityPub Actor where
data Repo u = Repo
{ repoActor :: Actor u
, repoTeam :: LocalURI
, repoTeam :: Maybe LocalURI
, repoVcs :: VersionControlSystem
}
@ -463,16 +463,16 @@ instance ActivityPub Repo where
fail "Actor type isn't Repository"
fmap (h,) $
Repo a
<$> withAuthorityO h (o .:| "team")
<$> withAuthorityMaybeO h (o .:|? "team")
<*> o .: "versionControlSystem"
toSeries authority (Repo actor team vcs)
= toSeries authority actor
<> "team" .= ObjURI authority team
<> "team" .= (ObjURI authority <$> team)
<> "versionControlSystem" .= vcs
data TicketTracker u = TicketTracker
{ ticketTrackerActor :: Actor u
, ticketTrackerTeam :: LocalURI
, ticketTrackerTeam :: Maybe LocalURI
}
instance ActivityPub TicketTracker where
@ -483,10 +483,10 @@ instance ActivityPub TicketTracker where
fail "Actor type isn't TicketTracker"
fmap (h,) $
TicketTracker a
<$> withAuthorityO h (o .:| "team")
<$> withAuthorityMaybeO h (o .:|? "team")
toSeries authority (TicketTracker actor team)
= toSeries authority actor
<> "team" .= ObjURI authority team
<> "team" .= (ObjURI authority <$> team)
data CollectionType = CollectionTypeUnordered | CollectionTypeOrdered
@ -1085,7 +1085,7 @@ encodeTicketLocal
data MergeRequest u = MergeRequest
{ mrOrigin :: Maybe (ObjURI u)
, mrTarget :: LocalURI
, mrTarget :: Either LocalURI (Branch u)
, mrBundle :: Either (ObjURI u) (Authority u, Bundle u)
}
@ -1097,12 +1097,16 @@ instance ActivityPub MergeRequest where
unless (typ == ("Offer" :: Text)) $
fail "type isn't Offer"
ObjURI a target <- o .: "target"
target <- o .:+ "target"
let (a, target') =
case target of
Left (ObjURI h lu) -> (h, Left lu)
Right (Doc h branch) -> (h, Right branch)
fmap (a,) $
MergeRequest
<$> o .:? "origin"
<*> pure target
<*> pure target'
<*> (second fromDoc . toEither <$> o .: "object")
where
fromDoc (Doc h v) = (h, v)
@ -1110,7 +1114,7 @@ instance ActivityPub MergeRequest where
toSeries h (MergeRequest morigin target bundle)
= "type" .= ("Offer" :: Text)
<> "origin" .=? morigin
<> "target" .= ObjURI h target
<> "target" .=+ bimap (ObjURI h) (Doc h) target
<> "object" .= fromEither (second (uncurry Doc) bundle)
data Ticket u = Ticket

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -17,9 +17,12 @@ module Yesod.Hashids
( YesodHashids (..)
, KeyHashid ()
, keyHashidText
, encodeKeyHashidPure
, getEncodeKeyHashid
, encodeKeyHashid
, decodeKeyHashidPure
, decodeKeyHashid
, decodeKeyHashidF
, decodeKeyHashidM
@ -83,6 +86,14 @@ 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
:: ( MonadSite m
, YesodHashids (SiteEnv m)
@ -90,9 +101,9 @@ decodeKeyHashid
)
=> KeyHashid record
-> m (Maybe (Key record))
decodeKeyHashid (KeyHashid t) = do
decodeKeyHashid khid = do
ctx <- asksSite siteHashidsContext
return $ fmap toSqlKey $ decodeInt64 ctx $ encodeUtf8 t
return $ decodeKeyHashidPure ctx khid
decodeKeyHashidF
:: ( MonadFail m

View file

@ -29,7 +29,9 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<p>
Vervis is a web application written in the Haskell programming language and
the Yesod web framework. It's free as in freedom, under AGPLv3. It's being
developed by fr33domlover, who can be found under this nickname on Freenode
developed by fr33domlover, who can be found under this nickname on
<a href="https://libera.chat">
Libera Chat
in the #peers channel.
<p>
@ -55,53 +57,37 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
and
<a href="https://socialhub.activitypub.rocks/c/software/forgefed">forum
<h2>Repos
<table>
<tr>
<th>Sharer
<th>Project
<th>Repo
<th>VCS
<th>Last change
$forall (sharer, mproj, repo, vcs, ago) <- rowsRepo
<tr>
<td>
<a href=@{SharerR sharer}>#{shr2text sharer}
<td>
$maybe proj <- mproj
<a href=@{ProjectR sharer proj}>#{prj2text proj}
$nothing
(none)
<td>
<a href=@{RepoR sharer repo}>#{rp2text repo}
<td>
$case vcs
$of VCSDarcs
Darcs
$of VCSGit
Git
<td>
$maybe t <- ago
#{t}
$nothing
Error
<h2>Projects without repos
<table>
<tr>
<th>Sharer
<th>Project
$forall (E.Value sharer, E.Value project) <- rowsProject
<tr>
<td>
<a href=@{SharerR sharer}>#{shr2text sharer}
<td>
<a href=@{ProjectR sharer project}>#{prj2text project}
<h2>People
<p>
See
<a href=@{PeopleR}>people</a>.
<ul>
$forall (Entity personID person, Entity _ actor) <- people
<a href=@{PersonR $ hashPerson personID}>
~#{username2text $ personUsername person} #{actorName actor}
<h2>Groups
<ul>
$forall (Entity groupID _, Entity _ actor) <- groups
<a href=@{GroupR $ hashGroup groupID}>
&#{keyHashidText $ hashGroup groupID} #{actorName actor}
<h2>Repos
<ul>
$forall (Entity repoID _, Entity _ actor) <- repos
<a href=@{RepoR $ hashRepo repoID}>
^#{keyHashidText $ hashRepo repoID} #{actorName actor}
<h2>Decks
<ul>
$forall (Entity deckID _, Entity _ actor) <- decks
<a href=@{DeckR $ hashDeck deckID}>
=#{keyHashidText $ hashDeck deckID} #{actorName actor}
<h2>Looms
<ul>
$forall (Entity loomID _, Entity _ actor) <- looms
<a href=@{LoomR $ hashLoom loomID}>
+#{keyHashidText $ hashLoom loomID} #{actorName actor}

View file

@ -13,7 +13,7 @@ $# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<header>
$maybe (Entity _pid person, verified, sharer, unread) <- mperson
$maybe (Entity _ person, hash, verified, unread) <- mperson
<div>
$if verified
<span>
@ -21,19 +21,19 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<span .username>#{personLogin person}</span>]
$if unread > 0
<span>
<a href=@{NotificationsR $ sharerIdent sharer}>
<a href=@{NotificationsR}>
🔔#{unread}
<span>
<a href=@{SharerInboxR $ sharerIdent sharer}>
<a href=@{PersonInboxR hash}>
[📥 Inbox]
<span>
<a href=@{SharerOutboxR $ sharerIdent sharer}>
<a href=@{PersonOutboxR hash}>
[📤 Outbox]
<span>
<a href=@{SharerFollowersR $ sharerIdent sharer}>
<a href=@{PersonFollowersR hash}>
[🐤 Followers]
<span>
<a href=@{SharerFollowingR $ sharerIdent sharer}>
<a href=@{PersonFollowingR hash}>
[🐔 Following]
<span>
<a href=@{BrowseR}>
@ -52,7 +52,7 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
it. Or <a href=@{AuthR LogoutR}>Log out.
$if unread > 0
<span>
<a href=@{NotificationsR $ sharerIdent sharer}>
<a href=@{NotificationsR}>
🔔#{unread}
$nothing
<div>

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -18,8 +18,8 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
^{actorLinkW author}
<span .time>
$case author
$of MessageTreeNodeLocal lmid s
<a href=@{MessageR (sharerIdent s) (encodeHid lmid)}>
$of MessageTreeNodeLocal lmid pid
<a href=@{PersonMessageR (hashPerson pid) (encodeHid lmid)}>
#{showTime $ messageCreated msg}
$of MessageTreeNodeRemote h luMsg _luAuthor _mname
<a href="#{renderObjURI $ ObjURI h luMsg}"}>

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -16,6 +16,6 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
^{followButton}
<p>#{personAbout person}
<p>#{actorDesc actor}
^{personNavW shr}
^{personNavW ep}

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -16,7 +16,7 @@ $if null notifications
<p>
Nothing new here :-)
$else
<form method=POST action=@{NotificationsR shr} enctype=#{enctypeAll}>
<form method=POST action=@{NotificationsR} enctype=#{enctypeAll}>
^{widgetAll}
<div class="submit">
<input type="submit" value="Mark all as read">
@ -37,7 +37,7 @@ $else
$nothing
^{renderPrettyJSONSkylighting obj}
<form method=POST action=@{NotificationsR shr} enctype=#{enctype}>
<form method=POST action=@{NotificationsR} enctype=#{enctype}>
^{widget}
<div class="submit">
<input type="submit" value="Mark as read">

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2019, 2020 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -15,30 +15,27 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<div>
<span>
[[ 🧙
<a href=@{SharerR shr}>
#{shr2text shr}
<a href=@{PersonR personHash}>
~#{username2text $ personUsername person}
]] ::
<span>
<a href=@{SharerInboxR shr}>
<a href=@{PersonInboxR personHash}>
[📥 Inbox]
<span>
<a href=@{SharerOutboxR shr}>
<a href=@{PersonOutboxR personHash}>
[📤 Outbox]
<span>
<a href=@{SharerFollowersR shr}>
<a href=@{PersonFollowersR personHash}>
[🐤 Followers]
<span>
<a href=@{SharerFollowingR shr}>
<a href=@{PersonFollowingR personHash}>
[🐔 Following]
<span>
<a href=@{ProjectsR shr}>
<a href="">
[🏗 Projects]
<span>
<a href=@{ReposR shr}>
<a href="">
[🗃 Repositories]
<span>
<a href=@{WorkflowsR shr}>
<a href="">
[🔁 Workflows]
<span>
<a href=@{SharerTicketsR shr}>
[🐛 Tickets]

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -21,32 +21,3 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<li>
<a href=@{PublishR}>
Publish an activity
<li>
<p>Projects:
<ul>
$forall project <- projects
<li>
<a href=@{ProjectR ident project}>#{prj2text project}
<li>
<a href=@{ProjectNewR ident}>Create new…
<li>
<p>Standalone repos:
<ul>
$forall repo <- repos
<li>
<a href=@{RepoR ident repo}>#{rp2text repo}
<li>
<a href=@{RepoNewR ident}>Create new…
<li>
<a href=@{SharerInboxR ident}>Inbox
<li>
<a href=@{KeysR}>SSH keys
<li>
<a href=@{ProjectRolesR ident}>Roles
<li>
<a href=@{ClaimRequestsPersonR}>Ticket claim requests

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -14,15 +14,12 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<div>
<span>
$maybe name <- projectName project
#{name}
$nothing
#{prj2text proj}
$maybe desc <- projectDesc project
#{actorName actor}
-
<span>#{desc}
<span>
#{actorDesc actor}
^{personNavW shar}
^{personNavW $ Entity deckID deck}
^{projectNavW project workflow wsharer shar proj}

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2018, 2019 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2018, 2020 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2018, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -18,11 +18,11 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<th>Hash
<th>Message
<th>Time
$forall LogEntry author hash message (_, time) <- entries
$forall LogEntry author changeHash message (_, time) <- entries
<tr>
<td>#{author}
<td .hash>
<a href=@{RepoCommitR shr rp hash}>
#{T.take 10 hash}
<a href=@{RepoCommitR hash changeHash}>
#{T.take 10 changeHash}
<td>#{message}
<td>#{time}

View file

@ -1,6 +1,6 @@
$# This file is part of Vervis.
$#
$# Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2022 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -17,11 +17,11 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<ul>
$forall branch <- branches
<li>
<a href=@{RepoSourceR shar repo [branch]}>#{branch}
<a href=@{RepoBranchSourceR hash branch []}>#{branch}
<h2>Tags
<ul>
$forall tag <- tags
<li>
<a href=@{RepoSourceR shar repo [tag]}>#{tag}
<a href=@{RepoBranchSourceR hash tag []}>#{tag}

View file

@ -13,14 +13,11 @@ $# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
$case actor
$of MessageTreeNodeLocal _lmid s
<a href=@{SharerR $ sharerIdent s}>
$maybe name <- sharerName s
#{name}
$nothing
#{shr2text $ sharerIdent s}
$of MessageTreeNodeLocal _lmid pid
<a href=@{PersonR $ hashPerson pid}>
~#{keyHashidText $ hashPerson pid}
<span>
./s/#{shr2text $ sharerIdent s}
./people/#{keyHashidText $ hashPerson pid}
$of MessageTreeNodeRemote h _luMsg luAuthor mname
<a href="#{renderObjURI $ ObjURI h luAuthor}">
$maybe name <- mname

377
th/models
View file

@ -13,9 +13,9 @@
-- with this software. If not, see
-- <http://creativecommons.org/publicdomain/zero/1.0/>.
-------------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------------
-- ========================================================================= --
-- Remote Object
-- ========================================================================= --
Instance
host Host
@ -28,50 +28,40 @@ RemoteObject
UniqueRemoteObject instance ident
-------------------------------------------------------------------------------
-- People
-------------------------------------------------------------------------------
RemoteActivity
ident RemoteObjectId
content PersistJSONObject
received UTCTime
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueRemoteActivity ident
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
UnfetchedRemoteActor
ident RemoteObjectId
since UTCTime Maybe
Sharer
ident ShrIdent
UniqueUnfetchedRemoteActor ident
RemoteActor
ident RemoteObjectId
name Text Maybe
created UTCTime
inbox LocalURI
followers LocalURI Maybe
errorSince UTCTime Maybe
UniqueSharer ident
UniqueRemoteActor ident
Person
ident SharerId
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
about Text
inbox InboxId
outbox OutboxId
followers FollowerSetId
RemoteCollection
ident RemoteObjectId
UniquePersonIdent ident
UniquePersonLogin login
UniquePersonEmail email
UniquePersonInbox inbox
UniquePersonOutbox outbox
UniquePersonFollowers followers
UniqueRemoteCollection ident
-- ========================================================================= --
-- Local Actor
-- ========================================================================= --
-------------------------------------------------------------------------------
-- Outbox
-------------------------------------------------------------------------------
Outbox
@ -80,6 +70,10 @@ OutboxItem
activity PersistJSONObject
published UTCTime
-------------------------------------------------------------------------------
-- Inbox
-------------------------------------------------------------------------------
Inbox
InboxItem
@ -93,13 +87,6 @@ InboxItemLocal
UniqueInboxItemLocal inbox activity
UniqueInboxItemLocalItem item
RemoteActivity
ident RemoteObjectId
content PersistJSONObject
received UTCTime
UniqueRemoteActivity ident
InboxItemRemote
inbox InboxId
activity RemoteActivityId
@ -108,6 +95,50 @@ InboxItemRemote
UniqueInboxItemRemote inbox activity
UniqueInboxItemRemoteItem item
-------------------------------------------------------------------------------
-- Followers
-------------------------------------------------------------------------------
FollowerSet
-------------------------------------------------------------------------------
-- Actors
-------------------------------------------------------------------------------
Actor
name Text
desc Text
createdAt UTCTime
inbox InboxId
outbox OutboxId
followers FollowerSetId
UniqueActorInbox inbox
UniqueActorOutbox outbox
UniqueActorFollowers followers
Person
username Username
login Text
passphraseHash ByteString
email EmailAddress
verified Bool
verifiedKey Text
verifiedKeyCreated UTCTime
resetPassKey Text
resetPassKeyCreated UTCTime
actor ActorId
-- reviewFollow Bool
UniquePersonUsername username
UniquePersonLogin login
UniquePersonEmail email
UniquePersonActor actor
-- ========================================================================= --
-- Delivery
-- ========================================================================= --
UnlinkedDelivery
recipient UnfetchedRemoteActorId
activity OutboxItemId
@ -133,17 +164,17 @@ Forwarding
UniqueForwarding recipient activity
ForwarderSharer
ForwarderPerson
task ForwardingId
sender SharerId
sender PersonId
UniqueForwarderSharer task
UniqueForwarderPerson task
ForwarderProject
ForwarderGroup
task ForwardingId
sender ProjectId
sender GroupId
UniqueForwarderProject task
UniqueForwarderGroup task
ForwarderRepo
task ForwardingId
@ -151,6 +182,25 @@ ForwarderRepo
UniqueForwarderRepo task
ForwarderLoom
task ForwardingId
sender LoomId
UniqueForwarderLoom task
ForwarderDeck
task ForwardingId
sender DeckId
UniqueForwarderDeck task
-- ========================================================================= --
-- ========================================================================= --
-------------------------------------------------------------------------------
-- People
-------------------------------------------------------------------------------
VerifKey
ident LocalRefURI
instance InstanceId
@ -166,25 +216,12 @@ VerifKeySharedUsage
UniqueVerifKeySharedUsage key user
UnfetchedRemoteActor
ident RemoteObjectId
since UTCTime Maybe
UniqueUnfetchedRemoteActor ident
RemoteActor
ident RemoteObjectId
name Text Maybe
inbox LocalURI
followers LocalURI Maybe
errorSince UTCTime Maybe
UniqueRemoteActor ident
RemoteCollection
ident RemoteObjectId
UniqueRemoteCollection ident
--RemoteFollowRequest
-- actor RemoteActorId
-- target PersonId
--
-- UniqueRemoteFollowRequest actor target
--
FollowRemoteRequest
person PersonId
@ -197,27 +234,31 @@ FollowRemoteRequest
UniqueFollowRemoteRequestActivity activity
FollowRemote
person PersonId
actor ActorId
recip RemoteActorId -- actor managing the followed object
target FedURI -- the followed object
public Bool
follow OutboxItemId
accept RemoteActivityId
UniqueFollowRemote person target
UniqueFollowRemote actor target
UniqueFollowRemoteFollow follow
UniqueFollowRemoteAccept accept
FollowerSet
--FollowRequest
-- person PersonId
-- target FollowerSetId
--
-- UniqueFollowRequest person target
Follow
person PersonId
actor ActorId
target FollowerSetId
public Bool
follow OutboxItemId
accept OutboxItemId
UniqueFollow person target
UniqueFollow actor target
UniqueFollowFollow follow
UniqueFollowAccept accept
@ -241,9 +282,9 @@ SshKey
UniqueSshKey person ident
Group
ident SharerId
actor ActorId
UniqueGroup ident
UniqueGroupActor actor
GroupMember
person PersonId
@ -253,13 +294,12 @@ GroupMember
UniqueGroupMember person group
-- I'm removing the 'sharer' field, so all roles are now public for everyone to
-- use! This is temporary, until I figure out a sane plan for federated roles
Role
ident RlIdent
sharer SharerId
desc Text
UniqueRole sharer ident
RoleInherit
parent RoleId
child RoleId
@ -276,12 +316,8 @@ RoleAccess
-- Projects
-------------------------------------------------------------------------------
Project
Deck
actor ActorId
ident PrjIdent
sharer SharerId
name Text Maybe
desc Text Maybe
workflow WorkflowId
nextTicket Int
wiki RepoId Maybe
@ -289,37 +325,40 @@ Project
collabAnon RoleId Maybe
create OutboxItemId
UniqueProjectActor actor
UniqueProjectCreate create
UniqueProject ident sharer
UniqueDeckActor actor
UniqueDeckCreate create
Loom
nextTicket Int
actor ActorId
repo RepoId
create OutboxItemId
UniqueLoomActor actor
UniqueLoomRepo repo
UniqueLoomCreate create
Repo
ident RpIdent
sharer SharerId
vcs VersionControlSystem
project ProjectId Maybe
desc Text Maybe
project DeckId Maybe
mainBranch Text
collabUser RoleId Maybe
collabAnon RoleId Maybe
inbox InboxId
outbox OutboxId
followers FollowerSetId
actor ActorId
create OutboxItemId
UniqueRepo ident sharer
UniqueRepoInbox inbox
UniqueRepoOutbox outbox
UniqueRepoFollowers followers
UniqueRepoActor actor
UniqueRepoCreate create
-- I removed the 'sharer' field so Workflows don't specify who controls them
-- For now there's no way to create new ones, and what's already in the DB can
-- be publicly experimented with, until I make a plan for federated workflows
Workflow
sharer SharerId
ident WflIdent
name Text Maybe
desc Text Maybe
scope WorkflowScope
UniqueWorkflow sharer ident
WorkflowField
workflow WorkflowId
ident FldIdent
@ -377,69 +416,37 @@ Ticket
title Text -- HTML
source Text -- Pandoc Markdown
description Text -- HTML
assignee PersonId Maybe
status TicketStatus
-- UniqueTicket project number
LocalTicket
ticket TicketId
discuss DiscussionId
followers FollowerSetId
UniqueLocalTicket ticket
UniqueLocalTicketDiscussion discuss
UniqueLocalTicketFollowers followers
RemoteTicket
ticket TicketAuthorRemoteId
ident RemoteObjectId
discuss RemoteDiscussionId
UniqueRemoteTicket ticket
UniqueRemoteTicketIdent ident
UniqueRemoteTicketDiscuss discuss
TicketContextLocal
ticket TicketId
accept OutboxItemId
UniqueTicketContextLocal ticket
UniqueTicketContextLocalAccept accept
-- UniqueTicket project number
UniqueTicketDiscuss discuss
UniqueTicketFollowers followers
UniqueTicketAccept accept
TicketProjectLocal
context TicketContextLocalId
project ProjectId
TicketAssignee
ticket TicketId
person PersonId
UniqueTicketProjectLocal context
UniqueTicketAssignee ticket person
TicketRepoLocal
context TicketContextLocalId
repo RepoId
TicketDeck
ticket TicketId
deck DeckId
UniqueTicketDeck ticket
TicketLoom
ticket TicketId
loom LoomId
branch Text Maybe
UniqueTicketRepoLocal context
TicketProjectRemote
ticket TicketAuthorLocalId
tracker RemoteActorId
project RemoteObjectId Maybe -- specify if not same as tracker
-- For MRs it may be either a remote repo or
-- a branch of it
UniqueTicketProjectRemote ticket
TicketProjectRemoteAccept
ticket TicketProjectRemoteId
activity RemoteActivityId
accept Bool
result LocalURI Maybe
UniqueTicketProjectRemoteAccept ticket
UniqueTicketProjectRemoteAcceptActivity activity
UniqueTicketLoom ticket
TicketAuthorLocal
ticket LocalTicketId
ticket TicketId
author PersonId
open OutboxItemId
@ -447,22 +454,15 @@ TicketAuthorLocal
UniqueTicketAuthorLocalOpen open
TicketAuthorRemote
ticket TicketContextLocalId
ticket TicketId
author RemoteActorId
open RemoteActivityId
UniqueTicketAuthorRemote ticket
UniqueTicketAuthorRemoteOpen open
TicketUnderProject
project TicketContextLocalId
author TicketAuthorLocalId
UniqueTicketUnderProjectProject project
UniqueTicketUnderProjectAuthor author
Bundle
ticket TicketId
ticket TicketLoomId
Patch
bundle BundleId
@ -470,28 +470,24 @@ Patch
type PatchMediaType
content Text
TicketDependencyOffer
offer InboxItemId
child LocalTicketId
UniqueTicketDependencyOffer offer
RemoteTicketDependency
ident RemoteObjectId
child LocalTicketId
child TicketId
accept RemoteActivityId
UniqueRemoteTicketDependency ident
UniqueRemoteTicketDependencyAccept accept
LocalTicketDependency
parent LocalTicketId
parent TicketId
created UTCTime
accept OutboxItemId
UniqueLocalTicketDependencyAccept accept
TicketDependencyChildLocal
dep LocalTicketDependencyId
child LocalTicketId
child TicketId
UniqueTicketDependencyChildLocal dep
@ -526,7 +522,7 @@ TicketClaimRequest
UniqueTicketClaimRequest person ticket
TicketResolve
ticket LocalTicketId
ticket TicketId
accept OutboxItemId
UniqueTicketResolve ticket
@ -604,11 +600,17 @@ CollabTopicLocalRepo
UniqueCollabTopicLocalRepo collab
CollabTopicLocalProject
CollabTopicLocalDeck
collab CollabId
project ProjectId
deck DeckId
UniqueCollabTopicLocalProject collab
UniqueCollabTopicLocalDeck collab
CollabTopicLocalLoom
collab CollabId
loom LoomId
UniqueCollabTopicLocalLoom collab
CollabTopicRemote
collab CollabId
@ -654,3 +656,24 @@ CollabFulfillsLocalTopicCreation
collab CollabId
UniqueCollabFulfillsLocalTopicCreation collab
------------------------------------------------------------------------------
------------------------------------------------------------------------------
--RepoRemoteCollab
-- repo RepoId
-- collab RemoteActorId
-- role RoleId Maybe
-- cap Text
--
-- UniqueRepoRemoteCollab repo collab
-- UniqueRepoRemoteCollabCap cap
--
--ProjectRemoteCollab
-- project DeckId
-- collab RemoteActorId
-- role RoleId Maybe
-- cap Text
--
-- UniqueProjectRemoteCollab project person
-- UniqueProjectRemoteCollabCap cap

373
th/routes
View file

@ -17,205 +17,260 @@
-- Yesod misc
-- ----------------------------------------------------------------------------
/static StaticR Static appStatic
/favicon.svg FaviconSvgR GET
/favicon.png FaviconPngR GET
/robots.txt RobotsR GET
/highlight/#Text/style.css HighlightStyleR GET
-- /highlight/#Text/style.css HighlightStyleR GET
-- ----------------------------------------------------------------------------
-- Internal
-- ----------------------------------------------------------------------------
/post-receive PostReceiveR POST
-- ----------------------------------------------------------------------------
-- Federation
-- ----------------------------------------------------------------------------
/publish PublishR GET POST
/inbox InboxDebugR GET
/akey1 ActorKey1R GET
/akey2 ActorKey2R GET
-- ----------------------------------------------------------------------------
-- Current user
-- ----------------------------------------------------------------------------
/ HomeR GET
-- /k KeysR GET POST
-- /k/!new KeyNewR GET
-- /k/#KyIdent KeyR GET DELETE POST
/auth/!resend ResendVerifyEmailR GET
/auth AuthR Auth getAuth
/oauth DvaraR Dvara getDvara
/k KeysR GET POST
/k/!new KeyNewR GET
/k/#KyIdent KeyR GET DELETE POST
/cr ClaimRequestsPersonR GET
-- /cr ClaimRequestsPersonR GET
-- ----------------------------------------------------------------------------
-- People
-- ----------------------------------------------------------------------------
/s SharersR GET
/s/#ShrIdent SharerR GET
/s/#ShrIdent/inbox SharerInboxR GET POST
/s/#ShrIdent/notifications NotificationsR GET POST
/s/#ShrIdent/outbox SharerOutboxR GET POST
/s/#ShrIdent/outbox/#OutboxItemKeyHashid SharerOutboxItemR GET
/s/#ShrIdent/followers SharerFollowersR GET
/s/#ShrIdent/following SharerFollowingR GET
/s/#ShrIdent/follow SharerFollowR POST
/s/#ShrIdent/unfollow SharerUnfollowR POST
-- /g/!new GroupNewR GET
-- /g/#ShrIdent/m GroupMembersR GET POST
-- /g/#ShrIdent/m/!new GroupMemberNewR GET
-- /g/#ShrIdent/m/#ShrIdent GroupMemberR GET DELETE POST
/s/#ShrIdent/k/#SshKeyKeyHashid SshKeyR GET
/p PeopleR GET
/g GroupsR GET POST
/g/!new GroupNewR GET
/g/#ShrIdent/m GroupMembersR GET POST
/g/#ShrIdent/m/!new GroupMemberNewR GET
/g/#ShrIdent/m/#ShrIdent GroupMemberR GET DELETE POST
/s/#ShrIdent/pr ProjectRolesR GET POST
/s/#ShrIdent/pr/!new ProjectRoleNewR GET
/s/#ShrIdent/pr/#RlIdent ProjectRoleR GET DELETE POST
/s/#ShrIdent/pr/#RlIdent/a ProjectRoleOpsR GET POST
/s/#ShrIdent/pr/#RlIdent/a/!new ProjectRoleOpNewR GET
-- /s/#ShrIdent/pr ProjectRolesR GET POST
-- /s/#ShrIdent/pr/!new ProjectRoleNewR GET
-- /s/#ShrIdent/pr/#RlIdent ProjectRoleR GET DELETE POST
-- /s/#ShrIdent/pr/#RlIdent/a ProjectRoleOpsR GET POST
-- /s/#ShrIdent/pr/#RlIdent/a/!new ProjectRoleOpNewR GET
-- ----------------------------------------------------------------------------
-- Projects
-- ----------------------------------------------------------------------------
/browse BrowseR GET
-- /s/#ShrIdent/r ReposR GET
/s/#ShrIdent/r ReposR GET POST
/s/#ShrIdent/r/!new RepoNewR GET
/s/#ShrIdent/r/#RpIdent RepoR GET PUT DELETE POST
/s/#ShrIdent/r/#RpIdent/inbox RepoInboxR GET POST
/s/#ShrIdent/r/#RpIdent/outbox RepoOutboxR GET
/s/#ShrIdent/r/#RpIdent/outbox/#OutboxItemKeyHashid RepoOutboxItemR GET
/s/#ShrIdent/r/#RpIdent/team RepoTeamR GET
/s/#ShrIdent/r/#RpIdent/followers RepoFollowersR GET
/s/#ShrIdent/r/#RpIdent/edit RepoEditR GET
/s/#ShrIdent/r/#RpIdent/follow RepoFollowR POST
/s/#ShrIdent/r/#RpIdent/unfollow RepoUnfollowR POST
/s/#ShrIdent/r/#RpIdent/s/+Texts RepoSourceR GET
/s/#ShrIdent/r/#RpIdent/c RepoHeadChangesR GET
/s/#ShrIdent/r/#RpIdent/b/#Text RepoBranchR GET
/s/#ShrIdent/r/#RpIdent/c/#Text RepoChangesR GET
/s/#ShrIdent/r/#RpIdent/p/#Text RepoCommitR GET
/s/#ShrIdent/r/#RpIdent/d RepoDevsR GET POST
/s/#ShrIdent/r/#RpIdent/d/!new RepoDevNewR GET
/s/#ShrIdent/r/#RpIdent/d/#ShrIdent RepoDevR GET DELETE POST
/s/#ShrIdent/r/#RpIdent/mr RepoProposalsR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid RepoProposalR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/d RepoProposalDiscussionR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/deps RepoProposalDepsR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/rdeps RepoProposalReverseDepsR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/followers RepoProposalFollowersR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/events RepoProposalEventsR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/b/#BundleKeyHashid RepoProposalBundleR GET
/s/#ShrIdent/r/#RpIdent/mr/#LocalTicketKeyHashid/b/#BundleKeyHashid/pt/#PatchKeyHashid RepoProposalBundlePatchR GET
/s/#ShrIdent/r/#RpIdent/_darcs/+Texts DarcsDownloadR GET
/s/#ShrIdent/r/#RpIdent/info/refs GitRefDiscoverR GET
/s/#ShrIdent/r/#RpIdent/git-upload-pack GitUploadRequestR POST
/s/#ShrIdent/p ProjectsR GET POST
/s/#ShrIdent/p/!new ProjectNewR GET
/s/#ShrIdent/p/#PrjIdent ProjectR GET PUT POST
/s/#ShrIdent/p/#PrjIdent/inbox ProjectInboxR GET POST
/s/#ShrIdent/p/#PrjIdent/outbox ProjectOutboxR GET
/s/#ShrIdent/p/#PrjIdent/outbox/#OutboxItemKeyHashid ProjectOutboxItemR GET
/s/#ShrIdent/p/#PrjIdent/team ProjectTeamR GET
/s/#ShrIdent/p/#PrjIdent/followers ProjectFollowersR GET
/s/#ShrIdent/p/#PrjIdent/edit ProjectEditR GET
/s/#ShrIdent/p/#PrjIdent/follow ProjectFollowR POST
/s/#ShrIdent/p/#PrjIdent/unfollow ProjectUnfollowR POST
/s/#ShrIdent/p/#PrjIdent/d ProjectDevsR GET POST
/s/#ShrIdent/p/#PrjIdent/d/!new ProjectDevNewR GET
/s/#ShrIdent/p/#PrjIdent/d/#ShrIdent ProjectDevR GET DELETE POST
/s/#ShrIdent/p/#PrjIdent/tcr ClaimRequestsProjectR GET
-- /s/#ShrIdent/p/#PrjIdent/tcr ClaimRequestsProjectR GET
-- /w GlobalWorkflowsR GET POST
-- /w/!new GlobalWorkflowNewR GET
-- /w/#WflIdent GlobalWorkflowR GET DELETE POST
/s/#ShrIdent/w WorkflowsR GET POST
/s/#ShrIdent/w/!new WorkflowNewR GET
/s/#ShrIdent/w/#WflIdent WorkflowR GET DELETE POST
/s/#ShrIdent/w/#WflIdent/f WorkflowFieldsR GET POST
/s/#ShrIdent/w/#WflIdent/f/!new WorkflowFieldNewR GET
/s/#ShrIdent/w/#WflIdent/f/#FldIdent WorkflowFieldR GET DELETE POST
/s/#ShrIdent/w/#WflIdent/e WorkflowEnumsR GET POST
/s/#ShrIdent/w/#WflIdent/e/!new WorkflowEnumNewR GET
/s/#ShrIdent/w/#WflIdent/e/#EnmIdent WorkflowEnumR GET DELETE POST
/s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c WorkflowEnumCtorsR GET POST
/s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c/!new WorkflowEnumCtorNewR GET
/s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c/#Text WorkflowEnumCtorR PUT DELETE POST
-- /s/#ShrIdent/w WorkflowsR GET POST
-- /s/#ShrIdent/w/!new WorkflowNewR GET
-- /s/#ShrIdent/w/#WflIdent WorkflowR GET DELETE POST
-- /s/#ShrIdent/w/#WflIdent/f WorkflowFieldsR GET POST
-- /s/#ShrIdent/w/#WflIdent/f/!new WorkflowFieldNewR GET
-- /s/#ShrIdent/w/#WflIdent/f/#FldIdent WorkflowFieldR GET DELETE POST
-- /s/#ShrIdent/w/#WflIdent/e WorkflowEnumsR GET POST
-- /s/#ShrIdent/w/#WflIdent/e/!new WorkflowEnumNewR GET
-- /s/#ShrIdent/w/#WflIdent/e/#EnmIdent WorkflowEnumR GET DELETE POST
-- /s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c WorkflowEnumCtorsR GET POST
-- /s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c/!new WorkflowEnumCtorNewR GET
-- /s/#ShrIdent/w/#WflIdent/e/#EnmIdent/c/#Text WorkflowEnumCtorR PUT DELETE POST
/s/#ShrIdent/m/#LocalMessageKeyHashid MessageR GET
-- /s/#ShrIdent/p/#PrjIdent/w/+Texts WikiPageR GET
/tdeps/#TicketDepKeyHashid TicketDepR GET
/s/#ShrIdent/p/#PrjIdent/t ProjectTicketsR GET POST
/s/#ShrIdent/p/#PrjIdent/t/!tree ProjectTicketTreeR GET
/s/#ShrIdent/p/#PrjIdent/t/!new ProjectTicketNewR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid ProjectTicketR GET PUT DELETE POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/edit ProjectTicketEditR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/accept ProjectTicketAcceptR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/close ProjectTicketCloseR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/open ProjectTicketOpenR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/claim ProjectTicketClaimR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/unclaim ProjectTicketUnclaimR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/assign ProjectTicketAssignR GET POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/unassign ProjectTicketUnassignR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/follow ProjectTicketFollowR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/unfollow ProjectTicketUnfollowR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/cr ClaimRequestsTicketR GET POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/cr/new ClaimRequestNewR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/d ProjectTicketDiscussionR GET POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/d/!reply ProjectTicketTopReplyR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/d/#MessageKeyHashid ProjectTicketMessageR POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/d/#MessageKeyHashid/reply ProjectTicketReplyR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/deps ProjectTicketDepsR GET POST
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/deps/!new ProjectTicketDepNewR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/deps/#LocalTicketKeyHashid TicketDepOldR POST DELETE
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/rdeps ProjectTicketReverseDepsR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/participants ProjectTicketParticipantsR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/team ProjectTicketTeamR GET
/s/#ShrIdent/p/#PrjIdent/t/#LocalTicketKeyHashid/events ProjectTicketEventsR GET
/s/#ShrIdent/t SharerTicketsR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid SharerTicketR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/d SharerTicketDiscussionR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/deps SharerTicketDepsR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/rdeps SharerTicketReverseDepsR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/followers SharerTicketFollowersR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/team SharerTicketTeamR GET
/s/#ShrIdent/t/#TicketAuthorLocalKeyHashid/events SharerTicketEventsR GET
/s/#ShrIdent/mr SharerProposalsR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid SharerProposalR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/d SharerProposalDiscussionR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/deps SharerProposalDepsR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/rdeps SharerProposalReverseDepsR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/followers SharerProposalFollowersR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/events SharerProposalEventsR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/b/#BundleKeyHashid SharerProposalBundleR GET
/s/#ShrIdent/mr/#TicketAuthorLocalKeyHashid/b/#BundleKeyHashid/pt/#PatchKeyHashid SharerProposalBundlePatchR GET
/s/#ShrIdent/p/#PrjIdent/w/+Texts WikiPageR GET
------------------------------------------------------------------------------
-- New route structure
------------------------------------------------------------------------------
---- Static Files ------------------------------------------------------------
/static StaticR Static appStatic
/favicon.svg FaviconSvgR GET
/favicon.png FaviconPngR GET
/robots.txt RobotsR GET
---- Authentication ----------------------------------------------------------
/auth/!resend ResendVerifyEmailR GET
/auth AuthR Auth getAuth
/oauth DvaraR Dvara getDvara
/akey1 ActorKey1R GET
/akey2 ActorKey2R GET
---- Client ------------------------------------------------------------------
/ HomeR GET
/browse BrowseR GET
/notifications NotificationsR GET POST
/publish PublishR GET POST
/inbox InboxDebugR GET
---- Person ------------------------------------------------------------------
/people/#PersonKeyHashid PersonR GET
/people/#PersonKeyHashid/inbox PersonInboxR GET POST
/people/#PersonKeyHashid/outbox PersonOutboxR GET POST
/people/#PersonKeyHashid/outbox/#OutboxItemKeyHashid PersonOutboxItemR GET
/people/#PersonKeyHashid/followers PersonFollowersR GET
/people/#PersonKeyHashid/following PersonFollowingR GET
/people/#PersonKeyHashid/ssh-keys/#SshKeyKeyHashid SshKeyR GET
/people/#PersonKeyHashid/messages/#LocalMessageKeyHashid PersonMessageR GET
/people/#PersonKeyHashid/follow PersonFollowR POST
/people/#PersonKeyHashid/unfollow PersonUnfollowR POST
---- Group ------------------------------------------------------------------
/groups/#GroupKeyHashid GroupR GET
/groups/#GroupKeyHashid/inbox GroupInboxR GET POST
/groups/#GroupKeyHashid/outbox GroupOutboxR GET
/groups/#GroupKeyHashid/outbox/#OutboxItemKeyHashid GroupOutboxItemR GET
/groups/#GroupKeyHashid/followers GroupFollowersR GET
---- Repo --------------------------------------------------------------------
/repos/#RepoKeyHashid RepoR GET
/repos/#RepoKeyHashid/inbox RepoInboxR GET POST
/repos/#RepoKeyHashid/outbox RepoOutboxR GET
/repos/#RepoKeyHashid/outbox/#OutboxItemKeyHashid RepoOutboxItemR GET
/repos/#RepoKeyHashid/followers RepoFollowersR GET
/repos/#RepoKeyHashid/_darcs/+Texts DarcsDownloadR GET
/repos/#RepoKeyHashid/info/refs GitRefDiscoverR GET
/repos/#RepoKeyHashid/git-upload-pack GitUploadRequestR POST
/repos/#RepoKeyHashid/source/+Texts RepoSourceR GET
/repos/#RepoKeyHashid/source-by/#Text/+Texts RepoBranchSourceR GET
/repos/#RepoKeyHashid/commits RepoCommitsR GET
/repos/#RepoKeyHashid/commits-by/#Text RepoBranchCommitsR GET
/repos/#RepoKeyHashid/commits/#Text RepoCommitR GET
/new-repo RepoNewR GET POST
/repos/#RepoKeyHashid/delete RepoDeleteR POST
/repos/#RepoKeyHashid/edit RepoEditR GET POST
/repos/#RepoKeyHashid/follow RepoFollowR POST
/repos/#RepoKeyHashid/unfollow RepoUnfollowR POST
/post-receive PostReceiveR POST
---- Deck --------------------------------------------------------------------
/decks/#DeckKeyHashid DeckR GET
/decks/#DeckKeyHashid/inbox DeckInboxR GET POST
/decks/#DeckKeyHashid/outbox DeckOutboxR GET
/decks/#DeckKeyHashid/outbox/#OutboxItemKeyHashid DeckOutboxItemR GET
/decks/#DeckKeyHashid/followers DeckFollowersR GET
/decks/#DeckKeyHashid/tickets DeckTicketsR GET
/decks/#DeckKeyHashid/tree DeckTreeR GET
/new-deck DeckNewR GET POST
/decks/#DeckKeyHashid/delete DeckDeleteR POST
/decks/#DeckKeyHashid/edit DeckEditR GET POST
/decks/#DeckKeyHashid/follow DeckFollowR POST
/decks/#DeckKeyHashid/unfollow DeckUnfollowR POST
---- Ticket ------------------------------------------------------------------
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid TicketR GET
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/discussion TicketDiscussionR GET
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/events TicketEventsR GET
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/followers TicketFollowersR GET
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/deps TicketDepsR GET
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/rdeps TicketReverseDepsR GET
-- /decks/#DeckKeyHashid/new-ticket TicketNewR GET POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/edit TicketEditR GET POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/delete TicketDeleteR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/accept TicketAcceptR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/close TicketCloseR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/open TicketOpenR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/claim TicketClaimR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/unclaim TicketUnclaimR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/assign TicketAssignR GET POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/unassign TicketUnassignR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/follow TicketFollowR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/unfollow TicketUnfollowR POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/reply TicketTopReplyR GET POST
---- Ticket Dependency -------------------------------------------------------
/decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/deps/#TicketDepKeyHashid TicketDepR GET
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/new-dep TicketDepNewR GET POST
-- /decks/#DeckKeyHashid/tickets/#TicketDeckKeyHashid/deps/#TicketDepKeyHashid/delete TicketDepDeleteR POST
---- Loom --------------------------------------------------------------------
/looms/#LoomKeyHashid LoomR GET
/looms/#LoomKeyHashid/inbox LoomInboxR GET POST
/looms/#LoomKeyHashid/outbox LoomOutboxR GET
/looms/#LoomKeyHashid/outbox/#OutboxItemKeyHashid LoomOutboxItemR GET
/looms/#LoomKeyHashid/followers LoomFollowersR GET
/looms/#LoomKeyHashid/cloths LoomClothsR GET
-- /new-loom LoomNewR GET POST
-- /looms/#LoomKeyHashid/delete LoomDeleteR POST
-- /looms/#LoomKeyHashid/edit LoomEditR GET POST
-- /looms/#LoomKeyHashid/follow LoomFollowR POST
-- /looms/#LoomKeyHashid/unfollow LoomUnfollowR POST
---- Cloth -------------------------------------------------------------------
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid ClothR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/discussion ClothDiscussionR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/events ClothEventsR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/followers ClothFollowersR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/deps ClothDepsR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/rdeps ClothReverseDepsR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/bundles/#BundleKeyHashid BundleR GET
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/bundles/#BundleKeyHashid/patches/#PatchKeyHashid PatchR GET
-- /looms/#LoomKeyHashid/new-cloth ClothNewR GET POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/edit ClothEditR GET POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/delete ClothDeleteR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/accept ClothAcceptR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/close ClothCloseR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/open ClothOpenR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/claim ClothClaimR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/unclaim ClothUnclaimR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/assign ClothAssignR GET POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/unassign ClothUnassignR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/follow ClothFollowR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/unfollow ClothUnfollowR POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/reply ClothTopReplyR GET POST
---- Cloth Dependency --------------------------------------------------------
/looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/deps/#TicketDepKeyHashid ClothDepR GET
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/new-dep ClothDepNewR GET POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/deps/#TicketDepKeyHashid/delete ClothDepDeleteR POST

View file

@ -49,6 +49,7 @@ library
Data.Aeson.Local
Data.Attoparsec.ByteString.Local
Data.Binary.Local
-- Data.Bitraversable.Local
Data.ByteString.Char8.Local
Data.ByteString.Local
Data.CaseInsensitive.Local
@ -102,6 +103,7 @@ library
Text.Jasmine.Local
Web.ActivityAccess
Web.ActivityPub
-- Web.Capability
Web.Hashids.Local
Web.PathPieces.Local
Yesod.ActivityPub
@ -118,7 +120,7 @@ library
Vervis.Access
Vervis.ActivityPub
Vervis.ActivityPub.Recipient
Vervis.Actor
Vervis.ActorKey
Vervis.API
Vervis.Application
@ -126,58 +128,61 @@ library
Vervis.BinaryBody
Vervis.Changes
Vervis.ChangeFeed
Vervis.Client
--Vervis.Class.Actor
--Vervis.Client
Vervis.Cloth
Vervis.Colour
Vervis.Content
Vervis.Darcs
Vervis.Delivery
Vervis.Discussion
Vervis.Federation
Vervis.Federation.Auth
Vervis.Federation.Discussion
Vervis.Federation.Offer
Vervis.Federation.Push
Vervis.Federation.Ticket
--Vervis.Federation.Discussion
--Vervis.Federation.Offer
--Vervis.Federation.Push
--Vervis.Federation.Ticket
Vervis.Federation.Util
Vervis.FedURI
Vervis.Field.Key
-- Vervis.Field.Key
Vervis.Field.Person
Vervis.Field.Project
Vervis.Field.Repo
Vervis.Field.Role
Vervis.Field.Sharer
Vervis.Field.Ticket
Vervis.Field.Workflow
--Vervis.Field.Project
--Vervis.Field.Repo
--Vervis.Field.Role
--Vervis.Field.Sharer
--Vervis.Field.Ticket
-- Vervis.Field.Workflow
Vervis.Form.Discussion
Vervis.Form.Group
Vervis.Form.Key
Vervis.Form.Project
Vervis.Form.Repo
Vervis.Form.Role
Vervis.Form.Ticket
Vervis.Form.Workflow
--Vervis.Form.Group
-- Vervis.Form.Key
--Vervis.Form.Project
--Vervis.Form.Repo
--Vervis.Form.Role
--Vervis.Form.Ticket
-- Vervis.Form.Workflow
Vervis.Formatting
Vervis.Foundation
Vervis.Git
Vervis.GraphProxy
Vervis.Handler.Client
Vervis.Handler.Cloth
Vervis.Handler.Common
Vervis.Handler.Deck
Vervis.Handler.Discussion
Vervis.Handler.Git
-- Vervis.Handler.Git
Vervis.Handler.Group
Vervis.Handler.Home
Vervis.Handler.Inbox
Vervis.Handler.Key
Vervis.Handler.Patch
--Vervis.Handler.Inbox
--Vervis.Handler.Key
Vervis.Handler.Loom
Vervis.Handler.Person
Vervis.Handler.Project
Vervis.Handler.Repo
Vervis.Handler.Repo.Darcs
Vervis.Handler.Repo.Git
Vervis.Handler.Role
Vervis.Handler.Sharer
--Vervis.Handler.Repo.Darcs
--Vervis.Handler.Repo.Git
--Vervis.Handler.Role
--Vervis.Handler.Sharer
Vervis.Handler.Ticket
Vervis.Handler.Wiki
Vervis.Handler.Workflow
-- Vervis.Handler.Wiki
-- Vervis.Handler.Workflow
Vervis.Hook
Vervis.KeyFile
Vervis.Migration
@ -193,12 +198,13 @@ library
Vervis.Model.Workflow
Vervis.Paginate
Vervis.Palette
Vervis.Patch
Vervis.Path
Vervis.Query
Vervis.Readme
Vervis.Recipient
Vervis.RemoteActorStore
Vervis.Role
--Vervis.Repo
--Vervis.Role
Vervis.Secure
Vervis.Settings
Vervis.Settings.StaticFiles
@ -211,13 +217,13 @@ library
Vervis.Time
Vervis.Widget
Vervis.Widget.Discussion
Vervis.Widget.Project
Vervis.Widget.Person
--Vervis.Widget.Project
Vervis.Widget.Repo
Vervis.Widget.Role
Vervis.Widget.Sharer
Vervis.Widget.Ticket
Vervis.Widget.Workflow
Vervis.Wiki
--Vervis.Widget.Role
--Vervis.Widget.Ticket
-- Vervis.Widget.Workflow
-- Vervis.Wiki
Vervis.WorkItem
-- other-modules:
default-extensions: TemplateHaskell
@ -244,6 +250,8 @@ library
-- for parsing commands sent over SSH and Darcs patch
-- metadata
, attoparsec
-- For LocalActorBy and LocalStageBy
, barbies
, base
-- for hex display of Darcs patch hashes
, base16-bytestring
@ -399,7 +407,7 @@ library
if flag(dev) || flag(library-only)
cpp-options: -DDEVELOPMENT
ghc-options: -Wall -fwarn-tabs -O0 -Werror=incomplete-patterns
ghc-options: -Wall -fwarn-tabs -O0 -Werror=incomplete-patterns -Werror=missing-fields
else
ghc-options: -Wall -fwarn-tabs -O2