basic object parsing

This commit is contained in:
me 2024-12-17 10:46:59 +02:00
parent c17f1ef53e
commit 77361d72f3
5 changed files with 230 additions and 27 deletions

View file

@ -84,7 +84,6 @@ migrateUp = \case
) )
|] |]
pure () pure ()
"follower" -> do "follower" -> do
[] <- [] <-
DB.run DB.run
@ -95,7 +94,6 @@ migrateUp = \case
) )
|] |]
pure () pure ()
name -> error $ "unexpected migration: " <> show name name -> error $ "unexpected migration: " <> show name
migrateDown :: (HasCallStack) => DB.MigrationName -> DB.SQLite () migrateDown :: (HasCallStack) => DB.MigrationName -> DB.SQLite ()

View file

@ -19,7 +19,6 @@ routes db detailsFile =
Twain.send $ Twain.send $
Twain.redirect302 $ Twain.redirect302 $
fromString ("/" <> details.username) fromString ("/" <> details.username)
, -- Match actor , -- Match actor
Twain.get (Fedi.matchUser $ unsafePerformIO $ fetchUserDetails detailsFile) do Twain.get (Fedi.matchUser $ unsafePerformIO $ fetchUserDetails detailsFile) do
request <- Twain.request request <- Twain.request
@ -41,6 +40,20 @@ routes db detailsFile =
details <- liftIO $ fetchUserDetails detailsFile details <- liftIO $ fetchUserDetails detailsFile
notes <- map noteToCreate <$> liftIO db.getNotes notes <- map noteToCreate <$> liftIO db.getNotes
Fedi.handleCreateNote details notes
, -- Match inbox
Twain.get (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
let
handle activity = do
liftIO (print activity)
pure $ Fedi.jsonLD $ A.encode activity
Fedi.handleInbox handle
, -- Match Create object
Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
details <- liftIO $ fetchUserDetails detailsFile
notes <- map noteToCreate <$> liftIO db.getNotes
Fedi.handleCreateNote details notes Fedi.handleCreateNote details notes
, -- Match Note object , -- Match Note object
Twain.get (Fedi.matchNote $ unsafePerformIO $ fetchUserDetails detailsFile) do Twain.get (Fedi.matchNote $ unsafePerformIO $ fetchUserDetails detailsFile) do

View file

@ -14,7 +14,11 @@ import Web.Twain.Types qualified as Twain
routes :: UserDetails -> [Twain.Middleware] routes :: UserDetails -> [Twain.Middleware]
routes details = routes details =
[ Twain.get (matchUser details) do [ Twain.get "/" do
Twain.send $
Twain.redirect302 $
fromString ("/" <> details.username)
, Twain.get (matchUser details) do
handleUser details handleUser details
, Twain.get (matchOutbox details) do , Twain.get (matchOutbox details) do
handleOutbox details [] handleOutbox details []
@ -22,10 +26,7 @@ routes details =
handleCreateNote details [] handleCreateNote details []
, Twain.get (matchNote details) do , Twain.get (matchNote details) do
handleNote details [] handleNote details []
, -- , Twain.post (matchInbox details) do , Twain.get matchWebfinger do
-- handleInbox details undefined
Twain.get matchWebfinger do
handleWebfinger details handleWebfinger details
] ]
@ -164,14 +165,15 @@ handleOutbox details items = do
-- * Inbox -- * Inbox
-- matchInbox :: UserDetails -> Twain.PathPattern matchInbox :: UserDetails -> Twain.PathPattern
-- matchInbox details = matchInbox details =
-- fromString ("/" <> details.username <> "/inbox") fromString ("/" <> details.username <> "/inbox")
--
-- handleInbox :: UserDetails -> (Activity -> Twain.ResponderM b) -> Twain.ResponderM b handleInbox :: (AnyActivity -> Twain.ResponderM Twain.Response) -> Twain.ResponderM b
-- handleInbox _details _handle = do handleInbox handle = do
-- let response = undefined activity <- Twain.fromBody
-- Twain.send $ jsonLD response response <- handle activity
Twain.send response
-- * Other stuff -- * Other stuff

View file

@ -1,9 +1,13 @@
{-# LANGUAGE RecordWildCards #-}
module Fedi.Types where module Fedi.Types where
import Control.Monad (guard)
import Data.Aeson qualified as A import Data.Aeson qualified as A
import Data.Aeson.Types qualified as A import Data.Aeson.Types qualified as A
import Data.Text qualified as T import Data.Text qualified as T
import Fedi.UserDetails import Fedi.UserDetails
import Prelude hiding (id, last)
-- | An Object is what everything is here. -- | An Object is what everything is here.
-- <https://www.w3.org/TR/activitystreams-vocabulary/#object-types> -- <https://www.w3.org/TR/activitystreams-vocabulary/#object-types>
@ -69,6 +73,30 @@ instance (ToObject a) => ToObject (Object a) where
] ]
] ]
instance (A.FromJSON a) => A.FromJSON (Object a) where
parseJSON object = do
otype <- A.parseJSON object
flip (A.withObject "Object") object $ \v -> do
id <- v A..:? "id"
content <- v A..:? "content"
published <- v A..:? "published"
replies <- v A..:? "replies"
attachment <- v A..:? "attachment"
attributedTo <- v A..:? "attributedTo"
tag <- pure Nothing -- v A..:? "tag"
to <- v A..:? "to"
cc <- v A..:? "cc"
inReplyTo <- v A..:? "inReplyTo"
url <- v A..:? "url"
name <- v A..:? "name"
icon <- v A..:? "icon"
image <- v A..:? "image"
preview <- pure Nothing -- v A..:? "preview"
summary <- v A..:? "summary"
updated <- v A..:? "updated"
mediaType <- v A..:? "mediaType"
pure $ Object {..}
newtype ObjectId = ObjectId {unwrap :: String} newtype ObjectId = ObjectId {unwrap :: String}
deriving (Show, Eq, A.FromJSON, A.ToJSON) via String deriving (Show, Eq, A.FromJSON, A.ToJSON) via String
@ -81,10 +109,16 @@ data LinkOrObject a
| CCollection [LinkOrObject a] | CCollection [LinkOrObject a]
deriving (Show) deriving (Show)
instance (A.FromJSON a) => A.FromJSON (LinkOrObject a) where
parseJSON = \case
A.String str -> pure $ LLink (Link $ T.unpack str)
A.Array objs -> CCollection <$> traverse A.parseJSON (toList objs)
value -> OObject <$> A.parseJSON value
getAttributedTo :: LinkOrObject a -> Link getAttributedTo :: LinkOrObject a -> Link
getAttributedTo = \case getAttributedTo = \case
LLink link -> link LLink link -> link
OObject obj -> Link (maybe (ObjectId "") id obj.id).unwrap OObject obj -> Link (fromMaybe (ObjectId "") obj.id).unwrap
CCollection list -> CCollection list ->
maybe (Link "") getAttributedTo (listToMaybe list) maybe (Link "") getAttributedTo (listToMaybe list)
@ -102,6 +136,10 @@ instance A.ToJSON AnyMedia where
toJSON = \case toJSON = \case
ImageMedia obj -> A.toJSON obj ImageMedia obj -> A.toJSON obj
instance A.FromJSON AnyMedia where
parseJSON value =
ImageMedia <$> A.parseJSON value
type Image = Object TypeImage type Image = Object TypeImage
data TypeImage = TypeImage deriving (Show) data TypeImage = TypeImage deriving (Show)
@ -110,6 +148,13 @@ instance ToObject TypeImage where
toObject TypeImage = toObject TypeImage =
["type" A..= ("Image" :: String)] ["type" A..= ("Image" :: String)]
instance A.FromJSON TypeImage where
parseJSON =
A.withObject "TypeImage" \value -> do
(i :: String) <- value A..: "type"
guard (i == "Image")
pure TypeImage
data Name data Name
= StringName String = StringName String
| ObjectName (LinkOrObject Actor) | ObjectName (LinkOrObject Actor)
@ -120,6 +165,11 @@ instance A.ToJSON Name where
StringName str -> A.toJSON str StringName str -> A.toJSON str
ObjectName loo -> A.toJSON loo ObjectName loo -> A.toJSON loo
instance A.FromJSON Name where
parseJSON = \case
A.String str -> pure $ StringName (T.unpack str)
value -> ObjectName <$> A.parseJSON value
type Content = T.Text type Content = T.Text
type MediaType = String type MediaType = String
@ -144,6 +194,15 @@ instance ToObject TypeNote where
, "sensitive" A..= note.sensitive , "sensitive" A..= note.sensitive
] ]
instance A.FromJSON TypeNote where
parseJSON =
A.withObject "TypeNote" \value -> do
likes <- value A..: "likes"
shares <- value A..: "shares"
replies <- value A..: "replies"
sensitive <- value A..: "sensitive"
pure TypeNote {..}
type Tag = Object TypeTag type Tag = Object TypeTag
data TypeTag data TypeTag
@ -166,6 +225,13 @@ instance ToObject TypeShare where
[ "type" A..= ("Share" :: String) [ "type" A..= ("Share" :: String)
] ]
instance A.FromJSON TypeShare where
parseJSON =
A.withObject "TypeShare" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Share")
pure TypeShare {..}
-- * Activities -- * Activities
-- | An Activity is a superset of an Object with one of the following types, -- | An Activity is a superset of an Object with one of the following types,
@ -196,6 +262,15 @@ instance (ToObject t) => ToObject (TypeActivity t) where
] ]
<> toObject activity.atype <> toObject activity.atype
instance (A.FromJSON a) => A.FromJSON (TypeActivity a) where
parseJSON object = do
atype <- A.parseJSON object
flip (A.withObject "TypeActivity") object \value -> do
actor <- value A..: "actor"
target <- value A..: "target"
origin <- value A..: "origin"
pure TypeActivity {..}
-- type Announce = Object (TypeActivity TypeAnnounce) -- type Announce = Object (TypeActivity TypeAnnounce)
-- data TypeAnnounce = TypeAnnounce deriving Show -- data TypeAnnounce = TypeAnnounce deriving Show
-- instance ToObject TypeAnnounce where -- instance ToObject TypeAnnounce where
@ -217,17 +292,36 @@ instance ToObject TypeCreate where
, "object" A..= create.object , "object" A..= create.object
] ]
instance A.FromJSON TypeCreate where
parseJSON =
A.withObject "TypeCreate" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Create")
object <- value A..: "object"
pure TypeCreate {..}
type Follow = Object (TypeActivity TypeFollow) type Follow = Object (TypeActivity TypeFollow)
data TypeFollow data TypeFollow
= TypeFollow = TypeFollow
{ object :: Actor { object :: Actor
} }
deriving Show deriving (Show)
instance ToObject TypeFollow where instance ToObject TypeFollow where
toObject follow = toObject follow =
[ "type" A..= ("Follow" :: String) [ "type" A..= ("Follow" :: String)
, "object" A..= follow.object , "object" A..= follow.object
] ]
instance A.FromJSON TypeFollow where
parseJSON =
A.withObject "TypeFollow" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Follow")
object <- value A..: "object"
pure TypeFollow {..}
-- --
type Like = Object (TypeActivity TypeLike) type Like = Object (TypeActivity TypeLike)
@ -238,12 +332,19 @@ instance ToObject TypeLike where
[ "type" A..= ("Like" :: String) [ "type" A..= ("Like" :: String)
] ]
instance A.FromJSON TypeLike where
parseJSON =
A.withObject "TypeLike" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Like")
pure TypeLike {..}
data AnyActivity data AnyActivity
= -- ActivityAnnounce Announce = -- ActivityAnnounce Announce
ActivityCreate Create ActivityCreate Create
| ActivityFollow Follow | ActivityFollow Follow
-- | ActivityLike Like | -- | ActivityLike Like
| ActivityAccept Accept ActivityAccept Accept
| ActivityReject Reject | ActivityReject Reject
deriving (Show) deriving (Show)
@ -252,39 +353,66 @@ instance A.ToJSON AnyActivity where
-- ActivityAnnounce obj -> A.toJSON obj -- ActivityAnnounce obj -> A.toJSON obj
ActivityCreate obj -> A.toJSON obj ActivityCreate obj -> A.toJSON obj
ActivityFollow obj -> A.toJSON obj ActivityFollow obj -> A.toJSON obj
-- ActivityLike obj -> A.toJSON obj -- ActivityLike obj -> A.toJSON obj
ActivityAccept obj -> A.toJSON obj ActivityAccept obj -> A.toJSON obj
ActivityReject obj -> A.toJSON obj ActivityReject obj -> A.toJSON obj
instance A.FromJSON AnyActivity where
parseJSON value =
flip (A.withObject "AnyActivity") value \v -> do
typ :: String <- v A..: "type"
case typ of
"Create" -> ActivityCreate <$> A.parseJSON value
"Follow" -> ActivityFollow <$> A.parseJSON value
"Accept" -> ActivityAccept <$> A.parseJSON value
"Reject" -> ActivityReject <$> A.parseJSON value
_ -> fail ("Parsing '" <> typ <> "' not yet implemented.")
-- * Accept Reject Add Remove -- * Accept Reject Add Remove
type Accept = Object (TypeActivity TypeAccept) type Accept = Object (TypeActivity TypeAccept)
data TypeAccept data TypeAccept
= TypeAccept = TypeAccept
{ object :: AnyActivity { object :: AnyActivity
} }
deriving Show deriving (Show)
instance ToObject TypeAccept where instance ToObject TypeAccept where
toObject obj = toObject obj =
[ "type" A..= ("Accept" :: String) [ "type" A..= ("Accept" :: String)
, "object" A..= obj.object , "object" A..= obj.object
] ]
instance A.FromJSON TypeAccept where
parseJSON =
A.withObject "TypeAccept" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Accept")
object <- value A..: "object"
pure TypeAccept {..}
type Reject = Object (TypeActivity TypeReject) type Reject = Object (TypeActivity TypeReject)
data TypeReject data TypeReject
= TypeReject = TypeReject
{ object :: AnyActivity { object :: AnyActivity
} }
deriving Show deriving (Show)
instance ToObject TypeReject where instance ToObject TypeReject where
toObject obj = toObject obj =
[ "type" A..= ("Reject" :: String) [ "type" A..= ("Reject" :: String)
, "object" A..= obj.object , "object" A..= obj.object
] ]
instance A.FromJSON TypeReject where
parseJSON =
A.withObject "TypeReject" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Reject")
object <- value A..: "object"
pure TypeReject {..}
-- * Actors -- * Actors
@ -292,6 +420,10 @@ instance ToObject TypeReject where
-- <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)
instance A.FromJSON Actor where
parseJSON =
fmap ActorPerson . A.parseJSON
instance A.ToJSON Actor where instance A.ToJSON Actor where
toJSON = \case toJSON = \case
ActorPerson obj -> A.toJSON obj ActorPerson obj -> A.toJSON obj
@ -303,6 +435,19 @@ instance ToObject Actor where
-- | A Person is an object that has the type 'Person'. -- | A Person is an object that has the type 'Person'.
type Person = Object TypePerson type Person = Object TypePerson
instance A.FromJSON TypePerson where
parseJSON =
A.withObject "Person" \value -> do
typ :: String <- value A..: "type"
guard (typ == "Person")
preferredUsername <- value A..: "preferredUsername"
publicKey <- value A..: "publicKey"
inbox <- value A..: "inbox"
outbox <- value A..: "outbox"
following <- value A..: "following"
followers <- value A..: "followers"
pure TypePerson {..}
data TypePerson data TypePerson
= TypePerson = TypePerson
{ preferredUsername :: String { preferredUsername :: String
@ -341,6 +486,14 @@ instance A.ToJSON PublicKey where
, "publicKeyPem" A..= pk.publicKeyPem , "publicKeyPem" A..= pk.publicKeyPem
] ]
instance A.FromJSON PublicKey where
parseJSON =
A.withObject "PublicKey" \value -> do
pkid <- value A..: "id"
owner <- value A..: "owner"
publicKeyPem <- value A..: "publicKeyPem"
pure PublicKey {..}
-- * Collections -- * Collections
type Collection e = Object (CollectionType (Unordered e)) type Collection e = Object (CollectionType (Unordered e))
@ -373,6 +526,15 @@ instance (ToObject t) => ToObject (CollectionType t) where
] ]
] ]
instance (A.FromJSON t) => A.FromJSON (CollectionType t) where
parseJSON value = do
ctype <- A.parseJSON value
flip (A.withObject "CollectionType") value \v -> do
first <- v A..:? "first"
last <- v A..:? "last"
current <- v A..:? "current"
pure CollectionType {..}
data Unordered e data Unordered e
= UnorderedCollectionType = UnorderedCollectionType
{ items :: [e] { items :: [e]
@ -386,6 +548,14 @@ instance (A.ToJSON e) => ToObject (Unordered e) where
, "items" A..= collection.items , "items" A..= collection.items
] ]
instance (A.FromJSON e) => A.FromJSON (Unordered e) where
parseJSON = do
A.withObject "Unordered" \v -> do
typ :: String <- v A..: "type"
guard (typ == "Collection")
items <- fromMaybe [] <$> v A..:? "items"
pure UnorderedCollectionType {..}
data Ordered e data Ordered e
= OrderedCollectionType = OrderedCollectionType
{ orderedItems :: [e] { orderedItems :: [e]
@ -399,6 +569,14 @@ instance (A.ToJSON e) => ToObject (Ordered e) where
, "orderedItems" A..= collection.orderedItems , "orderedItems" A..= collection.orderedItems
] ]
instance (A.FromJSON e) => A.FromJSON (Ordered e) where
parseJSON = do
A.withObject "Ordered" \v -> do
typ :: String <- v A..: "type"
guard (typ == "OrderedCollection")
orderedItems <- fromMaybe [] <$> v A..:? "orderedItems"
pure OrderedCollectionType {..}
data OrderedPage e data OrderedPage e
= OrderedCollectionPageType = OrderedCollectionPageType
{ partOf :: Url { partOf :: Url
@ -417,3 +595,14 @@ instance (A.ToJSON e) => ToObject (OrderedPage e) where
, "prev" A..= page.prev , "prev" A..= page.prev
, "next" A..= page.next , "next" A..= page.next
] ]
instance (A.FromJSON e) => A.FromJSON (OrderedPage e) where
parseJSON = do
A.withObject "OrderedPage" \v -> do
typ :: String <- v A..: "type"
guard (typ == "OrderedCollectionPage")
partOf <- v A..: "partOf"
prev <- v A..:? "prev"
next <- v A..:? "next"
porderedItems <- fromMaybe [] <$> v A..:? "orderedItems"
pure OrderedCollectionPageType {..}

View file

@ -4,12 +4,13 @@ module Fedi.UserDetails (
) where ) where
import Data.Aeson qualified as A import Data.Aeson qualified as A
import Data.List as Export (find) import Data.Foldable as Export
import Data.Maybe as Export (fromMaybe, listToMaybe, maybeToList) import Data.Maybe as Export (fromMaybe, listToMaybe, maybeToList)
import Data.String as Export (fromString) import Data.String as Export (fromString)
import Data.Text as Export (Text) import Data.Text as Export (Text)
import Data.Text qualified as T import Data.Text qualified as T
import Data.Time as Export (UTCTime) import Data.Time as Export (UTCTime)
import Data.Traversable as Export
import GHC.Generics as Export (Generic) import GHC.Generics as Export (Generic)
type Url = String type Url = String