Enable replies

This commit is contained in:
silverpill 2021-09-22 16:32:44 +00:00
parent 087a077f7d
commit 520e5399aa
9 changed files with 103 additions and 9 deletions

View file

@ -77,6 +77,7 @@ POST /api/v1/media
GET /api/v2/search GET /api/v2/search
POST /api/v1/statuses POST /api/v1/statuses
GET /api/v1/statuses/{status_id} GET /api/v1/statuses/{status_id}
GET /api/v1/statuses/{status_id}/context
GET /api/v1/timelines/home GET /api/v1/timelines/home
``` ```

View file

@ -0,0 +1 @@
ALTER TABLE post ADD COLUMN in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE;

View file

@ -33,6 +33,7 @@ CREATE TABLE post (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE, author_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
content TEXT NOT NULL, content TEXT NOT NULL,
in_reply_to_id UUID REFERENCES post (id) ON DELETE CASCADE,
ipfs_cid VARCHAR(200), ipfs_cid VARCHAR(200),
token_id INTEGER, token_id INTEGER,
token_tx_id VARCHAR(200), token_tx_id VARCHAR(200),

View file

@ -122,6 +122,8 @@ pub async fn receive_activity(
} }
let post_data = PostCreateData { let post_data = PostCreateData {
content, content,
// TODO: parse inReplyTo field
in_reply_to_id: None,
attachments: attachments, attachments: attachments,
created_at: object.published, created_at: object.published,
}; };

View file

@ -13,4 +13,6 @@ pub const PERSON: &str = "Person";
pub const DOCUMENT: &str = "Document"; pub const DOCUMENT: &str = "Document";
pub const IMAGE: &str = "Image"; pub const IMAGE: &str = "Image";
pub const NOTE: &str = "Note"; pub const NOTE: &str = "Note";
// Misc
pub const PROPERTY_VALUE: &str = "PropertyValue"; pub const PROPERTY_VALUE: &str = "PropertyValue";

View file

@ -13,6 +13,7 @@ pub struct Status {
pub created_at: DateTime<Utc>, pub created_at: DateTime<Utc>,
pub account: Account, pub account: Account,
pub content: String, pub content: String,
pub in_reply_to_id: Option<Uuid>,
pub media_attachments: Vec<Attachment>, pub media_attachments: Vec<Attachment>,
// Extra fields // Extra fields
@ -32,6 +33,7 @@ impl Status {
created_at: post.created_at, created_at: post.created_at,
account: account, account: account,
content: post.content, content: post.content,
in_reply_to_id: post.in_reply_to_id,
media_attachments: attachments, media_attachments: attachments,
ipfs_cid: post.ipfs_cid, ipfs_cid: post.ipfs_cid,
token_id: post.token_id, token_id: post.token_id,
@ -47,6 +49,8 @@ pub struct StatusData {
#[serde(rename = "media_ids[]")] #[serde(rename = "media_ids[]")]
pub media_ids: Option<Vec<Uuid>>, pub media_ids: Option<Vec<Uuid>>,
pub in_reply_to_id: Option<Uuid>,
} }
impl From<StatusData> for PostCreateData { impl From<StatusData> for PostCreateData {
@ -54,6 +58,7 @@ impl From<StatusData> for PostCreateData {
fn from(value: StatusData) -> Self { fn from(value: StatusData) -> Self {
Self { Self {
content: value.status, content: value.status,
in_reply_to_id: value.in_reply_to_id,
attachments: value.media_ids.unwrap_or(vec![]), attachments: value.media_ids.unwrap_or(vec![]),
created_at: None, created_at: None,
} }

View file

@ -14,7 +14,12 @@ use crate::ipfs::store as ipfs_store;
use crate::ipfs::utils::{IPFS_LOGO, get_ipfs_url}; use crate::ipfs::utils::{IPFS_LOGO, get_ipfs_url};
use crate::mastodon_api::users::auth::get_current_user; use crate::mastodon_api::users::auth::get_current_user;
use crate::models::profiles::queries::get_followers; use crate::models::profiles::queries::get_followers;
use crate::models::posts::queries::{create_post, get_post_by_id, update_post}; use crate::models::posts::queries::{
create_post,
get_post_by_id,
get_thread,
update_post,
};
use crate::models::posts::types::PostCreateData; use crate::models::posts::types::PostCreateData;
use super::types::{Status, StatusData}; use super::types::{Status, StatusData};
@ -66,6 +71,20 @@ async fn get_status(
Ok(HttpResponse::Ok().json(status)) Ok(HttpResponse::Ok().json(status))
} }
#[get("/{status_id}/context")]
async fn get_context(
config: web::Data<Config>,
db_pool: web::Data<Pool>,
web::Path(status_id): web::Path<Uuid>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let statuses: Vec<Status> = get_thread(db_client, &status_id).await?
.into_iter()
.map(|post| Status::from_post(post, &config.instance_url()))
.collect();
Ok(HttpResponse::Ok().json(statuses))
}
// https://docs.opensea.io/docs/metadata-standards // https://docs.opensea.io/docs/metadata-standards
#[derive(Serialize)] #[derive(Serialize)]
struct PostMetadata { struct PostMetadata {
@ -154,6 +173,7 @@ pub fn status_api_scope() -> Scope {
.service(create_status) .service(create_status)
// Routes with status ID // Routes with status ID
.service(get_status) .service(get_status)
.service(get_context)
.service(make_permanent) .service(make_permanent)
.service(get_signature) .service(get_signature)
} }

View file

@ -13,7 +13,8 @@ pub async fn get_posts(
db_client: &impl GenericClient, db_client: &impl GenericClient,
current_user_id: &Uuid, current_user_id: &Uuid,
) -> Result<Vec<Post>, DatabaseError> { ) -> Result<Vec<Post>, DatabaseError> {
// Select posts from follows + own posts // Select posts from follows + own posts.
// Do not select replies
let rows = db_client.query( let rows = db_client.query(
" "
SELECT SELECT
@ -25,11 +26,14 @@ pub async fn get_posts(
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
post.in_reply_to_id IS NULL
AND (
post.author_id = $1 post.author_id = $1
OR EXISTS ( OR EXISTS (
SELECT 1 FROM relationship SELECT 1 FROM relationship
WHERE source_id = $1 AND target_id = post.author_id WHERE source_id = $1 AND target_id = post.author_id
) )
)
ORDER BY post.created_at DESC ORDER BY post.created_at DESC
", ",
&[&current_user_id], &[&current_user_id],
@ -76,11 +80,21 @@ pub async fn create_post(
let created_at = data.created_at.unwrap_or(Utc::now()); let created_at = data.created_at.unwrap_or(Utc::now());
let post_row = transaction.query_one( let post_row = transaction.query_one(
" "
INSERT INTO post (id, author_id, content, created_at) INSERT INTO post (
VALUES ($1, $2, $3, $4) id, author_id, content,
in_reply_to_id,
created_at
)
VALUES ($1, $2, $3, $4, $5)
RETURNING post RETURNING post
", ",
&[&post_id, &author_id, &data.content, &created_at], &[
&post_id,
&author_id,
&data.content,
&data.in_reply_to_id,
&created_at,
],
).await?; ).await?;
let attachment_rows = transaction.query( let attachment_rows = transaction.query(
" "
@ -103,6 +117,7 @@ pub async fn create_post(
id: db_post.id, id: db_post.id,
author: author, author: author,
content: db_post.content, content: db_post.content,
in_reply_to_id: db_post.in_reply_to_id,
attachments: db_attachments, attachments: db_attachments,
ipfs_cid: db_post.ipfs_cid, ipfs_cid: db_post.ipfs_cid,
token_id: db_post.token_id, token_id: db_post.token_id,
@ -137,6 +152,47 @@ pub async fn get_post_by_id(
Ok(post) Ok(post)
} }
pub async fn get_thread(
db_client: &impl GenericClient,
post_id: &Uuid,
) -> Result<Vec<Post>, DatabaseError> {
// TODO: limit recursion depth
let rows = db_client.query(
"
WITH RECURSIVE
ancestors (id, in_reply_to_id) AS (
SELECT post.id, post.in_reply_to_id FROM post
WHERE post.id = $1
UNION ALL
SELECT post.id, post.in_reply_to_id FROM post
JOIN ancestors ON post.id = ancestors.in_reply_to_id
),
context (id, path) AS (
SELECT ancestors.id, ARRAY[ancestors.id] FROM ancestors
WHERE ancestors.in_reply_to_id IS NULL
UNION
SELECT post.id, array_append(context.path, post.id) FROM post
JOIN context ON post.in_reply_to_id = context.id
)
SELECT
post, actor_profile,
ARRAY(
SELECT media_attachment
FROM media_attachment WHERE post_id = post.id
) AS attachments
FROM post
JOIN context ON post.id = context.id
JOIN actor_profile ON post.author_id = actor_profile.id
ORDER BY context.path
",
&[&post_id],
).await?;
let posts: Vec<Post> = rows.iter()
.map(|row| Post::try_from(row))
.collect::<Result<_, _>>()?;
Ok(posts)
}
pub async fn get_post_by_ipfs_cid( pub async fn get_post_by_ipfs_cid(
db_client: &impl GenericClient, db_client: &impl GenericClient,
ipfs_cid: &str, ipfs_cid: &str,

View file

@ -16,6 +16,7 @@ pub struct DbPost {
pub id: Uuid, pub id: Uuid,
pub author_id: Uuid, pub author_id: Uuid,
pub content: String, pub content: String,
pub in_reply_to_id: Option<Uuid>,
pub ipfs_cid: Option<String>, pub ipfs_cid: Option<String>,
pub token_id: Option<i32>, pub token_id: Option<i32>,
pub token_tx_id: Option<String>, pub token_tx_id: Option<String>,
@ -27,6 +28,7 @@ pub struct Post {
pub id: Uuid, pub id: Uuid,
pub author: DbActorProfile, pub author: DbActorProfile,
pub content: String, pub content: String,
pub in_reply_to_id: Option<Uuid>,
pub attachments: Vec<DbMediaAttachment>, pub attachments: Vec<DbMediaAttachment>,
pub ipfs_cid: Option<String>, pub ipfs_cid: Option<String>,
pub token_id: Option<i32>, pub token_id: Option<i32>,
@ -46,6 +48,7 @@ impl TryFrom<&Row> for Post {
id: db_post.id, id: db_post.id,
author: db_profile, author: db_profile,
content: db_post.content, content: db_post.content,
in_reply_to_id: db_post.in_reply_to_id,
attachments: db_attachments, attachments: db_attachments,
ipfs_cid: db_post.ipfs_cid, ipfs_cid: db_post.ipfs_cid,
token_id: db_post.token_id, token_id: db_post.token_id,
@ -58,6 +61,7 @@ impl TryFrom<&Row> for Post {
pub struct PostCreateData { pub struct PostCreateData {
pub content: String, pub content: String,
pub in_reply_to_id: Option<Uuid>,
pub attachments: Vec<Uuid>, pub attachments: Vec<Uuid>,
pub created_at: Option<DateTime<Utc>>, pub created_at: Option<DateTime<Utc>>,
} }
@ -83,6 +87,7 @@ mod tests {
fn test_validate_post_data() { fn test_validate_post_data() {
let mut post_data_1 = PostCreateData { let mut post_data_1 = PostCreateData {
content: " ".to_string(), content: " ".to_string(),
in_reply_to_id: None,
attachments: vec![], attachments: vec![],
created_at: None, created_at: None,
}; };
@ -93,6 +98,7 @@ mod tests {
fn test_trimming() { fn test_trimming() {
let mut post_data_2 = PostCreateData { let mut post_data_2 = PostCreateData {
content: "test ".to_string(), content: "test ".to_string(),
in_reply_to_id: None,
attachments: vec![], attachments: vec![],
created_at: None, created_at: None,
}; };