post3 0.1.0

Pluggable S3-compatible object storage — core library with PostgreSQL and filesystem backends
Documentation
use sqlx::{PgPool, Postgres, Transaction};
use uuid::Uuid;

use crate::error::Post3Error;
use crate::models::MultipartUploadRow;

pub struct MultipartUploadsRepository;

impl MultipartUploadsRepository {
    pub async fn create_in_tx(
        tx: &mut Transaction<'_, Postgres>,
        bucket_id: Uuid,
        key: &str,
        upload_id: &str,
        content_type: &str,
    ) -> Result<MultipartUploadRow, Post3Error> {
        let row = sqlx::query_as::<_, MultipartUploadRow>(
            "INSERT INTO multipart_uploads (bucket_id, key, upload_id, content_type) \
             VALUES ($1, $2, $3, $4) RETURNING *",
        )
        .bind(bucket_id)
        .bind(key)
        .bind(upload_id)
        .bind(content_type)
        .fetch_one(&mut **tx)
        .await?;

        Ok(row)
    }

    pub async fn get_by_upload_id(
        db: &PgPool,
        upload_id: &str,
    ) -> Result<Option<MultipartUploadRow>, Post3Error> {
        let row = sqlx::query_as::<_, MultipartUploadRow>(
            "SELECT * FROM multipart_uploads WHERE upload_id = $1",
        )
        .bind(upload_id)
        .fetch_optional(db)
        .await?;

        Ok(row)
    }

    pub async fn delete_in_tx(
        tx: &mut Transaction<'_, Postgres>,
        id: Uuid,
    ) -> Result<(), Post3Error> {
        sqlx::query("DELETE FROM multipart_uploads WHERE id = $1")
            .bind(id)
            .execute(&mut **tx)
            .await?;
        Ok(())
    }

    pub async fn delete_by_upload_id(
        db: &PgPool,
        upload_id: &str,
    ) -> Result<bool, Post3Error> {
        let result = sqlx::query("DELETE FROM multipart_uploads WHERE upload_id = $1")
            .bind(upload_id)
            .execute(db)
            .await?;
        Ok(result.rows_affected() > 0)
    }

    pub async fn list(
        db: &PgPool,
        bucket_id: Uuid,
        prefix: Option<&str>,
        key_marker: Option<&str>,
        upload_id_marker: Option<&str>,
        max_uploads: i64,
    ) -> Result<Vec<MultipartUploadRow>, Post3Error> {
        let rows = match (prefix, key_marker) {
            (Some(pfx), Some(marker)) => {
                let pattern = format!("{pfx}%");
                // When key_marker is provided, return uploads with key > marker,
                // or same key but upload_id > upload_id_marker
                if let Some(uid_marker) = upload_id_marker {
                    sqlx::query_as::<_, MultipartUploadRow>(
                        "SELECT * FROM multipart_uploads \
                         WHERE bucket_id = $1 AND key LIKE $2 \
                         AND (key > $3 OR (key = $3 AND upload_id > $4)) \
                         ORDER BY key ASC, upload_id ASC LIMIT $5",
                    )
                    .bind(bucket_id)
                    .bind(pattern)
                    .bind(marker)
                    .bind(uid_marker)
                    .bind(max_uploads)
                    .fetch_all(db)
                    .await?
                } else {
                    sqlx::query_as::<_, MultipartUploadRow>(
                        "SELECT * FROM multipart_uploads \
                         WHERE bucket_id = $1 AND key LIKE $2 AND key > $3 \
                         ORDER BY key ASC, upload_id ASC LIMIT $4",
                    )
                    .bind(bucket_id)
                    .bind(pattern)
                    .bind(marker)
                    .bind(max_uploads)
                    .fetch_all(db)
                    .await?
                }
            }
            (Some(pfx), None) => {
                let pattern = format!("{pfx}%");
                sqlx::query_as::<_, MultipartUploadRow>(
                    "SELECT * FROM multipart_uploads \
                     WHERE bucket_id = $1 AND key LIKE $2 \
                     ORDER BY key ASC, upload_id ASC LIMIT $3",
                )
                .bind(bucket_id)
                .bind(pattern)
                .bind(max_uploads)
                .fetch_all(db)
                .await?
            }
            (None, Some(marker)) => {
                if let Some(uid_marker) = upload_id_marker {
                    sqlx::query_as::<_, MultipartUploadRow>(
                        "SELECT * FROM multipart_uploads \
                         WHERE bucket_id = $1 \
                         AND (key > $2 OR (key = $2 AND upload_id > $3)) \
                         ORDER BY key ASC, upload_id ASC LIMIT $4",
                    )
                    .bind(bucket_id)
                    .bind(marker)
                    .bind(uid_marker)
                    .bind(max_uploads)
                    .fetch_all(db)
                    .await?
                } else {
                    sqlx::query_as::<_, MultipartUploadRow>(
                        "SELECT * FROM multipart_uploads \
                         WHERE bucket_id = $1 AND key > $2 \
                         ORDER BY key ASC, upload_id ASC LIMIT $3",
                    )
                    .bind(bucket_id)
                    .bind(marker)
                    .bind(max_uploads)
                    .fetch_all(db)
                    .await?
                }
            }
            (None, None) => {
                sqlx::query_as::<_, MultipartUploadRow>(
                    "SELECT * FROM multipart_uploads \
                     WHERE bucket_id = $1 \
                     ORDER BY key ASC, upload_id ASC LIMIT $2",
                )
                .bind(bucket_id)
                .bind(max_uploads)
                .fetch_all(db)
                .await?
            }
        };

        Ok(rows)
    }
}