Utoipa adds auth requirements
This commit is contained in:
parent
2d92ac03ef
commit
9707f4a96d
3 changed files with 182 additions and 6 deletions
|
@ -7,7 +7,6 @@ use tower_http::{
|
||||||
normalize_path::{NormalizePath, NormalizePathLayer},
|
normalize_path::{NormalizePath, NormalizePathLayer},
|
||||||
services::ServeDir,
|
services::ServeDir,
|
||||||
};
|
};
|
||||||
use utoipa::OpenApi;
|
|
||||||
use utoipa_axum::router::OpenApiRouter;
|
use utoipa_axum::router::OpenApiRouter;
|
||||||
use utoipa_scalar::{Scalar, Servable};
|
use utoipa_scalar::{Scalar, Servable};
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ pub fn make_router(app: App) -> NormalizePath<Router> {
|
||||||
.fallback_service(ServeDir::new(&app.web_dir_path))
|
.fallback_service(ServeDir::new(&app.web_dir_path))
|
||||||
.layer(CompressionLayer::new());
|
.layer(CompressionLayer::new());
|
||||||
|
|
||||||
let (open_api_router, open_api) = OpenApiRouter::with_openapi(doc::ApiDoc::openapi())
|
let (open_api_router, open_api) = OpenApiRouter::with_openapi(doc::open_api())
|
||||||
.nest("/api", api::router())
|
.nest("/api", api::router())
|
||||||
.split_for_parts();
|
.split_for_parts();
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,10 @@ async fn get_initial_setup(
|
||||||
get,
|
get,
|
||||||
path = "/settings",
|
path = "/settings",
|
||||||
tag = "Configuration",
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = dto::Settings),
|
(status = 200, body = dto::Settings),
|
||||||
),
|
),
|
||||||
|
@ -141,6 +145,10 @@ async fn get_settings(
|
||||||
put,
|
put,
|
||||||
path = "/settings",
|
path = "/settings",
|
||||||
tag = "Configuration",
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
request_body = dto::NewSettings,
|
request_body = dto::NewSettings,
|
||||||
)]
|
)]
|
||||||
async fn put_settings(
|
async fn put_settings(
|
||||||
|
@ -172,6 +180,10 @@ async fn put_settings(
|
||||||
get,
|
get,
|
||||||
path = "/mount_dirs",
|
path = "/mount_dirs",
|
||||||
tag = "Configuration",
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::MountDir>),
|
(status = 200, body = Vec<dto::MountDir>),
|
||||||
),
|
),
|
||||||
|
@ -189,6 +201,10 @@ async fn get_mount_dirs(
|
||||||
put,
|
put,
|
||||||
path = "/mount_dirs",
|
path = "/mount_dirs",
|
||||||
tag = "Configuration",
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
request_body = Vec<dto::MountDir>,
|
request_body = Vec<dto::MountDir>,
|
||||||
)]
|
)]
|
||||||
async fn put_mount_dirs(
|
async fn put_mount_dirs(
|
||||||
|
@ -236,6 +252,10 @@ async fn post_auth(
|
||||||
get,
|
get,
|
||||||
path = "/users",
|
path = "/users",
|
||||||
tag = "User Management",
|
tag = "User Management",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::User>),
|
(status = 200, body = Vec<dto::User>),
|
||||||
),
|
),
|
||||||
|
@ -253,6 +273,10 @@ async fn get_users(
|
||||||
post,
|
post,
|
||||||
path = "/user",
|
path = "/user",
|
||||||
tag = "User Management",
|
tag = "User Management",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
request_body = dto::NewUser,
|
request_body = dto::NewUser,
|
||||||
responses(
|
responses(
|
||||||
(status = 200),
|
(status = 200),
|
||||||
|
@ -275,6 +299,10 @@ async fn post_user(
|
||||||
put,
|
put,
|
||||||
path = "/user/{name}",
|
path = "/user/{name}",
|
||||||
tag = "User Management",
|
tag = "User Management",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
request_body = dto::UserUpdate,
|
request_body = dto::UserUpdate,
|
||||||
responses(
|
responses(
|
||||||
(status = 200),
|
(status = 200),
|
||||||
|
@ -309,6 +337,10 @@ async fn put_user(
|
||||||
delete,
|
delete,
|
||||||
path = "/user/{name}",
|
path = "/user/{name}",
|
||||||
tag = "User Management",
|
tag = "User Management",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200),
|
(status = 200),
|
||||||
(status = 404),
|
(status = 404),
|
||||||
|
@ -329,7 +361,15 @@ async fn delete_user(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(post, path = "/trigger_index", tag = "Configuration")]
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/trigger_index",
|
||||||
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
|
)]
|
||||||
async fn post_trigger_index(
|
async fn post_trigger_index(
|
||||||
_admin_rights: AdminRights,
|
_admin_rights: AdminRights,
|
||||||
State(scanner): State<scanner::Scanner>,
|
State(scanner): State<scanner::Scanner>,
|
||||||
|
@ -342,6 +382,10 @@ async fn post_trigger_index(
|
||||||
get,
|
get,
|
||||||
path = "/index_status",
|
path = "/index_status",
|
||||||
tag = "Configuration",
|
tag = "Configuration",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = dto::IndexStatus),
|
(status = 200, body = dto::IndexStatus),
|
||||||
)
|
)
|
||||||
|
@ -423,6 +467,10 @@ fn albums_to_response(albums: Vec<index::Album>, api_version: APIMajorVersion) -
|
||||||
get,
|
get,
|
||||||
path = "/browse",
|
path = "/browse",
|
||||||
tag = "File Browser",
|
tag = "File Browser",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8)
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8)
|
||||||
),
|
),
|
||||||
|
@ -446,6 +494,10 @@ async fn get_browse_root(
|
||||||
get,
|
get,
|
||||||
path = "/browse/{*path}",
|
path = "/browse/{*path}",
|
||||||
tag = "File Browser",
|
tag = "File Browser",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
("path", allow_reserved),
|
("path", allow_reserved),
|
||||||
|
@ -471,6 +523,10 @@ async fn get_browse(
|
||||||
get,
|
get,
|
||||||
path = "/flatten",
|
path = "/flatten",
|
||||||
tag = "File Browser",
|
tag = "File Browser",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
),
|
),
|
||||||
|
@ -495,6 +551,10 @@ async fn get_flatten_root(
|
||||||
get,
|
get,
|
||||||
path = "/flatten/{*path}",
|
path = "/flatten/{*path}",
|
||||||
tag = "File Browser",
|
tag = "File Browser",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
("path", allow_reserved),
|
("path", allow_reserved),
|
||||||
|
@ -521,6 +581,10 @@ async fn get_flatten(
|
||||||
get,
|
get,
|
||||||
path = "/albums",
|
path = "/albums",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::AlbumHeader>),
|
(status = 200, body = Vec<dto::AlbumHeader>),
|
||||||
)
|
)
|
||||||
|
@ -544,6 +608,10 @@ async fn get_albums(
|
||||||
get,
|
get,
|
||||||
path = "/artists",
|
path = "/artists",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::ArtistHeader>),
|
(status = 200, body = Vec<dto::ArtistHeader>),
|
||||||
)
|
)
|
||||||
|
@ -567,6 +635,10 @@ async fn get_artists(
|
||||||
get,
|
get,
|
||||||
path = "/artists/{artist}",
|
path = "/artists/{artist}",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("artist",)),
|
params(("artist",)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = dto::Artist),
|
(status = 200, body = dto::Artist),
|
||||||
|
@ -584,6 +656,10 @@ async fn get_artist(
|
||||||
get,
|
get,
|
||||||
path = "/artists/{artists}/albums/{album}",
|
path = "/artists/{artists}/albums/{album}",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("artists",),
|
("artists",),
|
||||||
("album",),
|
("album",),
|
||||||
|
@ -608,6 +684,10 @@ async fn get_album(
|
||||||
post, // post because of https://github.com/whatwg/fetch/issues/551
|
post, // post because of https://github.com/whatwg/fetch/issues/551
|
||||||
path = "/songs",
|
path = "/songs",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
request_body = dto::GetSongsBulkInput,
|
request_body = dto::GetSongsBulkInput,
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = dto::GetSongsBulkOutput),
|
(status = 200, body = dto::GetSongsBulkOutput),
|
||||||
|
@ -639,6 +719,10 @@ async fn get_songs(
|
||||||
get,
|
get,
|
||||||
path = "/peaks/{*path}",
|
path = "/peaks/{*path}",
|
||||||
tag = "Media",
|
tag = "Media",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("path", allow_reserved)),
|
params(("path", allow_reserved)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = [u8]),
|
(status = 200, body = [u8]),
|
||||||
|
@ -659,6 +743,10 @@ async fn get_peaks(
|
||||||
get,
|
get,
|
||||||
path = "/albums/random",
|
path = "/albums/random",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
dto::GetRandomAlbumsParameters,
|
dto::GetRandomAlbumsParameters,
|
||||||
|
@ -689,6 +777,10 @@ async fn get_random_albums(
|
||||||
get,
|
get,
|
||||||
path = "/albums/recent",
|
path = "/albums/recent",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
dto::GetRecentAlbumsParameters
|
dto::GetRecentAlbumsParameters
|
||||||
|
@ -716,6 +808,10 @@ async fn get_recent_albums(
|
||||||
get,
|
get,
|
||||||
path = "/genres",
|
path = "/genres",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::GenreHeader>),
|
(status = 200, body = Vec<dto::GenreHeader>),
|
||||||
)
|
)
|
||||||
|
@ -738,6 +834,10 @@ async fn get_genres(
|
||||||
get,
|
get,
|
||||||
path = "/genres/{genre}",
|
path = "/genres/{genre}",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("genre",)),
|
params(("genre",)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::Genre>),
|
(status = 200, body = Vec<dto::Genre>),
|
||||||
|
@ -755,6 +855,10 @@ async fn get_genre(
|
||||||
get,
|
get,
|
||||||
path = "/genres/{genre}/albums",
|
path = "/genres/{genre}/albums",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("genre",)),
|
params(("genre",)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::AlbumHeader>),
|
(status = 200, body = Vec<dto::AlbumHeader>),
|
||||||
|
@ -779,6 +883,10 @@ async fn get_genre_albums(
|
||||||
get,
|
get,
|
||||||
path = "/genres/{genre}/artists",
|
path = "/genres/{genre}/artists",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("genre",)),
|
params(("genre",)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::ArtistHeader>),
|
(status = 200, body = Vec<dto::ArtistHeader>),
|
||||||
|
@ -803,6 +911,10 @@ async fn get_genre_artists(
|
||||||
get,
|
get,
|
||||||
path = "/genres/{genre}/songs",
|
path = "/genres/{genre}/songs",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("genre",)),
|
params(("genre",)),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = dto::SongList),
|
(status = 200, body = dto::SongList),
|
||||||
|
@ -829,6 +941,10 @@ async fn get_genre_songs(
|
||||||
get,
|
get,
|
||||||
path = "/search/{*query}",
|
path = "/search/{*query}",
|
||||||
tag = "Collection",
|
tag = "Collection",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
("query", allow_reserved),
|
("query", allow_reserved),
|
||||||
|
@ -874,6 +990,10 @@ async fn get_search(
|
||||||
get,
|
get,
|
||||||
path = "/playlists",
|
path = "/playlists",
|
||||||
tag = "Playlists",
|
tag = "Playlists",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, body = Vec<dto::PlaylistHeader>),
|
(status = 200, body = Vec<dto::PlaylistHeader>),
|
||||||
)
|
)
|
||||||
|
@ -892,6 +1012,10 @@ async fn get_playlists(
|
||||||
put,
|
put,
|
||||||
path = "/playlist/{name}",
|
path = "/playlist/{name}",
|
||||||
tag = "Playlists",
|
tag = "Playlists",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("name",)),
|
params(("name",)),
|
||||||
request_body = dto::SavePlaylistInput,
|
request_body = dto::SavePlaylistInput,
|
||||||
)]
|
)]
|
||||||
|
@ -918,6 +1042,10 @@ async fn put_playlist(
|
||||||
get,
|
get,
|
||||||
path = "/playlist/{name}",
|
path = "/playlist/{name}",
|
||||||
tag = "Playlists",
|
tag = "Playlists",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
|
||||||
("name",),
|
("name",),
|
||||||
|
@ -955,6 +1083,10 @@ async fn get_playlist(
|
||||||
delete,
|
delete,
|
||||||
path = "/playlist/{name}",
|
path = "/playlist/{name}",
|
||||||
tag = "Playlists",
|
tag = "Playlists",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("name",)),
|
params(("name",)),
|
||||||
)]
|
)]
|
||||||
async fn delete_playlist(
|
async fn delete_playlist(
|
||||||
|
@ -972,6 +1104,10 @@ async fn delete_playlist(
|
||||||
get,
|
get,
|
||||||
path = "/audio/{*path}",
|
path = "/audio/{*path}",
|
||||||
tag = "Media",
|
tag = "Media",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(("path", allow_reserved)),
|
params(("path", allow_reserved)),
|
||||||
responses(
|
responses(
|
||||||
(status = 206, body = [u8]),
|
(status = 206, body = [u8]),
|
||||||
|
@ -1002,6 +1138,10 @@ async fn get_audio(
|
||||||
get,
|
get,
|
||||||
path = "/thumbnail/{*path}",
|
path = "/thumbnail/{*path}",
|
||||||
tag = "Media",
|
tag = "Media",
|
||||||
|
security(
|
||||||
|
("auth_token" = []),
|
||||||
|
("auth_query_param" = []),
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("path", allow_reserved),
|
("path", allow_reserved),
|
||||||
dto::ThumbnailOptions
|
dto::ThumbnailOptions
|
||||||
|
|
|
@ -1,4 +1,41 @@
|
||||||
use utoipa::OpenApi;
|
use utoipa::openapi::{
|
||||||
|
security::{ApiKey, ApiKeyValue, HttpAuthScheme, HttpBuilder, SecurityScheme},
|
||||||
|
ComponentsBuilder, ContactBuilder, InfoBuilder, License, OpenApi, OpenApiBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
pub fn open_api() -> OpenApi {
|
||||||
pub struct ApiDoc;
|
let auth_token_description = "Authentication token acquired from the `/auth` endpoint";
|
||||||
|
|
||||||
|
OpenApiBuilder::new()
|
||||||
|
.info(
|
||||||
|
InfoBuilder::new()
|
||||||
|
.title(env!("CARGO_PKG_NAME"))
|
||||||
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
|
.license(Some(License::new("MIT")))
|
||||||
|
.contact(Some(
|
||||||
|
ContactBuilder::new().name(Some("Antoine Gersant")).build(),
|
||||||
|
))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.components(Some(
|
||||||
|
ComponentsBuilder::new()
|
||||||
|
.security_scheme(
|
||||||
|
"auth_header",
|
||||||
|
SecurityScheme::Http(
|
||||||
|
HttpBuilder::new()
|
||||||
|
.scheme(HttpAuthScheme::Bearer)
|
||||||
|
.description(Some(auth_token_description))
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.security_scheme(
|
||||||
|
"auth_query_param",
|
||||||
|
SecurityScheme::ApiKey(ApiKey::Query(ApiKeyValue::with_description(
|
||||||
|
"auth_token",
|
||||||
|
auth_token_description,
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue