add insert likes
This commit is contained in:
parent
c684f52e55
commit
22d1ce3764
9 changed files with 316 additions and 50 deletions
155
app/DB.hs
155
app/DB.hs
|
@ -8,13 +8,16 @@ module DB (
|
||||||
DB.Int64,
|
DB.Int64,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Data.Aeson qualified as A
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
|
import Data.Text.Encoding qualified as T
|
||||||
import Data.Typeable
|
import Data.Typeable
|
||||||
import Database.Sqlite.Easy qualified as DB
|
import Database.Sqlite.Easy qualified as DB
|
||||||
import Fedi
|
import Fedi
|
||||||
import GHC.Stack (HasCallStack)
|
import GHC.Stack (HasCallStack)
|
||||||
import Text.RawString.QQ
|
import Text.RawString.QQ
|
||||||
|
import Data.ByteString.Lazy qualified as BSL
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -25,6 +28,7 @@ data DB
|
||||||
{ getNotes :: IO [Note]
|
{ getNotes :: IO [Note]
|
||||||
, getNote :: DB.Int64 -> IO (Maybe Note)
|
, getNote :: DB.Int64 -> IO (Maybe Note)
|
||||||
, insertNote :: NoteEntry -> IO (DB.Int64, Note)
|
, insertNote :: NoteEntry -> IO (DB.Int64, Note)
|
||||||
|
, insertLike :: LikeEntry -> IO DB.Int64
|
||||||
, insertFollower
|
, insertFollower
|
||||||
:: forall a
|
:: forall a
|
||||||
. (Typeable a) => FollowerEntry -> (DB.Int64 -> IO a) -> IO a
|
. (Typeable a) => FollowerEntry -> (DB.Int64 -> IO a) -> IO a
|
||||||
|
@ -62,6 +66,23 @@ data Follower
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
data LikeEntry
|
||||||
|
= LikeEntry
|
||||||
|
{ likeUrl :: Url
|
||||||
|
, likeActorUrl :: Link
|
||||||
|
, likeNoteUrl :: Link
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data DbLike
|
||||||
|
= DbLike
|
||||||
|
{ likeId :: DB.Int64
|
||||||
|
, likeUrl :: ObjectId
|
||||||
|
, likeActorUrl :: Link
|
||||||
|
, likeNoteUrl :: Link
|
||||||
|
}
|
||||||
|
deriving (Show, Fedi.Generic, A.FromJSON)
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
-- * Handler smart constructor
|
-- * Handler smart constructor
|
||||||
|
@ -78,6 +99,8 @@ mkDB connstr details = do
|
||||||
\noteid -> DB.withPool pool (getNoteFromDb noteid)
|
\noteid -> DB.withPool pool (getNoteFromDb noteid)
|
||||||
, insertNote =
|
, insertNote =
|
||||||
\note -> DB.withPool pool (insertNoteToDb (actorUrl details) note)
|
\note -> DB.withPool pool (insertNoteToDb (actorUrl details) note)
|
||||||
|
, insertLike =
|
||||||
|
\like -> DB.withPool pool (insertLikeToDb like)
|
||||||
, insertFollower =
|
, insertFollower =
|
||||||
\follower handle -> DB.withPool pool $ DB.transaction do
|
\follower handle -> DB.withPool pool $ DB.transaction do
|
||||||
id' <- insertFollowerToDb follower
|
id' <- insertFollowerToDb follower
|
||||||
|
@ -101,6 +124,7 @@ migrations :: [DB.MigrationName]
|
||||||
migrations =
|
migrations =
|
||||||
[ "note"
|
[ "note"
|
||||||
, "follower"
|
, "follower"
|
||||||
|
, "like"
|
||||||
]
|
]
|
||||||
|
|
||||||
migrateUp :: (HasCallStack) => DB.MigrationName -> DB.SQLite ()
|
migrateUp :: (HasCallStack) => DB.MigrationName -> DB.SQLite ()
|
||||||
|
@ -129,6 +153,17 @@ migrateUp = \case
|
||||||
)
|
)
|
||||||
|]
|
|]
|
||||||
pure ()
|
pure ()
|
||||||
|
"like" -> do
|
||||||
|
[] <-
|
||||||
|
DB.run
|
||||||
|
[r| create table like(
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
like_url text not null unique,
|
||||||
|
actor_url text not null,
|
||||||
|
note_url text not null
|
||||||
|
)
|
||||||
|
|]
|
||||||
|
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 ()
|
||||||
|
@ -139,6 +174,9 @@ migrateDown = \case
|
||||||
"follower" -> do
|
"follower" -> do
|
||||||
[] <- DB.run "DROP TABLE follower"
|
[] <- DB.run "DROP TABLE follower"
|
||||||
pure ()
|
pure ()
|
||||||
|
"like" -> do
|
||||||
|
[] <- DB.run "DROP TABLE like"
|
||||||
|
pure ()
|
||||||
name -> error $ "unexpected migration: " <> show name
|
name -> error $ "unexpected migration: " <> show name
|
||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -159,6 +197,11 @@ insertNoteToDb actor note = do
|
||||||
[n] <- map decodeNoteRow <$> uncurry DB.runWith (insertNoteSQL actor note)
|
[n] <- map decodeNoteRow <$> uncurry DB.runWith (insertNoteSQL actor note)
|
||||||
pure n
|
pure n
|
||||||
|
|
||||||
|
insertLikeToDb :: LikeEntry -> DB.SQLite DB.Int64
|
||||||
|
insertLikeToDb like = do
|
||||||
|
[n] <- map decodeIntRow <$> uncurry DB.runWith (insertLikeSQL like)
|
||||||
|
pure n
|
||||||
|
|
||||||
insertFollowerToDb :: FollowerEntry -> DB.SQLite DB.Int64
|
insertFollowerToDb :: FollowerEntry -> DB.SQLite DB.Int64
|
||||||
insertFollowerToDb follower = do
|
insertFollowerToDb follower = do
|
||||||
[n] <- map decodeIntRow <$> uncurry DB.runWith (insertFollowerSQL follower)
|
[n] <- map decodeIntRow <$> uncurry DB.runWith (insertFollowerSQL follower)
|
||||||
|
@ -179,16 +222,48 @@ getNotesSQL :: (DB.SQL, [DB.SQLData])
|
||||||
getNotesSQL =
|
getNotesSQL =
|
||||||
( [r|
|
( [r|
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id as nid,
|
||||||
actor || '/notes/' || id,
|
actor || '/notes/' || id,
|
||||||
published,
|
published,
|
||||||
actor,
|
actor,
|
||||||
content,
|
content,
|
||||||
name,
|
name,
|
||||||
inReplyTo,
|
inReplyTo,
|
||||||
|
url,
|
||||||
|
json_group_array(like) FILTER (WHERE like IS NOT NULL) as likes
|
||||||
|
FROM
|
||||||
|
( SELECT
|
||||||
|
note.*,
|
||||||
|
CASE
|
||||||
|
WHEN like.id IS NOT NULL
|
||||||
|
THEN json_object(
|
||||||
|
'likeId',
|
||||||
|
like.id,
|
||||||
|
'likeUrl',
|
||||||
|
like.like_url,
|
||||||
|
'likeActorUrl',
|
||||||
|
like.actor_url,
|
||||||
|
'likeNoteUrl',
|
||||||
|
like.note_url
|
||||||
|
)
|
||||||
|
ELSE NULL
|
||||||
|
END AS like
|
||||||
|
FROM
|
||||||
|
( SELECT * FROM note
|
||||||
|
WHERE inReplyTo IS NULL
|
||||||
|
) as note
|
||||||
|
LEFT JOIN like
|
||||||
|
ON note.url = like.note_url
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
id,
|
||||||
|
actor,
|
||||||
|
published,
|
||||||
|
actor,
|
||||||
|
content,
|
||||||
|
name,
|
||||||
|
inReplyTo,
|
||||||
url
|
url
|
||||||
FROM note
|
|
||||||
WHERE inReplyTo IS NULL
|
|
||||||
ORDER BY published DESC
|
ORDER BY published DESC
|
||||||
|]
|
|]
|
||||||
, []
|
, []
|
||||||
|
@ -205,10 +280,38 @@ getNoteSQL noteid =
|
||||||
content,
|
content,
|
||||||
name,
|
name,
|
||||||
inReplyTo,
|
inReplyTo,
|
||||||
|
url,
|
||||||
|
json_group_array(like) FILTER (WHERE like IS NOT NULL) as likes
|
||||||
|
FROM
|
||||||
|
( SELECT
|
||||||
|
note.*,
|
||||||
|
CASE
|
||||||
|
WHEN like.id IS NOT NULL
|
||||||
|
THEN json_object(
|
||||||
|
'likeId',
|
||||||
|
like.id,
|
||||||
|
'likeUrl',
|
||||||
|
like.like_url,
|
||||||
|
'likeActorUrl',
|
||||||
|
like.actor_url,
|
||||||
|
'likeNoteUrl',
|
||||||
|
like.note_url
|
||||||
|
)
|
||||||
|
ELSE NULL
|
||||||
|
END AS like
|
||||||
|
FROM (SELECT * FROM note WHERE id = ?) as note
|
||||||
|
LEFT JOIN like
|
||||||
|
ON note.url = like.note_url
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
id,
|
||||||
|
actor,
|
||||||
|
published,
|
||||||
|
actor,
|
||||||
|
content,
|
||||||
|
name,
|
||||||
|
inReplyTo,
|
||||||
url
|
url
|
||||||
FROM note
|
|
||||||
WHERE note.id = ?
|
|
||||||
ORDER BY published DESC
|
|
||||||
|]
|
|]
|
||||||
, [DB.SQLInteger noteid]
|
, [DB.SQLInteger noteid]
|
||||||
)
|
)
|
||||||
|
@ -238,6 +341,23 @@ insertNoteSQL actor note =
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
insertLikeSQL :: LikeEntry -> (DB.SQL, [DB.SQLData])
|
||||||
|
insertLikeSQL like =
|
||||||
|
( [r|
|
||||||
|
INSERT INTO outer_like(like_url, actor_url, note_url)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
RETURNING
|
||||||
|
id as id,
|
||||||
|
like_url,
|
||||||
|
actor_url,
|
||||||
|
note_url
|
||||||
|
|]
|
||||||
|
, [ DB.SQLText (T.pack like.likeUrl)
|
||||||
|
, DB.SQLText (T.pack like.likeActorUrl.unwrap)
|
||||||
|
, DB.SQLText (T.pack like.likeNoteUrl.unwrap)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
insertFollowerSQL :: FollowerEntry -> (DB.SQL, [DB.SQLData])
|
insertFollowerSQL :: FollowerEntry -> (DB.SQL, [DB.SQLData])
|
||||||
insertFollowerSQL follower =
|
insertFollowerSQL follower =
|
||||||
( [r|
|
( [r|
|
||||||
|
@ -290,9 +410,14 @@ decodeNoteRow = \case
|
||||||
, nullableString -> Just name
|
, nullableString -> Just name
|
||||||
, nullableString -> Just inReplyTo
|
, nullableString -> Just inReplyTo
|
||||||
, nullableString -> Just url
|
, nullableString -> Just url
|
||||||
|
, fromJson -> Just (dblikes :: [DbLike])
|
||||||
] ->
|
] ->
|
||||||
let
|
let
|
||||||
emptyNote = emptyUserNote $ T.unpack actor
|
emptyNote = emptyUserNote $ T.unpack actor
|
||||||
|
likes =
|
||||||
|
map
|
||||||
|
(\like -> aLike like.likeUrl like.likeActorUrl like.likeNoteUrl)
|
||||||
|
dblikes
|
||||||
in
|
in
|
||||||
( noteid
|
( noteid
|
||||||
, emptyNote
|
, emptyNote
|
||||||
|
@ -306,8 +431,18 @@ decodeNoteRow = \case
|
||||||
, otype =
|
, otype =
|
||||||
emptyNote.otype
|
emptyNote.otype
|
||||||
{ likes =
|
{ likes =
|
||||||
emptyNote.otype.likes
|
emptyUnorderedCollection
|
||||||
{ id = Just $ ObjectId $ T.unpack noteidurl <> "/likes"
|
{ id = Just $ ObjectId $ T.unpack noteidurl <> "/likes"
|
||||||
|
, otype =
|
||||||
|
CollectionType
|
||||||
|
{ ctype =
|
||||||
|
UnorderedCollectionType
|
||||||
|
{ items = likes
|
||||||
|
}
|
||||||
|
, first = Nothing
|
||||||
|
, last = Nothing
|
||||||
|
, current = Nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
, shares =
|
, shares =
|
||||||
emptyNote.otype.shares
|
emptyNote.otype.shares
|
||||||
|
@ -346,3 +481,9 @@ toNullableString :: Maybe String -> DB.SQLData
|
||||||
toNullableString = \case
|
toNullableString = \case
|
||||||
Nothing -> DB.SQLNull
|
Nothing -> DB.SQLNull
|
||||||
Just str -> DB.SQLText (T.pack str)
|
Just str -> DB.SQLText (T.pack str)
|
||||||
|
|
||||||
|
fromJson :: A.FromJSON a => DB.SQLData -> Maybe [a]
|
||||||
|
fromJson = \case
|
||||||
|
DB.SQLNull -> Just []
|
||||||
|
DB.SQLText str -> A.decode (BSL.fromStrict $ T.encodeUtf8 str)
|
||||||
|
_ -> Nothing
|
||||||
|
|
|
@ -15,6 +15,7 @@ import Network.Wai.Middleware.RequestLogger qualified as Logger
|
||||||
import Network.Wai.Middleware.RequestSizeLimit qualified as Limit
|
import Network.Wai.Middleware.RequestSizeLimit qualified as Limit
|
||||||
import Network.Wai.Middleware.Routed qualified as Wai
|
import Network.Wai.Middleware.Routed qualified as Wai
|
||||||
import Routes
|
import Routes
|
||||||
|
import Fedi qualified as Fedi
|
||||||
import System.Environment (getArgs, lookupEnv)
|
import System.Environment (getArgs, lookupEnv)
|
||||||
import Web.Twain qualified as Twain
|
import Web.Twain qualified as Twain
|
||||||
|
|
||||||
|
@ -135,9 +136,7 @@ mkFediApp connStr = do
|
||||||
lookupEnv "FEDI_DETAILS"
|
lookupEnv "FEDI_DETAILS"
|
||||||
<&> maybe (error "missing FEDI_DETAILS") id
|
<&> maybe (error "missing FEDI_DETAILS") id
|
||||||
|
|
||||||
details <-
|
details <- Fedi.readUserDetailsFile detailsFile
|
||||||
A.eitherDecodeFileStrict detailsFile
|
|
||||||
<&> either (\err -> error $ "could not read file '" <> detailsFile <> "'.\n" <> err) id
|
|
||||||
|
|
||||||
db <- mkDB connStr details
|
db <- mkDB connStr details
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module Routes where
|
module Routes where
|
||||||
|
|
||||||
import Control.Concurrent.Async qualified as Async
|
|
||||||
import Control.Logger.Simple qualified as Log
|
import Control.Logger.Simple qualified as Log
|
||||||
import DB
|
import DB
|
||||||
import Data.Aeson qualified as A
|
import Data.Aeson qualified as A
|
||||||
|
@ -10,7 +9,7 @@ import Data.Text qualified as T
|
||||||
import Fedi qualified as Fedi
|
import Fedi qualified as Fedi
|
||||||
import Html
|
import Html
|
||||||
import Lucid qualified as H
|
import Lucid qualified as H
|
||||||
import Routes.Inbox.Follow
|
import Routes.Inbox
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
import Web.Twain qualified as Twain
|
import Web.Twain qualified as Twain
|
||||||
|
|
||||||
|
@ -125,36 +124,3 @@ 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
|
|
||||||
Log.logDebug $ "Inbox request: " <> Fedi.pJson activity
|
|
||||||
case activity of
|
|
||||||
Fedi.ActivityFollow follow ->
|
|
||||||
handleInboxFollow details db activity follow
|
|
||||||
Fedi.ActivityUndo
|
|
||||||
( Fedi.Object
|
|
||||||
{ otype =
|
|
||||||
Fedi.TypeActivity
|
|
||||||
{ atype =
|
|
||||||
Fedi.TypeUndo
|
|
||||||
{ object = Fedi.ActivityFollow follow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) ->
|
|
||||||
handleInboxUnfollow details db activity follow
|
|
||||||
_ -> do
|
|
||||||
Log.logError $ "Unsupported activity: " <> Fedi.pShow activity
|
|
||||||
Twain.next
|
|
||||||
|
|
||||||
sendFollowers :: Fedi.UserDetails -> DB -> Fedi.AnyActivity -> IO ()
|
|
||||||
sendFollowers details db message = do
|
|
||||||
Log.logDebug $ "Sending to followers: " <> Fedi.pJson message
|
|
||||||
followers <- db.getFollowers
|
|
||||||
Fedi.for_ followers \follower -> do
|
|
||||||
Async.async $ do
|
|
||||||
Log.logDebug $ "Sending to follower: " <> Fedi.pShow follower.actorId
|
|
||||||
bs <- Fedi.sendPost details (T.unpack follower.actorId <> "/inbox") message
|
|
||||||
Log.logDebug $ "Sent to follower: " <> Fedi.pShow (follower.actorId, bs)
|
|
||||||
|
|
45
app/Routes/Inbox.hs
Normal file
45
app/Routes/Inbox.hs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
module Routes.Inbox where
|
||||||
|
|
||||||
|
import Control.Concurrent.Async qualified as Async
|
||||||
|
import Control.Logger.Simple qualified as Log
|
||||||
|
import DB
|
||||||
|
import Data.Text qualified as T
|
||||||
|
import Fedi qualified as Fedi
|
||||||
|
import Routes.Inbox.Follow
|
||||||
|
import Routes.Inbox.Like
|
||||||
|
import Web.Twain qualified as Twain
|
||||||
|
|
||||||
|
handleInbox :: DB -> FilePath -> Fedi.AnyActivity -> Twain.ResponderM Twain.Response
|
||||||
|
handleInbox db detailsFile activity = do
|
||||||
|
details <- liftIO $ Fedi.readUserDetailsFile detailsFile
|
||||||
|
Log.logDebug $ "Inbox request: " <> Fedi.pJson activity
|
||||||
|
case activity of
|
||||||
|
Fedi.ActivityFollow follow ->
|
||||||
|
handleInboxFollow details db activity follow
|
||||||
|
Fedi.ActivityLike like ->
|
||||||
|
handleInboxLike db like
|
||||||
|
Fedi.ActivityUndo
|
||||||
|
( Fedi.Object
|
||||||
|
{ otype =
|
||||||
|
Fedi.TypeActivity
|
||||||
|
{ atype =
|
||||||
|
Fedi.TypeUndo
|
||||||
|
{ object = Fedi.ActivityFollow follow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) ->
|
||||||
|
handleInboxUnfollow details db activity follow
|
||||||
|
_ -> do
|
||||||
|
Log.logError $ "Unsupported activity: " <> Fedi.pShow activity
|
||||||
|
Twain.next
|
||||||
|
|
||||||
|
sendFollowers :: Fedi.UserDetails -> DB -> Fedi.AnyActivity -> IO ()
|
||||||
|
sendFollowers details db message = do
|
||||||
|
Log.logDebug $ "Sending to followers: " <> Fedi.pJson message
|
||||||
|
followers <- db.getFollowers
|
||||||
|
Fedi.for_ followers \follower -> do
|
||||||
|
Async.async $ do
|
||||||
|
Log.logDebug $ "Sending to follower: " <> Fedi.pShow follower.actorId
|
||||||
|
bs <- Fedi.sendPost details (T.unpack follower.actorId <> "/inbox") message
|
||||||
|
Log.logDebug $ "Sent to follower: " <> Fedi.pShow (follower.actorId, bs)
|
72
app/Routes/Inbox/Like.hs
Normal file
72
app/Routes/Inbox/Like.hs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
module Routes.Inbox.Like where
|
||||||
|
|
||||||
|
import Control.Logger.Simple qualified as Log
|
||||||
|
import DB
|
||||||
|
import Fedi qualified as Fedi
|
||||||
|
import Web.Twain qualified as Twain
|
||||||
|
|
||||||
|
handleInboxLike
|
||||||
|
:: DB
|
||||||
|
-> Fedi.Like
|
||||||
|
-> Twain.ResponderM Twain.Response
|
||||||
|
handleInboxLike db like = do
|
||||||
|
let
|
||||||
|
id' = like.id
|
||||||
|
actor = like.otype.actor
|
||||||
|
note = like.otype.atype.object
|
||||||
|
case id' of
|
||||||
|
Just id'' -> do
|
||||||
|
let
|
||||||
|
likeEntry =
|
||||||
|
( LikeEntry
|
||||||
|
{ likeUrl = fromString id''.unwrap
|
||||||
|
, likeActorUrl = actor
|
||||||
|
, likeNoteUrl = note
|
||||||
|
}
|
||||||
|
)
|
||||||
|
operation = do
|
||||||
|
likeid <- db.insertLike likeEntry
|
||||||
|
Log.logInfo ("New like: " <> Fedi.pShow (likeid, likeEntry))
|
||||||
|
liftIO operation
|
||||||
|
pure $ Twain.text ""
|
||||||
|
Nothing ->
|
||||||
|
Twain.next
|
||||||
|
|
||||||
|
{-
|
||||||
|
handleInboxUnlike
|
||||||
|
:: DB
|
||||||
|
-> Fedi.Like
|
||||||
|
-> Twain.ResponderM Twain.Response
|
||||||
|
handleInboxUnlike db like = do
|
||||||
|
let
|
||||||
|
id' = like.id
|
||||||
|
actor = like.otype.actor
|
||||||
|
note = like.otype.atype.object
|
||||||
|
case id' of
|
||||||
|
Just id'' -> do
|
||||||
|
let
|
||||||
|
followerEntry =
|
||||||
|
( LikeEntry
|
||||||
|
{ likeUrl = fromString id''.unwrap
|
||||||
|
, likeActorUrl = actor
|
||||||
|
, likeNoteUrl = note
|
||||||
|
}
|
||||||
|
)
|
||||||
|
operation sendAccept = do
|
||||||
|
deleteFollower
|
||||||
|
db
|
||||||
|
LikeEntry
|
||||||
|
( \deletedId' -> do
|
||||||
|
let
|
||||||
|
deletedId = Fedi.fromMaybe 0 deletedId'
|
||||||
|
sendAccept deletedId
|
||||||
|
<* Log.logInfo ("Deleted follower: " <> Fedi.pShow deletedId)
|
||||||
|
)
|
||||||
|
|
||||||
|
liftIO $ acceptRequest details actor activity operation
|
||||||
|
|
||||||
|
pure $ Twain.text ""
|
||||||
|
Nothing ->
|
||||||
|
Twain.next
|
||||||
|
|
||||||
|
-}
|
|
@ -91,6 +91,8 @@ executable fediserve
|
||||||
Html
|
Html
|
||||||
Css
|
Css
|
||||||
Routes
|
Routes
|
||||||
|
Routes.Inbox
|
||||||
|
Routes.Inbox.Like
|
||||||
Routes.Inbox.Follow
|
Routes.Inbox.Follow
|
||||||
Routes.Inbox.Accept
|
Routes.Inbox.Accept
|
||||||
-- other-extensions:
|
-- other-extensions:
|
||||||
|
@ -102,6 +104,7 @@ executable fediserve
|
||||||
, wai-extra
|
, wai-extra
|
||||||
, warp
|
, warp
|
||||||
, twain
|
, twain
|
||||||
|
, bytestring
|
||||||
, text
|
, text
|
||||||
, sqlite-easy
|
, sqlite-easy
|
||||||
, raw-strings-qq
|
, raw-strings-qq
|
||||||
|
@ -121,6 +124,9 @@ executable fediserve
|
||||||
ViewPatterns
|
ViewPatterns
|
||||||
DuplicateRecordFields
|
DuplicateRecordFields
|
||||||
NoFieldSelectors
|
NoFieldSelectors
|
||||||
|
GeneralizedNewtypeDeriving
|
||||||
|
DeriveAnyClass
|
||||||
|
DerivingStrategies
|
||||||
ghc-options: -Wall -O -threaded -rtsopts -with-rtsopts=-N
|
ghc-options: -Wall -O -threaded -rtsopts -with-rtsopts=-N
|
||||||
|
|
||||||
test-suite fedi-test
|
test-suite fedi-test
|
||||||
|
|
|
@ -76,3 +76,12 @@ decodeBase64 = Base64.decodeBase64Lenient
|
||||||
makeDigest :: ByteString -> ByteString
|
makeDigest :: ByteString -> ByteString
|
||||||
makeDigest message =
|
makeDigest message =
|
||||||
BA.convert (Crypto.hash message :: Crypto.Digest Crypto.SHA256)
|
BA.convert (Crypto.hash message :: Crypto.Digest Crypto.SHA256)
|
||||||
|
|
||||||
|
sha1short :: Show a => a -> String
|
||||||
|
sha1short =
|
||||||
|
( take 10
|
||||||
|
. show
|
||||||
|
. (Crypto.hash :: ByteString -> Crypto.Digest Crypto.SHA1)
|
||||||
|
. fromString
|
||||||
|
. show
|
||||||
|
)
|
||||||
|
|
|
@ -350,27 +350,33 @@ instance A.FromJSON TypeUndo where
|
||||||
--
|
--
|
||||||
type Like = Object (TypeActivity TypeLike)
|
type Like = Object (TypeActivity TypeLike)
|
||||||
|
|
||||||
data TypeLike = TypeLike deriving (Show, Eq)
|
data TypeLike
|
||||||
|
= TypeLike
|
||||||
|
{ object :: Link
|
||||||
|
}
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
instance ToObject TypeLike where
|
instance ToObject TypeLike where
|
||||||
toObject TypeLike =
|
toObject like =
|
||||||
[ "type" A..= ("Like" :: String)
|
[ "type" A..= ("Like" :: String)
|
||||||
|
, "object" A..= like.object
|
||||||
]
|
]
|
||||||
|
|
||||||
instance A.FromJSON TypeLike where
|
instance A.FromJSON TypeLike where
|
||||||
parseJSON =
|
parseJSON =
|
||||||
A.withObject "TypeLike" \value -> do
|
A.withObject "TypeLike" \value -> do
|
||||||
typ :: String <- value A..: "type"
|
typ :: String <- value A..: "type"
|
||||||
|
object <- value A..: "object"
|
||||||
guard (typ == "Like")
|
guard (typ == "Like")
|
||||||
pure TypeLike
|
pure TypeLike{..}
|
||||||
|
|
||||||
data AnyActivity
|
data AnyActivity
|
||||||
= -- ActivityAnnounce Announce
|
= -- ActivityAnnounce Announce
|
||||||
ActivityCreate Create
|
ActivityCreate Create
|
||||||
| ActivityUndo Undo
|
| ActivityUndo Undo
|
||||||
| ActivityFollow Follow
|
| ActivityFollow Follow
|
||||||
| -- | ActivityLike Like
|
| ActivityLike Like
|
||||||
ActivityAccept Accept
|
| ActivityAccept Accept
|
||||||
| ActivityReject Reject
|
| ActivityReject Reject
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
@ -380,6 +386,7 @@ instance A.ToJSON AnyActivity where
|
||||||
ActivityCreate obj -> A.toJSON obj
|
ActivityCreate obj -> A.toJSON obj
|
||||||
ActivityUndo obj -> A.toJSON obj
|
ActivityUndo obj -> A.toJSON obj
|
||||||
ActivityFollow obj -> A.toJSON obj
|
ActivityFollow obj -> A.toJSON obj
|
||||||
|
ActivityLike 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
|
||||||
|
@ -392,6 +399,7 @@ instance A.FromJSON AnyActivity where
|
||||||
"Create" -> ActivityCreate <$> A.parseJSON value
|
"Create" -> ActivityCreate <$> A.parseJSON value
|
||||||
"Undo" -> ActivityUndo <$> A.parseJSON value
|
"Undo" -> ActivityUndo <$> A.parseJSON value
|
||||||
"Follow" -> ActivityFollow <$> A.parseJSON value
|
"Follow" -> ActivityFollow <$> A.parseJSON value
|
||||||
|
"Like" -> ActivityLike <$> A.parseJSON value
|
||||||
"Accept" -> ActivityAccept <$> A.parseJSON value
|
"Accept" -> ActivityAccept <$> A.parseJSON value
|
||||||
"Reject" -> ActivityReject <$> A.parseJSON value
|
"Reject" -> ActivityReject <$> A.parseJSON value
|
||||||
_ -> fail ("Parsing '" <> typ <> "' not yet implemented.")
|
_ -> fail ("Parsing '" <> typ <> "' not yet implemented.")
|
||||||
|
|
|
@ -174,3 +174,23 @@ makeAccept accept =
|
||||||
, origin = Nothing
|
, origin = Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- | Create a 'Like'.
|
||||||
|
aLike :: ObjectId -> Link -> Link -> Like
|
||||||
|
aLike id' actor object =
|
||||||
|
emptyObject
|
||||||
|
{ id = Just id'
|
||||||
|
, otype = typeActivityLike actor object
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | A 'TypeLike'.
|
||||||
|
typeActivityLike :: Link -> Link -> TypeActivity TypeLike
|
||||||
|
typeActivityLike actor object =
|
||||||
|
TypeActivity
|
||||||
|
{ actor = actor
|
||||||
|
, atype = TypeLike
|
||||||
|
{ object = object
|
||||||
|
}
|
||||||
|
, target = Nothing
|
||||||
|
, origin = Nothing
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue