Compare commits
2 Commits
5a0a897461
...
67b210a7bf
| Author | SHA1 | Date | |
|---|---|---|---|
| 67b210a7bf | |||
| f9a39d5ff9 |
30
.gitea/workflows/docker.yml
Normal file
30
.gitea/workflows/docker.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to Gitea container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.betalupi.com
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.DEPLOY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: git.betalupi.com/mark/pile:latest
|
||||||
|
cache-from: type=registry,ref=git.betalupi.com/mark/pile:cache
|
||||||
|
cache-to: type=registry,ref=git.betalupi.com/mark/pile:cache,mode=max
|
||||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
FROM rust:1.94-bookworm AS base
|
||||||
|
|
||||||
|
#
|
||||||
|
# MARK: Build
|
||||||
|
#
|
||||||
|
FROM base AS build
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates wget unzip \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app/rust
|
||||||
|
COPY . .
|
||||||
|
RUN cargo build --release --workspace
|
||||||
|
RUN cargo test --release --workspace
|
||||||
|
|
||||||
|
#
|
||||||
|
# MARK: Release
|
||||||
|
#
|
||||||
|
FROM debian:bookworm AS deploy
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=build \
|
||||||
|
/app/rust/target/release/pile \
|
||||||
|
/app/bin/
|
||||||
|
|
||||||
|
ENV PATH="/app/bin:$PATH"
|
||||||
|
ENV RUST_BACKTRACE=full
|
||||||
|
|
||||||
|
ENTRYPOINT [""]
|
||||||
@@ -20,6 +20,7 @@ pub struct ExtractQuery {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
download: bool,
|
download: bool,
|
||||||
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a specific field from an item's metadata.
|
/// Extract a specific field from an item's metadata.
|
||||||
@@ -31,6 +32,7 @@ pub struct ExtractQuery {
|
|||||||
("source" = String, Query, description = "Source label"),
|
("source" = String, Query, description = "Source label"),
|
||||||
("key" = String, Query, description = "Item key"),
|
("key" = String, Query, description = "Item key"),
|
||||||
("path" = String, Query, description = "Object path (e.g. $.flac.title); repeat for fallbacks"),
|
("path" = String, Query, description = "Object path (e.g. $.flac.title); repeat for fallbacks"),
|
||||||
|
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Field value as JSON"),
|
(status = 200, description = "Field value as JSON"),
|
||||||
@@ -112,18 +114,27 @@ pub async fn get_extract(
|
|||||||
time_ms = start.elapsed().as_millis()
|
time_ms = start.elapsed().as_millis()
|
||||||
);
|
);
|
||||||
|
|
||||||
let disposition = if params.download {
|
let disposition_type = if params.download {
|
||||||
"attachment"
|
"attachment"
|
||||||
} else {
|
} else {
|
||||||
"inline"
|
"inline"
|
||||||
};
|
};
|
||||||
|
let file_name = params.name.unwrap_or_else(|| {
|
||||||
|
params
|
||||||
|
.key
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(¶ms.key)
|
||||||
|
.to_owned()
|
||||||
|
});
|
||||||
|
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
PileValue::String(s) => (
|
PileValue::String(s) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(header::CONTENT_TYPE, "text/plain".to_owned()),
|
(header::CONTENT_TYPE, "text/plain".to_owned()),
|
||||||
(header::CONTENT_DISPOSITION, disposition.to_owned()),
|
(header::CONTENT_DISPOSITION, disposition),
|
||||||
],
|
],
|
||||||
s.to_string(),
|
s.to_string(),
|
||||||
)
|
)
|
||||||
@@ -132,7 +143,7 @@ pub async fn get_extract(
|
|||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(header::CONTENT_TYPE, mime.to_string()),
|
(header::CONTENT_TYPE, mime.to_string()),
|
||||||
(header::CONTENT_DISPOSITION, disposition.to_owned()),
|
(header::CONTENT_DISPOSITION, disposition),
|
||||||
],
|
],
|
||||||
bytes.as_ref().clone(),
|
bytes.as_ref().clone(),
|
||||||
)
|
)
|
||||||
@@ -140,7 +151,7 @@ pub async fn get_extract(
|
|||||||
_ => match value.to_json(&extract_state).await {
|
_ => match value.to_json(&extract_state).await {
|
||||||
Ok(json) => (
|
Ok(json) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(header::CONTENT_DISPOSITION, disposition.to_owned())],
|
[(header::CONTENT_DISPOSITION, disposition)],
|
||||||
Json(json),
|
Json(json),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub struct FieldQuery {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
download: bool,
|
download: bool,
|
||||||
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a specific field from an item's metadata.
|
/// Extract a specific field from an item's metadata.
|
||||||
@@ -31,6 +32,7 @@ pub struct FieldQuery {
|
|||||||
("source" = String, Query, description = "Source label"),
|
("source" = String, Query, description = "Source label"),
|
||||||
("key" = String, Query, description = "Item key"),
|
("key" = String, Query, description = "Item key"),
|
||||||
("field" = String, Query, description = "Schema field"),
|
("field" = String, Query, description = "Schema field"),
|
||||||
|
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Field value as JSON"),
|
(status = 200, description = "Field value as JSON"),
|
||||||
@@ -98,18 +100,27 @@ pub async fn get_field(
|
|||||||
time_ms = start.elapsed().as_millis()
|
time_ms = start.elapsed().as_millis()
|
||||||
);
|
);
|
||||||
|
|
||||||
let disposition = if params.download {
|
let disposition_type = if params.download {
|
||||||
"attachment"
|
"attachment"
|
||||||
} else {
|
} else {
|
||||||
"inline"
|
"inline"
|
||||||
};
|
};
|
||||||
|
let file_name = params.name.unwrap_or_else(|| {
|
||||||
|
params
|
||||||
|
.key
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(¶ms.key)
|
||||||
|
.to_owned()
|
||||||
|
});
|
||||||
|
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
PileValue::String(s) => (
|
PileValue::String(s) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(header::CONTENT_TYPE, "text/plain".to_owned()),
|
(header::CONTENT_TYPE, "text/plain".to_owned()),
|
||||||
(header::CONTENT_DISPOSITION, disposition.to_owned()),
|
(header::CONTENT_DISPOSITION, disposition),
|
||||||
],
|
],
|
||||||
s.to_string(),
|
s.to_string(),
|
||||||
)
|
)
|
||||||
@@ -118,7 +129,7 @@ pub async fn get_field(
|
|||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(header::CONTENT_TYPE, mime.to_string()),
|
(header::CONTENT_TYPE, mime.to_string()),
|
||||||
(header::CONTENT_DISPOSITION, disposition.to_owned()),
|
(header::CONTENT_DISPOSITION, disposition),
|
||||||
],
|
],
|
||||||
bytes.as_ref().clone(),
|
bytes.as_ref().clone(),
|
||||||
)
|
)
|
||||||
@@ -126,7 +137,7 @@ pub async fn get_field(
|
|||||||
_ => match value.to_json(&extract_state).await {
|
_ => match value.to_json(&extract_state).await {
|
||||||
Ok(json) => (
|
Ok(json) => (
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(header::CONTENT_DISPOSITION, disposition.to_owned())],
|
[(header::CONTENT_DISPOSITION, disposition)],
|
||||||
Json(json),
|
Json(json),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ use crate::Datasets;
|
|||||||
pub struct ItemQuery {
|
pub struct ItemQuery {
|
||||||
source: String,
|
source: String,
|
||||||
key: String,
|
key: String,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
download: bool,
|
download: bool,
|
||||||
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a `Range: bytes=...` header value.
|
/// Parse a `Range: bytes=...` header value.
|
||||||
@@ -48,6 +50,7 @@ fn parse_byte_range(s: &str) -> Option<(Option<u64>, Option<u64>)> {
|
|||||||
params(
|
params(
|
||||||
("source" = String, Query, description = "Source label"),
|
("source" = String, Query, description = "Source label"),
|
||||||
("key" = String, Query, description = "Item key"),
|
("key" = String, Query, description = "Item key"),
|
||||||
|
("name" = Option<String>, Query, description = "Downloaded filename; defaults to the last segment of the key"),
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Raw item bytes"),
|
(status = 200, description = "Raw item bytes"),
|
||||||
@@ -163,11 +166,20 @@ pub async fn item_get(
|
|||||||
StatusCode::OK
|
StatusCode::OK
|
||||||
};
|
};
|
||||||
|
|
||||||
let disposition = if params.download {
|
let disposition_type = if params.download {
|
||||||
"attachment"
|
"attachment"
|
||||||
} else {
|
} else {
|
||||||
"inline"
|
"inline"
|
||||||
};
|
};
|
||||||
|
let file_name = params.name.unwrap_or_else(|| {
|
||||||
|
params
|
||||||
|
.key
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(¶ms.key)
|
||||||
|
.to_owned()
|
||||||
|
});
|
||||||
|
let disposition = format!("{disposition_type}; filename=\"{file_name}\"");
|
||||||
|
|
||||||
let mut builder = axum::http::Response::builder()
|
let mut builder = axum::http::Response::builder()
|
||||||
.status(status)
|
.status(status)
|
||||||
|
|||||||
Reference in New Issue
Block a user