1
0
Fork 0
mirror of https://code.sup39.dev/repos/Wqawg synced 2025-03-20 04:46:22 +09:00
vervis/src/Vervis/Handler/Ticket.hs

1539 lines
64 KiB
Haskell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{- This file is part of Vervis.
-
- Written in 2016, 2018, 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.Handler.Ticket
( getTicketsR
, getTicketTreeR
, getTicketNewR
, getTicketR
, putTicketR
, deleteTicketR
, postTicketR
, getTicketEditR
, postTicketAcceptR
, postTicketCloseR
, postTicketOpenR
, postTicketClaimR
, postTicketUnclaimR
, getTicketAssignR
, postTicketAssignR
, postTicketUnassignR
, getClaimRequestsPersonR
, getClaimRequestsProjectR
, getClaimRequestsTicketR
, postClaimRequestsTicketR
, getClaimRequestNewR
, getTicketDiscussionR
, postTicketDiscussionR
, getMessageR
, postTicketMessageR
, getTicketTopReplyR
, getTicketReplyR
, getTicketDepsR
, postTicketDepsR
, getTicketDepNewR
, postTicketDepOldR
, deleteTicketDepOldR
, getTicketReverseDepsR
, getTicketDepR
, getTicketParticipantsR
, getTicketTeamR
, getTicketEventsR
, getSharerTicketsR
, getSharerTicketR
, getSharerTicketDiscussionR
, getSharerTicketDepsR
, getSharerTicketReverseDepsR
, getSharerTicketFollowersR
, getSharerTicketTeamR
, getSharerTicketEventsR
)
where
import Control.Applicative (liftA2)
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Logger.CallStack
import Control.Monad.Trans.Except
import Data.Aeson (encode)
import Data.Bifunctor
import Data.Bitraversable
import Data.Bool (bool)
import Data.Default.Class (def)
import Data.Foldable (traverse_)
import Data.Maybe
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Time.Calendar (Day (..))
import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Data.Time.Format (formatTime, defaultTimeLocale)
import Data.Traversable (for)
import Database.Persist
import Network.HTTP.Types (StdMethod (DELETE, POST))
import Text.Blaze.Html (Html, toHtml, preEscapedToHtml)
import Text.Blaze.Html.Renderer.Text
import Text.HTML.SanitizeXSS
import Yesod.Auth (requireAuthId, maybeAuthId)
import Yesod.Core hiding (logWarn)
import Yesod.Core.Handler
import Yesod.Form.Functions (runFormGet, runFormPost)
import Yesod.Form.Types (FormResult (..))
import Yesod.Persist.Core (runDB, get404, getBy404)
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T (filter, intercalate, pack)
import qualified Data.Text.Lazy as TL
import qualified Database.Esqueleto as E
import Database.Persist.Sql.Graph.TransitiveReduction (trrFix)
import Data.Aeson.Encode.Pretty.ToEncoding
import Data.MediaType
import Network.FedURI
import Web.ActivityPub hiding (Ticket (..), TicketDependency)
import Yesod.ActivityPub
import Yesod.Auth.Unverified
import Yesod.FedURI
import Yesod.Hashids
import Yesod.MonadSite
import Yesod.RenderSource
import qualified Web.ActivityPub as AP
import Data.Either.Local
import Data.Maybe.Local (partitionMaybePairs)
import Data.Paginate.Local
import Database.Persist.Local
import Yesod.Persist.Local
import Vervis.API
import Vervis.Federation
import Vervis.FedURI
import Vervis.Form.Ticket
import Vervis.Foundation
import Vervis.Handler.Discussion
import Vervis.GraphProxy (ticketDepGraph)
import Vervis.Model
import Vervis.Model.Ident
import Vervis.Model.Ticket
import Vervis.Model.Workflow
import Vervis.Paginate
import Vervis.Settings
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
getTicketsR :: ShrIdent -> PrjIdent -> Handler TypedContent
getTicketsR 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 <-
map (ticketProjectLocalTicket . entityVal) <$>
selectList
[TicketProjectLocalProject ==. jid]
[ Desc TicketProjectLocalTicket
, OffsetBy off
, LimitTo lim
]
selectKeysList [LocalTicketTicket <-. tids] [Desc LocalTicketTicket]
getPageAndNavCount countAllTickets selectTickets
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
host <- asksSite siteInstanceHost
encodeTicketKey <- getEncodeKeyHashid
let ticketUrl = TicketR shr prj . encodeTicketKey
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 (encodeRouteHome . ticketUrl) tickets
}
where
here = TicketsR shr prj
encodeStrict = BL.toStrict . encode
getTicketTreeR :: ShrIdent -> PrjIdent -> Handler Html
getTicketTreeR shr prj = do
(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
getTicketNewR :: ShrIdent -> PrjIdent -> Handler Html
getTicketNewR 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")
getTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketR shar proj ltkhid = do
mpid <- maybeAuthId
( wshr, wfl,
author, massignee, mcloser, ticket, lticket, tparams, eparams, cparams,
deps, rdeps) <-
runDB $ do
(jid, wshr, wid, wfl) <- do
Entity s sharer <- getBy404 $ UniqueSharer shar
Entity p project <- getBy404 $ UniqueProject proj s
w <- get404 $ projectWorkflow project
wsharer <-
if workflowSharer w == s
then return sharer
else get404 $ workflowSharer w
return
( p
, sharerIdent wsharer
, projectWorkflow project
, workflowIdent w
)
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
Entity tplid tpl <- getBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
author <-
requireEitherAlt
(do mtal <- getValBy $ UniqueTicketAuthorLocal ltid
for mtal $ \ tal -> do
_ <- getBy404 $ UniqueTicketUnderProjectProject tplid
p <- getJust $ ticketAuthorLocalAuthor tal
getJust $ personIdent p
)
(do mtar <- getValBy $ UniqueTicketAuthorRemote tplid
for mtar $ \ tar -> do
ra <- getJust $ ticketAuthorRemoteAuthor tar
ro <- getJust $ remoteActorIdent ra
i <- getJust $ remoteObjectInstance ro
return (i, ro, ra)
)
"Ticket doesn't have author"
"Ticket has both local and remote author"
ticket <- get404 tid
massignee <- for (ticketAssignee ticket) $ \ apid -> do
person <- get404 apid
sharer <- get404 $ personIdent person
return (sharer, fromMaybe False $ (== apid) <$> mpid)
mcloser <-
case ticketStatus ticket of
TSClosed ->
case ticketCloser ticket of
Just pidCloser -> Just <$> do
person <- getJust pidCloser
getJust $ personIdent person
Nothing -> error "Closer not set for closed ticket"
_ ->
case ticketCloser ticket of
Just _ -> error "Closer set for open ticket"
Nothing -> return Nothing
tparams <- getTicketTextParams tid wid
eparams <- getTicketEnumParams tid wid
cparams <- getTicketClasses tid wid
deps <- E.select $ E.from $ \ (dep `E.InnerJoin` t `E.InnerJoin` lt) -> do
E.on $ t E.^. TicketId E.==. lt E.^. LocalTicketTicket
E.on $ dep E.^. TicketDependencyChild E.==. t E.^. TicketId
E.where_ $ dep E.^. TicketDependencyParent E.==. E.val tid
return (lt E.^. LocalTicketId, t)
rdeps <- E.select $ E.from $ \ (dep `E.InnerJoin` t `E.InnerJoin` lt) -> do
E.on $ t E.^. TicketId E.==. lt E.^. LocalTicketTicket
E.on $ dep E.^. TicketDependencyParent E.==. t E.^. TicketId
E.where_ $ dep E.^. TicketDependencyChild E.==. E.val tid
return (lt E.^. LocalTicketId, t)
return
( wshr, wfl
, author, massignee, mcloser, ticket, lticket
, tparams, eparams, cparams
, deps, rdeps
)
encodeHid <- getEncodeKeyHashid
let desc :: Widget
desc = toWidget $ preEscapedToMarkup $ ticketDescription ticket
discuss =
discussionW
(return $ localTicketDiscuss lticket)
(TicketTopReplyR shar proj ltkhid)
(TicketReplyR shar proj ltkhid . encodeHid)
cRelevant <- newIdent
cIrrelevant <- newIdent
let relevant filt =
bool cIrrelevant cRelevant $
case ticketStatus ticket of
TSNew -> wffNew filt
TSTodo -> wffTodo filt
TSClosed -> wffClosed filt
hLocal <- getsYesod siteInstanceHost
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let host =
case author of
Left _ -> hLocal
Right (i, _, _) -> instanceHost i
ticketAP = AP.Ticket
{ AP.ticketLocal = Just
( hLocal
, AP.TicketLocal
{ AP.ticketId =
encodeRouteLocal $ TicketR shar proj ltkhid
, AP.ticketContext =
encodeRouteLocal $ ProjectR shar proj
, AP.ticketReplies =
encodeRouteLocal $ TicketDiscussionR shar proj ltkhid
, AP.ticketParticipants =
encodeRouteLocal $ TicketParticipantsR shar proj ltkhid
, AP.ticketTeam =
encodeRouteLocal $ TicketTeamR shar proj ltkhid
, AP.ticketEvents =
encodeRouteLocal $ TicketEventsR shar proj ltkhid
, AP.ticketDeps =
encodeRouteLocal $ TicketDepsR shar proj ltkhid
, AP.ticketReverseDeps =
encodeRouteLocal $ TicketReverseDepsR 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.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.ticketIsResolved = ticketStatus ticket == TSClosed
}
provideHtmlAndAP' host ticketAP $
let followButton =
followW
(TicketFollowR shar proj ltkhid)
(TicketUnfollowR shar proj ltkhid)
(return $ localTicketFollowers lticket)
in $(widgetFile "ticket/one")
putTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
putTicketR shr prj ltkhid = do
(tid, ticket, wid) <- runDB $ do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
Entity pid project <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == pid) notFound
return (tid, ticket, projectWorkflow project)
((result, widget), enctype) <-
runFormPost $ editTicketContentForm tid ticket wid
case result of
FormSuccess (ticket', tparams, eparams, cparams) -> do
newDescHtml <-
case renderPandocMarkdown $ ticketSource ticket' of
Left err -> do
setMessage $ toHtml err
redirect $ TicketEditR shr prj ltkhid
Right t -> return t
let ticket'' = ticket' { ticketDescription = newDescHtml }
runDB $ do
replace tid ticket''
let (tdel, tins, tupd) = partitionMaybePairs tparams
deleteWhere [TicketParamTextId <-. tdel]
let mktparam (fid, v) = TicketParamText
{ ticketParamTextTicket = tid
, ticketParamTextField = fid
, ticketParamTextValue = v
}
insertMany_ $ map mktparam tins
traverse_
(\ (aid, (_fid, v)) ->
update aid [TicketParamTextValue =. v]
)
tupd
let (edel, eins, eupd) = partitionMaybePairs eparams
deleteWhere [TicketParamEnumId <-. edel]
let mkeparam (fid, v) = TicketParamEnum
{ ticketParamEnumTicket = tid
, ticketParamEnumField = fid
, ticketParamEnumValue = v
}
insertMany_ $ map mkeparam eins
traverse_
(\ (aid, (_fid, v)) ->
update aid [TicketParamEnumValue =. v]
)
eupd
let (cdel, cins, _ckeep) = partitionMaybePairs cparams
deleteWhere [TicketParamClassId <-. cdel]
let mkcparam fid = TicketParamClass
{ ticketParamClassTicket = tid
, ticketParamClassField = fid
}
insertMany_ $ map mkcparam cins
setMessage "Ticket updated."
redirect $ TicketR shr prj ltkhid
FormMissing -> do
setMessage "Field(s) missing."
defaultLayout $(widgetFile "ticket/edit")
FormFailure _l -> do
setMessage "Ticket update failed, see errors below."
defaultLayout $(widgetFile "ticket/edit")
deleteTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
deleteTicketR _shr _prj _ltkhid =
--TODO: I can easily implement this, but should it even be possible to
--delete tickets?
error "Not implemented"
postTicketR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketR shr prj ltkhid = do
mmethod <- lookupPostParam "_method"
case mmethod of
Just "PUT" -> putTicketR shr prj ltkhid
Just "DELETE" -> deleteTicketR shr prj ltkhid
_ -> notFound
getTicketEditR :: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getTicketEditR shr prj ltkhid = do
(tid, ticket, wid) <- runDB $ do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
Entity pid project <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == pid) notFound
return (tid, ticket, projectWorkflow project)
((_result, widget), enctype) <-
runFormPost $ editTicketContentForm tid ticket wid
defaultLayout $(widgetFile "ticket/edit")
postTicketAcceptR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketAcceptR shr prj ltkhid = do
succ <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case ticketStatus ticket of
TSNew -> do
update tid [TicketStatus =. TSTodo]
return True
_ -> return False
setMessage $
if succ
then "Ticket accepted."
else "Ticket is already accepted."
redirect $ TicketR shr prj ltkhid
postTicketCloseR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketCloseR shr prj ltkhid = do
pid <- requireAuthId
now <- liftIO getCurrentTime
succ <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case ticketStatus ticket of
TSClosed -> return False
_ -> do
update tid
[ TicketAssignee =. Nothing
, TicketStatus =. TSClosed
, TicketClosed =. now
, TicketCloser =. Just pid
]
return True
setMessage $
if succ
then "Ticket closed."
else "Ticket is already closed."
redirect $ TicketR shr prj ltkhid
postTicketOpenR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketOpenR shr prj ltkhid = do
pid <- requireAuthId
now <- liftIO getCurrentTime
succ <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case ticketStatus ticket of
TSClosed -> do
update tid
[ TicketStatus =. TSTodo
, TicketCloser =. Nothing
]
return True
_ -> return False
setMessage $
if succ
then "Ticket reopened"
else "Ticket is already open."
redirect $ TicketR shr prj ltkhid
postTicketClaimR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketClaimR shr prj ltkhid = do
pid <- requireAuthId
mmsg <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case (ticketStatus ticket, ticketAssignee ticket) of
(TSNew, _) ->
return $
Just "The ticket isnt accepted yet. Cant claim it."
(TSClosed, _) ->
return $
Just "The ticket is closed. Cant claim closed tickets."
(TSTodo, Just _) ->
return $
Just "The ticket is already assigned to someone."
(TSTodo, Nothing) -> do
update tid [TicketAssignee =. Just pid]
return Nothing
setMessage $ fromMaybe "The ticket is now assigned to you." mmsg
redirect $ TicketR shr prj ltkhid
postTicketUnclaimR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketUnclaimR shr prj ltkhid = do
pid <- requireAuthId
mmsg <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case ((== pid) <$> ticketAssignee ticket, ticketStatus ticket) of
(Nothing, _) ->
return $ Just "The ticket is already unassigned."
(Just False, _) ->
return $ Just "The ticket is assigned to someone else."
(Just True, TSNew) -> do
logWarn "Found a new claimed ticket, this is invalid"
return $
Just "The ticket isnt accepted yet. Cant unclaim it."
(Just True, TSClosed) -> do
logWarn "Found a closed claimed ticket, this is invalid"
return $
Just "The ticket is closed. Cant unclaim closed tickets."
(Just True, TSTodo) -> do
update tid [TicketAssignee =. Nothing]
return Nothing
setMessage $ fromMaybe "The ticket is now unassigned." mmsg
redirect $ TicketR shr prj ltkhid
getTicketAssignR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getTicketAssignR shr prj ltkhid = do
vpid <- requireAuthId
(jid, Entity tid ticket) <- runDB $ do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity j _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == j) notFound
return (j, Entity tid ticket)
let msg t = do
setMessage t
redirect $ TicketR shr prj ltkhid
case (ticketStatus ticket, ticketAssignee ticket) of
(TSNew, _) -> msg "The ticket isnt accepted yet. Cant assign it."
(TSClosed, _) -> msg "The ticket is closed. Cant assign it."
(TSTodo, Just _) -> msg "The ticket is already assigned to someone."
(TSTodo, Nothing) -> do
((_result, widget), enctype) <-
runFormPost $ assignTicketForm vpid jid
defaultLayout $(widgetFile "ticket/assign")
postTicketAssignR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketAssignR shr prj ltkhid = do
vpid <- requireAuthId
(jid, Entity tid ticket) <- runDB $ do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity j _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == j) notFound
return (j, Entity tid ticket)
let msg t = do
setMessage t
redirect $ TicketR shr prj ltkhid
case (ticketStatus ticket, ticketAssignee ticket) of
(TSNew, _) -> msg "The ticket isnt accepted yet. Cant assign it."
(TSClosed, _) -> msg "The ticket is closed. Cant assign it."
(TSTodo, Just _) -> msg "The ticket is already assigned to someone."
(TSTodo, Nothing) -> do
((result, widget), enctype) <-
runFormPost $ assignTicketForm vpid jid
case result of
FormSuccess pid -> do
sharer <- runDB $ do
update tid [TicketAssignee =. Just pid]
person <- getJust pid
getJust $ personIdent person
let si = sharerIdent sharer
msg $ toHtml $
"The ticket is now assigned to " <> shr2text si <> "."
FormMissing -> do
setMessage "Field(s) missing."
defaultLayout $(widgetFile "ticket/assign")
FormFailure _l -> do
setMessage "Ticket assignment failed, see errors below."
defaultLayout $(widgetFile "ticket/assign")
postTicketUnassignR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketUnassignR shr prj ltkhid = do
pid <- requireAuthId
mmsg <- runDB $ do
Entity tid ticket <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity p _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
ticket <- getJust tid
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == p) notFound
return $ Entity tid ticket
case ((== pid) <$> ticketAssignee ticket, ticketStatus ticket) of
(Nothing, _) ->
return $ Just "The ticket is already unassigned."
(Just True, _) ->
return $ Just "The ticket is assigned to you, unclaim instead."
(Just False, TSNew) -> do
logWarn "Found a new claimed ticket, this is invalid"
return $
Just "The ticket isnt accepted yet. Cant unclaim it."
(Just False, TSClosed) -> do
logWarn "Found a closed claimed ticket, this is invalid"
return $
Just "The ticket is closed. Cant unclaim closed tickets."
(Just False, TSTodo) -> do
update tid [TicketAssignee =. Nothing]
return Nothing
setMessage $ fromMaybe "The ticket is now unassigned." mmsg
redirect $ TicketR shr prj ltkhid
-- | The logged-in user gets a list of the ticket claim requests they have
-- opened, in any project.
getClaimRequestsPersonR :: Handler Html
getClaimRequestsPersonR = do
pid <- requireAuthId
rqs <- runDB $ E.select $ E.from $
\ (tcr `E.InnerJoin` ticket `E.InnerJoin` lticket `E.InnerJoin` tpl `E.InnerJoin` project `E.InnerJoin` sharer) -> do
E.on $ project E.^. ProjectSharer E.==. sharer E.^. SharerId
E.on $ tpl E.^. TicketProjectLocalProject E.==. project E.^. ProjectId
E.on $ ticket E.^. TicketId E.==. tpl E.^. TicketProjectLocalTicket
E.on $ ticket E.^. TicketId E.==. lticket E.^. LocalTicketTicket
E.on $ tcr E.^. TicketClaimRequestTicket E.==. ticket E.^. TicketId
E.where_ $ tcr E.^. TicketClaimRequestPerson E.==. E.val pid
E.orderBy [E.desc $ tcr E.^. TicketClaimRequestCreated]
return
( sharer E.^. SharerIdent
, project E.^. ProjectIdent
, lticket E.^. LocalTicketId
, ticket E.^. TicketTitle
, tcr E.^. TicketClaimRequestCreated
)
encodeHid <- getEncodeKeyHashid
defaultLayout $(widgetFile "person/claim-requests")
-- | Get a list of ticket claim requests for a given project.
getClaimRequestsProjectR :: ShrIdent -> PrjIdent -> Handler Html
getClaimRequestsProjectR shr prj = do
rqs <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
E.select $ E.from $
\ ( tcr `E.InnerJoin`
ticket `E.InnerJoin`
lticket `E.InnerJoin`
tpl `E.InnerJoin`
person `E.InnerJoin`
sharer
) -> do
E.on $ person E.^. PersonIdent E.==. sharer E.^. SharerId
E.on $ tcr E.^. TicketClaimRequestPerson E.==. person E.^. PersonId
E.on $ ticket E.^. TicketId E.==. tpl E.^. TicketProjectLocalTicket
E.on $ ticket E.^. TicketId E.==. lticket E.^. LocalTicketTicket
E.on $ tcr E.^. TicketClaimRequestTicket E.==. ticket E.^. TicketId
E.where_ $ tpl E.^. TicketProjectLocalProject E.==. E.val jid
E.orderBy [E.desc $ tcr E.^. TicketClaimRequestCreated]
return
( sharer
, lticket E.^. LocalTicketId
, ticket E.^. TicketTitle
, tcr E.^. TicketClaimRequestCreated
)
encodeHid <- getEncodeKeyHashid
defaultLayout $(widgetFile "project/claim-request/list")
-- | Get a list of ticket claim requests for a given ticket.
getClaimRequestsTicketR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getClaimRequestsTicketR shr prj ltkhid = do
rqs <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
E.select $ E.from $ \ (tcr `E.InnerJoin` person `E.InnerJoin` sharer) -> do
E.on $ person E.^. PersonIdent E.==. sharer E.^. SharerId
E.on $ tcr E.^. TicketClaimRequestPerson E.==. person E.^. PersonId
E.where_ $ tcr E.^. TicketClaimRequestTicket E.==. E.val tid
E.orderBy [E.desc $ tcr E.^. TicketClaimRequestCreated]
return (sharer, tcr)
defaultLayout $(widgetFile "ticket/claim-request/list")
getClaimRequestNewR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getClaimRequestNewR shr prj ltkhid = do
((_result, widget), etype) <- runFormPost claimRequestForm
defaultLayout $(widgetFile "ticket/claim-request/new")
postClaimRequestsTicketR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postClaimRequestsTicketR shr prj ltkhid = do
((result, widget), etype) <- runFormPost claimRequestForm
case result of
FormSuccess msg -> do
now <- liftIO getCurrentTime
pid <- requireAuthId
runDB $ do
tid <- do
Entity s _ <- getBy404 $ UniqueSharer shr
Entity j _ <- getBy404 $ UniqueProject prj s
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == j) notFound
return tid
let cr = TicketClaimRequest
{ ticketClaimRequestPerson = pid
, ticketClaimRequestTicket = tid
, ticketClaimRequestMessage = msg
, ticketClaimRequestCreated = now
}
insert_ cr
setMessage "Ticket claim request opened."
redirect $ TicketR shr prj ltkhid
FormMissing -> do
setMessage "Field(s) missing."
defaultLayout $(widgetFile "ticket/claim-request/new")
FormFailure _l -> do
setMessage "Submission failed, see errors below."
defaultLayout $(widgetFile "ticket/claim-request/new")
selectDiscussionId
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> AppDB DiscussionId
selectDiscussionId shr prj ltkhid = do
Entity sid _sharer <- getBy404 $ UniqueSharer shr
Entity jid _project <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
return $ localTicketDiscuss lticket
getTicketDiscussionR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getTicketDiscussionR shar proj ltkhid = do
encodeHid <- getEncodeKeyHashid
getDiscussion
(TicketReplyR shar proj ltkhid . encodeHid)
(TicketTopReplyR shar proj ltkhid)
(selectDiscussionId shar proj ltkhid)
postTicketDiscussionR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketDiscussionR shr prj ltkhid = do
hLocal <- getsYesod $ appInstanceHost . appSettings
postTopReply
hLocal
[ProjectR shr prj]
[ ProjectFollowersR shr prj
, TicketParticipantsR shr prj ltkhid
, TicketTeamR shr prj ltkhid
]
(TicketR shr prj ltkhid)
(ProjectR shr prj)
(TicketDiscussionR shr prj ltkhid)
(const $ TicketR shr prj ltkhid)
getMessageR :: ShrIdent -> KeyHashid LocalMessage -> Handler TypedContent
getMessageR shr hid = do
lmid <- decodeKeyHashid404 hid
getDiscussionMessage shr lmid
postTicketMessageR
:: ShrIdent
-> PrjIdent
-> KeyHashid LocalTicket
-> KeyHashid Message
-> Handler Html
postTicketMessageR shr prj ltkhid mkhid = do
encodeHid <- getEncodeKeyHashid
mid <- decodeKeyHashid404 mkhid
hLocal <- getsYesod $ appInstanceHost . appSettings
postReply
hLocal
[ProjectR shr prj]
[ ProjectFollowersR shr prj
, TicketParticipantsR shr prj ltkhid
, TicketTeamR shr prj ltkhid
]
(TicketR shr prj ltkhid)
(ProjectR shr prj)
(TicketReplyR shr prj ltkhid . encodeHid)
(TicketMessageR shr prj ltkhid . encodeHid)
(const $ TicketR shr prj ltkhid)
(selectDiscussionId shr prj ltkhid)
mid
getTicketTopReplyR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getTicketTopReplyR shr prj ltkhid =
getTopReply $ TicketDiscussionR shr prj ltkhid
getTicketReplyR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> KeyHashid Message -> Handler Html
getTicketReplyR shr prj ltkhid mkhid = do
encodeHid <- getEncodeKeyHashid
mid <- decodeKeyHashid404 mkhid
getReply
(TicketReplyR shr prj ltkhid . encodeHid)
(TicketMessageR shr prj ltkhid . encodeHid)
(selectDiscussionId shr prj ltkhid)
mid
getTicketDeps
:: Bool -> ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketDeps forward shr prj ltkhid = do
(deps, rows) <- unzip <$> runDB getDepsFromDB
depsAP <- makeDepsCollection deps
encodeHid <- getEncodeKeyHashid
provideHtmlAndAP depsAP $(widgetFile "ticket/dep/list")
where
getDepsFromDB = do
let from' =
if forward then TicketDependencyParent else TicketDependencyChild
to' =
if forward then TicketDependencyChild else TicketDependencyParent
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
fmap (map toRow) $ E.select $ E.from $
\ ( td
`E.InnerJoin` t
`E.InnerJoin` lt
`E.InnerJoin` tpl
`E.LeftOuterJoin` (tal `E.InnerJoin` p `E.InnerJoin` s)
`E.LeftOuterJoin` (tar `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 $ tar E.?. TicketAuthorRemoteAuthor E.==. ra E.?. RemoteActorId
E.on $ E.just (tpl E.^. TicketProjectLocalId) E.==. tar E.?. TicketAuthorRemoteTicket
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.on $ t E.^. TicketId E.==. tpl E.^. TicketProjectLocalTicket
E.on $ t E.^. TicketId E.==. lt E.^. LocalTicketTicket
E.on $ td E.^. to' E.==. t E.^. TicketId
E.where_ $ td E.^. from' E.==. E.val tid
E.orderBy [E.asc $ t E.^. TicketId]
return
( td E.^. TicketDependencyId
, lt E.^. LocalTicketId
, s
, i
, ro
, ra
, t E.^. TicketTitle
, t E.^. TicketStatus
)
where
toRow (E.Value dep, E.Value ltid, ms, mi, mro, mra, E.Value title, E.Value status) =
( dep
, ( ltid
, case (ms, mi, mro, mra) of
(Just s, Nothing, Nothing, Nothing) ->
Left $ entityVal s
(Nothing, Just i, Just ro, Just ra) ->
Right (entityVal i, entityVal ro, entityVal ra)
_ -> error "Ticket author DB invalid state"
, title
, status
)
)
makeDepsCollection tdids = do
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeKeyHashid <- getEncodeKeyHashid
let here =
let route = if forward then TicketDepsR else TicketReverseDepsR
in route shr prj ltkhid
return Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ length tdids
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems =
map (encodeRouteHome . TicketDepR . encodeKeyHashid) tdids
}
getTicketDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketDepsR = getTicketDeps True
postTicketDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
postTicketDepsR shr prj ltkhid = do
(jid, tid) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
return (jid, tid)
((result, widget), enctype) <- runFormPost $ ticketDepForm jid tid
case result of
FormSuccess ctid -> do
pidAuthor <- requireVerifiedAuthId
now <- liftIO getCurrentTime
runDB $ do
let td = TicketDependency
{ ticketDependencyParent = tid
, ticketDependencyChild = ctid
, ticketDependencyAuthor = pidAuthor
, ticketDependencySummary = "(A ticket dependency)"
, ticketDependencyCreated = now
}
insert_ td
trrFix td ticketDepGraph
setMessage "Ticket dependency added."
redirect $ TicketR shr prj ltkhid
FormMissing -> do
setMessage "Field(s) missing."
defaultLayout $(widgetFile "ticket/dep/new")
FormFailure _l -> do
setMessage "Submission failed, see errors below."
defaultLayout $(widgetFile "ticket/dep/new")
getTicketDepNewR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler Html
getTicketDepNewR shr prj ltkhid = do
(jid, tid) <- runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lticket <- get404 ltid
let tid = localTicketTicket lticket
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
return (jid, tid)
((_result, widget), enctype) <- runFormPost $ ticketDepForm jid tid
defaultLayout $(widgetFile "ticket/dep/new")
postTicketDepOldR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> KeyHashid LocalTicket -> Handler Html
postTicketDepOldR shr prj pnum cnum = do
mmethod <- lookupPostParam "_method"
case mmethod of
Just "DELETE" -> deleteTicketDepOldR shr prj pnum cnum
_ -> notFound
deleteTicketDepOldR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> KeyHashid LocalTicket -> Handler Html
deleteTicketDepOldR shr prj pnum cnum = do
runDB $ do
Entity sid _ <- getBy404 $ UniqueSharer shr
Entity jid _ <- getBy404 $ UniqueProject prj sid
pltid <- decodeKeyHashid404 pnum
plt <- get404 pltid
let ptid = localTicketTicket plt
ptpl <- getValBy404 $ UniqueTicketProjectLocal ptid
unless (ticketProjectLocalProject ptpl == jid) notFound
cltid <- decodeKeyHashid404 cnum
clt <- get404 cltid
let ctid = localTicketTicket clt
ctpl <- getValBy404 $ UniqueTicketProjectLocal ctid
unless (ticketProjectLocalProject ctpl == jid) notFound
Entity tdid _ <- getBy404 $ UniqueTicketDependency ptid ctid
delete tdid
setMessage "Ticket dependency removed."
redirect $ TicketDepsR shr prj pnum
getTicketReverseDepsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketReverseDepsR = getTicketDeps False
getTicketDepR :: KeyHashid TicketDependency -> Handler TypedContent
getTicketDepR tdkhid = do
tdid <- decodeKeyHashid404 tdkhid
( td,
(sParent, jParent, ltParent),
(sChild, jChild, ltChild),
(sAuthor, pAuthor)
) <- runDB $ do
tdep <- get404 tdid
(,,,) tdep
<$> getTicket (ticketDependencyParent tdep)
<*> getTicket (ticketDependencyChild tdep)
<*> getAuthor (ticketDependencyAuthor tdep)
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeHid <- getEncodeKeyHashid
let ticketRoute s j lt =
TicketR (sharerIdent s) (projectIdent j) (encodeHid lt)
here = TicketDepR tdkhid
tdepAP = AP.TicketDependency
{ ticketDepId = Just $ encodeRouteHome here
, ticketDepParent =
encodeRouteHome $ ticketRoute sParent jParent ltParent
, ticketDepChild =
encodeRouteHome $ ticketRoute sChild jChild ltChild
, ticketDepAttributedTo =
encodeRouteLocal $ SharerR $ sharerIdent sAuthor
, ticketDepPublished = Just $ ticketDependencyCreated td
, ticketDepUpdated = Just $ ticketDependencyCreated td
, ticketDepSummary = TextHtml $ ticketDependencySummary td
}
provideHtmlAndAP tdepAP $ redirectToPrettyJSON here
where
getTicket tid = do
ltid <- do
mltid <- getKeyBy $ UniqueLocalTicket tid
case mltid of
Nothing -> error "No LocalTicket"
Just v -> return v
tpl <- do
mtpl <- getValBy $ UniqueTicketProjectLocal tid
case mtpl of
Nothing -> error "No TicketProjectLocal"
Just v -> return v
j <- getJust $ ticketProjectLocalProject tpl
s <- getJust $ projectSharer j
return (s, j, ltid)
getAuthor pid = do
p <- getJust pid
s <- getJust $ personIdent p
return (s, p)
getTicketParticipantsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketParticipantsR shr prj ltkhid = getFollowersCollection here getFsid
where
here = TicketParticipantsR shr prj ltkhid
getFsid = do
sid <- getKeyBy404 $ UniqueSharer shr
jid <- getKeyBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lt <- get404 ltid
let tid = localTicketTicket lt
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
return $ localTicketFollowers lt
getTicketTeamR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketTeamR shr prj ltkhid = do
memberShrs <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
jid <- getKeyBy404 $ UniqueProject prj sid
ltid <- decodeKeyHashid404 ltkhid
lt <- get404 ltid
let tid = localTicketTicket lt
tpl <- getValBy404 $ UniqueTicketProjectLocal tid
unless (ticketProjectLocalProject tpl == jid) notFound
id_ <-
requireEitherAlt
(getKeyBy $ UniquePersonIdent sid)
(getKeyBy $ UniqueGroup sid)
"Found sharer that is neither person nor group"
"Found sharer that is both person and group"
case id_ of
Left pid -> return [shr]
Right gid -> do
pids <-
map (groupMemberPerson . entityVal) <$>
selectList [GroupMemberGroup ==. gid] []
sids <-
map (personIdent . entityVal) <$>
selectList [PersonId <-. pids] []
map (sharerIdent . entityVal) <$>
selectList [SharerId <-. sids] []
let here = TicketTeamR shr prj ltkhid
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let team = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ length memberShrs
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems = map (encodeRouteHome . SharerR) memberShrs
}
provideHtmlAndAP team $ redirectToPrettyJSON here
getTicketEventsR
:: ShrIdent -> PrjIdent -> KeyHashid LocalTicket -> Handler TypedContent
getTicketEventsR _shr _prj _ltkhid = error "TODO not implemented"
getSharerTicket
:: ShrIdent
-> KeyHashid TicketAuthorLocal
-> AppDB
( Entity TicketAuthorLocal
, Entity LocalTicket
, Entity Ticket
, Either (Entity TicketProjectLocal) ()
)
getSharerTicket shr talkhid = do
pid <- do
sid <- getKeyBy404 $ UniqueSharer shr
getKeyBy404 $ UniquePersonIdent sid
talid <- decodeKeyHashid404 talkhid
tal <- get404 talid
unless (ticketAuthorLocalAuthor tal == pid) notFound
let ltid = ticketAuthorLocalTicket tal
lt <- getJust ltid
let tid = localTicketTicket lt
t <- getJust tid
project <-
requireEitherAlt
(do mtpl <- getBy $ UniqueTicketProjectLocal tid
for mtpl $ \ etpl@(Entity tplid tpl) -> do
mtup1 <- getBy $ UniqueTicketUnderProjectProject tplid
mtup2 <- getBy $ UniqueTicketUnderProjectAuthor talid
unless (isJust mtup1 == isJust mtup2) $
error "TUP points to unrelated TAL and TPL!"
unless (isNothing mtup1) notFound
return etpl
)
(return Nothing
)
"Ticket doesn't have project"
"Ticket has both local and remote project"
return (Entity talid tal, Entity ltid lt, Entity tid t, project)
getSharerTicketsR :: ShrIdent -> Handler TypedContent
getSharerTicketsR shr = do
(total, pages, mpage) <- runDB $ do
sid <- getKeyBy404 $ UniqueSharer shr
pid <- getKeyBy404 $ UniquePersonIdent sid
getPageAndNavCount (countTickets pid) (selectTickets pid)
encodeRouteHome <- getEncodeRouteHome
encodeRouteLocal <- getEncodeRouteLocal
encodeRoutePageLocal <- getEncodeRoutePageLocal
let pageUrl = encodeRoutePageLocal here
encodeTicketKey <- getEncodeKeyHashid
let ticketUrl = SharerTicketR shr . encodeTicketKey
case mpage of
Nothing -> provide $ 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 $ 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
here = SharerTicketsR shr
provide :: ActivityPub a => a URIMode -> Handler TypedContent
provide a = provideHtmlAndAP a $ redirectToPrettyJSON here
countTickets pid = fmap toOne $
E.select $ E.from $ \ (tal `E.LeftOuterJoin` tup) -> do
E.on $ E.just (tal E.^. TicketAuthorLocalId) E.==. tup E.?. TicketUnderProjectAuthor
E.where_ $
tal E.^. TicketAuthorLocalAuthor E.==. E.val pid E.&&.
E.isNothing (tup E.?. TicketUnderProjectId)
return $ E.count $ tal E.^. TicketAuthorLocalId
where
toOne [x] = E.unValue x
toOne [] = error "toOne = 0"
toOne _ = error "toOne > 1"
selectTickets pid off lim =
E.select $ E.from $ \ (tal `E.LeftOuterJoin` tup) -> do
E.on $ E.just (tal E.^. TicketAuthorLocalId) E.==. tup E.?. TicketUnderProjectAuthor
E.where_ $
tal E.^. TicketAuthorLocalAuthor E.==. E.val pid E.&&.
E.isNothing (tup E.?. TicketUnderProjectId)
E.orderBy [E.desc $ tal E.^. TicketAuthorLocalId]
E.offset $ fromIntegral off
E.limit $ fromIntegral lim
return $ tal E.^. TicketAuthorLocalId
getSharerTicketR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketR shr talkhid = do
(ticket, project, massignee) <- runDB $ do
(_, _, Entity _ t, tp) <- getSharerTicket shr talkhid
(,,) t
<$> bitraverse
(\ (Entity _ tpl) -> do
j <- getJust $ ticketProjectLocalProject tpl
s <- getJust $ projectSharer j
return (s, j)
)
return
tp
<*> (for (ticketAssignee t) $ \ pidAssignee -> do
p <- getJust pidAssignee
getJust $ personIdent p
)
hLocal <- getsYesod siteInstanceHost
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
let ticketAP = AP.Ticket
{ AP.ticketLocal = Just
( hLocal
, AP.TicketLocal
{ AP.ticketId =
encodeRouteLocal $ SharerTicketR shr talkhid
, AP.ticketContext =
encodeRouteLocal $
case project of
Left (s, j) ->
ProjectR (sharerIdent s) (projectIdent j)
Right () -> error "No TPR yet!"
, AP.ticketReplies =
encodeRouteLocal $ SharerTicketDiscussionR shr talkhid
, AP.ticketParticipants =
encodeRouteLocal $ SharerTicketFollowersR shr talkhid
, AP.ticketTeam =
encodeRouteLocal $ SharerTicketTeamR shr talkhid
, AP.ticketEvents =
encodeRouteLocal $ SharerTicketEventsR shr talkhid
, AP.ticketDeps =
encodeRouteLocal $ SharerTicketDepsR shr talkhid
, AP.ticketReverseDeps =
encodeRouteLocal $ SharerTicketReverseDepsR shr talkhid
}
)
, AP.ticketAttributedTo = encodeRouteLocal $ SharerR shr
, AP.ticketPublished = Just $ ticketCreated ticket
, AP.ticketUpdated = Nothing
, AP.ticketSummary = TextHtml $ ticketTitle ticket
, AP.ticketContent = TextHtml $ ticketDescription ticket
, AP.ticketSource = TextPandocMarkdown $ ticketSource ticket
, AP.ticketAssignedTo =
encodeRouteHome . SharerR . sharerIdent <$> massignee
, AP.ticketIsResolved = ticketStatus ticket == TSClosed
}
provideHtmlAndAP ticketAP $ redirectToPrettyJSON here
where
here = SharerTicketR shr talkhid
getSharerTicketDiscussionR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketDiscussionR shr talkhid = do
(locals, remotes) <- runDB $ do
(_, Entity _ lt, _, _) <- getSharerTicket shr talkhid
let did = localTicketDiscuss lt
(,) <$> selectLocals did <*> selectRemotes did
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeHid <- getEncodeKeyHashid
let localUri' = localUri encodeRouteHome encodeHid
replies = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ length locals + length remotes
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems =
map localUri' locals ++ map remoteUri remotes
}
provideHtmlAndAP replies $ redirectToPrettyJSON here
where
here = SharerTicketDiscussionR shr talkhid
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
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)
selectRemotes did =
E.select $ E.from $
\ (m `E.InnerJoin` rm `E.InnerJoin` ro `E.InnerJoin` i) -> do
E.on $ ro E.^. RemoteObjectInstance E.==. i E.^. InstanceId
E.on $ rm E.^. RemoteMessageIdent E.==. ro E.^. RemoteObjectId
E.on $ m E.^. MessageId E.==. rm E.^. RemoteMessageRest
E.where_ $
m E.^. MessageRoot E.==. E.val did E.&&.
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)
remoteUri (E.Value h, E.Value lu) = ObjURI h lu
getSharerTicketDeps
:: Bool -> ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketDeps forward shr talkhid = do
tdids <- runDB $ do
(_, _, Entity tid _, _) <- getSharerTicket shr talkhid
let (from, to) =
if forward
then (TicketDependencyParent, TicketDependencyChild)
else (TicketDependencyChild, TicketDependencyParent)
E.select $ E.from $ \ (td `E.InnerJoin` t) -> do
E.on $ td E.^. to E.==. t E.^. TicketId
E.where_ $ td E.^. from E.==. E.val tid
return $ td E.^. TicketDependencyId
encodeRouteLocal <- getEncodeRouteLocal
encodeRouteHome <- getEncodeRouteHome
encodeHid <- getEncodeKeyHashid
let deps = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just $ length tdids
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems =
map (encodeRouteHome . TicketDepR . encodeHid . E.unValue)
tdids
}
provideHtmlAndAP deps $ redirectToPrettyJSON here
where
here =
let route =
if forward then SharerTicketDepsR else SharerTicketReverseDepsR
in route shr talkhid
getSharerTicketDepsR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketDepsR = getSharerTicketDeps True
getSharerTicketReverseDepsR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketReverseDepsR = getSharerTicketDeps False
getSharerTicketFollowersR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketFollowersR shr talkhid = getFollowersCollection here getFsid
where
here = SharerTicketFollowersR shr talkhid
getFsid = do
(_, Entity _ lt, _, _) <- getSharerTicket shr talkhid
return $ localTicketFollowers lt
getSharerTicketTeamR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketTeamR shr talkhid = do
_ <- runDB $ getSharerTicket shr talkhid
encodeRouteLocal <- getEncodeRouteLocal
let team = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeUnordered
, collectionTotalItems = Just 0
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems = [] :: [Text]
}
provideHtmlAndAP team $ redirectToPrettyJSON here
where
here = SharerTicketTeamR shr talkhid
getSharerTicketEventsR
:: ShrIdent -> KeyHashid TicketAuthorLocal -> Handler TypedContent
getSharerTicketEventsR shr talkhid = do
_ <- runDB $ getSharerTicket shr talkhid
encodeRouteLocal <- getEncodeRouteLocal
let team = Collection
{ collectionId = encodeRouteLocal here
, collectionType = CollectionTypeOrdered
, collectionTotalItems = Just 0
, collectionCurrent = Nothing
, collectionFirst = Nothing
, collectionLast = Nothing
, collectionItems = [] :: [Text]
}
provideHtmlAndAP team $ redirectToPrettyJSON here
where
here = SharerTicketEventsR shr talkhid