Compare commits

..

3 commits

Author SHA1 Message Date
me
cd5d615609 refactor Fedi.Routes 2024-11-04 17:43:42 +02:00
me
2632c44d0e nicer dates in html 2024-11-04 11:20:05 +02:00
me
1fde45736d follows and inbox 2024-11-03 13:54:15 +02:00
13 changed files with 389 additions and 206 deletions

View file

@ -6,6 +6,7 @@ import Data.String (fromString)
import Data.Text qualified as T import Data.Text qualified as T
import Fedi qualified as Fedi import Fedi qualified as Fedi
import Lucid qualified as H import Lucid qualified as H
import Text.RawString.QQ (r)
-- * HTML -- * HTML
@ -52,10 +53,12 @@ userHtml details = do
notesHtml :: [Fedi.Note] -> Html notesHtml :: [Fedi.Note] -> Html
notesHtml notes = do notesHtml notes = do
H.div_ [H.class_ "notes"] $ mapM_ noteHtml notes H.div_ [H.class_ "notes"] $ mapM_ noteHtml notes
H.script_ $ T.pack localDateJs
-- | A single post as HTML. -- | A single post as HTML.
noteHtml :: Fedi.Note -> Html noteHtml :: Fedi.Note -> Html
noteHtml note = do noteHtml note = do
let noteid = T.pack (maybe "" (\i -> i.unwrap) note.id)
H.div_ [H.class_ "note"] $ do H.div_ [H.class_ "note"] $ do
H.div_ [H.class_ "note-header"] $ do H.div_ [H.class_ "note-header"] $ do
case note.name of case note.name of
@ -68,12 +71,13 @@ noteHtml note = do
H.p_ $ H.a_ [H.href_ (T.pack url)] $ fromString url H.p_ $ H.a_ [H.href_ (T.pack url)] $ fromString url
Nothing -> pure () Nothing -> pure ()
H.a_ Fedi.for_ note.published \published ->
[ H.href_ (T.pack (maybe "" (\i -> i.unwrap) note.id)) H.a_
, H.class_ "note-time" [ H.href_ noteid
, H.title_ "See note page" , H.class_ "note-time"
] , H.title_ "See note page"
(H.toHtml (T.pack (show note.published))) ]
(H.span_ [H.class_ $ "note-date-published"] $ H.toHtml (show published))
H.div_ [H.class_ $ "note-content " <> checkDirection (maybe "" id note.content)] $ do H.div_ [H.class_ $ "note-content " <> checkDirection (maybe "" id note.content)] $ do
H.toHtmlRaw (maybe "" id note.content) H.toHtmlRaw (maybe "" id note.content)
@ -127,3 +131,13 @@ newNoteHtml details = do
, H.value_ "Post" , H.value_ "Post"
] ]
) )
localDateJs :: String
localDateJs = [r|
let collection = document.querySelectorAll(".note-date-published");
for (let i = 0; i < collection.length; i++) {
let date = new Date(collection[i].innerHTML);
collection[i].innerHTML = date.toLocaleString();
}
|]

View file

@ -11,6 +11,16 @@ import Html
import Lucid qualified as H import Lucid qualified as H
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import Web.Twain qualified as Twain import Web.Twain qualified as Twain
import Network.HTTP.Req
( runReq
, defaultHttpConfig
, req
, POST(POST)
, ReqBodyJson(ReqBodyJson)
, jsonResponse
, responseBody
, https
)
routes :: DB -> FilePath -> [Twain.Middleware] routes :: DB -> FilePath -> [Twain.Middleware]
routes db detailsFile = routes db detailsFile =
@ -43,12 +53,8 @@ routes db detailsFile =
Fedi.handleCreateNote details notes Fedi.handleCreateNote details notes
, -- Match inbox , -- Match inbox
Twain.get (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do Twain.get (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
let Fedi.handleInbox (handleInbox db detailsFile)
handle activity = do
liftIO (print activity)
pure $ Fedi.jsonLD $ A.encode activity
Fedi.handleInbox handle
, -- Match Create object , -- Match Create object
Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
details <- liftIO $ fetchUserDetails detailsFile details <- liftIO $ fetchUserDetails detailsFile
@ -70,10 +76,20 @@ routes db detailsFile =
Nothing -> Twain.next Nothing -> Twain.next
Just thenote -> Just thenote ->
Twain.send $ Twain.html $ H.renderBS $ actorPage details [thenote] Twain.send $ Twain.html $ H.renderBS $ actorPage details [thenote]
, -- Followers
Twain.get (Fedi.matchFollowers $ unsafePerformIO $ fetchUserDetails detailsFile) do
details <- liftIO $ fetchUserDetails detailsFile
Fedi.handleFollowers details
, -- Following
Twain.get (Fedi.matchFollowing $ unsafePerformIO $ fetchUserDetails detailsFile) do
details <- liftIO $ fetchUserDetails detailsFile
Fedi.handleFollowing details
, -- Match webfinger , -- Match webfinger
Twain.get Fedi.matchWebfinger do Twain.get Fedi.matchWebfinger do
details <- liftIO $ fetchUserDetails detailsFile details <- liftIO $ fetchUserDetails detailsFile
Fedi.handleWebfinger details Fedi.handleWebfinger details
--------------------------------------------------------------------------------------------
, -- Admin page , -- Admin page
Twain.get (fromString $ "/" <> (unsafePerformIO $ fetchUserDetails detailsFile).username <> "/admin") do Twain.get (fromString $ "/" <> (unsafePerformIO $ fetchUserDetails detailsFile).username <> "/admin") do
details <- liftIO $ fetchUserDetails detailsFile details <- liftIO $ fetchUserDetails detailsFile
@ -109,3 +125,48 @@ fetchUserDetails detailsFile =
noteToCreate :: Fedi.Note -> Fedi.Create noteToCreate :: Fedi.Note -> Fedi.Create
noteToCreate note = Fedi.makeCreateNote note noteToCreate note = Fedi.makeCreateNote note
handleInbox :: DB -> FilePath -> Fedi.AnyActivity -> Twain.ResponderM Twain.Response
handleInbox db detailsFile activity = do
details <- liftIO $ fetchUserDetails detailsFile
case activity of
Fedi.ActivityFollow follow -> do
let
id' = follow.id
actor = follow.otype.actor
object = follow.otype.atype.object
case id' of
Just id'' -> do
if object == Fedi.LLink (Fedi.Link $ Fedi.actorUrl details)
then do
liftIO do
insertId <- db.insertFollower FollowerEntry
{ actorId = fromString actor.unwrap
, followId = fromString id''.unwrap
}
(result :: A.Value) <- sendRequest
(id''.unwrap <> "/inbox")
( Fedi.makeAccept
follow
(Fedi.actorUrl details <> "/accepts/follows/" <> show insertId)
)
print result
pure $ Fedi.jsonLD "{}"
else Twain.next
Nothing ->
Twain.next
_ -> do
liftIO (print activity)
Twain.next
sendRequest :: (A.ToJSON input, A.FromJSON output) => Fedi.Url -> input -> IO output
sendRequest url payload = do
runReq defaultHttpConfig do
r <-
req
POST
(https $ fromString url)
(ReqBodyJson payload)
jsonResponse
mempty
pure $ responseBody r

View file

@ -18,10 +18,17 @@ library
exposed-modules: exposed-modules:
Fedi Fedi
Fedi.Helpers Fedi.Helpers
Fedi.Routes
Fedi.Types Fedi.Types
Fedi.UserDetails Fedi.UserDetails
Fedi.Webfinger Fedi.Webfinger
Fedi.Routes
Fedi.Routes.Helpers
Fedi.Routes.User
Fedi.Routes.Inbox
Fedi.Routes.Outbox
Fedi.Routes.Notes
Fedi.Routes.Follow
-- other-modules: -- other-modules:
-- other-extensions: -- other-extensions:
build-depends: build-depends:
@ -77,6 +84,7 @@ executable fedi
, raw-strings-qq , raw-strings-qq
, securemem , securemem
, lucid2 , lucid2
, req
hs-source-dirs: app hs-source-dirs: app
default-language: GHC2021 default-language: GHC2021

View file

@ -151,3 +151,18 @@ emptyOrderedCollectionPage url =
, current = Nothing , current = Nothing
} }
} }
makeAccept :: Follow -> Url -> Accept
makeAccept theirFollow myfollowId =
emptyObject
{ id = Just $ ObjectId myfollowId
, otype =
TypeActivity
{ actor = theirFollow.otype.actor
, atype = TypeAccept
{ object = ActivityFollow theirFollow
}
, target = Nothing
, origin = Nothing
}
}

View file

@ -1,14 +1,18 @@
module Fedi.Routes where module Fedi.Routes
( module Fedi.Routes
, module Export
)
where
import Data.Aeson qualified as A
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Fedi.Helpers
import Fedi.Types
import Fedi.UserDetails import Fedi.UserDetails
import Fedi.Webfinger
import Web.Twain qualified as Twain import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
import Fedi.Routes.Helpers as Export
import Fedi.Routes.User as Export
import Fedi.Routes.Inbox as Export
import Fedi.Routes.Outbox as Export
import Fedi.Routes.Notes as Export
import Fedi.Routes.Follow as Export
-- * Routes -- * Routes
@ -29,160 +33,3 @@ routes details =
, Twain.get matchWebfinger do , Twain.get matchWebfinger do
handleWebfinger details handleWebfinger details
] ]
jsonLD :: BSL.ByteString -> Twain.Response
jsonLD =
Twain.raw
Twain.status200
[(Twain.hContentType, "application/activity+json; charset=utf-8")]
-- * Create
matchCreateNote :: UserDetails -> Twain.PathPattern
matchCreateNote details = fromString ("/" <> details.username <> "/notes/:note_id/create")
handleCreateNote :: UserDetails -> [Create] -> Twain.ResponderM a
handleCreateNote details items = do
noteId <- Twain.param "note_id"
let
createUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/notes/"
<> noteId
<> "/create"
let
content =
find (\create -> create.id == Just (ObjectId createUrl)) items
Twain.send $ jsonLD (A.encode content)
-- * Note
matchNote :: UserDetails -> Twain.PathPattern
matchNote details = fromString ("/" <> details.username <> "/notes/:note_id")
handleNote :: UserDetails -> [Note] -> Twain.ResponderM a
handleNote details items = do
noteId <- Twain.param "note_id"
let
noteUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/notes/"
<> noteId
let
content =
find (\note -> note.id == Just (ObjectId noteUrl)) items
Twain.send $ jsonLD (A.encode content)
-- * User
matchUser :: UserDetails -> Twain.PathPattern
matchUser details = fromString ("/" <> details.username)
handleUser :: UserDetails -> Twain.ResponderM a
handleUser details = do
let
content = makeActor details
Twain.send $ jsonLD (A.encode content)
-- * Webfinger
matchWebfinger :: Twain.PathPattern
matchWebfinger = "/.well-known/webfinger"
handleWebfinger :: UserDetails -> Twain.ResponderM b
handleWebfinger details = do
resource <- Twain.param "resource"
let
webfinger = makeWebfinger details
if resource == ppSubject webfinger.subject
then do
Twain.send $ jsonLD (A.encode webfinger)
else do
Twain.next
-- * Outbox
matchOutbox :: UserDetails -> Twain.PathPattern
matchOutbox details =
fromString ("/" <> details.username <> "/outbox")
handleOutbox :: UserDetails -> [AnyActivity] -> Twain.ResponderM b
handleOutbox details items = do
isPage <- Twain.queryParamMaybe "page"
let
outboxUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/outbox"
response =
case isPage of
Just True ->
let
empty = emptyOrderedCollectionPage outboxUrl
content :: OutboxPage
content =
empty
{ id = Just $ ObjectId $ outboxUrl <> "?page=true"
, otype =
empty.otype
{ ctype =
empty.otype.ctype
{ partOf = outboxUrl
, porderedItems = items
}
}
}
in
A.encode content
_ ->
let
content :: Outbox
content =
emptyOrderedCollection
{ id = Just $ ObjectId outboxUrl
, summary = Just $ fromString $ details.username <> "'s notes"
, otype =
emptyOrderedCollection.otype
{ ctype =
emptyOrderedCollection.otype.ctype
{ orderedItems = items
}
, first = Just $ outboxUrl <> "?page=true"
, last = Just $ outboxUrl <> "?page=true"
}
}
in
A.encode content
Twain.send $ jsonLD response
-- * Inbox
matchInbox :: UserDetails -> Twain.PathPattern
matchInbox details =
fromString ("/" <> details.username <> "/inbox")
handleInbox :: (AnyActivity -> Twain.ResponderM Twain.Response) -> Twain.ResponderM b
handleInbox handle = do
activity <- Twain.fromBody
response <- handle activity
Twain.send response
-- * Other stuff
checkContentTypeAccept :: Twain.Request -> Bool
checkContentTypeAccept request =
case lookup Twain.hAccept request.requestHeaders of
Just bs ->
("application/activity+json" `BS.isInfixOf` bs)
|| ( ("application/ld+json" `BS.isInfixOf` bs)
&& ("profile=\"https://www.w3.org/ns/activitystreams\"" `BS.isInfixOf` bs)
)
Nothing -> False

43
src/Fedi/Routes/Follow.hs Normal file
View file

@ -0,0 +1,43 @@
module Fedi.Routes.Follow where
import Data.Aeson qualified as A
import Fedi.Helpers
import Fedi.Types
import Fedi.UserDetails
import Fedi.Routes.Helpers
import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
-- * Followers
matchFollowers :: UserDetails -> Twain.PathPattern
matchFollowers details =
fromString ("/" <> details.username <> "/followers")
handleFollowers :: UserDetails -> Twain.ResponderM b
handleFollowers details = do
let
collection :: Collection ()
collection =
emptyUnorderedCollection
{ id = Just $ ObjectId $ actorUrl details <> "/followers"
, summary = Just $ fromString $ details.username <> "'s followers"
}
Twain.send $ jsonLD (A.encode collection)
-- * Following
matchFollowing :: UserDetails -> Twain.PathPattern
matchFollowing details =
fromString ("/" <> details.username <> "/following")
handleFollowing :: UserDetails -> Twain.ResponderM b
handleFollowing details = do
let
collection :: Collection ()
collection =
emptyUnorderedCollection
{ id = Just $ ObjectId $ actorUrl details <> "/following"
, summary = Just $ fromString $ details.username <> " is following"
}
Twain.send $ jsonLD (A.encode collection)

View file

@ -0,0 +1,21 @@
module Fedi.Routes.Helpers where
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as BSL
import Web.Twain qualified as Twain
jsonLD :: BSL.ByteString -> Twain.Response
jsonLD =
Twain.raw
Twain.status200
[(Twain.hContentType, "application/activity+json; charset=utf-8")]
checkContentTypeAccept :: Twain.Request -> Bool
checkContentTypeAccept request =
case lookup Twain.hAccept request.requestHeaders of
Just bs ->
("application/activity+json" `BS.isInfixOf` bs)
|| ( ("application/ld+json" `BS.isInfixOf` bs)
&& ("profile=\"https://www.w3.org/ns/activitystreams\"" `BS.isInfixOf` bs)
)
Nothing -> False

19
src/Fedi/Routes/Inbox.hs Normal file
View file

@ -0,0 +1,19 @@
module Fedi.Routes.Inbox where
import Fedi.Types
import Fedi.UserDetails
import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
-- * Inbox
matchInbox :: UserDetails -> Twain.PathPattern
matchInbox details =
fromString ("/" <> details.username <> "/inbox")
handleInbox :: (AnyActivity -> Twain.ResponderM Twain.Response) -> Twain.ResponderM b
handleInbox handle = do
activity <- Twain.fromBody
-- sig <- Twain.header "Signature"
response <- handle activity
Twain.send response

51
src/Fedi/Routes/Notes.hs Normal file
View file

@ -0,0 +1,51 @@
module Fedi.Routes.Notes where
import Data.Aeson qualified as A
import Fedi.Types
import Fedi.UserDetails
import Fedi.Routes.Helpers
import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
-- * Create
matchCreateNote :: UserDetails -> Twain.PathPattern
matchCreateNote details = fromString ("/" <> details.username <> "/notes/:note_id/create")
handleCreateNote :: UserDetails -> [Create] -> Twain.ResponderM a
handleCreateNote details items = do
noteId <- Twain.param "note_id"
let
createUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/notes/"
<> noteId
<> "/create"
let
content =
find (\create -> create.id == Just (ObjectId createUrl)) items
Twain.send $ jsonLD (A.encode content)
-- * Note
matchNote :: UserDetails -> Twain.PathPattern
matchNote details = fromString ("/" <> details.username <> "/notes/:note_id")
handleNote :: UserDetails -> [Note] -> Twain.ResponderM a
handleNote details items = do
noteId <- Twain.param "note_id"
let
noteUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/notes/"
<> noteId
let
content =
find (\note -> note.id == Just (ObjectId noteUrl)) items
Twain.send $ jsonLD (A.encode content)

66
src/Fedi/Routes/Outbox.hs Normal file
View file

@ -0,0 +1,66 @@
module Fedi.Routes.Outbox where
import Data.Aeson qualified as A
import Fedi.Helpers
import Fedi.Types
import Fedi.UserDetails
import Fedi.Routes.Helpers
import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
-- * Outbox
matchOutbox :: UserDetails -> Twain.PathPattern
matchOutbox details =
fromString ("/" <> details.username <> "/outbox")
handleOutbox :: UserDetails -> [AnyActivity] -> Twain.ResponderM b
handleOutbox details items = do
isPage <- Twain.queryParamMaybe "page"
let
outboxUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/outbox"
response =
case isPage of
Just True ->
let
empty = emptyOrderedCollectionPage outboxUrl
content :: OutboxPage
content =
empty
{ id = Just $ ObjectId $ outboxUrl <> "?page=true"
, otype =
empty.otype
{ ctype =
empty.otype.ctype
{ partOf = outboxUrl
, porderedItems = items
}
}
}
in
A.encode content
_ ->
let
content :: Outbox
content =
emptyOrderedCollection
{ id = Just $ ObjectId outboxUrl
, summary = Just $ fromString $ details.username <> "'s notes"
, otype =
emptyOrderedCollection.otype
{ ctype =
emptyOrderedCollection.otype.ctype
{ orderedItems = items
}
, first = Just $ outboxUrl <> "?page=true"
, last = Just $ outboxUrl <> "?page=true"
}
}
in
A.encode content
Twain.send $ jsonLD response

36
src/Fedi/Routes/User.hs Normal file
View file

@ -0,0 +1,36 @@
module Fedi.Routes.User where
import Data.Aeson qualified as A
import Fedi.Helpers
import Fedi.UserDetails
import Fedi.Webfinger
import Fedi.Routes.Helpers
import Web.Twain qualified as Twain
import Web.Twain.Types qualified as Twain
-- * User
matchUser :: UserDetails -> Twain.PathPattern
matchUser details = fromString ("/" <> details.username)
handleUser :: UserDetails -> Twain.ResponderM a
handleUser details = do
let
content = makeActor details
Twain.send $ jsonLD (A.encode content)
-- * Webfinger
matchWebfinger :: Twain.PathPattern
matchWebfinger = "/.well-known/webfinger"
handleWebfinger :: UserDetails -> Twain.ResponderM b
handleWebfinger details = do
resource <- Twain.param "resource"
let
webfinger = makeWebfinger details
if resource == ppSubject webfinger.subject
then do
Twain.send $ jsonLD (A.encode webfinger)
else do
Twain.next

View file

@ -37,7 +37,7 @@ data Object typ
mediaType :: Maybe MediaType mediaType :: Maybe MediaType
-- , duration :: Maybe String -- , duration :: Maybe String
} }
deriving (Show) deriving (Show, Eq)
class ToObject a where class ToObject a where
toObject :: a -> [A.Pair] toObject :: a -> [A.Pair]
@ -101,13 +101,13 @@ newtype ObjectId = ObjectId {unwrap :: String}
deriving (Show, Eq, A.FromJSON, A.ToJSON) via String deriving (Show, Eq, A.FromJSON, A.ToJSON) via String
newtype Link = Link {unwrap :: Url} newtype Link = Link {unwrap :: Url}
deriving (Show, A.FromJSON, A.ToJSON) via Url deriving (Show, Eq, A.FromJSON, A.ToJSON) via Url
data LinkOrObject a data LinkOrObject a
= LLink Link = LLink Link
| OObject (Object a) | OObject (Object a)
| CCollection [LinkOrObject a] | CCollection [LinkOrObject a]
deriving (Show) deriving (Show, Eq)
instance (A.FromJSON a) => A.FromJSON (LinkOrObject a) where instance (A.FromJSON a) => A.FromJSON (LinkOrObject a) where
parseJSON = \case parseJSON = \case
@ -130,7 +130,7 @@ instance (ToObject o) => A.ToJSON (LinkOrObject o) where
data AnyMedia data AnyMedia
= ImageMedia Image = ImageMedia Image
deriving (Show) deriving (Show, Eq)
instance A.ToJSON AnyMedia where instance A.ToJSON AnyMedia where
toJSON = \case toJSON = \case
@ -142,7 +142,7 @@ instance A.FromJSON AnyMedia where
type Image = Object TypeImage type Image = Object TypeImage
data TypeImage = TypeImage deriving (Show) data TypeImage = TypeImage deriving (Show, Eq)
instance ToObject TypeImage where instance ToObject TypeImage where
toObject TypeImage = toObject TypeImage =
@ -158,7 +158,7 @@ instance A.FromJSON TypeImage where
data Name data Name
= StringName String = StringName String
| ObjectName (LinkOrObject Actor) | ObjectName (LinkOrObject Actor)
deriving (Show) deriving (Show, Eq)
instance A.ToJSON Name where instance A.ToJSON Name where
toJSON = \case toJSON = \case
@ -184,7 +184,7 @@ data TypeNote
, replies :: Collection Note , replies :: Collection Note
, sensitive :: Bool , sensitive :: Bool
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypeNote where instance ToObject TypeNote where
toObject note = toObject note =
@ -209,16 +209,16 @@ data TypeTag
= TypeTag = TypeTag
{ href :: Url { href :: Url
} }
deriving (Show) deriving (Show, Eq)
type Preview = Object TypePreview type Preview = Object TypePreview
data TypePreview = TypePreview data TypePreview = TypePreview
deriving (Show) deriving (Show, Eq)
type Share = Object TypeShare type Share = Object TypeShare
data TypeShare = TypeShare deriving (Show) data TypeShare = TypeShare deriving (Show, Eq)
instance ToObject TypeShare where instance ToObject TypeShare where
toObject TypeShare = toObject TypeShare =
@ -230,7 +230,7 @@ instance A.FromJSON TypeShare where
A.withObject "TypeShare" \value -> do A.withObject "TypeShare" \value -> do
typ :: String <- value A..: "type" typ :: String <- value A..: "type"
guard (typ == "Share") guard (typ == "Share")
pure TypeShare {..} pure TypeShare
-- * Activities -- * Activities
@ -248,7 +248,7 @@ data TypeActivity t
-- , result :: Maybe String -- , result :: Maybe String
-- , instrument :: Maybe String -- , instrument :: Maybe String
} }
deriving (Show) deriving (Show, Eq)
instance (ToObject t) => ToObject (TypeActivity t) where instance (ToObject t) => ToObject (TypeActivity t) where
toObject activity = toObject activity =
@ -284,7 +284,7 @@ data TypeCreate
= TypeCreate = TypeCreate
{ object :: Note { object :: Note
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypeCreate where instance ToObject TypeCreate where
toObject create = toObject create =
@ -304,9 +304,9 @@ type Follow = Object (TypeActivity TypeFollow)
data TypeFollow data TypeFollow
= TypeFollow = TypeFollow
{ object :: Actor { object :: LinkOrObject Actor
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypeFollow where instance ToObject TypeFollow where
toObject follow = toObject follow =
@ -325,7 +325,7 @@ instance A.FromJSON TypeFollow where
-- --
type Like = Object (TypeActivity TypeLike) type Like = Object (TypeActivity TypeLike)
data TypeLike = TypeLike deriving (Show) data TypeLike = TypeLike deriving (Show, Eq)
instance ToObject TypeLike where instance ToObject TypeLike where
toObject TypeLike = toObject TypeLike =
@ -337,7 +337,7 @@ instance A.FromJSON TypeLike where
A.withObject "TypeLike" \value -> do A.withObject "TypeLike" \value -> do
typ :: String <- value A..: "type" typ :: String <- value A..: "type"
guard (typ == "Like") guard (typ == "Like")
pure TypeLike {..} pure TypeLike
data AnyActivity data AnyActivity
= -- ActivityAnnounce Announce = -- ActivityAnnounce Announce
@ -346,7 +346,7 @@ data AnyActivity
| -- | ActivityLike Like | -- | ActivityLike Like
ActivityAccept Accept ActivityAccept Accept
| ActivityReject Reject | ActivityReject Reject
deriving (Show) deriving (Show, Eq)
instance A.ToJSON AnyActivity where instance A.ToJSON AnyActivity where
toJSON = \case toJSON = \case
@ -376,7 +376,7 @@ data TypeAccept
= TypeAccept = TypeAccept
{ object :: AnyActivity { object :: AnyActivity
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypeAccept where instance ToObject TypeAccept where
toObject obj = toObject obj =
@ -398,7 +398,7 @@ data TypeReject
= TypeReject = TypeReject
{ object :: AnyActivity { object :: AnyActivity
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypeReject where instance ToObject TypeReject where
toObject obj = toObject obj =
@ -418,7 +418,9 @@ instance A.FromJSON TypeReject where
-- | An Actor is an object that has one of the following types. -- | An Actor is an object that has one of the following types.
-- <https://www.w3.org/TR/activitystreams-vocabulary/#actor-types> -- <https://www.w3.org/TR/activitystreams-vocabulary/#actor-types>
data Actor = ActorPerson Person deriving (Show) data Actor
= ActorPerson Person
deriving (Show, Eq)
instance A.FromJSON Actor where instance A.FromJSON Actor where
parseJSON = parseJSON =
@ -457,7 +459,7 @@ data TypePerson
, following :: Link , following :: Link
, followers :: Link , followers :: Link
} }
deriving (Show) deriving (Show, Eq)
instance ToObject TypePerson where instance ToObject TypePerson where
toObject person = toObject person =
@ -476,7 +478,7 @@ data PublicKey
, owner :: Url , owner :: Url
, publicKeyPem :: Pem , publicKeyPem :: Pem
} }
deriving (Show) deriving (Show, Eq)
instance A.ToJSON PublicKey where instance A.ToJSON PublicKey where
toJSON pk = toJSON pk =
@ -513,7 +515,7 @@ data CollectionType t
, last :: Maybe Url , last :: Maybe Url
, current :: Maybe Url , current :: Maybe Url
} }
deriving (Show) deriving (Show, Eq)
instance (ToObject t) => ToObject (CollectionType t) where instance (ToObject t) => ToObject (CollectionType t) where
toObject collection = toObject collection =
@ -539,7 +541,7 @@ data Unordered e
= UnorderedCollectionType = UnorderedCollectionType
{ items :: [e] { items :: [e]
} }
deriving (Show) deriving (Show, Eq)
instance (A.ToJSON e) => ToObject (Unordered e) where instance (A.ToJSON e) => ToObject (Unordered e) where
toObject collection = toObject collection =
@ -560,7 +562,7 @@ data Ordered e
= OrderedCollectionType = OrderedCollectionType
{ orderedItems :: [e] { orderedItems :: [e]
} }
deriving (Show) deriving (Show, Eq)
instance (A.ToJSON e) => ToObject (Ordered e) where instance (A.ToJSON e) => ToObject (Ordered e) where
toObject collection = toObject collection =
@ -584,7 +586,7 @@ data OrderedPage e
, next :: Maybe Url , next :: Maybe Url
, porderedItems :: [e] , porderedItems :: [e]
} }
deriving (Show) deriving (Show, Eq)
instance (A.ToJSON e) => ToObject (OrderedPage e) where instance (A.ToJSON e) => ToObject (OrderedPage e) where
toObject page = toObject page =

View file

@ -20,7 +20,7 @@ type Domain = String
type Username = String type Username = String
newtype Pem = Pem T.Text newtype Pem = Pem T.Text
deriving (Show) deriving (Show, Eq)
deriving (A.FromJSON) via T.Text deriving (A.FromJSON) via T.Text
instance A.ToJSON Pem where instance A.ToJSON Pem where
@ -37,7 +37,7 @@ data UserDetails
, publicPem :: Pem , publicPem :: Pem
, privatePem :: FilePath , privatePem :: FilePath
} }
deriving (Show, Generic, A.FromJSON) deriving (Show, Eq, Generic, A.FromJSON)
actorUrl :: UserDetails -> Url actorUrl :: UserDetails -> Url
actorUrl details = actorUrl details =