mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-06-02 05:01:32 +00:00
Try using utoipa for list posts endpoint (ref #2937)
This commit is contained in:
parent
e4b739320c
commit
4869b16bd8
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -2218,9 +2218,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
|
|||
|
||||
[[package]]
|
||||
name = "http-signature-normalization"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8f45adbef81d7ea3bd7e9bcc6734b7245dad05a14abdcc7ddc0988791d63515"
|
||||
checksum = "8b93438e69bb70b5c01d144da52b9e17505d84b9b84a6dc70d2750365df9ca6e"
|
||||
dependencies = [
|
||||
"httpdate",
|
||||
]
|
||||
|
@ -2245,7 +2245,7 @@ dependencies = [
|
|||
"actix-web",
|
||||
"base64 0.13.1",
|
||||
"futures-util",
|
||||
"http-signature-normalization 0.6.0",
|
||||
"http-signature-normalization 0.6.1",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -2617,6 +2617,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"utoipa",
|
||||
"uuid",
|
||||
"webpage",
|
||||
]
|
||||
|
@ -2677,6 +2678,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"utoipa",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -2824,6 +2826,7 @@ dependencies = [
|
|||
"tracing-opentelemetry 0.17.4",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -6104,6 +6107,32 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
|
||||
|
||||
[[package]]
|
||||
name = "utoipa"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-gen"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.4"
|
||||
|
|
|
@ -110,6 +110,7 @@ rustls = { version ="0.21.2", features = ["dangerous_configuration"]}
|
|||
futures-util = "0.3.28"
|
||||
tokio-postgres = "0.7.8"
|
||||
tokio-postgres-rustls = "0.10.0"
|
||||
utoipa = { version = "3", features = ["actix_extras"] }
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
@ -147,4 +148,5 @@ rustls = { workspace = true }
|
|||
futures-util = { workspace = true }
|
||||
tokio-postgres = { workspace = true }
|
||||
tokio-postgres-rustls = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
utoipa = { workspace = true }
|
||||
|
|
|
@ -16,7 +16,7 @@ doctest = false
|
|||
[features]
|
||||
full = ["tracing", "rosetta-i18n", "chrono", "lemmy_utils",
|
||||
"lemmy_db_views/full", "lemmy_db_views_actor/full", "lemmy_db_views_moderator/full",
|
||||
"percent-encoding", "encoding", "reqwest-middleware", "webpage", "ts-rs"]
|
||||
"percent-encoding", "encoding", "reqwest-middleware", "webpage", "ts-rs", "utoipa"]
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_views = { workspace = true }
|
||||
|
@ -42,3 +42,4 @@ tokio = { workspace = true }
|
|||
reqwest = { workspace = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
actix-web = { workspace = true }
|
||||
utoipa = { workspace = true, optional = true }
|
||||
|
|
|
@ -9,9 +9,9 @@ use lemmy_db_views::structs::{PostReportView, PostView};
|
|||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
#[cfg(feature = "full")]
|
||||
use {ts_rs::TS, utoipa::ToSchema};
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
|
@ -64,7 +64,7 @@ pub struct GetPostResponse {
|
|||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", derive(TS, ToSchema))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get a list of posts.
|
||||
pub struct GetPosts {
|
||||
|
@ -79,7 +79,7 @@ pub struct GetPosts {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", derive(TS, ToSchema))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The post list response.
|
||||
pub struct GetPostsResponse {
|
||||
|
|
|
@ -38,8 +38,9 @@ async-trait = { workspace = true }
|
|||
anyhow = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
html2md = "0.2.14"
|
||||
serde_with = { workspace = true }
|
||||
utoipa = { workspace = true }
|
||||
html2md = "0.2.14"
|
||||
http-signature-normalization-actix = { version = "0.6.2", default-features = false, features = ["server", "sha-2"] }
|
||||
enum_delegate = "0.2.0"
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use crate::{
|
||||
api::{listing_type_with_default, PerformApub},
|
||||
api::listing_type_with_default,
|
||||
fetcher::resolve_actor_identifier,
|
||||
objects::community::ApubCommunity,
|
||||
};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{Json, Query},
|
||||
};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
post::{GetPosts, GetPostsResponse},
|
||||
|
@ -13,54 +17,62 @@ use lemmy_db_schema::source::{community::Community, local_site::LocalSite};
|
|||
use lemmy_db_views::post_view::PostQuery;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl PerformApub for GetPosts {
|
||||
type Response = GetPostsResponse;
|
||||
// TODO: we need to duplicate the param/response structs here, and also in /src/lib.rs
|
||||
// TODO: context_path workaround is needed because scopes arent supported, so another duplicated info
|
||||
// https://github.com/juhaku/utoipa/issues/121
|
||||
#[utoipa::path(
|
||||
context_path = "/post",
|
||||
request_body = GetPosts,
|
||||
responses(
|
||||
(status = 200, description = "List posts according to query parameters", body = GetPostsResponse)
|
||||
)
|
||||
)]
|
||||
#[get("list")]
|
||||
#[tracing::instrument(skip(context))]
|
||||
// TODO: activitypub_federation::config::Data should impl actix_web::web::Data
|
||||
pub async fn list_posts(
|
||||
data: Query<GetPosts>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GetPostsResponse>, LemmyError> {
|
||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
|
||||
let local_site = LocalSite::read(context.pool()).await?;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
async fn perform(&self, context: &Data<LemmyContext>) -> Result<GetPostsResponse, LemmyError> {
|
||||
let data: &GetPosts = self;
|
||||
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await;
|
||||
let local_site = LocalSite::read(context.pool()).await?;
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
let sort = data.sort;
|
||||
|
||||
let sort = data.sort;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let community_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, context, &None, true)
|
||||
.await
|
||||
.ok()
|
||||
.map(|c| c.id)
|
||||
} else {
|
||||
data.community_id
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?;
|
||||
|
||||
let is_mod_or_admin =
|
||||
is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
let posts = PostQuery::builder()
|
||||
.pool(context.pool())
|
||||
.local_user(local_user_view.map(|l| l.local_user).as_ref())
|
||||
.listing_type(Some(listing_type))
|
||||
.sort(sort)
|
||||
.community_id(community_id)
|
||||
.saved_only(saved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.is_mod_or_admin(Some(is_mod_or_admin))
|
||||
.build()
|
||||
.list()
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let community_id = if let Some(name) = &data.community_name {
|
||||
resolve_actor_identifier::<ApubCommunity, Community>(name, &context, &None, true)
|
||||
.await
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
|
||||
.ok()
|
||||
.map(|c| c.id)
|
||||
} else {
|
||||
data.community_id
|
||||
};
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
Ok(GetPostsResponse { posts })
|
||||
}
|
||||
let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?;
|
||||
|
||||
let is_mod_or_admin = is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
let posts = PostQuery::builder()
|
||||
.pool(context.pool())
|
||||
.local_user(local_user_view.map(|l| l.local_user).as_ref())
|
||||
.listing_type(Some(listing_type))
|
||||
.sort(sort)
|
||||
.community_id(community_id)
|
||||
.saved_only(saved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.is_mod_or_admin(Some(is_mod_or_admin))
|
||||
.build()
|
||||
.list()
|
||||
.await
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?;
|
||||
|
||||
Ok(Json(GetPostsResponse { posts }))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_db_schema::{newtypes::CommunityId, source::local_site::LocalSite, List
|
|||
use lemmy_utils::error::LemmyError;
|
||||
|
||||
mod list_comments;
|
||||
mod list_posts;
|
||||
pub mod list_posts;
|
||||
mod read_community;
|
||||
mod read_person;
|
||||
mod resolve_object;
|
||||
|
|
|
@ -62,7 +62,6 @@ use lemmy_api_common::{
|
|||
EditPost,
|
||||
FeaturePost,
|
||||
GetPost,
|
||||
GetPosts,
|
||||
GetSiteMetadata,
|
||||
ListPostReports,
|
||||
LockPost,
|
||||
|
@ -100,7 +99,10 @@ use lemmy_api_common::{
|
|||
},
|
||||
};
|
||||
use lemmy_api_crud::PerformCrud;
|
||||
use lemmy_apub::{api::PerformApub, SendActivity};
|
||||
use lemmy_apub::{
|
||||
api::{list_posts::list_posts, PerformApub},
|
||||
SendActivity,
|
||||
};
|
||||
use lemmy_utils::rate_limit::RateLimitCell;
|
||||
use serde::Deserialize;
|
||||
|
||||
|
@ -186,7 +188,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
)
|
||||
.route("/lock", web::post().to(route_post::<LockPost>))
|
||||
.route("/feature", web::post().to(route_post::<FeaturePost>))
|
||||
.route("/list", web::get().to(route_get_apub::<GetPosts>))
|
||||
.service(list_posts)
|
||||
.route("/like", web::post().to(route_post::<CreatePostLike>))
|
||||
.route("/save", web::put().to(route_post::<SavePost>))
|
||||
.route("/report", web::post().to(route_post::<CreatePostReport>))
|
||||
|
|
31
src/lib.rs
31
src/lib.rs
|
@ -12,6 +12,7 @@ use actix_web::{middleware, web::Data, App, HttpServer, Result};
|
|||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
lemmy_db_views::structs::SiteView,
|
||||
post::{GetPosts, GetPostsResponse},
|
||||
request::build_user_agent,
|
||||
utils::{
|
||||
check_private_instance_and_federation_enabled,
|
||||
|
@ -28,23 +29,51 @@ use lemmy_utils::{error::LemmyError, rate_limit::RateLimitCell, settings::SETTIN
|
|||
use reqwest::Client;
|
||||
use reqwest_middleware::ClientBuilder;
|
||||
use reqwest_tracing::TracingMiddleware;
|
||||
use std::{env, thread, time::Duration};
|
||||
use std::{env, process::exit, thread, time::Duration};
|
||||
use tracing::subscriber::set_global_default;
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, Layer, Registry};
|
||||
use url::Url;
|
||||
use utoipa::{openapi::Server, Modify, OpenApi};
|
||||
|
||||
/// Max timeout for http requests
|
||||
pub(crate) const REQWEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(modifiers(&ServerAddon))]
|
||||
// TODO: components is incomplete, we need to manually include all nested response structs
|
||||
#[openapi(
|
||||
paths(lemmy_apub::api::list_posts::list_posts),
|
||||
components(schemas(GetPosts, GetPostsResponse))
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
struct ServerAddon;
|
||||
|
||||
impl Modify for ServerAddon {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
// TODO: api prefix also needs to be duplicated
|
||||
openapi.servers = Some(vec![Server::new("/api/v3")])
|
||||
}
|
||||
}
|
||||
|
||||
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
|
||||
pub async fn start_lemmy_server() -> Result<(), LemmyError> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let scheduled_tasks_enabled = args.get(1) != Some(&"--disable-scheduled-tasks".to_string());
|
||||
|
||||
if args.get(1) == Some(&"--print-api-docs".to_string()) {
|
||||
println!(
|
||||
"{}",
|
||||
ApiDoc::openapi()
|
||||
.to_pretty_json()
|
||||
.expect("print openapi json")
|
||||
);
|
||||
exit(0);
|
||||
}
|
||||
let settings = SETTINGS.to_owned();
|
||||
|
||||
// Run the DB migrations
|
||||
|
|
Loading…
Reference in a new issue