Compare commits

..

No commits in common. "4db2fe8fae314b7ec08e92bc5de8e90b9d13e899" and "776e3d7e4eec6d6a270cae2391ead9ddc26b509a" have entirely different histories.

6 changed files with 98 additions and 135 deletions

View file

@ -41,8 +41,8 @@ mkFediApp :: IO Twain.Application
mkFediApp = do mkFediApp = do
detailsFile <- lookupEnv "FEDI_DETAILS" detailsFile <- lookupEnv "FEDI_DETAILS"
<&> maybe (error "missing FEDI_DETAILS") id <&> maybe (error "missing FEDI_DETAILS") id
details <- A.eitherDecodeFileStrict detailsFile details <- A.decodeFileStrict detailsFile
<&> either (\err -> error $ "could not read file '" <> detailsFile <> "'.\n" <> err) id <&> maybe (error $ "could not read file '" <> detailsFile <> "'.") id
pure $ foldr ($) pure $ foldr ($)
(Twain.notFound $ Twain.send $ Twain.text "Error: not found.") (Twain.notFound $ Twain.send $ Twain.text "Error: not found.")

View file

@ -52,7 +52,6 @@ library
GeneralizedNewtypeDeriving GeneralizedNewtypeDeriving
DeriveAnyClass DeriveAnyClass
DerivingStrategies DerivingStrategies
DuplicateRecordFields
executable fedi executable fedi
import: warnings import: warnings

View file

@ -1,6 +1,5 @@
module Fedi.Activity where module Fedi.Activity where
import Data.Maybe (listToMaybe)
import Data.Aeson qualified as A import Data.Aeson qualified as A
import Data.Text qualified as T import Data.Text qualified as T
import Fedi.Types import Fedi.Types
@ -9,7 +8,7 @@ import Data.Time (UTCTime)
data Activity data Activity
= Create = Create
{ id :: ActivityUrl { createId :: ActivityUrl
, actor :: ActorId , actor :: ActorId
, object :: Object , object :: Object
} }
@ -21,13 +20,13 @@ data Object
data Note data Note
= Note = Note
{ id :: NoteId { noteId :: NoteId
, published :: UTCTime , notePublished :: UTCTime
, actor :: ActorId , noteActor :: ActorId
, content :: T.Text , noteContent :: T.Text
, name :: Maybe String , noteName :: Maybe String
, url :: Maybe Url , noteUrl :: Maybe Url
, replies :: Collection Unordered Note , noteReplies :: Collection Unordered Note
} }
type NoteId = Url type NoteId = Url
@ -36,37 +35,29 @@ type Following = [Actor]
type Inbox = Collection Ordered Activity type Inbox = Collection Ordered Activity
type Outbox = Collection Unordered Activity type Outbox = Collection Unordered Activity
type OutboxPage = OrderedCollectionPage Activity
data Ordered data Ordered
data Unordered data Unordered
data OrderedCollectionPage a
= OrderedCollectionPage
{ id :: Url
, partOf :: Url
, orderedItems :: [a]
}
data Collection order a data Collection order a
= Collection = Collection
{ summary :: String { collectionSummary :: String
, items :: [a] , collectionItems :: [a]
} }
instance A.ToJSON Note where instance A.ToJSON Note where
toJSON note = toJSON note =
A.object $ A.object $
[ "type" A..= ("Note" :: String) [ "type" A..= ("Note" :: String)
, "id" A..= note.id , "id" A..= note.noteId
, "published" A..= note.published , "published" A..= note.notePublished
, "attributedTo" A..= note.actor , "attributedTo" A..= note.noteActor
, "content" A..= note.content , "content" A..= note.noteContent
, "name" A..= note.name , "name" A..= note.noteName
, "replies" A..= note.replies , "replies" A..= note.noteReplies
] ]
<> [ "name" A..= name | Just name <- [note.name] ] <> [ "name" A..= name | Just name <- [note.noteName] ]
<> [ "url" A..= url | Just url <- [note.url] ] <> [ "url" A..= url | Just url <- [note.noteUrl] ]
instance A.ToJSON Object where instance A.ToJSON Object where
toJSON = \case toJSON = \case
@ -80,7 +71,7 @@ instance A.ToJSON Activity where
[ "https://www.w3.org/ns/activitystreams" :: String [ "https://www.w3.org/ns/activitystreams" :: String
] ]
, "type" A..= ("Create" :: String) , "type" A..= ("Create" :: String)
, "id" A..= create.id , "id" A..= create.createId
, "actor" A..= create.actor , "actor" A..= create.actor
, "object" A..= create.object , "object" A..= create.object
] ]
@ -92,11 +83,9 @@ instance A.ToJSON a => A.ToJSON (Collection Ordered a) where
[ "https://www.w3.org/ns/activitystreams" :: String [ "https://www.w3.org/ns/activitystreams" :: String
] ]
, "type" A..= ("OrderedCollection" :: String) , "type" A..= ("OrderedCollection" :: String)
, "summary" A..= collection.summary , "summary" A..= collection.collectionSummary
, "totalItems" A..= length collection.items , "totalItems" A..= length collection.collectionItems
, "orderedItems" A..= collection.items , "orderedItems" A..= collection.collectionItems
, "first" A..= listToMaybe (take 1 collection.items)
, "last" A..= listToMaybe (take 1 $ reverse collection.items)
] ]
instance A.ToJSON a => A.ToJSON (Collection Unordered a) where instance A.ToJSON a => A.ToJSON (Collection Unordered a) where
@ -106,20 +95,24 @@ instance A.ToJSON a => A.ToJSON (Collection Unordered a) where
[ "https://www.w3.org/ns/activitystreams" :: String [ "https://www.w3.org/ns/activitystreams" :: String
] ]
, "type" A..= ("Collection" :: String) , "type" A..= ("Collection" :: String)
, "summary" A..= collection.summary , "summary" A..= collection.collectionSummary
, "totalItems" A..= length collection.items , "totalItems" A..= length collection.collectionItems
, "items" A..= collection.items , "items" A..= collection.collectionItems
, "first" A..= listToMaybe (take 1 collection.items)
, "last" A..= listToMaybe (take 1 $ reverse collection.items)
] ]
instance A.ToJSON a => A.ToJSON (OrderedCollectionPage a) where {-
toJSON collection = "@context": "https://www.w3.org/ns/activitystreams",
A.object "summary": "Sally's notes",
[ "@context" A..= "type": "OrderedCollection",
[ "https://www.w3.org/ns/activitystreams" :: String "totalItems": 2,
] "orderedItems": [
, "type" A..= ("OrderedCollectionPage" :: String) {
, "partOf" A..= collection.partOf "type": "Note",
, "orderedItems" A..= collection.orderedItems "name": "A Simple Note"
},
{
"type": "Note",
"name": "Another Simple Note"
}
] ]
-}

View file

@ -5,12 +5,12 @@ import Fedi.Types
data Actor data Actor
= Actor = Actor
{ id :: Url { actorId :: Url
, name :: String , actorName :: String
, preferredUsername :: String , actorPreferredUsername :: String
, summary :: String , actorSummary :: String
, icon :: Url , actorIcon :: Url
, publicKey :: PublicKey , actorPublicKey :: PublicKey
} }
deriving Show deriving Show
@ -22,9 +22,9 @@ data ActorType
data PublicKey data PublicKey
= PublicKey = PublicKey
{ id :: Url { pkId :: Url
, owner :: Url , pkOwner :: Url
, publicKeyPem :: Pem , pkPublicKeyPem :: Pem
} }
deriving Show deriving Show
@ -34,16 +34,16 @@ makeActor details =
url = "https://" <> details.domain url = "https://" <> details.domain
actorUrl = url <> "/" <> details.username actorUrl = url <> "/" <> details.username
in Actor in Actor
{ id = actorUrl { actorId = actorUrl
, name = details.name , actorName = details.name
, preferredUsername = details.username , actorPreferredUsername = details.username
, summary = details.summary , actorSummary = details.summary
, icon = details.icon , actorIcon = details.icon
, publicKey = , actorPublicKey =
PublicKey PublicKey
{ id = actorUrl <> "#main-key" { pkId = actorUrl <> "#main-key"
, owner = actorUrl , pkOwner = actorUrl
, publicKeyPem = details.publicPem , pkPublicKeyPem = details.publicPem
} }
} }
@ -54,19 +54,19 @@ instance A.ToJSON Actor where
[ "https://www.w3.org/ns/activitystreams" :: String [ "https://www.w3.org/ns/activitystreams" :: String
, "https://w3id.org/security/v1" , "https://w3id.org/security/v1"
] ]
, "id" A..= actor.id , "id" A..= actor.actorId
, "type" A..= Person , "type" A..= Person
, "name" A..= actor.name , "name" A..= actor.actorName
, "preferredUsername" A..= actor.preferredUsername , "preferredUsername" A..= actor.actorPreferredUsername
, "summary" A..= actor.summary , "summary" A..= actor.actorSummary
, "icon" A..= A.object , "icon" A..= A.object
[ "type" A..= ("Image" :: String) [ "type" A..= ("Image" :: String)
, "mediaType" A..= ("image/png" :: String) , "mediaType" A..= ("image/png" :: String)
, "url" A..= actor.icon , "url" A..= actor.actorIcon
] ]
, "inbox" A..= (actor.id <> "/inbox") , "inbox" A..= (actor.actorId <> "/inbox")
, "outbox" A..= (actor.id <> "/outbox") , "outbox" A..= (actor.actorId <> "/outbox")
, "publicKey" A..= actor.publicKey , "publicKey" A..= actor.actorPublicKey
] ]
instance A.ToJSON ActorType where instance A.ToJSON ActorType where
@ -75,7 +75,7 @@ instance A.ToJSON ActorType where
instance A.ToJSON PublicKey where instance A.ToJSON PublicKey where
toJSON pk = toJSON pk =
A.object A.object
[ "id" A..= pk.id [ "id" A..= pk.pkId
, "owner" A..= pk.owner , "owner" A..= pk.pkOwner
, "publicKeyPem" A..= pk.publicKeyPem , "publicKeyPem" A..= pk.pkPublicKeyPem
] ]

View file

@ -18,7 +18,7 @@ routes details =
handleUser details handleUser details
, Twain.get (matchOutbox details) do , Twain.get (matchOutbox details) do
handleOutbox details [] handleOutbox details
, Twain.get matchWebfinger do , Twain.get matchWebfinger do
handleWebfinger details handleWebfinger details
@ -49,7 +49,7 @@ handleWebfinger :: UserDetails -> Twain.ResponderM b
handleWebfinger details = do handleWebfinger details = do
resource <- Twain.param "resource" resource <- Twain.param "resource"
let webfinger = makeWebfinger details let webfinger = makeWebfinger details
if resource == ppSubject webfinger.subject if resource == ppSubject webfinger.wfSubject
then do then do
Twain.send $ jsonLD (A.encode webfinger) Twain.send $ jsonLD (A.encode webfinger)
else do else do
@ -61,36 +61,7 @@ matchOutbox :: UserDetails -> Twain.PathPattern
matchOutbox details = matchOutbox details =
fromString ("/" <> details.username <> "/outbox") fromString ("/" <> details.username <> "/outbox")
handleOutbox :: UserDetails -> [Activity] -> Twain.ResponderM b handleOutbox :: UserDetails -> Twain.ResponderM b
handleOutbox details items = do handleOutbox details = do
isPage <- Twain.queryParamMaybe "page" let content = Collection { collectionSummary = details.username <> "'s notes", collectionItems = [] } :: Outbox
let Twain.send $ jsonLD (A.encode content)
outboxUrl =
"https://"
<> details.domain
<> "/"
<> details.username
<> "/outbox"
let
response =
case isPage of
Just True ->
let
content :: OutboxPage
content =
OrderedCollectionPage
{ id = outboxUrl <> "?page=true"
, partOf = outboxUrl
, orderedItems = items
}
in A.encode content
_ ->
let
content :: Outbox
content =
Collection
{ summary = details.username <> "'s notes"
, items = items
}
in A.encode content
Twain.send $ jsonLD response

View file

@ -6,27 +6,27 @@ import Fedi.Types
data Webfinger data Webfinger
= Webfinger = Webfinger
{ subject :: Subject { wfSubject :: Subject
, links :: [Link] , wfLinks :: [Link]
} }
deriving Show deriving Show
data Subject data Subject
= Subject = Subject
{ username :: Username { subjectUsername :: Username
, domain :: Domain , subjectDomain :: Domain
} }
deriving Show deriving Show
ppSubject :: Subject -> String ppSubject :: Subject -> String
ppSubject subject = ppSubject subject =
"acct:" <> subject.username <> "@" <> subject.domain "acct:" <> subject.subjectUsername <> "@" <> subject.subjectDomain
data Link data Link
= Link = Link
{ rel :: Rel { linkRel :: Rel
, type_ :: LinkType , linkType :: LinkType
, href :: Url , linkHref :: Url
} }
deriving Show deriving Show
@ -36,15 +36,15 @@ makeWebfinger details =
url = "https://" <> details.domain url = "https://" <> details.domain
in in
Webfinger Webfinger
{ subject = Subject { wfSubject = Subject
{ username = details.username { subjectUsername = details.username
, domain = details.domain , subjectDomain = details.domain
} }
, links = , wfLinks =
[ Link [ Link
{ rel = Self { linkRel = Self
, type_ = ActivityJson , linkType = ActivityJson
, href = url <> "/" <> details.username , linkHref = url <> "/" <> details.username
} }
] ]
} }
@ -55,8 +55,8 @@ makeWebfinger details =
instance A.ToJSON Webfinger where instance A.ToJSON Webfinger where
toJSON webfinger = toJSON webfinger =
A.object A.object
[ "subject" A..= webfinger.subject [ "subject" A..= webfinger.wfSubject
, "links" A..= webfinger.links , "links" A..= webfinger.wfLinks
] ]
instance A.ToJSON Subject where instance A.ToJSON Subject where
@ -66,7 +66,7 @@ instance A.ToJSON Subject where
instance A.ToJSON Link where instance A.ToJSON Link where
toJSON link = toJSON link =
A.object A.object
[ "rel" A..= link.rel [ "rel" A..= link.linkRel
, "type" A..= link.type_ , "type" A..= link.linkType
, "href" A..= link.href , "href" A..= link.linkHref
] ]