diff --git a/packages/server/src/api/files.rs b/packages/server/src/api/files.rs new file mode 100644 index 0000000..277b64a --- /dev/null +++ b/packages/server/src/api/files.rs @@ -0,0 +1,257 @@ +use crate::auth::security::Authenticated; +use crate::{ + error::Result, + files::{self, repository::PdsRepository}, + pds::{rpc, space}, +}; +use crate_protocol::{ + BlobRef, CreateFile, DuplicateFile, FileEntry, FileKind, FileList, FileRecord, RevisionInput, + UpdateFile, VersionList, +}; +use poem_openapi::{ + OpenApi, + param::{Path, Query}, + payload::Json, +}; + +pub struct FilesApi; + +#[OpenApi] +impl FilesApi { + #[oai(path = "/api/v1/files", method = "get", operation_id = "listFiles")] + async fn list( + &self, + auth: Authenticated, + #[oai(name = "parentId")] parent: Query>, + trash: Query>, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + if !space::exists(&session, &space).await? { + return Ok(Json(FileList { files: vec![] })); + } + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::read::list( + &repo, + parent.as_deref(), + trash.unwrap_or(false), + &space.uri, + &did, + ) + .await?, + )) + } + + #[oai(path = "/api/v1/files/:id", method = "get", operation_id = "getFile")] + async fn get(&self, auth: Authenticated, id: Path) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::read::get(&repo, &id).await?.entry(&space.uri, &did), + )) + } + + #[oai(path = "/api/v1/files", method = "post", operation_id = "createFile")] + async fn create( + &self, + auth: Authenticated, + input: Json, + ) -> Result> { + crate_protocol::validate_name(&input.name).map_err(crate::error::ApiError::bad)?; + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + files::read::parent(&repo, input.parent_id.as_deref(), None).await?; + space::ensure(&session, &space).await?; + let blob = if input.kind == FileKind::File { + #[derive(serde::Deserialize)] + struct Uploaded { + blob: BlobRef, + } + let uploaded = + rpc::upload::(&session, vec![], "application/octet-stream").await?; + files::content::verify_blob(&uploaded.blob, &[])?; + Some(uploaded.blob) + } else { + None + }; + let now = chrono::Utc::now().to_rfc3339(); + let record = FileRecord { + name: input.name.clone(), + parent_id: input.parent_id.clone(), + is_directory: input.kind == FileKind::Directory, + created_at: now.clone(), + updated_at: now, + trashed_at: None, + blob, + }; + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::mutation::create(&repo, record) + .await? + .entry(&space.uri, &did), + )) + } + + #[oai( + path = "/api/v1/files/:id", + method = "put", + operation_id = "updateFile" + )] + async fn update( + &self, + auth: Authenticated, + id: Path, + input: Json, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::mutation::metadata(&repo, &id, input.0) + .await? + .entry(&space.uri, &did), + )) + } + + #[oai( + path = "/api/v1/files/:id/duplicate", + method = "post", + operation_id = "duplicateFile" + )] + async fn duplicate( + &self, + auth: Authenticated, + id: Path, + input: Json, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::duplicate::duplicate(&repo, &id, input.0) + .await? + .entry(&space.uri, &did), + )) + } + + #[oai( + path = "/api/v1/files/:id/trash", + method = "post", + operation_id = "trashFile" + )] + async fn trash( + &self, + auth: Authenticated, + id: Path, + input: Json, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::mutation::trash(&repo, &id, &input.revision, false) + .await? + .entry(&space.uri, &did), + )) + } + + #[oai( + path = "/api/v1/files/:id/restore", + method = "post", + operation_id = "restoreFile" + )] + async fn restore( + &self, + auth: Authenticated, + id: Path, + input: Json, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::mutation::trash(&repo, &id, &input.revision, true) + .await? + .entry(&space.uri, &did), + )) + } + + #[oai( + path = "/api/v1/files/:id/versions", + method = "get", + operation_id = "listVersions" + )] + async fn versions(&self, auth: Authenticated, id: Path) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::versions::list(&repo, &id, &space.uri, &did).await?, + )) + } + + #[oai( + path = "/api/v1/files/:id/versions/:versionId/restore", + method = "post", + operation_id = "restoreVersion" + )] + async fn restore_version( + &self, + auth: Authenticated, + id: Path, + #[oai(name = "versionId")] version: Path, + input: Json, + ) -> Result> { + let (did, session) = auth.0; + let space = space::personal(&did); + let repo = PdsRepository { + session: &session, + space: &space, + did: &did, + }; + Ok(Json( + files::versions::restore(&repo, &id, &version, &input.revision) + .await? + .entry(&space.uri, &did), + )) + } +} diff --git a/packages/server/src/api/mod.rs b/packages/server/src/api/mod.rs new file mode 100644 index 0000000..a787351 --- /dev/null +++ b/packages/server/src/api/mod.rs @@ -0,0 +1,11 @@ +pub mod auth; +pub mod content; +pub mod files; + +pub fn service() -> poem_openapi::OpenApiService { + poem_openapi::OpenApiService::new( + (auth::AuthApi, files::FilesApi, content::ContentApi), + "Crate web companion API", + "1.0.0", + ) +} diff --git a/packages/server/src/bin/export-openapi.rs b/packages/server/src/bin/export-openapi.rs new file mode 100644 index 0000000..6384bff --- /dev/null +++ b/packages/server/src/bin/export-openapi.rs @@ -0,0 +1,3 @@ +fn main() { + println!("{}", crate_server::api::service().spec()); +}