Project routes, handlers and creation UI

This commit is contained in:
Pere Lev 2023-06-26 22:02:54 +03:00
parent 3db602e3bd
commit 372fd35f2c
No known key found for this signature in database
GPG key ID: 5252C5C863E5E57D
31 changed files with 885 additions and 86 deletions

View file

@ -4,3 +4,9 @@ Project
UniqueProjectActor actor
UniqueProjectCreate create
CollabTopicProject
collab CollabId
project ProjectId
UniqueCollabTopicProject collab

View file

@ -173,6 +173,9 @@ verifyResourceAddressed localRecips resource = do
verify (GrantResourceLoom l) = do
routes <- lookup l $ recipLooms localRecips
guard $ routeLoom $ familyLoom routes
verify (GrantResourceProject r) = do
routes <- lookup r $ recipProjects localRecips
guard $ routeProject routes
verifyRemoteAddressed
:: Monad m => [(Host, NonEmpty LocalURI)] -> FedURI -> ExceptT Text m ()
@ -2054,6 +2057,7 @@ actorOutboxItem (LocalActorGroup _) = error "No outbox for Group yet"
actorOutboxItem (LocalActorRepo r) = RepoOutboxItemR r
actorOutboxItem (LocalActorDeck d) = DeckOutboxItemR d
actorOutboxItem (LocalActorLoom l) = LoomOutboxItemR l
actorOutboxItem (LocalActorProject l) = ProjectOutboxItemR l
offerDepC
:: Entity Person

View file

@ -48,6 +48,7 @@ module Vervis.Actor
, RepoRoutes (..)
, DeckRoutes (..)
, LoomRoutes (..)
, ProjectRoutes (..)
, DeckFamilyRoutes (..)
, LoomFamilyRoutes (..)
, RecipientRoutes (..)
@ -138,6 +139,7 @@ data LocalActorBy f
| LocalActorRepo (f Repo)
| LocalActorDeck (f Deck)
| LocalActorLoom (f Loom)
| LocalActorProject (f Project)
deriving (Generic, FunctorB, ConstraintsB)
deriving instance AllBF Eq f LocalActorBy => Eq (LocalActorBy f)
@ -156,6 +158,7 @@ hashLocalActorPure ctx = f
f (LocalActorRepo r) = LocalActorRepo $ encodeKeyHashidPure ctx r
f (LocalActorDeck d) = LocalActorDeck $ encodeKeyHashidPure ctx d
f (LocalActorLoom l) = LocalActorLoom $ encodeKeyHashidPure ctx l
f (LocalActorProject j) = LocalActorProject $ encodeKeyHashidPure ctx j
getHashLocalActor
:: (MonadActor m, StageHashids (ActorEnv m))
@ -180,6 +183,7 @@ unhashLocalActorPure ctx = f
f (LocalActorRepo r) = LocalActorRepo <$> decodeKeyHashidPure ctx r
f (LocalActorDeck d) = LocalActorDeck <$> decodeKeyHashidPure ctx d
f (LocalActorLoom l) = LocalActorLoom <$> decodeKeyHashidPure ctx l
f (LocalActorProject j) = LocalActorProject <$> decodeKeyHashidPure ctx j
unhashLocalActor
:: (MonadActor m, StageHashids (ActorEnv m))
@ -258,6 +262,12 @@ data LoomRoutes = LoomRoutes
}
deriving Eq
data ProjectRoutes = ProjectRoutes
{ routeProject :: Bool
, routeProjectFollowers :: Bool
}
deriving Eq
data DeckFamilyRoutes = DeckFamilyRoutes
{ familyDeck :: DeckRoutes
, familyTickets :: [(KeyHashid TicketDeck, TicketRoutes)]
@ -276,6 +286,7 @@ data RecipientRoutes = RecipientRoutes
, recipRepos :: [(KeyHashid Repo , RepoRoutes)]
, recipDecks :: [(KeyHashid Deck , DeckFamilyRoutes)]
, recipLooms :: [(KeyHashid Loom , LoomFamilyRoutes)]
, recipProjects :: [(KeyHashid Project, ProjectRoutes)]
}
deriving Eq
@ -346,7 +357,6 @@ data Env = forall y. (Typeable y, Yesod y) => Env
, envHashidsContext :: HashidsContext
, envActorKeys :: Maybe (TVar (ActorKey, ActorKey, Bool))
, envDeliveryTheater :: DeliveryTheater URIMode
--, envYesodSite :: y
, envYesodRender :: YesodRender y
, envHttpManager :: Manager
, envFetch :: ActorFetchShare
@ -469,6 +479,7 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
looms <- unhashKeys $ recipLooms recips
for looms $ \ (loomID, (LoomFamilyRoutes loom cloths)) ->
(loomID,) . (loom,) <$> unhashKeys cloths
projects <- unhashKeys $ recipProjects recips
-- Grab local actor sets whose stages are allowed for delivery
let allowStages'
@ -489,6 +500,8 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
filter (allowStages' fst routeDeck LocalActorDeck) decksAndTickets
loomsAndClothsForStages =
filter (allowStages' fst routeLoom LocalActorLoom) loomsAndCloths
projectsForStages =
filter (allowStages' id routeProject LocalActorProject) projects
-- Grab local actors being addressed
let localActorsForSelf = concat
@ -497,6 +510,7 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
, [ LocalActorRepo key | (key, routes) <- repos, routeRepo routes ]
, [ LocalActorDeck key | (key, (routes, _)) <- decksAndTickets, routeDeck routes ]
, [ LocalActorLoom key | (key, (routes, _)) <- loomsAndCloths, routeLoom routes ]
, [ LocalActorProject key | (key, routes) <- projects, routeProject routes ]
]
-- Grab local actors whose followers are going to be delivered to
@ -510,6 +524,8 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
[ key | (key, (routes, _)) <- decksAndTicketsForStages, routeDeckFollowers routes ]
loomIDsForFollowers =
[ key | (key, (routes, _)) <- loomsAndClothsForStages, routeLoomFollowers routes ]
projectIDsForFollowers =
[ key | (key, routes) <- projectsForStages, routeProjectFollowers routes ]
-- Grab tickets and cloths whose followers are going to be delivered to
let ticketSetsForFollowers =
@ -540,6 +556,7 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
, selectActorIDs repoActor repoIDsForFollowers
, selectActorIDs deckActor deckIDsForFollowers
, selectActorIDs loomActor loomIDsForFollowers
, selectActorIDs projectActor projectIDsForFollowers
]
ticketIDs <-
concat <$>
@ -566,6 +583,7 @@ sendToLocalActors authorAndId body requireOwner mauthor maidAuthor recips = do
, selectFollowers LocalActorRepo RepoActor followerSetIDs
, selectFollowers LocalActorDeck DeckActor followerSetIDs
, selectFollowers LocalActorLoom LoomActor followerSetIDs
, selectFollowers LocalActorProject ProjectActor followerSetIDs
]
remotes <- getRemoteFollowers followerSetIDs
return (locals, remotes)

View file

@ -92,6 +92,9 @@ verifyResourceAddressed localRecips resource = do
verify (GrantResourceLoom l) = do
routes <- lookup l $ recipLooms localRecips
guard $ routeLoom $ familyLoom routes
verify (GrantResourceProject r) = do
routes <- lookup r $ recipProjects localRecips
guard $ routeProject routes
verifyRecipientAddressed localRecips recipient = do
recipientHash <- hashGrantRecip recipient
@ -384,6 +387,7 @@ clientInvite now personMeID (ClientMsg maybeCap localRecips remoteRecips fwdHost
Left (GrantResourceRepo r) -> Just $ LocalActorRepo r
Left (GrantResourceDeck d) -> Just $ LocalActorDeck d
Left (GrantResourceLoom l) -> Just $ LocalActorLoom l
Left (GrantResourceProject l) -> Just $ LocalActorProject l
Right _ -> Nothing
, case recipientHash of
Left (GrantRecipPerson p) -> Just $ LocalActorPerson p
@ -395,6 +399,7 @@ clientInvite now personMeID (ClientMsg maybeCap localRecips remoteRecips fwdHost
Left (GrantResourceRepo r) -> Just $ LocalStageRepoFollowers r
Left (GrantResourceDeck d) -> Just $ LocalStageDeckFollowers d
Left (GrantResourceLoom l) -> Just $ LocalStageLoomFollowers l
Left (GrantResourceProject l) -> Just $ LocalStageProjectFollowers l
Right _ -> Nothing
, case recipientHash of
Left (GrantRecipPerson p) -> Just $ LocalStagePersonFollowers p
@ -484,6 +489,7 @@ clientRemove now personMeID (ClientMsg maybeCap localRecips remoteRecips fwdHost
Left (GrantResourceRepo r) -> Just $ LocalActorRepo r
Left (GrantResourceDeck d) -> Just $ LocalActorDeck d
Left (GrantResourceLoom l) -> Just $ LocalActorLoom l
Left (GrantResourceProject l) -> Just $ LocalActorProject l
Right _ -> Nothing
, case recipientHash of
Left (GrantRecipPerson p) -> Just $ LocalActorPerson p
@ -495,6 +501,7 @@ clientRemove now personMeID (ClientMsg maybeCap localRecips remoteRecips fwdHost
Left (GrantResourceRepo r) -> Just $ LocalStageRepoFollowers r
Left (GrantResourceDeck d) -> Just $ LocalStageDeckFollowers d
Left (GrantResourceLoom l) -> Just $ LocalStageLoomFollowers l
Left (GrantResourceProject l) -> Just $ LocalStageProjectFollowers l
Right _ -> Nothing
, case recipientHash of
Left (GrantRecipPerson p) -> Just $ LocalStagePersonFollowers p

View file

@ -0,0 +1,62 @@
{- This file is part of Vervis.
-
- Written in 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Actor.Project
(
)
where
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)
import Data.Foldable
import Data.Text (Text)
import Data.Time.Clock
import Database.Persist
import Yesod.Persist.Core
import qualified Data.Text as T
import Control.Concurrent.Actor
import Network.FedURI
import Yesod.MonadSite
import qualified Web.ActivityPub as AP
import Control.Monad.Trans.Except.Local
import Database.Persist.Local
import Vervis.Actor
import Vervis.Cloth
import Vervis.Data.Discussion
import Vervis.FedURI
import Vervis.Federation.Util
import Vervis.Foundation
import Vervis.Model
import Vervis.Persist.Discussion
import Vervis.Ticket
projectBehavior :: UTCTime -> ProjectId -> VerseExt -> ActE (Text, Act (), Next)
projectBehavior now projectID (Left _verse@(Verse _authorIdMsig body)) =
case AP.activitySpecific $ actbActivity body of
_ -> throwE "Unsupported activity type for Project"
projectBehavior _ _ (Right _) = throwE "ClientMsgs aren't supported for Project"
instance VervisActor Project where
actorBehavior = projectBehavior

View file

@ -110,6 +110,7 @@ import Vervis.Actor.Deck
import Vervis.Actor.Group
import Vervis.Actor.Loom
import Vervis.Actor.Person
import Vervis.Actor.Project
import Vervis.Actor.Repo
import Vervis.Darcs
import Vervis.Data.Actor
@ -130,6 +131,7 @@ import Vervis.Handler.Group
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
@ -349,6 +351,7 @@ makeFoundation appSettings = do
, selectAll LocalActorRepo
, selectAll LocalActorDeck
, selectAll LocalActorLoom
, selectAll LocalActorProject
]
where
selectAll

View file

@ -37,6 +37,7 @@ module Vervis.Client
, createDeck
, createLoom
, createRepo
, createProject
, invite
, remove
)
@ -949,6 +950,27 @@ createRepo senderHash name desc = do
return (Nothing, audience, detail)
createProject
:: (MonadHandler m, HandlerSite m ~ App, MonadSite m, SiteEnv m ~ App)
=> KeyHashid Person
-> Text
-> Text
-> m (Maybe HTML, [Aud URIMode], AP.ActorDetail)
createProject senderHash name desc = do
let audAuthor =
AudLocal [] [LocalStagePersonFollowers senderHash]
audience = [audAuthor]
detail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeProject
, AP.actorUsername = Nothing
, AP.actorName = Just name
, AP.actorSummary = Just desc
}
return (Nothing, audience, detail)
invite
:: PersonId
-> FedURI
@ -1012,6 +1034,8 @@ invite personID uRecipient uResource role = do
AudLocal [LocalActorDeck d] [LocalStageDeckFollowers d]
Left (GrantResourceLoom l) ->
AudLocal [LocalActorLoom l] [LocalStageLoomFollowers l]
Left (GrantResourceProject l) ->
AudLocal [LocalActorProject l] [LocalStageProjectFollowers l]
Right (remoteActor, ObjURI h lu) ->
AudRemote h
[lu]
@ -1093,6 +1117,8 @@ remove personID uRecipient uResource = do
AudLocal [LocalActorDeck d] [LocalStageDeckFollowers d]
Left (GrantResourceLoom l) ->
AudLocal [LocalActorLoom l] [LocalStageLoomFollowers l]
Left (GrantResourceProject l) ->
AudLocal [LocalActorProject l] [LocalStageProjectFollowers l]
Right (remoteActor, ObjURI h lu) ->
AudRemote h
[lu]

View file

@ -80,6 +80,7 @@ parseLocalActivityURI luAct = do
parseOutboxItemRoute (RepoOutboxItemR r i) = Just (LocalActorRepo r, i)
parseOutboxItemRoute (DeckOutboxItemR d i) = Just (LocalActorDeck d, i)
parseOutboxItemRoute (LoomOutboxItemR l i) = Just (LocalActorLoom l, i)
parseOutboxItemRoute (ProjectOutboxItemR r i) = Just (LocalActorProject r, i)
parseOutboxItemRoute _ = Nothing
parseLocalActivityURI'
@ -141,6 +142,7 @@ activityRoute (LocalActorGroup g) = GroupOutboxItemR g
activityRoute (LocalActorRepo r) = RepoOutboxItemR r
activityRoute (LocalActorDeck d) = DeckOutboxItemR d
activityRoute (LocalActorLoom l) = LoomOutboxItemR l
activityRoute (LocalActorProject r) = ProjectOutboxItemR r
stampRoute :: LocalActorBy KeyHashid -> KeyHashid SigKey -> Route App
stampRoute (LocalActorPerson p) = PersonStampR p
@ -148,6 +150,7 @@ stampRoute (LocalActorGroup g) = GroupStampR g
stampRoute (LocalActorRepo r) = RepoStampR r
stampRoute (LocalActorDeck d) = DeckStampR d
stampRoute (LocalActorLoom l) = LoomStampR l
stampRoute (LocalActorProject r) = ProjectStampR r
parseStampRoute
:: Route App -> Maybe (LocalActorBy KeyHashid, KeyHashid SigKey)
@ -156,6 +159,7 @@ parseStampRoute (GroupStampR g i) = Just (LocalActorGroup g, i)
parseStampRoute (RepoStampR r i) = Just (LocalActorRepo r, i)
parseStampRoute (DeckStampR d i) = Just (LocalActorDeck d, i)
parseStampRoute (LoomStampR l i) = Just (LocalActorLoom l, i)
parseStampRoute (ProjectStampR r i) = Just (LocalActorProject r, i)
parseStampRoute _ = Nothing
localActorID :: LocalActorBy Entity -> ActorId
@ -164,6 +168,7 @@ localActorID (LocalActorGroup (Entity _ g)) = groupActor g
localActorID (LocalActorRepo (Entity _ r)) = repoActor r
localActorID (LocalActorDeck (Entity _ d)) = deckActor d
localActorID (LocalActorLoom (Entity _ l)) = loomActor l
localActorID (LocalActorProject (Entity _ r)) = projectActor r
parseFedURIOld
:: ( MonadSite m

View file

@ -85,6 +85,7 @@ import Vervis.Model
parseGrantResource (RepoR r) = Just $ GrantResourceRepo r
parseGrantResource (DeckR d) = Just $ GrantResourceDeck d
parseGrantResource (LoomR l) = Just $ GrantResourceLoom l
parseGrantResource (ProjectR l) = Just $ GrantResourceProject l
parseGrantResource _ = Nothing
data GrantRecipBy f = GrantRecipPerson (f Person)
@ -230,11 +231,6 @@ parseGrant h (AP.Grant object context target mresult mstart mend allows deleg) =
resourceHash
"Grant resource contains invalid hashid"
else pure $ Right lu
where
parseGrantResource (RepoR r) = Just $ GrantResourceRepo r
parseGrantResource (DeckR d) = Just $ GrantResourceDeck d
parseGrantResource (LoomR l) = Just $ GrantResourceLoom l
parseGrantResource _ = Nothing
parseTarget u@(ObjURI h lu) = do
hl <- hostIsLocal h
if hl
@ -277,11 +273,13 @@ grantResourceActorID :: GrantResourceBy Identity -> ActorId
grantResourceActorID (GrantResourceRepo (Identity r)) = repoActor r
grantResourceActorID (GrantResourceDeck (Identity d)) = deckActor d
grantResourceActorID (GrantResourceLoom (Identity l)) = loomActor l
grantResourceActorID (GrantResourceProject (Identity l)) = projectActor l
data GrantResourceBy f
= GrantResourceRepo (f Repo)
| GrantResourceDeck (f Deck)
| GrantResourceLoom (f Loom)
| GrantResourceProject (f Project)
deriving (Generic, FunctorB, TraversableB, ConstraintsB)
deriving instance AllBF Eq f GrantResourceBy => Eq (GrantResourceBy f)
@ -294,6 +292,8 @@ unhashGrantResourcePure ctx = f
GrantResourceDeck <$> decodeKeyHashidPure ctx d
f (GrantResourceLoom l) =
GrantResourceLoom <$> decodeKeyHashidPure ctx l
f (GrantResourceProject l) =
GrantResourceProject <$> decodeKeyHashidPure ctx l
unhashGrantResource resource = do
ctx <- asksSite siteHashidsContext
@ -317,6 +317,8 @@ hashGrantResource (GrantResourceDeck k) =
GrantResourceDeck <$> encodeKeyHashid k
hashGrantResource (GrantResourceLoom k) =
GrantResourceLoom <$> encodeKeyHashid k
hashGrantResource (GrantResourceProject k) =
GrantResourceProject <$> encodeKeyHashid k
hashGrantResource' (GrantResourceRepo k) =
GrantResourceRepo <$> WAP.encodeKeyHashid k
@ -324,6 +326,8 @@ hashGrantResource' (GrantResourceDeck k) =
GrantResourceDeck <$> WAP.encodeKeyHashid k
hashGrantResource' (GrantResourceLoom k) =
GrantResourceLoom <$> WAP.encodeKeyHashid k
hashGrantResource' (GrantResourceProject k) =
GrantResourceProject <$> WAP.encodeKeyHashid k
getGrantResource (GrantResourceRepo k) e =
GrantResourceRepo <$> getEntityE k e
@ -331,6 +335,8 @@ getGrantResource (GrantResourceDeck k) e =
GrantResourceDeck <$> getEntityE k e
getGrantResource (GrantResourceLoom k) e =
GrantResourceLoom <$> getEntityE k e
getGrantResource (GrantResourceProject k) e =
GrantResourceProject <$> getEntityE k e
getGrantResource404 = maybe notFound return <=< getGrantResourceEntity
where
@ -340,8 +346,11 @@ getGrantResource404 = maybe notFound return <=< getGrantResourceEntity
fmap GrantResourceDeck <$> getEntity k
getGrantResourceEntity (GrantResourceLoom k) =
fmap GrantResourceLoom <$> getEntity k
getGrantResourceEntity (GrantResourceProject k) =
fmap GrantResourceProject <$> getEntity k
grantResourceLocalActor :: GrantResourceBy f -> LocalActorBy f
grantResourceLocalActor (GrantResourceRepo r) = LocalActorRepo r
grantResourceLocalActor (GrantResourceDeck d) = LocalActorDeck d
grantResourceLocalActor (GrantResourceLoom l) = LocalActorLoom l
grantResourceLocalActor (GrantResourceProject l) = LocalActorProject l

View file

@ -222,3 +222,4 @@ messageRoute (LocalActorGroup g) = GroupMessageR g
messageRoute (LocalActorRepo r) = RepoMessageR r
messageRoute (LocalActorDeck d) = DeckMessageR d
messageRoute (LocalActorLoom l) = LoomMessageR l
messageRoute (LocalActorProject l) = ProjectMessageR l

View file

@ -16,10 +16,14 @@
module Vervis.Form.Tracker
( NewDeck (..)
, newDeckForm
, NewProject (..)
, newProjectForm
, NewLoom (..)
, newLoomForm
, DeckInvite (..)
, deckInviteForm
, ProjectInvite (..)
, projectInviteForm
--, NewProjectCollab (..)
--, newProjectCollabForm
--, editProjectForm
@ -56,6 +60,16 @@ newDeckForm = renderDivs $ NewDeck
<$> areq textField "Name*" Nothing
<*> areq textField "Description" Nothing
data NewProject = NewProject
{ npName :: Text
, npDesc :: Text
}
newProjectForm :: Form NewProject
newProjectForm = renderDivs $ NewProject
<$> areq textField "Name*" Nothing
<*> areq textField "Description" Nothing
data NewLoom = NewLoom
{ nlName :: Text
, nlDesc :: Text
@ -115,6 +129,38 @@ deckInviteForm deckID = renderDivs $ DeckInvite
l
selectRole = selectField optionsEnum
data ProjectInvite = ProjectInvite
{ jiPerson :: PersonId
, jiRole :: AP.Role
}
projectInviteForm :: ProjectId -> Form ProjectInvite
projectInviteForm projectID = renderDivs $ ProjectInvite
<$> areq selectPerson "Person*" Nothing
<*> areq selectRole "Role*" Nothing
where
selectPerson = selectField $ do
l <- runDB $ E.select $
E.from $ \ (person `E.InnerJoin` actor `E.LeftOuterJoin` (recip `E.InnerJoin` topic)) -> do
E.on $ recip E.^. CollabRecipLocalCollab E.==. topic E.^. CollabTopicProjectCollab E.&&.
topic E.^. CollabTopicProjectProject E.==. E.val projectID
E.on $ person E.^. PersonId E.==. recip E.^. CollabRecipLocalPerson
E.on $ person E.^. PersonActor E.==. actor E.^. ActorId
E.where_ $ E.isNothing $ E.just $ recip E.^. CollabRecipLocalId
return (person, actor)
optionsPairs $
map (\ (Entity pid p, Entity _ a) ->
( T.concat
[ actorName a
, " ~"
, username2text $ personUsername p
]
, pid
)
)
l
selectRole = selectField optionsEnum
{-
editProjectAForm :: SharerId -> Entity Project -> AForm Handler Project
editProjectAForm sid (Entity jid project) = Project

View file

@ -157,6 +157,7 @@ type LoomKeyHashid = KeyHashid Loom
type TicketDeckKeyHashid = KeyHashid TicketDeck
type TicketLoomKeyHashid = KeyHashid TicketLoom
type SigKeyKeyHashid = KeyHashid SigKey
type ProjectKeyHashid = KeyHashid Project
-- This is where we define all of the routes in our application. For a full
-- explanation of the syntax, please see:
@ -982,3 +983,20 @@ instance YesodBreadcrumbs App where
ClothReplyOnR l c _ -> ("Reply", Just $ ClothR l c)
ClothDepR l c p -> (keyHashidText p, Just $ ClothDepsR l c)
ProjectR d -> ("Project $" <> keyHashidText d, Just HomeR)
ProjectInboxR d -> ("Inbox", Just $ ProjectR d)
ProjectOutboxR d -> ("Outbox", Just $ ProjectR d)
ProjectOutboxItemR d i -> (keyHashidText i, Just $ ProjectOutboxR d)
ProjectFollowersR d -> ("Followers", Just $ ProjectR d)
ProjectMessageR d m -> ("Message #" <> keyHashidText m, Just $ ProjectR d)
ProjectNewR -> ("New Project", Just HomeR)
ProjectStampR d k -> ("Stamp #" <> keyHashidText k, Just $ ProjectR d)
ProjectCollabsR d -> ("Collaborators", Just $ ProjectR d)
ProjectInviteR d -> ("Invite", Just $ ProjectR d)
ProjectRemoveR _ _ -> ("", Nothing)

View file

@ -122,7 +122,7 @@ getHomeR = do
where
personalOverview :: Entity Person -> Handler Html
personalOverview (Entity pid _person) = do
(repos, decks, looms) <- runDB $ (,,)
(repos, decks, looms, projects) <- runDB $ (,,,)
<$> (E.select $ E.from $ \ (recip `E.InnerJoin` collab `E.InnerJoin` topic `E.InnerJoin` enable `E.InnerJoin` repo `E.InnerJoin` actor) -> do
E.on $ repo E.^. RepoActor E.==. actor E.^. ActorId
E.on $ topic E.^. CollabTopicRepoRepo E.==. repo E.^. RepoId
@ -153,15 +153,26 @@ getHomeR = do
E.orderBy [E.asc $ loom E.^. LoomId]
return (loom, actor, collab)
)
<*> (E.select $ E.from $ \ (recip `E.InnerJoin` collab `E.InnerJoin` topic `E.InnerJoin` enable `E.InnerJoin` project `E.InnerJoin` actor) -> do
E.on $ project E.^. ProjectActor E.==. actor E.^. ActorId
E.on $ topic E.^. CollabTopicProjectProject E.==. project E.^. ProjectId
E.on $ topic E.^. CollabTopicProjectCollab E.==. enable E.^. CollabEnableCollab
E.on $ recip E.^. CollabRecipLocalCollab E.==. topic E.^. CollabTopicProjectCollab
E.on $ recip E.^. CollabRecipLocalCollab E.==. collab E.^. CollabId
E.where_ $ recip E.^. CollabRecipLocalPerson E.==. E.val pid
E.orderBy [E.asc $ project E.^. ProjectId]
return (project, actor, collab)
)
hashRepo <- getEncodeKeyHashid
hashDeck <- getEncodeKeyHashid
hashLoom <- getEncodeKeyHashid
hashProject <- getEncodeKeyHashid
defaultLayout $(widgetFile "personal-overview")
getBrowseR :: Handler Html
getBrowseR = do
(people, groups, repos, decks, looms) <- runDB $
(,,,,)
(people, groups, repos, decks, looms, projects) <- 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]
@ -187,6 +198,11 @@ getBrowseR = do
E.orderBy [E.asc $ loom E.^. LoomId]
return (loom, actor)
)
<*> (E.select $ E.from $ \ (project `E.InnerJoin` actor) -> do
E.on $ project E.^. ProjectActor E.==. actor E.^. ActorId
E.orderBy [E.asc $ project E.^. ProjectId]
return (project, actor)
)
{-
now <- liftIO getCurrentTime
repoRows <- forM repos $
@ -209,6 +225,7 @@ getBrowseR = do
hashRepo <- getEncodeKeyHashid
hashDeck <- getEncodeKeyHashid
hashLoom <- getEncodeKeyHashid
hashProject <- getEncodeKeyHashid
defaultLayout $ do
setTitle "Welcome to Vervis!"
$(widgetFile "browse")

View file

@ -0,0 +1,332 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
module Vervis.Handler.Project
( getProjectR
, getProjectInboxR
, postProjectInboxR
, getProjectOutboxR
, getProjectOutboxItemR
, getProjectFollowersR
, getProjectMessageR
, getProjectNewR
, postProjectNewR
, getProjectStampR
, getProjectCollabsR
, getProjectInviteR
, postProjectInviteR
, postProjectRemoveR
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe
import Data.Aeson
import Data.Bitraversable
import Data.ByteString (ByteString)
import Data.Default.Class
import Data.Foldable
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Time.Clock
import Data.Traversable
import Database.Persist
import Network.HTTP.Types.Method
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, runFormGet)
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.Form.Local
import Yesod.Persist.Local
import Vervis.Access
import Vervis.API
import Vervis.Federation.Auth
import Vervis.Federation.Collab
import Vervis.Federation.Discussion
import Vervis.Federation.Offer
import Vervis.Federation.Ticket
import Vervis.FedURI
import Vervis.Form.Ticket
import Vervis.Form.Tracker
import Vervis.Foundation
import Vervis.Model
import Vervis.Paginate
import Vervis.Persist.Actor
import Vervis.Persist.Collab
import Vervis.Recipient
import Vervis.Settings
import Vervis.Ticket
import Vervis.TicketFilter
import Vervis.Time
import Vervis.Web.Actor
import Vervis.Widget
import Vervis.Widget.Person
import Vervis.Widget.Ticket
import Vervis.Widget.Tracker
import qualified Vervis.Client as C
getProjectR :: KeyHashid Project -> Handler TypedContent
getProjectR projectHash = do
projectID <- decodeKeyHashid404 projectHash
(project, actor, sigKeyIDs) <- runDB $ do
d <- get404 projectID
let aid = projectActor d
a <- getJust aid
sigKeys <- selectKeysList [SigKeyActor ==. aid] [Asc SigKeyId]
return (d, a, sigKeys)
encodeRouteLocal <- getEncodeRouteLocal
hashSigKey <- getEncodeKeyHashid
perActor <- asksSite $ appPerActorKeys . appSettings
let projectAP = AP.Project
{ AP.projectActor = AP.Actor
{ AP.actorLocal = AP.ActorLocal
{ AP.actorId = encodeRouteLocal $ ProjectR projectHash
, AP.actorInbox = encodeRouteLocal $ ProjectInboxR projectHash
, AP.actorOutbox =
Just $ encodeRouteLocal $ ProjectOutboxR projectHash
, AP.actorFollowers =
Just $ encodeRouteLocal $ ProjectFollowersR projectHash
, AP.actorFollowing = Nothing
, AP.actorPublicKeys =
map (Left . encodeRouteLocal) $
if perActor
then map (ProjectStampR projectHash . hashSigKey) sigKeyIDs
else [ActorKey1R, ActorKey2R]
, AP.actorSshKeys = []
}
, AP.actorDetail = AP.ActorDetail
{ AP.actorType = AP.ActorTypeProject
, AP.actorUsername = Nothing
, AP.actorName = Just $ actorName actor
, AP.actorSummary = Just $ actorDesc actor
}
}
, AP.projectTracker = Nothing
, AP.projectChildren = []
, AP.projectParents = []
, AP.projectComponents = []
}
provideHtmlAndAP projectAP $ redirectToPrettyJSON here
where
here = ProjectR projectHash
getProjectInboxR :: KeyHashid Project -> Handler TypedContent
getProjectInboxR = getInbox ProjectInboxR projectActor
postProjectInboxR :: KeyHashid Project -> Handler ()
postProjectInboxR projectHash = do
projectID <- decodeKeyHashid404 projectHash
postInbox $ LocalActorProject projectID
getProjectOutboxR :: KeyHashid Project -> Handler TypedContent
getProjectOutboxR = getOutbox ProjectOutboxR ProjectOutboxItemR projectActor
getProjectOutboxItemR
:: KeyHashid Project -> KeyHashid OutboxItem -> Handler TypedContent
getProjectOutboxItemR = getOutboxItem ProjectOutboxItemR projectActor
getProjectFollowersR :: KeyHashid Project -> Handler TypedContent
getProjectFollowersR = getActorFollowersCollection ProjectFollowersR projectActor
getProjectMessageR :: KeyHashid Project -> KeyHashid LocalMessage -> Handler Html
getProjectMessageR _ _ = notFound
getProjectNewR :: Handler Html
getProjectNewR = do
((_result, widget), enctype) <- runFormPost newProjectForm
defaultLayout $(widgetFile "project/new")
postProjectNewR :: Handler Html
postProjectNewR = do
NewProject name desc <- runFormPostRedirect ProjectNewR newProjectForm
personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID
(maybeSummary, audience, detail) <- C.createProject personHash name desc
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput Nothing maybeSummary audience $ AP.CreateActivity $ AP.Create (AP.CreateProject detail Nothing) Nothing
result <-
runExceptT $
handleViaActor personID Nothing localRecips remoteRecips fwdHosts action
case result of
Left e -> do
setMessage $ toHtml e
redirect ProjectNewR
Right createID -> do
maybeProjectID <- runDB $ getKeyBy $ UniqueProjectCreate createID
case maybeProjectID of
Nothing -> error "Can't find the newly created project"
Just projectID -> do
projectHash <- encodeKeyHashid projectID
setMessage "New project created"
redirect $ ProjectR projectHash
getProjectStampR :: KeyHashid Project -> KeyHashid SigKey -> Handler TypedContent
getProjectStampR = servePerActorKey projectActor LocalActorProject
getProjectCollabsR :: KeyHashid Project -> Handler Html
getProjectCollabsR projectHash = do
projectID <- decodeKeyHashid404 projectHash
(project, actor, collabs, invites, joins) <- runDB $ do
project <- get404 projectID
actor <- getJust $ projectActor project
collabs <- do
grants <-
getTopicGrants CollabTopicProjectCollab CollabTopicProjectProject projectID
for grants $ \ (role, actor, ct, time) ->
(,role,ct,time) <$> getPersonWidgetInfo actor
invites <- do
invites' <-
getTopicInvites CollabTopicProjectCollab CollabTopicProjectProject projectID
for invites' $ \ (inviter, recip, time, role) -> (,,,)
<$> (getPersonWidgetInfo =<< bitraverse grabPerson pure inviter)
<*> getPersonWidgetInfo recip
<*> pure time
<*> pure role
joins <- do
joins' <-
getTopicJoins CollabTopicProjectCollab CollabTopicProjectProject projectID
for joins' $ \ (recip, time, role) ->
(,time,role) <$> getPersonWidgetInfo recip
return (project, actor, collabs, invites, joins)
defaultLayout $(widgetFile "project/collab/list")
where
grabPerson actorID = do
actorByKey <- getLocalActor actorID
case actorByKey of
LocalActorPerson personID -> return personID
_ -> error "Surprise, local inviter actor isn't a Person"
getProjectInviteR :: KeyHashid Project -> Handler Html
getProjectInviteR projectHash = do
projectID <- decodeKeyHashid404 projectHash
((_result, widget), enctype) <- runFormPost $ projectInviteForm projectID
defaultLayout $(widgetFile "project/collab/new")
postProjectInviteR :: KeyHashid Project -> Handler Html
postProjectInviteR projectHash = do
projectID <- decodeKeyHashid404 projectHash
ProjectInvite recipPersonID role <-
runFormPostRedirect (ProjectInviteR projectHash) $ projectInviteForm projectID
personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID
recipPersonHash <- encodeKeyHashid recipPersonID
encodeRouteHome <- getEncodeRouteHome
result <- runExceptT $ do
(maybeSummary, audience, invite) <- do
let uRecipient = encodeRouteHome $ PersonR recipPersonHash
uResource = encodeRouteHome $ ProjectR projectHash
C.invite personID uRecipient uResource role
grantID <- do
maybeItem <- lift $ runDB $ getGrant CollabTopicProjectCollab CollabTopicProjectProject projectID personID
fromMaybeE maybeItem "You need to be a collaborator in the Project to invite people"
grantHash <- encodeKeyHashid grantID
let uCap = encodeRouteHome $ ProjectOutboxItemR projectHash grantHash
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput (Just uCap) maybeSummary audience $ AP.InviteActivity invite
let cap =
Left (LocalActorProject projectID, LocalActorProject projectHash, grantID)
handleViaActor
personID (Just cap) localRecips remoteRecips fwdHosts action
case result of
Left e -> do
setMessage $ toHtml e
redirect $ ProjectInviteR projectHash
Right inviteID -> do
setMessage "Invite sent"
redirect $ ProjectCollabsR projectHash
postProjectRemoveR :: KeyHashid Project -> CollabTopicProjectId -> Handler Html
postProjectRemoveR projectHash ctID = do
projectID <- decodeKeyHashid404 projectHash
personEntity@(Entity personID person) <- requireAuth
personHash <- encodeKeyHashid personID
encodeRouteHome <- getEncodeRouteHome
result <- runExceptT $ do
mpidOrU <- lift $ runDB $ runMaybeT $ do
CollabTopicProject collabID projectID' <- MaybeT $ get ctID
guard $ projectID' == projectID
_ <- MaybeT $ getBy $ UniqueCollabEnable collabID
member <-
Left <$> MaybeT (getValBy $ UniqueCollabRecipLocal collabID) <|>
Right <$> MaybeT (getValBy $ UniqueCollabRecipRemote collabID)
lift $
bitraverse
(pure . collabRecipLocalPerson)
(getRemoteActorURI <=< getJust . collabRecipRemoteActor)
member
pidOrU <- maybe notFound pure mpidOrU
(maybeSummary, audience, remove) <- do
uRecipient <-
case pidOrU of
Left pid -> encodeRouteHome . PersonR <$> encodeKeyHashid pid
Right u -> pure u
let uResource = encodeRouteHome $ ProjectR projectHash
C.remove personID uRecipient uResource
grantID <- do
maybeItem <- lift $ runDB $ getGrant CollabTopicProjectCollab CollabTopicProjectProject projectID personID
fromMaybeE maybeItem "You need to be a collaborator in the Project to remove people"
grantHash <- encodeKeyHashid grantID
let uCap = encodeRouteHome $ ProjectOutboxItemR projectHash grantHash
(localRecips, remoteRecips, fwdHosts, action) <-
C.makeServerInput (Just uCap) maybeSummary audience $ AP.RemoveActivity remove
let cap =
Left (LocalActorProject projectID, LocalActorProject projectHash, grantID)
handleViaActor
personID (Just cap) localRecips remoteRecips fwdHosts action
case result of
Left e -> do
setMessage $ toHtml e
Right removeID ->
setMessage "Remove sent"
redirect $ ProjectCollabsR projectHash

View file

@ -100,6 +100,10 @@ instance Hashable LoomId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
instance Hashable ProjectId where
hashWithSalt salt = hashWithSalt salt . fromSqlKey
hash = hash . fromSqlKey
{-
instance PersistEntityGraph Ticket TicketDependency where
sourceParam = ticketDependencyParent

View file

@ -81,14 +81,16 @@ getLocalActorEnt actorID = do
mr <- getBy $ UniqueRepoActor actorID
md <- getBy $ UniqueDeckActor actorID
ml <- getBy $ UniqueLoomActor actorID
mj <- getBy $ UniqueProjectActor actorID
return $
case (mp, mg, mr, md, ml) of
(Nothing, Nothing, Nothing, Nothing, Nothing) -> error "Unused ActorId"
(Just p, Nothing, Nothing, Nothing, Nothing) -> LocalActorPerson p
(Nothing, Just g, Nothing, Nothing, Nothing) -> LocalActorGroup g
(Nothing, Nothing, Just r, Nothing, Nothing) -> LocalActorRepo r
(Nothing, Nothing, Nothing, Just d, Nothing) -> LocalActorDeck d
(Nothing, Nothing, Nothing, Nothing, Just l) -> LocalActorLoom l
case (mp, mg, mr, md, ml, mj) of
(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) -> error "Unused ActorId"
(Just p, Nothing, Nothing, Nothing, Nothing, Nothing) -> LocalActorPerson p
(Nothing, Just g, Nothing, Nothing, Nothing, Nothing) -> LocalActorGroup g
(Nothing, Nothing, Just r, Nothing, Nothing, Nothing) -> LocalActorRepo r
(Nothing, Nothing, Nothing, Just d, Nothing, Nothing) -> LocalActorDeck d
(Nothing, Nothing, Nothing, Nothing, Just l, Nothing) -> LocalActorLoom l
(Nothing, Nothing, Nothing, Nothing, Nothing, Just j) -> LocalActorProject j
_ -> error "Multi-usage of an ActorId"
getLocalActorEntity
@ -105,6 +107,8 @@ getLocalActorEntity (LocalActorDeck d) =
fmap (LocalActorDeck . Entity d) <$> get d
getLocalActorEntity (LocalActorLoom l) =
fmap (LocalActorLoom . Entity l) <$> get l
getLocalActorEntity (LocalActorProject r) =
fmap (LocalActorProject . Entity r) <$> get r
verifyLocalActivityExistsInDB
:: MonadIO m

View file

@ -63,15 +63,18 @@ getCollabTopic collabID = do
maybeRepo <- getValBy $ UniqueCollabTopicRepo collabID
maybeDeck <- getValBy $ UniqueCollabTopicDeck collabID
maybeLoom <- getValBy $ UniqueCollabTopicLoom collabID
maybeProject <- getValBy $ UniqueCollabTopicProject collabID
return $
case (maybeRepo, maybeDeck, maybeLoom) of
(Nothing, Nothing, Nothing) -> error "Found Collab without topic"
(Just r, Nothing, Nothing) ->
case (maybeRepo, maybeDeck, maybeLoom, maybeProject) of
(Nothing, Nothing, Nothing, Nothing) -> error "Found Collab without topic"
(Just r, Nothing, Nothing, Nothing) ->
GrantResourceRepo $ collabTopicRepoRepo r
(Nothing, Just d, Nothing) ->
(Nothing, Just d, Nothing, Nothing) ->
GrantResourceDeck $ collabTopicDeckDeck d
(Nothing, Nothing, Just l) ->
(Nothing, Nothing, Just l, Nothing) ->
GrantResourceLoom $ collabTopicLoomLoom l
(Nothing, Nothing, Nothing, Just l) ->
GrantResourceProject $ collabTopicProjectProject l
_ -> error "Found Collab with multiple topics"
getCollabTopic'
@ -80,15 +83,18 @@ getCollabTopic' collabID = do
maybeRepo <- getBy $ UniqueCollabTopicRepo collabID
maybeDeck <- getBy $ UniqueCollabTopicDeck collabID
maybeLoom <- getBy $ UniqueCollabTopicLoom collabID
maybeProject <- getBy $ UniqueCollabTopicProject collabID
return $
case (maybeRepo, maybeDeck, maybeLoom) of
(Nothing, Nothing, Nothing) -> error "Found Collab without topic"
(Just (Entity k r), Nothing, Nothing) ->
case (maybeRepo, maybeDeck, maybeLoom, maybeProject) of
(Nothing, Nothing, Nothing, Nothing) -> error "Found Collab without topic"
(Just (Entity k r), Nothing, Nothing, Nothing) ->
(delete k, GrantResourceRepo $ collabTopicRepoRepo r)
(Nothing, Just (Entity k d), Nothing) ->
(Nothing, Just (Entity k d), Nothing, Nothing) ->
(delete k, GrantResourceDeck $ collabTopicDeckDeck d)
(Nothing, Nothing, Just (Entity k l)) ->
(Nothing, Nothing, Just (Entity k l), Nothing) ->
(delete k, GrantResourceLoom $ collabTopicLoomLoom l)
(Nothing, Nothing, Nothing, Just (Entity k l)) ->
(delete k, GrantResourceProject $ collabTopicProjectProject l)
_ -> error "Found Collab with multiple topics"
getGrantRecip (GrantRecipPerson k) e = GrantRecipPerson <$> getEntityE k e
@ -284,19 +290,7 @@ verifyCapability (capActor, capItem) actor resource requiredRole = do
throwE "Collab recipient is someone else"
-- Find the local topic, on which this Collab gives access
topic <- lift $ do
maybeRepo <- getValBy $ UniqueCollabTopicRepo collabID
maybeDeck <- getValBy $ UniqueCollabTopicDeck collabID
maybeLoom <- getValBy $ UniqueCollabTopicLoom collabID
case (maybeRepo, maybeDeck, maybeLoom) of
(Nothing, Nothing, Nothing) -> error "Collab without topic"
(Just r, Nothing, Nothing) ->
return $ GrantResourceRepo $ collabTopicRepoRepo r
(Nothing, Just d, Nothing) ->
return $ GrantResourceDeck $ collabTopicDeckDeck d
(Nothing, Nothing, Just l) ->
return $ GrantResourceLoom $ collabTopicLoomLoom l
_ -> error "Collab with multiple topics"
topic <- lift $ getCollabTopic collabID
-- Verify that topic is indeed the sender of the Grant
unless (grantResourceLocalActor topic == capActor) $

View file

@ -1,6 +1,7 @@
{- This file is part of Vervis.
-
- Written in 2016, 2019, 2020, 2022 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2016, 2019, 2020, 2022, 2023
- by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -92,6 +93,9 @@ getLocalAuthor lmid aid name = do
LocalActorLoom loomID -> do
loomHash <- encodeKeyHashid loomID
return $ "+" <> keyHashidText loomHash
LocalActorProject projectID -> do
projectHash <- encodeKeyHashid projectID
return $ "$" <> keyHashidText projectHash
return $ MessageTreeNodeLocal lmid authorByKey code name
getAllMessages :: AppDB DiscussionId -> Handler [MessageTreeNode]

View file

@ -69,6 +69,7 @@ module Vervis.Recipient
, RepoRoutes (..)
, DeckRoutes (..)
, LoomRoutes (..)
, ProjectRoutes (..)
, DeckFamilyRoutes (..)
, LoomFamilyRoutes (..)
, RecipientRoutes (..)
@ -192,6 +193,7 @@ 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 (ProjectR jkhid) = Just $ LocalActorProject jkhid
parseLocalActor _ = Nothing
renderLocalActor :: LocalActor -> Route App
@ -200,6 +202,7 @@ renderLocalActor (LocalActorGroup gkhid) = GroupR gkhid
renderLocalActor (LocalActorRepo rkhid) = RepoR rkhid
renderLocalActor (LocalActorDeck dkhid) = DeckR dkhid
renderLocalActor (LocalActorLoom lkhid) = LoomR lkhid
renderLocalActor (LocalActorProject jkhid) = ProjectR jkhid
data LocalStageBy f
= LocalStagePersonFollowers (f Person)
@ -213,6 +216,8 @@ data LocalStageBy f
| LocalStageLoomFollowers (f Loom)
| LocalStageClothFollowers (f Loom) (f TicketLoom)
| LocalStageProjectFollowers (f Project)
deriving (Generic, FunctorB, ConstraintsB)
deriving instance AllBF Eq f LocalStageBy => Eq (LocalStageBy f)
@ -235,6 +240,8 @@ parseLocalStage (LoomFollowersR lkhid) =
Just $ LocalStageLoomFollowers lkhid
parseLocalStage (ClothFollowersR lkhid ltkhid) =
Just $ LocalStageClothFollowers lkhid ltkhid
parseLocalStage (ProjectFollowersR jkhid) =
Just $ LocalStageProjectFollowers jkhid
parseLocalStage _ = Nothing
renderLocalStage :: LocalStage -> Route App
@ -252,6 +259,8 @@ renderLocalStage (LocalStageLoomFollowers lkhid) =
LoomFollowersR lkhid
renderLocalStage (LocalStageClothFollowers lkhid ltkhid) =
ClothFollowersR lkhid ltkhid
renderLocalStage (LocalStageProjectFollowers jkhid) =
ProjectFollowersR jkhid
parseLocalRecipient :: Route App -> Maybe (Either LocalActor LocalStage)
parseLocalRecipient r =
@ -263,6 +272,7 @@ localActorFollowers (LocalActorGroup g) = LocalStageGroupFollowers g
localActorFollowers (LocalActorRepo r) = LocalStageRepoFollowers r
localActorFollowers (LocalActorDeck d) = LocalStageDeckFollowers d
localActorFollowers (LocalActorLoom l) = LocalStageLoomFollowers l
localActorFollowers (LocalActorProject j) = LocalStageProjectFollowers j
-------------------------------------------------------------------------------
-- Converting between KeyHashid, Key, Identity and Entity
@ -327,6 +337,8 @@ hashLocalStagePure ctx = f
LocalStageClothFollowers
(encodeKeyHashidPure ctx l)
(encodeKeyHashidPure ctx c)
f (LocalStageProjectFollowers j) =
LocalStageProjectFollowers $ encodeKeyHashidPure ctx j
getHashLocalStage
:: (MonadSite m, YesodHashids (SiteEnv m))
@ -364,6 +376,8 @@ unhashLocalStagePure ctx = f
LocalStageClothFollowers
<$> decodeKeyHashidPure ctx l
<*> decodeKeyHashidPure ctx c
f (LocalStageProjectFollowers j) =
LocalStageProjectFollowers <$> decodeKeyHashidPure ctx j
unhashLocalStage
:: (MonadSite m, YesodHashids (SiteEnv m))
@ -405,6 +419,7 @@ getLocalActorID (LocalActorGroup g) = fmap groupActor <$> get g
getLocalActorID (LocalActorRepo r) = fmap repoActor <$> get r
getLocalActorID (LocalActorDeck d) = fmap deckActor <$> get d
getLocalActorID (LocalActorLoom l) = fmap loomActor <$> get l
getLocalActorID (LocalActorProject j) = fmap projectActor <$> get j
-------------------------------------------------------------------------------
-- Intermediate recipient types
@ -428,6 +443,8 @@ data LeafDeck = LeafDeck | LeafDeckFollowers deriving (Eq, Ord)
data LeafLoom = LeafLoom | LeafLoomFollowers deriving (Eq, Ord)
data LeafProject = LeafProject | LeafProjectFollowers deriving (Eq, Ord)
data PieceDeck
= PieceDeck LeafDeck
| PieceTicket (KeyHashid TicketDeck) LeafTicket
@ -444,6 +461,7 @@ data LocalRecipient
| RecipRepo (KeyHashid Repo) LeafRepo
| RecipDeck (KeyHashid Deck) PieceDeck
| RecipLoom (KeyHashid Loom) PieceLoom
| RecipProject (KeyHashid Project) LeafProject
deriving (Eq, Ord)
recipientFromActor :: LocalActor -> LocalRecipient
@ -457,6 +475,8 @@ recipientFromActor (LocalActorDeck dkhid) =
RecipDeck dkhid $ PieceDeck LeafDeck
recipientFromActor (LocalActorLoom lkhid) =
RecipLoom lkhid $ PieceLoom LeafLoom
recipientFromActor (LocalActorProject jkhid) =
RecipProject jkhid LeafProject
recipientFromStage :: LocalStage -> LocalRecipient
recipientFromStage (LocalStagePersonFollowers pkhid) =
@ -473,6 +493,8 @@ recipientFromStage (LocalStageLoomFollowers lkhid) =
RecipLoom lkhid $ PieceLoom LeafLoomFollowers
recipientFromStage (LocalStageClothFollowers lkhid ltkhid) =
RecipLoom lkhid $ PieceCloth ltkhid LeafClothFollowers
recipientFromStage (LocalStageProjectFollowers jkhid) =
RecipProject jkhid LeafProjectFollowers
-------------------------------------------------------------------------------
-- Recipient set types
@ -493,19 +515,22 @@ groupLocalRecipients = organize . partitionByActor
, [(KeyHashid Repo, LeafRepo)]
, [(KeyHashid Deck, PieceDeck)]
, [(KeyHashid Loom, PieceLoom)]
, [(KeyHashid Project, LeafProject)]
)
partitionByActor = foldl' f ([], [], [], [], [])
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)
f (p, g, r, d, l, j) (RecipPerson pkhid pleaf) =
((pkhid, pleaf) : p, g, r, d, l, j)
f (p, g, r, d, l, j) (RecipGroup gkhid gleaf) =
(p, (gkhid, gleaf) : g, r, d, l, j)
f (p, g, r, d, l, j) (RecipRepo rkhid rleaf) =
(p, g, (rkhid, rleaf) : r, d, l, j)
f (p, g, r, d, l, j) (RecipDeck dkhid dpiece) =
(p, g, r, (dkhid, dpiece) : d, l, j)
f (p, g, r, d, l, j) (RecipLoom lkhid lpiece) =
(p, g, r, d, (lkhid, lpiece) : l, j)
f (p, g, r, d, l, j) (RecipProject jkhid jleaf) =
(p, g, r, d, l, (jkhid, jleaf) : j)
organize
:: ( [(KeyHashid Person, LeafPerson)]
@ -513,9 +538,10 @@ groupLocalRecipients = organize . partitionByActor
, [(KeyHashid Repo, LeafRepo)]
, [(KeyHashid Deck, PieceDeck)]
, [(KeyHashid Loom, PieceLoom)]
, [(KeyHashid Project, LeafProject)]
)
-> RecipientRoutes
organize (p, g, r, d, l) = RecipientRoutes
organize (p, g, r, d, l, j) = RecipientRoutes
{ recipPeople =
map (second $ foldr orLP $ PersonRoutes False False) $ groupByKeySort p
, recipGroups =
@ -544,6 +570,8 @@ groupLocalRecipients = organize . partitionByActor
. partitionEithers . NE.toList . NE.map pl2either
) $
groupByKeySort l
, recipProjects =
map (second $ foldr orLJ $ ProjectRoutes False False) $ groupByKeySort j
}
where
groupByKey :: (Foldable f, Eq a) => f (a, b) -> [(a, NonEmpty b)]
@ -585,6 +613,11 @@ groupLocalRecipients = organize . partitionByActor
orLC _ cr@(ClothRoutes True) = cr
orLC LeafClothFollowers cr@(ClothRoutes _) = cr { routeClothFollowers = True }
orLJ :: LeafProject -> ProjectRoutes -> ProjectRoutes
orLJ _ rr@(ProjectRoutes True True) = rr
orLJ LeafProject rr@(ProjectRoutes _ _) = rr { routeProject = True }
orLJ LeafProjectFollowers rr@(ProjectRoutes _ _) = rr { routeProjectFollowers = True }
pd2either :: PieceDeck -> Either LeafDeck (KeyHashid TicketDeck, LeafTicket)
pd2either (PieceDeck ld) = Left ld
pd2either (PieceTicket ltkhid lt) = Right (ltkhid, lt)
@ -622,6 +655,7 @@ localRecipSieve' sieve allowPeople allowOthers routes = RecipientRoutes
, recipRepos = applySieve' applyRepo recipRepos
, recipDecks = applySieve' applyDeck recipDecks
, recipLooms = applySieve' applyLoom recipLooms
, recipProjects = applySieve' applyProject recipProjects
}
where
applySieve
@ -725,6 +759,17 @@ localRecipSieve' sieve allowPeople allowOthers routes = RecipientRoutes
then Nothing
else Just (lkhid, LoomFamilyRoutes loom cloths)
applyProject _ (This _) = Nothing
applyProject rkhid (That r) =
if allowOthers && routeProject r
then Just (rkhid, ProjectRoutes True False)
else Nothing
applyProject rkhid (These (ProjectRoutes r' rf') (ProjectRoutes r rf)) =
let merged = ProjectRoutes (r && (r' || allowOthers)) (rf && rf')
in if merged == ProjectRoutes False False
then Nothing
else Just (rkhid, merged)
actorIsAddressed :: RecipientRoutes -> LocalActor -> Bool
actorIsAddressed recips = isJust . verify
where
@ -743,6 +788,9 @@ actorIsAddressed recips = isJust . verify
verify (LocalActorLoom l) = do
routes <- lookup l $ recipLooms recips
guard $ routeLoom $ familyLoom routes
verify (LocalActorProject j) = do
routes <- lookup j $ recipProjects recips
guard $ routeProject routes
data ParsedAudience u = ParsedAudience
{ paudLocalRecips :: RecipientRoutes

View file

@ -378,6 +378,8 @@ getLocalActors actorIDs = do
selectKeysList [DeckActor <-. actorIDs] []
, map LocalActorLoom <$>
selectKeysList [LoomActor <-. actorIDs] []
, map LocalActorProject <$>
selectKeysList [ProjectActor <-. actorIDs] []
]
case compare (length localActors) (length actorIDs) of
LT -> error "Found actor ID not used by any specific actor"

View file

@ -1,6 +1,6 @@
{- This file is part of Vervis.
-
- Written in 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
- Written in 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
-
- Copying is an act of love. Please copy, reuse and share.
-
@ -16,6 +16,7 @@
module Vervis.Widget.Tracker
( deckNavW
, loomNavW
, projectNavW
)
where
@ -38,3 +39,8 @@ loomNavW (Entity loomID loom) actor = do
loomHash <- encodeKeyHashid loomID
hashRepo <- getEncodeKeyHashid
$(widgetFile "loom/widget/nav")
projectNavW :: Entity Project -> Actor -> Widget
projectNavW (Entity projectID project) actor = do
projectHash <- encodeKeyHashid projectID
$(widgetFile "project/widget/nav")

View file

@ -1715,6 +1715,7 @@ data CreateObject u
| CreateTicketTracker ActorDetail (Maybe (Authority u, ActorLocal u))
| CreateRepository ActorDetail VersionControlSystem (Maybe (Authority u, ActorLocal u))
| CreatePatchTracker ActorDetail (NonEmpty (ObjURI u)) (Maybe (Authority u, ActorLocal u))
| CreateProject ActorDetail (Maybe (Authority u, ActorLocal u))
parseCreateObject :: UriMode u => Object -> Parser (CreateObject u)
parseCreateObject o
@ -1737,6 +1738,11 @@ parseCreateObject o
repos <- o .:*+ "tracksPatchesFor"
ml <- parseActorLocal o
return $ CreatePatchTracker d repos ml
<|> do d <- parseActorDetail o
unless (actorType d == ActorTypeProject) $
fail "type isn't Project"
ml <- parseActorLocal o
return $ CreateProject d ml
encodeCreateObject :: UriMode u => CreateObject u -> Series
encodeCreateObject (CreateNote h note) = toSeries h note
@ -1751,6 +1757,8 @@ encodeCreateObject (CreatePatchTracker d repos ml)
= encodeActorDetail d
<> "tracksPatchesFor" .=*+ repos
<> maybe mempty (uncurry encodeActorLocal) ml
encodeCreateObject (CreateProject d ml) =
encodeActorDetail d <> maybe mempty (uncurry encodeActorLocal) ml
data Create u = Create
{ createObject :: CreateObject u
@ -1770,6 +1778,7 @@ parseCreate o a luActor = do
CreateTicketTracker _ _ -> return ()
CreateRepository _ _ _ -> return ()
CreatePatchTracker _ _ _ -> return ()
CreateProject _ _ -> return ()
Create obj <$> o .:? "target"
encodeCreate :: UriMode u => Create u -> Series

View file

@ -1,6 +1,7 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2018, 2019, 2022 by fr33domlover <fr33domlover@riseup.net>.
$# Written in 2016, 2018, 2019, 2022, 2023
$# by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
@ -96,3 +97,12 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<li>
<a href=@{LoomR $ hashLoom loomID}>
+#{keyHashidText $ hashLoom loomID} #{actorName actor}
<h2>Projects
<ul>
$forall (Entity projectID _, Entity _ actor) <- projects
<li>
<a href=@{ProjectR $ hashProject projectID}>
$#{keyHashidText $ hashProject projectID} #{actorName actor}
$# #{abcde}

View file

@ -33,6 +33,9 @@ $# <http://creativecommons.org/publicdomain/zero/1.0/>.
<li>
<a href=@{LoomNewR}>
Create a new patch tracker
<li>
<a href=@{ProjectNewR}>
Create a new project
<li>
<a href=@{PublishOfferMergeR}>
Open a merge request
@ -85,3 +88,14 @@ $# Comment on a ticket or merge request
]
<a href=@{LoomR $ hashLoom loomID}>
+#{keyHashidText $ hashLoom loomID} #{actorName actor}
<h2>Your projects
<ul>
$forall (Entity projectID _, Entity _ actor, Entity _ (Collab role)) <- projects
<li>
[
#{show role}
]
<a href=@{ProjectR $ hashProject projectID}>
$#{keyHashidText $ hashProject projectID} #{actorName actor}

View file

@ -0,0 +1,59 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
^{projectNavW (Entity projectID project) actor}
<h2>Collaborators
<table>
<tr>
<th>Role
<th>Collaborator
<th>Since
$forall (person, role, ctID, since) <- collabs
<tr>
<td>#{show role}
<td>^{personLinkFedW person}
<td>#{showDate since}
<td>^{buttonW POST "Remove" (ProjectRemoveR projectHash ctID)}
<h2>Invites
<table>
<tr>
<th>Inviter
<th>Invitee
<th>Role
<th>Time
$forall (inviter, invitee, time, role) <- invites
<tr>
<td>^{personLinkFedW inviter}
<td>^{personLinkFedW invitee}
<td>#{show role}
<td>#{showDate time}
<a href=@{ProjectInviteR projectHash}>Invite…
<h2>Joins
<table>
<tr>
<th>Joiner
<th>Role
<th>Time
$forall (joiner, time, role) <- joins
<tr>
<td>^{personLinkFedW joiner}
<td>#{show role}
<td>#{showDate time}

View file

@ -0,0 +1,18 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 2023 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<form method=POST action=@{ProjectInviteR projectHash} enctype=#{enctype}>
^{widget}
<div class="submit">
<input type="submit">

View file

@ -0,0 +1,18 @@
$# This file is part of Vervis.
$#
$# Written in 2016, 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/>.
<form method=POST action=@{ProjectNewR} enctype=#{enctype}>
^{widget}
<div class="submit">
<input type="submit">

View file

@ -0,0 +1,36 @@
$# This file is part of Vervis.
$#
$# Written in 2019, 2022, 2023 by fr33domlover <fr33domlover@riseup.net>.
$#
$# ♡ Copying is an act of love. Please copy, reuse and share.
$#
$# The author(s) have dedicated all copyright and related and neighboring
$# rights to this software to the public domain worldwide. This software is
$# distributed without any warranty.
$#
$# You should have received a copy of the CC0 Public Domain Dedication along
$# with this software. If not, see
$# <http://creativecommons.org/publicdomain/zero/1.0/>.
<div>
<span>
[[ 🏗
<a href=@{ProjectR projectHash}>
$#{keyHashidText projectHash} #{actorName actor}
]] ::
<span>
<a href=@{ProjectInboxR projectHash}>
[📥 Inbox]
<span>
<a href=@{ProjectOutboxR projectHash}>
[📤 Outbox]
<span>
<a href=@{ProjectFollowersR projectHash}>
[🐤 Followers]
<span>
<a href=@{ProjectCollabsR projectHash}>
[🤝 Collaborators]
<span>
[No wiki]
<span>
[✏ Edit]

View file

@ -647,13 +647,6 @@ CollabRecipRemoteJoin
-------------------------------- Collab topic --------------------------------
-- Removed for now, until I figure out whether/how to federate custom roles
--CollabRoleLocal
-- collab CollabId
-- role RoleId
--
-- UniqueCollabRoleLocal collab
CollabTopicRepo
collab CollabId
repo RepoId
@ -672,6 +665,12 @@ CollabTopicLoom
UniqueCollabTopicLoom collab
CollabTopicProject
collab CollabId
project ProjectId
UniqueCollabTopicProject collab
CollabEnable
collab CollabId
grant OutboxItemId

View file

@ -304,3 +304,21 @@
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/new-dep ClothDepNewR GET POST
-- /looms/#LoomKeyHashid/cloths/#TicketLoomKeyHashid/deps/#TicketDepKeyHashid/delete ClothDepDeleteR POST
---- Project -----------------------------------------------------------------
/projects/#ProjectKeyHashid ProjectR GET
/projects/#ProjectKeyHashid/inbox ProjectInboxR GET POST
/projects/#ProjectKeyHashid/outbox ProjectOutboxR GET
/projects/#ProjectKeyHashid/outbox/#OutboxItemKeyHashid ProjectOutboxItemR GET
/projects/#ProjectKeyHashid/followers ProjectFollowersR GET
/projects/#ProjectKeyHashid/messages/#LocalMessageKeyHashid ProjectMessageR GET
/new-project ProjectNewR GET POST
/projects/#ProjectKeyHashid/stamps/#SigKeyKeyHashid ProjectStampR GET
/projects/#ProjectKeyHashid/collabs ProjectCollabsR GET
/projects/#ProjectKeyHashid/invite ProjectInviteR GET POST
/projects/#ProjectKeyHashid/remove/#CollabTopicProjectId ProjectRemoveR POST

View file

@ -149,6 +149,7 @@ library
Vervis.Actor.Loom
Vervis.Actor.Person
Vervis.Actor.Person.Client
Vervis.Actor.Project
Vervis.Actor.Repo
Vervis.API
Vervis.Avatar
@ -210,6 +211,7 @@ library
Vervis.Handler.Person
Vervis.Handler.Repo
--Vervis.Handler.Role
Vervis.Handler.Project
--Vervis.Handler.Sharer
Vervis.Handler.Ticket
-- Vervis.Handler.Wiki