forked from jmug/stoatchat
feat(services/autumn): scaffold implementation of api
This commit is contained in:
1483
Cargo.lock
generated
1483
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,8 @@ members = [
|
|||||||
"crates/delta",
|
"crates/delta",
|
||||||
"crates/bonfire",
|
"crates/bonfire",
|
||||||
"crates/core/*",
|
"crates/core/*",
|
||||||
"crates/bindings/node",
|
"crates/services/*",
|
||||||
|
"crates/bindings/*",
|
||||||
]
|
]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
|||||||
10
crates/core/files/Cargo.toml
Normal file
10
crates/core/files/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-files"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
aes-gcm = "0.10.3"
|
||||||
|
|
||||||
|
aws-config = "1.5.5"
|
||||||
|
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
|
||||||
3
crates/core/files/src/lib.rs
Normal file
3
crates/core/files/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// pub fn
|
||||||
|
|
||||||
|
pub fn decrypt(data: &mut [u8]) {}
|
||||||
22
crates/services/autumn/Cargo.toml
Normal file
22
crates/services/autumn/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "revolt-autumn"
|
||||||
|
version = "0.7.13"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0.68"
|
||||||
|
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
|
revolt-config = { version = "0.7.13", path = "../../core/config" }
|
||||||
|
|
||||||
|
tempfile = "3.12.0"
|
||||||
|
axum_typed_multipart = "0.12.1"
|
||||||
|
axum = { version = "0.7.5", features = ["multipart"] }
|
||||||
|
|
||||||
|
utoipa-scalar = { version = "0.1.0", features = ["axum"] }
|
||||||
|
utoipa = { version = "4.2.3", features = ["axum_extras", "ulid"] }
|
||||||
160
crates/services/autumn/src/api.rs
Normal file
160
crates/services/autumn/src/api.rs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{DefaultBodyLimit, Multipart, Path},
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::{get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
|
||||||
|
use revolt_config::config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
pub async fn router() -> Router {
|
||||||
|
let config = config().await;
|
||||||
|
|
||||||
|
Router::new().route("/", get(root)).route(
|
||||||
|
"/:tag",
|
||||||
|
post(upload_file).layer(DefaultBodyLimit::max(
|
||||||
|
config.features.limits.global.body_limit_size,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Successful root response
|
||||||
|
#[derive(Serialize, Debug, ToSchema)]
|
||||||
|
pub struct RootResponse {
|
||||||
|
autumn: &'static str,
|
||||||
|
version: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capture crate version from Cargo
|
||||||
|
static CRATE_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
/// Root response from service
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Echo response", body = RootResponse)
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
async fn root() -> Json<RootResponse> {
|
||||||
|
Json(RootResponse {
|
||||||
|
autumn: "Hello, I am a file server!",
|
||||||
|
version: CRATE_VERSION,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Available tags to upload to
|
||||||
|
#[derive(Deserialize, Debug, ToSchema)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub enum Tag {
|
||||||
|
attachments,
|
||||||
|
avatars,
|
||||||
|
backgrounds,
|
||||||
|
icons,
|
||||||
|
banners,
|
||||||
|
emojis,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request body for upload
|
||||||
|
#[derive(ToSchema, TryFromMultipart)]
|
||||||
|
pub struct UploadPayload {
|
||||||
|
#[schema(format = Binary)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[form_data(limit = "unlimited")] // handled by axum
|
||||||
|
file: FieldData<NamedTempFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Successful upload response
|
||||||
|
#[derive(Serialize, Debug, ToSchema)]
|
||||||
|
pub struct UploadResponse {
|
||||||
|
/// ID to attach uploaded file to object
|
||||||
|
id: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload a file
|
||||||
|
///
|
||||||
|
/// Available tags and restrictions:
|
||||||
|
///
|
||||||
|
/// | Tag | Size | Resolution | Type |
|
||||||
|
/// | :-: | --: | :-- | :-: |
|
||||||
|
/// | attachments | 20 MB | - | Any |
|
||||||
|
/// | avatars | 4 MB | 40 MP or 10,000px | Image |
|
||||||
|
/// | backgrounds | 6 MB | 40 MP or 10,000px | Image |
|
||||||
|
/// | icons | 2.5 MB | 40 MP or 10,000px | Image |
|
||||||
|
/// | banners | 6 MB | 40 MP or 10,000px | Image |
|
||||||
|
/// | emojis | 500 KB | 40 MP or 10,000px | Image |
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/{tag}",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Upload was successful", body = UploadResponse)
|
||||||
|
),
|
||||||
|
params(
|
||||||
|
("tag" = Tag, Path, description = "Tag to upload to (e.g. attachments, icons, ...)")
|
||||||
|
),
|
||||||
|
request_body(content_type = "multipart/form-data", content = UploadPayload),
|
||||||
|
)]
|
||||||
|
async fn upload_file(
|
||||||
|
Path(tag): Path<Tag>,
|
||||||
|
TypedMultipart(UploadPayload { file }): TypedMultipart<UploadPayload>,
|
||||||
|
) -> axum::response::Result<Json<UploadResponse>> {
|
||||||
|
Ok(Json(UploadResponse { id: "aaa" }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch preview of file
|
||||||
|
///
|
||||||
|
/// Depending on the given tag, the file will be re-processed to fit the criteria:
|
||||||
|
///
|
||||||
|
/// | Tag | Image Resolution <sup>†</sup> |
|
||||||
|
/// | :-: | --- |
|
||||||
|
/// | attachments | Up to 1280px on any axis |
|
||||||
|
/// | avatars | Up to 128px on any axis |
|
||||||
|
/// | backgrounds | Up to 1280x720px |
|
||||||
|
/// | icons | Up to 128px on any axis |
|
||||||
|
/// | banners | Up to 480px on any axis |
|
||||||
|
/// | emojis | Up to 128px on any axis |
|
||||||
|
///
|
||||||
|
/// <sup>†</sup> aspect ratio will always be preserved
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/{tag}/{file_id}",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Generated preview", body = Vec<u8>)
|
||||||
|
),
|
||||||
|
params(
|
||||||
|
("tag" = Tag, Path, description = "Tag to fetch from (e.g. attachments, icons, ...)"),
|
||||||
|
("file_id" = String, Path, description = "File identifier")
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
async fn fetch_preview(
|
||||||
|
Path(tag): Path<Tag>,
|
||||||
|
Path(file_id): Path<String>,
|
||||||
|
) -> axum::response::Result<Response> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch original file
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/{tag}/{file_id}/{file_name}",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Generated preview", body = Vec<u8>)
|
||||||
|
),
|
||||||
|
params(
|
||||||
|
("tag" = Tag, Path, description = "Tag to fetch from (e.g. attachments, icons, ...)"),
|
||||||
|
("file_id" = String, Path, description = "File identifier"),
|
||||||
|
("file_name" = String, Path, description = "File name")
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
async fn fetch_file(
|
||||||
|
Path(tag): Path<Tag>,
|
||||||
|
Path(file_id): Path<String>,
|
||||||
|
Path(file_name): Path<String>,
|
||||||
|
) -> axum::response::Result<Response> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
65
crates/services/autumn/src/main.rs
Normal file
65
crates/services/autumn/src/main.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use utoipa::{
|
||||||
|
openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
|
||||||
|
Modify, OpenApi,
|
||||||
|
};
|
||||||
|
use utoipa_scalar::{Scalar, Servable as ScalarServable};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
|
// Configure logging and environment
|
||||||
|
revolt_config::configure!(api);
|
||||||
|
|
||||||
|
// Configure API schema
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
modifiers(&SecurityAddon),
|
||||||
|
paths(
|
||||||
|
api::root,
|
||||||
|
api::upload_file,
|
||||||
|
api::fetch_preview,
|
||||||
|
api::fetch_file
|
||||||
|
),
|
||||||
|
components(
|
||||||
|
schemas(
|
||||||
|
api::RootResponse,
|
||||||
|
api::Tag,
|
||||||
|
api::UploadPayload,
|
||||||
|
api::UploadResponse
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tags(
|
||||||
|
// (name = "Files", description = "File uploads API")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
struct ApiDoc;
|
||||||
|
|
||||||
|
struct SecurityAddon;
|
||||||
|
|
||||||
|
impl Modify for SecurityAddon {
|
||||||
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
|
if let Some(components) = openapi.components.as_mut() {
|
||||||
|
components.add_security_scheme(
|
||||||
|
"api_key",
|
||||||
|
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("todo_apikey"))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Axum and router
|
||||||
|
let app = Router::new()
|
||||||
|
.merge(Scalar::with_url("/scalar", ApiDoc::openapi()))
|
||||||
|
.nest("/", api::router().await);
|
||||||
|
|
||||||
|
// Configure TCP listener and bind
|
||||||
|
let address = SocketAddr::from((Ipv4Addr::UNSPECIFIED, 3000));
|
||||||
|
let listener = TcpListener::bind(&address).await?;
|
||||||
|
axum::serve(listener, app.into_make_service()).await
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user