Skip to content

Commit

Permalink
Add new endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ksindi committed Oct 21, 2023
1 parent 631d600 commit 29e3c72
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 2 deletions.
114 changes: 113 additions & 1 deletion backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ async fn app() -> Router {

Router::new()
.route("/upload", post(upload))
.route("/evaluations/:evaluation_id", get(get_evaluation))
.route(
"/evaluations/:evaluation_id",
get(get_evaluation).delete(delete_resume_and_evaluation),
)
.route(
"/evaluations/:evaluation_id/download_resume",
get(download_resume),
)
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
Expand Down Expand Up @@ -125,6 +132,69 @@ async fn upload() -> Result<impl IntoResponse, (StatusCode, String)> {
Ok((StatusCode::ACCEPTED, Json(result)).into_response())
}

async fn delete_resume_and_evaluation(
Path(evaluation_id): Path<String>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let s3_context = s3::S3Context::new().await;

let resume_key = format!("resumes/{}", evaluation_id.to_string());
let evaluation_key = format!("results/{}", evaluation_id.to_string());

tracing::info!("Checking if object exists: {}", resume_key);

let resume_exists = s3_context.object_exists(&resume_key).await.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to check resume status: {err}"),
)
})?;

if !resume_exists {
return Err((
StatusCode::NOT_FOUND,
format!("resume {} not found", evaluation_id),
));
}

tracing::info!("Checking if object exists: {}", evaluation_key);

let evaluation_exists = s3_context
.object_exists(&evaluation_key)
.await
.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to check evaluation status: {err}"),
)
})?;

if !evaluation_exists {
return Err((
StatusCode::NOT_FOUND,
format!("evaluation {} not found", evaluation_id),
));
}

s3_context.delete_object(&resume_key).await.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to delete resume: {err}"),
)
})?;

s3_context
.delete_object(&evaluation_key)
.await
.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to delete evaluation: {err}"),
)
})?;

Ok((StatusCode::ACCEPTED, "OK").into_response())
}

async fn get_evaluation(
Path(evaluation_id): Path<String>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
Expand Down Expand Up @@ -160,6 +230,48 @@ async fn get_evaluation(
Ok((StatusCode::ACCEPTED, Json(evaluation)).into_response())
}

async fn download_resume(
Path(evaluation_id): Path<String>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let s3_context = s3::S3Context::new().await;

let key = format!("resumes/{}", evaluation_id.to_string());

tracing::info!("Checking if object exists: {}", key);

let object_exists = s3_context.object_exists(&key).await.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to check resume status: {err}"),
)
})?;

if !object_exists {
return Err((
StatusCode::NOT_FOUND,
format!("resume {} not found", evaluation_id),
));
}

let filename = format!("resume-{}.pdf", evaluation_id);

let presigned_url = s3_context
.get_object_presigned_url(&key, &filename)
.await
.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("failed to get presigned url: {err}"),
)
})?;

let result = json!({
"download_url": presigned_url,
});

Ok((StatusCode::ACCEPTED, Json(result)).into_response())
}

#[tokio::main]
async fn main() {
logging::setup_logging();
Expand Down
36 changes: 35 additions & 1 deletion backend/src/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use anyhow::{Context, Result};
use aws_sdk_s3::presigning::PresigningConfig;
use aws_sdk_s3::primitives::ByteStream;

const PUT_EXPIRES_IN: Duration = Duration::from_secs(30 * 60);
const GET_EXPIRES_IN: Duration = Duration::from_secs(24 * 60 * 60); // 24 hours
const PUT_EXPIRES_IN: Duration = Duration::from_secs(30 * 60); // 30 minutes

#[derive(Debug, Clone)]
pub struct S3Context {
Expand Down Expand Up @@ -40,6 +41,24 @@ impl S3Context {
Ok(res.is_ok())
}

/// Generate presigned URL for downloading from S3.
pub async fn get_object_presigned_url(
&self,
key: &String,
filename: &String,
) -> Result<String> {
let presigned_req = &self
.client
.get_object()
.bucket(&self.bucket)
.key(key)
.response_content_disposition(format!("attachment; filename=\"{}\"", filename))
.presigned(PresigningConfig::expires_in(GET_EXPIRES_IN)?)
.await?;

Ok(presigned_req.uri().to_string())
}

/// Generate presigned URL for uploading to S3.
pub async fn put_object_presigned_url(&self, key: &String) -> Result<String> {
let presigned_req = &self
Expand Down Expand Up @@ -88,4 +107,19 @@ impl S3Context {

Ok(())
}

/// Delete S3 object.
pub async fn delete_object(&self, key: &str) -> Result<()> {
self.client
.delete_object()
.bucket(&self.bucket)
.key(key)
.send()
.await
.with_context(|| {
format!("Failed to delete {} from S3 bucket {}.", key, &self.bucket)
})?;

Ok(())
}
}

0 comments on commit 29e3c72

Please sign in to comment.