Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f64df90
feat: object put operation added
CodeMaster4711 Oct 9, 2025
28fef5a
feat: added body (not working)
CodeMaster4711 Oct 9, 2025
89ff37f
Fix code style issues with clippy
actions-user Oct 9, 2025
0238afc
fix: tmp file cleanup
Profiidev Oct 9, 2025
ef02110
fix: upload documents with bytes
CodeMaster4711 Oct 9, 2025
0774687
Fix code style issues with clippy
actions-user Oct 9, 2025
9c28d85
test: fix s3 auth tests
Profiidev Oct 10, 2025
c7059fa
Merge branch 'main' into feat/object-actions
Profiidev Oct 10, 2025
fa65ac2
test: added create bucket
Profiidev Oct 10, 2025
baaaca1
doc: updated readme
Profiidev Oct 10, 2025
0509601
feat: added delete object and integration tests
CodeMaster4711 Oct 12, 2025
60e3062
refactor: don't log frontend proxy at info level
Profiidev Oct 10, 2025
97ce068
feat: added instrumenting
Profiidev Oct 11, 2025
262abab
feat: added metrics
Profiidev Oct 11, 2025
cfc7422
feat: added some more logging
Profiidev Oct 12, 2025
654161a
refactor: better error reponse codes
Profiidev Oct 12, 2025
de1d0e4
fix(deps): update cargo (#42)
renovate[bot] Oct 13, 2025
252b09e
chore(deps): update npm (#43)
renovate[bot] Oct 14, 2025
3d171b2
chore(deps): update npm (#45)
renovate[bot] Oct 20, 2025
81b8ddd
chore(deps): update cargo (#44)
renovate[bot] Oct 20, 2025
a7607ff
chore(deps): update dependency vite to v7.1.11 [security] (#46)
renovate[bot] Oct 20, 2025
31f1b66
chore(deps): update npm (#47)
renovate[bot] Oct 27, 2025
565e0df
fix(deps): update rust crate centaurus to 0.4.0 (#48)
renovate[bot] Oct 27, 2025
70437e1
fix(deps): update cargo (#50)
renovate[bot] Nov 3, 2025
57c418c
chore(deps): update npm (#49)
renovate[bot] Nov 3, 2025
4eaa83f
chore: removed devenv
Profiidev Nov 3, 2025
9f06e6b
ci: fix cache skip
Profiidev Nov 3, 2025
13501c1
ci: fix cache save
Profiidev Nov 3, 2025
301971d
ci: fix cache save
Profiidev Nov 3, 2025
4ea5cb9
test: better port finding
Profiidev Nov 3, 2025
5a4bc91
Fix code style issues with Prettier
actions-user Nov 3, 2025
f5f7eca
fix: some small after merge fixes
Profiidev Nov 3, 2025
f90102a
Merge branch 'main' into feat/object-actions
Profiidev Nov 3, 2025
70f3a9c
fix: removed npm build cache
Profiidev Nov 3, 2025
94bd4ba
chore: updated comps
Profiidev Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
493 changes: 492 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
[workspace]
resolver = "3"
members = ["backend", "backend/migration", "backend/entity"]

[workspace.dependencies]
serde = { version = "1.0.223", features = ["derive"] }
sea-orm = { version = "1.1.16", features = [
"sqlx-sqlite",
"runtime-tokio-rustls",
] }
10 changes: 2 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@ WORKDIR /app/frontend
COPY frontend/package.json ./
COPY package-lock.json package.json ../

RUN \
--mount=type=cache,target=/app/node_modules,sharing=locked \
--mount=type=cache,target=/app/frontend/node_modules,sharing=locked \
npm ci
RUN npm ci

COPY frontend/svelte.config.js frontend/tsconfig.json frontend/vite.config.ts ./
COPY frontend/src ./src
COPY frontend/static ./static

RUN \
--mount=type=cache,target=/app/node_modules,sharing=locked \
--mount=type=cache,target=/app/frontend/node_modules,sharing=locked \
npm run build
RUN npm run build

FROM ghcr.io/profiidev/images/rust-musl-builder:main AS backend-planner

Expand Down
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# Ich will dich Monorepo
# Sagittarius

[Svelte Monorepo](https://github.com/Pandoks/svelteplex)
## Development

### Requirements

- [devenv](https://devenv.sh/)
- [direnv](https://direnv.net/)(optional but recommended)

### Setup

With `direnv`:

```bash
direnv allow
```

Without `direnv`:

```bash
devenv shell
```

### Running

```bash
docker compose up
```

## Building

The docker images is built automatically with GitHub Actions on every push to `main` branch and on every release.
To build the image locally, run:

```bash
docker build -t sagittarius .
```

## Testing

### Backend

```bash
cargo nextest run --workspace --all-features
```
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ PORT=8000
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
BASE_URL="http://localhost:5173"

STORAGE_PATH=./data
STORAGE_PATH=/data

DATABASE_URL="sqlite://data/sqlite.db"
DATABASE_LOGGING=true
4 changes: 3 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/data/
/data/
/Cargo.lock
/target/
14 changes: 12 additions & 2 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2024"

[dependencies]
serde = { workspace = true }
sea-orm = { workspace = true }
serde = { version = "1.0.223", features = ["derive"] }
sea-orm = { version = "1.1.16", features = [
"sqlx-sqlite",
"runtime-tokio-rustls",
] }
centaurus = { version = "0.4.4", features = [
"axum",
"logging",
Expand Down Expand Up @@ -67,6 +70,13 @@ reqwest = { version = "0.12.23", features = [
], default-features = false }
figment = { version = "0.10.19", features = ["env", "toml"] }

[dev-dependencies]
rust-s3 = { version = "0.37.0", default-features = false, features = [
"fail-on-err",
"tags",
"tokio-rustls-tls",
] }

[features]
test = []

Expand Down
7 changes: 5 additions & 2 deletions backend/entity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ version = "0.1.0"
edition = "2024"

[dependencies]
sea-orm = { workspace = true }
serde = { workspace = true }
serde = { version = "1.0.223", features = ["derive"] }
sea-orm = { version = "1.1.16", features = [
"sqlx-sqlite",
"runtime-tokio-rustls",
] }

[lib]
name = "entity"
Expand Down
63 changes: 49 additions & 14 deletions backend/src/s3/auth/body.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{fmt::Debug, path::PathBuf};
use std::{
fmt::Debug,
path::{Path, PathBuf},
};

use centaurus::{error::Result, req::xml::Xml};
use centaurus::{error::Result, path, req::xml::Xml};
use serde::Deserialize;
use tokio::{
fs::{File, OpenOptions},
Expand All @@ -9,6 +12,8 @@ use tokio::{
use tracing::instrument;
use uuid::Uuid;

use crate::s3::storage::TMP_DIR;

#[async_trait::async_trait]
pub trait Body: Sized {
type Writer: BodyWriter + Send;
Expand All @@ -23,7 +28,7 @@ pub trait Body: Sized {
pub struct TmpFile(pub PathBuf);
#[derive(Debug)]
#[allow(unused)]
pub struct FileWriter(File, PathBuf);
pub struct FileWriter(File, PathBuf, bool);

impl Drop for TmpFile {
#[instrument]
Expand All @@ -34,6 +39,14 @@ impl Drop for TmpFile {
}
}

impl Drop for FileWriter {
fn drop(&mut self) {
if self.2 && self.1.exists() {
let _ = std::fs::remove_file(&self.1);
}
}
}

#[async_trait::async_trait]
impl Body for () {
type Writer = ();
Expand All @@ -48,10 +61,13 @@ impl Body for TmpFile {
type Writer = FileWriter;

#[instrument]
async fn from_writer(writer: Self::Writer) -> Result<Self> {
async fn from_writer(mut writer: Self::Writer) -> Result<Self> {
writer.0.sync_all().await?;
drop(writer.0);
Ok(TmpFile(writer.1))
let path = writer.1.clone();
writer.2 = false; // Prevent deletion on
drop(writer);

Ok(TmpFile(path))
}
}

Expand Down Expand Up @@ -87,13 +103,13 @@ where

#[async_trait::async_trait]
pub trait BodyWriter: Sized + Debug {
async fn new() -> Result<Self>;
async fn new(data_dir: &Path) -> Result<Self>;
async fn write(&mut self, buf: &[u8]) -> Result<()>;
}

#[async_trait::async_trait]
impl BodyWriter for () {
async fn new() -> Result<Self> {
async fn new(_: &Path) -> Result<Self> {
Ok(())
}

Expand All @@ -104,15 +120,15 @@ impl BodyWriter for () {

#[async_trait::async_trait]
impl BodyWriter for FileWriter {
async fn new() -> Result<Self> {
let path = std::env::temp_dir().join(format!("sagittarius-{}", Uuid::new_v4()));
async fn new(data_dir: &Path) -> Result<Self> {
let path = path!(data_dir, TMP_DIR, Uuid::new_v4().to_string());
let file = OpenOptions::new()
.create_new(true)
.read(true)
.append(true)
.open(&path)
.await?;
Ok(FileWriter(file, path))
Ok(FileWriter(file, path, true))
}

async fn write(&mut self, buf: &[u8]) -> Result<()> {
Expand All @@ -124,7 +140,7 @@ impl BodyWriter for FileWriter {

#[async_trait::async_trait]
impl BodyWriter for Vec<u8> {
async fn new() -> Result<Self> {
async fn new(_: &Path) -> Result<Self> {
Ok(Vec::new())
}

Expand All @@ -140,7 +156,10 @@ mod test {

#[tokio::test]
async fn test_tmp_file() {
let mut writer = <TmpFile as Body>::Writer::new().await.unwrap();
let tmp_dir = std::env::temp_dir();
let data_dir = path!(&tmp_dir, TMP_DIR);
tokio::fs::create_dir_all(&data_dir).await.unwrap();
let mut writer = <TmpFile as Body>::Writer::new(&tmp_dir).await.unwrap();
writer.write(b"Hello, ").await.unwrap();
writer.write(b"world!").await.unwrap();
let body = <TmpFile as Body>::from_writer(writer).await.unwrap();
Expand All @@ -154,9 +173,25 @@ mod test {
assert!(!path.exists());
}

#[tokio::test]
async fn test_tmp_file_cancel() {
let tmp_dir = std::env::temp_dir();
let data_dir = path!(&tmp_dir, TMP_DIR);
tokio::fs::create_dir_all(&data_dir).await.unwrap();
let mut writer = <TmpFile as Body>::Writer::new(&tmp_dir).await.unwrap();
writer.write(b"Hello, ").await.unwrap();
writer.write(b"world!").await.unwrap();
let path = writer.1.clone();
drop(writer); // Drop without converting to body

// The file should be deleted
assert!(!path.exists());
}

#[tokio::test]
async fn test_vec_u8() {
let mut writer = <<Vec<u8> as Body>::Writer as BodyWriter>::new()
let data_dir = std::env::temp_dir();
let mut writer = <<Vec<u8> as Body>::Writer as BodyWriter>::new(&data_dir)
.await
.unwrap();
<Vec<u8> as BodyWriter>::write(&mut writer, b"Hello, ")
Expand Down
33 changes: 22 additions & 11 deletions backend/src/s3/auth/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ use memchr::memchr;
use sha2::{Digest, Sha256};
use tracing::instrument;

use crate::s3::{
auth::{
Identity, S3Auth, SECRET,
body::{Body as BodyTrait, BodyWriter},
credential::AWS4,
sig_v4::{CanonicalRequest, Payload, StringToSign},
use crate::{
config::EnvConfig,
s3::{
auth::{
Identity, S3Auth, SECRET,
body::{Body as BodyTrait, BodyWriter},
credential::AWS4,
sig_v4::{CanonicalRequest, Payload, StringToSign},
},
header::{AwzContentSha256, AwzContentSha256Header, AwzDate, AwzDecodedContentLength},
},
header::{AwzContentSha256, AwzContentSha256Header, AwzDate, AwzDecodedContentLength},
};

#[instrument]
Expand Down Expand Up @@ -64,7 +67,9 @@ pub async fn header_auth<T: BodyTrait>(req: Request) -> Result<S3Auth<T>> {
check_headers(&parts, auth)?;
}

let mut writer = T::Writer::new().await?;
let Ok(config) = parts.extract::<EnvConfig>().await;

let mut writer = T::Writer::new(&config.storage_path).await?;
let body = if content_hash.is_chunked() {
BodyOrHash::Body(body)
} else {
Expand Down Expand Up @@ -301,12 +306,17 @@ pub fn check_headers(parts: &Parts, auth: &AWS4) -> Result<()> {

#[cfg(test)]
mod test {
use crate::s3::auth::credential::AWS4Credential;
use crate::{config::EnvConfig, s3::auth::credential::AWS4Credential};

use super::*;

fn request(auth: bool) -> Request {
let mut builder = Request::builder().uri("http://localhost/");
unsafe {
std::env::set_var("STORAGE_PATH", "/tmp/s3");
}
let mut builder = Request::builder()
.uri("http://localhost/")
.extension(EnvConfig::default());

if auth {
builder = builder.header("Authorization", "AWS4-HMAC-SHA256 Credential=test/21240426/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=e737cff2fc158b249645312df82c5a72abc11a42e7b8a20a41cbff1f9430b4c1");
Expand Down Expand Up @@ -355,7 +365,8 @@ mod test {
#[tokio::test]
async fn test_process_chunks() {
let (mut parts, body) = process_chunks_data();
let mut writer = <Vec<u8> as BodyWriter>::new().await.unwrap();
let data_dir = std::env::temp_dir();
let mut writer = <Vec<u8> as BodyWriter>::new(&data_dir).await.unwrap();
let date = DateTime::parse_from_rfc3339("2124-04-26T00:00:00Z")
.unwrap()
.with_timezone(&Utc);
Expand Down
2 changes: 1 addition & 1 deletion backend/src/s3/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::s3::auth::{
body::Body, header::header_auth, multipart::multipart_auth, query::query_auth,
};

mod body;
pub mod body;
mod credential;
mod header;
mod multipart;
Expand Down
Loading