This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
**/.git
|
||||||
|
**/target
|
||||||
|
**/.claude
|
||||||
|
**/media
|
||||||
|
**/NUL
|
||||||
|
**/nul
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: Build and Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: ultradesu/furumusic
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_docker:
|
||||||
|
name: Build and Publish Docker Image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep '^version' Cargo.toml | head -1 | cut -d'"' -f2)
|
||||||
|
echo "cargo_version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
||||||
|
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "docker_tags=${IMAGE_NAME}:${TAG_NAME},${IMAGE_NAME}:${VERSION},${IMAGE_NAME}:latest" >> $GITHUB_OUTPUT
|
||||||
|
elif [[ "${{ github.ref }}" == refs/heads/* ]]; then
|
||||||
|
BRANCH=${GITHUB_REF#refs/heads/}
|
||||||
|
echo "docker_tags=${IMAGE_NAME}:${BRANCH},${IMAGE_NAME}:${VERSION},${IMAGE_NAME}:$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "docker_tags=${IMAGE_NAME}:$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.docker_tags }}
|
||||||
|
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
|
||||||
|
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/nul
|
/nul
|
||||||
|
/.claude
|
||||||
|
/media
|
||||||
|
|||||||
Generated
+27
-9
@@ -150,9 +150,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama"
|
name = "askama"
|
||||||
version = "0.16.0"
|
version = "0.15.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc"
|
checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama_macros",
|
"askama_macros",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -163,9 +163,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama_derive"
|
name = "askama_derive"
|
||||||
version = "0.16.0"
|
version = "0.15.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738"
|
checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama_parser",
|
"askama_parser",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -177,18 +177,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama_macros"
|
name = "askama_macros"
|
||||||
version = "0.16.0"
|
version = "0.15.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a"
|
checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama_derive",
|
"askama_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "askama_parser"
|
name = "askama_parser"
|
||||||
version = "0.16.0"
|
version = "0.15.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da"
|
checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
@@ -540,6 +540,8 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cot"
|
name = "cot"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86599f83c4c655eec5d93c17f0ae42f37435e5940cf99c06145813309f059f07"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"aide",
|
"aide",
|
||||||
@@ -559,7 +561,7 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"grass_compiler",
|
"grass",
|
||||||
"hex",
|
"hex",
|
||||||
"http",
|
"http",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@@ -591,6 +593,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cot_codegen"
|
name = "cot_codegen"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fb54edb7e3f83eacaf6d8e76da12448ba5d34e481193e59aa89820e034d3221"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.23.0",
|
"darling 0.23.0",
|
||||||
"heck",
|
"heck",
|
||||||
@@ -602,6 +606,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cot_core"
|
name = "cot_core"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439acd4526d5ca44174a30ba68842cd938751993cfbdc1451d2225d5ffc2a8c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -630,6 +636,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cot_macros"
|
name = "cot_macros"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "250406bc5d4d20de14064500759e91cf5a2bcabaca719688cd83f2a78a9735fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama_derive",
|
"askama_derive",
|
||||||
"cot_codegen",
|
"cot_codegen",
|
||||||
@@ -1312,6 +1320,16 @@ version = "0.32.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grass"
|
||||||
|
version = "0.13.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7a68216437ef68f0738e48d6c7bb9e6e6a92237e001b03d838314b068f33c94"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"grass_compiler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "grass_compiler"
|
name = "grass_compiler"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cot = { path = "../cot/cot", features = ["postgres", "json", "openapi", "swagger-ui"] }
|
cot = { version = "0.6.0", features = ["postgres", "json", "openapi", "swagger-ui"] }
|
||||||
schemars = { version = "0.9", features = ["derive"] }
|
schemars = { version = "0.9", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
openidconnect = "4.0"
|
openidconnect = "4.0"
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
FROM rust:1-slim AS builder
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends pkg-config libssl-dev ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
|
COPY build.rs ./build.rs
|
||||||
|
COPY prompts ./prompts
|
||||||
|
COPY src ./src
|
||||||
|
COPY templates ./templates
|
||||||
|
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
COPY --from=builder /app/target/release/furumusic /usr/local/bin/furumusic
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["furumusic", "-l", "0.0.0.0:8000"]
|
||||||
+9
-9
@@ -30,7 +30,7 @@ pub struct MediaFile {
|
|||||||
pub sha256_hash: LimitedString<64>,
|
pub sha256_hash: LimitedString<64>,
|
||||||
// Audio-specific fields (NULL for non-audio files)
|
// Audio-specific fields (NULL for non-audio files)
|
||||||
/// e.g. "mp3", "flac", "ogg", "wav"
|
/// e.g. "mp3", "flac", "ogg", "wav"
|
||||||
pub audio_format: Option<LimitedString<32>>,
|
pub audio_format: Option<String>,
|
||||||
/// Bitrate in kbps
|
/// Bitrate in kbps
|
||||||
pub audio_bitrate: Option<i32>,
|
pub audio_bitrate: Option<i32>,
|
||||||
/// Sample rate in Hz
|
/// Sample rate in Hz
|
||||||
@@ -57,7 +57,7 @@ pub struct Artist {
|
|||||||
pub image_file_id: Option<i64>,
|
pub image_file_id: Option<i64>,
|
||||||
pub is_hidden: bool,
|
pub is_hidden: bool,
|
||||||
/// NULL = human-created, non-NULL = LLM model that created it
|
/// NULL = human-created, non-NULL = LLM model that created it
|
||||||
pub model_name: Option<LimitedString<128>>,
|
pub model_name: Option<String>,
|
||||||
pub created_at: LimitedString<32>,
|
pub created_at: LimitedString<32>,
|
||||||
pub updated_at: LimitedString<32>,
|
pub updated_at: LimitedString<32>,
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ impl Artist {
|
|||||||
name_sort: LimitedString::new(&normalize_name(name)).unwrap(),
|
name_sort: LimitedString::new(&normalize_name(name)).unwrap(),
|
||||||
image_file_id: None,
|
image_file_id: None,
|
||||||
is_hidden: false,
|
is_hidden: false,
|
||||||
model_name: model_name.map(|s| LimitedString::new(s).unwrap()),
|
model_name: model_name.map(str::to_owned),
|
||||||
created_at: now.clone(),
|
created_at: now.clone(),
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
@@ -173,7 +173,7 @@ pub struct Release {
|
|||||||
pub total_discs: Option<i32>,
|
pub total_discs: Option<i32>,
|
||||||
pub is_hidden: bool,
|
pub is_hidden: bool,
|
||||||
/// NULL = human-created, non-NULL = LLM model that created it
|
/// NULL = human-created, non-NULL = LLM model that created it
|
||||||
pub model_name: Option<LimitedString<128>>,
|
pub model_name: Option<String>,
|
||||||
pub created_at: LimitedString<32>,
|
pub created_at: LimitedString<32>,
|
||||||
pub updated_at: LimitedString<32>,
|
pub updated_at: LimitedString<32>,
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ impl Release {
|
|||||||
total_tracks: None,
|
total_tracks: None,
|
||||||
total_discs: None,
|
total_discs: None,
|
||||||
is_hidden: false,
|
is_hidden: false,
|
||||||
model_name: model_name.map(|s| LimitedString::new(s).unwrap()),
|
model_name: model_name.map(str::to_owned),
|
||||||
created_at: now.clone(),
|
created_at: now.clone(),
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
@@ -355,7 +355,7 @@ pub struct Track {
|
|||||||
pub year: Option<i32>,
|
pub year: Option<i32>,
|
||||||
pub is_hidden: bool,
|
pub is_hidden: bool,
|
||||||
/// NULL = human-created, non-NULL = LLM model that created it
|
/// NULL = human-created, non-NULL = LLM model that created it
|
||||||
pub model_name: Option<LimitedString<128>>,
|
pub model_name: Option<String>,
|
||||||
pub created_at: LimitedString<32>,
|
pub created_at: LimitedString<32>,
|
||||||
pub updated_at: LimitedString<32>,
|
pub updated_at: LimitedString<32>,
|
||||||
}
|
}
|
||||||
@@ -585,7 +585,7 @@ impl Track {
|
|||||||
cover_file_id: None,
|
cover_file_id: None,
|
||||||
year,
|
year,
|
||||||
is_hidden: false,
|
is_hidden: false,
|
||||||
model_name: model_name.map(|s| LimitedString::new(s).unwrap()),
|
model_name: model_name.map(str::to_owned),
|
||||||
created_at: now.clone(),
|
created_at: now.clone(),
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
@@ -622,7 +622,7 @@ impl MediaFile {
|
|||||||
mime_type: LimitedString::new(mime_type).unwrap(),
|
mime_type: LimitedString::new(mime_type).unwrap(),
|
||||||
file_size_bytes,
|
file_size_bytes,
|
||||||
sha256_hash: LimitedString::new(sha256_hash).unwrap(),
|
sha256_hash: LimitedString::new(sha256_hash).unwrap(),
|
||||||
audio_format: audio_format.map(|s| LimitedString::new(s).unwrap()),
|
audio_format: audio_format.map(str::to_owned),
|
||||||
audio_bitrate,
|
audio_bitrate,
|
||||||
audio_sample_rate,
|
audio_sample_rate,
|
||||||
audio_bit_depth,
|
audio_bit_depth,
|
||||||
@@ -674,7 +674,7 @@ impl MediaFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn audio_format_str(&self) -> &str {
|
pub fn audio_format_str(&self) -> &str {
|
||||||
self.audio_format.as_ref().map_or("", |s| s.as_str())
|
self.audio_format.as_deref().unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn created_at_str(&self) -> &str {
|
pub fn created_at_str(&self) -> &str {
|
||||||
|
|||||||
+15
-18
@@ -20,8 +20,8 @@ pub struct ScheduledJob {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
pub cron_expression: LimitedString<100>,
|
pub cron_expression: LimitedString<100>,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub last_run_at: Option<LimitedString<32>>,
|
pub last_run_at: Option<String>,
|
||||||
pub next_run_at: Option<LimitedString<32>>,
|
pub next_run_at: Option<String>,
|
||||||
pub created_at: LimitedString<32>,
|
pub created_at: LimitedString<32>,
|
||||||
pub updated_at: LimitedString<32>,
|
pub updated_at: LimitedString<32>,
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,7 @@ impl ScheduledJob {
|
|||||||
"Updating cron expression"
|
"Updating cron expression"
|
||||||
);
|
);
|
||||||
existing.cron_expression = LimitedString::new(cron_expression).unwrap();
|
existing.cron_expression = LimitedString::new(cron_expression).unwrap();
|
||||||
existing.next_run_at = compute_next_run(cron_expression)
|
existing.next_run_at = compute_next_run(cron_expression);
|
||||||
.map(|s| LimitedString::new(&s).unwrap());
|
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if existing.description != description {
|
if existing.description != description {
|
||||||
@@ -73,7 +72,7 @@ impl ScheduledJob {
|
|||||||
cron_expression: LimitedString::new(cron_expression).unwrap(),
|
cron_expression: LimitedString::new(cron_expression).unwrap(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
last_run_at: None,
|
last_run_at: None,
|
||||||
next_run_at: next.map(|s| LimitedString::new(&s).unwrap()),
|
next_run_at: next,
|
||||||
created_at: now.clone(),
|
created_at: now.clone(),
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
@@ -98,11 +97,11 @@ impl ScheduledJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_run_at_str(&self) -> &str {
|
pub fn last_run_at_str(&self) -> &str {
|
||||||
self.last_run_at.as_ref().map_or("", |v| v.as_str())
|
self.last_run_at.as_deref().unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_run_at_str(&self) -> &str {
|
pub fn next_run_at_str(&self) -> &str {
|
||||||
self.next_run_at.as_ref().map_or("", |v| v.as_str())
|
self.next_run_at.as_deref().unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_by_name(db: &Database, name: &str) -> cot::db::Result<()> {
|
pub async fn delete_by_name(db: &Database, name: &str) -> cot::db::Result<()> {
|
||||||
@@ -127,7 +126,7 @@ pub struct JobRun {
|
|||||||
pub job_name: LimitedString<100>,
|
pub job_name: LimitedString<100>,
|
||||||
pub status: LimitedString<32>,
|
pub status: LimitedString<32>,
|
||||||
pub started_at: LimitedString<32>,
|
pub started_at: LimitedString<32>,
|
||||||
pub finished_at: Option<LimitedString<32>>,
|
pub finished_at: Option<String>,
|
||||||
pub duration_ms: Option<i64>,
|
pub duration_ms: Option<i64>,
|
||||||
pub log_output: Option<String>,
|
pub log_output: Option<String>,
|
||||||
pub error_message: Option<String>,
|
pub error_message: Option<String>,
|
||||||
@@ -154,7 +153,7 @@ impl JobRun {
|
|||||||
|
|
||||||
pub async fn set_completed(&mut self, db: &Database, duration_ms: i64, log: &str) -> cot::db::Result<()> {
|
pub async fn set_completed(&mut self, db: &Database, duration_ms: i64, log: &str) -> cot::db::Result<()> {
|
||||||
self.status = LimitedString::new("completed").unwrap();
|
self.status = LimitedString::new("completed").unwrap();
|
||||||
self.finished_at = Some(now_iso());
|
self.finished_at = Some(now_iso().to_string());
|
||||||
self.duration_ms = Some(duration_ms);
|
self.duration_ms = Some(duration_ms);
|
||||||
self.log_output = Some(log.to_owned());
|
self.log_output = Some(log.to_owned());
|
||||||
self.save(db).await
|
self.save(db).await
|
||||||
@@ -162,7 +161,7 @@ impl JobRun {
|
|||||||
|
|
||||||
pub async fn set_failed(&mut self, db: &Database, duration_ms: i64, log: &str, error: &str) -> cot::db::Result<()> {
|
pub async fn set_failed(&mut self, db: &Database, duration_ms: i64, log: &str, error: &str) -> cot::db::Result<()> {
|
||||||
self.status = LimitedString::new("failed").unwrap();
|
self.status = LimitedString::new("failed").unwrap();
|
||||||
self.finished_at = Some(now_iso());
|
self.finished_at = Some(now_iso().to_string());
|
||||||
self.duration_ms = Some(duration_ms);
|
self.duration_ms = Some(duration_ms);
|
||||||
self.log_output = Some(log.to_owned());
|
self.log_output = Some(log.to_owned());
|
||||||
self.error_message = Some(error.to_owned());
|
self.error_message = Some(error.to_owned());
|
||||||
@@ -224,7 +223,7 @@ impl JobRun {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn finished_at_str(&self) -> &str {
|
pub fn finished_at_str(&self) -> &str {
|
||||||
self.finished_at.as_ref().map_or("", |v| v.as_str())
|
self.finished_at.as_deref().unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn duration_display(&self) -> String {
|
pub fn duration_display(&self) -> String {
|
||||||
@@ -277,7 +276,7 @@ impl JobRunRow {
|
|||||||
job_name: LimitedString::new(&self.job_name).unwrap(),
|
job_name: LimitedString::new(&self.job_name).unwrap(),
|
||||||
status: LimitedString::new(&self.status).unwrap(),
|
status: LimitedString::new(&self.status).unwrap(),
|
||||||
started_at: LimitedString::new(&self.started_at).unwrap(),
|
started_at: LimitedString::new(&self.started_at).unwrap(),
|
||||||
finished_at: self.finished_at.map(|s| LimitedString::new(&s).unwrap()),
|
finished_at: self.finished_at,
|
||||||
duration_ms: self.duration_ms,
|
duration_ms: self.duration_ms,
|
||||||
log_output: self.log_output,
|
log_output: self.log_output,
|
||||||
error_message: self.error_message,
|
error_message: self.error_message,
|
||||||
@@ -968,7 +967,7 @@ impl SchedulerHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(db, job_name).await {
|
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(db, job_name).await {
|
||||||
sched_job.last_run_at = Some(now_iso());
|
sched_job.last_run_at = Some(now_iso().to_string());
|
||||||
sched_job.updated_at = now_iso();
|
sched_job.updated_at = now_iso();
|
||||||
let _ = sched_job.save(db).await;
|
let _ = sched_job.save(db).await;
|
||||||
}
|
}
|
||||||
@@ -993,8 +992,7 @@ impl SchedulerHandle {
|
|||||||
// Update DB
|
// Update DB
|
||||||
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(&self.shared_db, job_name).await {
|
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(&self.shared_db, job_name).await {
|
||||||
sched_job.cron_expression = LimitedString::new(new_cron).unwrap();
|
sched_job.cron_expression = LimitedString::new(new_cron).unwrap();
|
||||||
sched_job.next_run_at = compute_next_run(new_cron)
|
sched_job.next_run_at = compute_next_run(new_cron);
|
||||||
.map(|s| LimitedString::new(&s).unwrap());
|
|
||||||
sched_job.updated_at = now_iso();
|
sched_job.updated_at = now_iso();
|
||||||
let _ = sched_job.save(&self.shared_db).await;
|
let _ = sched_job.save(&self.shared_db).await;
|
||||||
}
|
}
|
||||||
@@ -1024,8 +1022,7 @@ impl SchedulerHandle {
|
|||||||
|
|
||||||
sched_job.enabled = enabled;
|
sched_job.enabled = enabled;
|
||||||
if enabled {
|
if enabled {
|
||||||
sched_job.next_run_at = compute_next_run(sched_job.cron_expression_str())
|
sched_job.next_run_at = compute_next_run(sched_job.cron_expression_str());
|
||||||
.map(|s| LimitedString::new(&s).unwrap());
|
|
||||||
}
|
}
|
||||||
sched_job.updated_at = now_iso();
|
sched_job.updated_at = now_iso();
|
||||||
let _ = sched_job.save(&self.shared_db).await;
|
let _ = sched_job.save(&self.shared_db).await;
|
||||||
@@ -1312,7 +1309,7 @@ pub async fn trigger_job_now(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(db, job_name).await {
|
if let Ok(Some(mut sched_job)) = ScheduledJob::get_by_name(db, job_name).await {
|
||||||
sched_job.last_run_at = Some(now_iso());
|
sched_job.last_run_at = Some(now_iso().to_string());
|
||||||
sched_job.updated_at = now_iso();
|
sched_job.updated_at = now_iso();
|
||||||
let _ = sched_job.save(db).await;
|
let _ = sched_job.save(db).await;
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-23
@@ -13,9 +13,9 @@ pub struct User {
|
|||||||
id: Auto<i64>,
|
id: Auto<i64>,
|
||||||
#[model(unique)]
|
#[model(unique)]
|
||||||
username: LimitedString<255>,
|
username: LimitedString<255>,
|
||||||
password: Option<PasswordHash>,
|
password: Option<String>,
|
||||||
email: Option<LimitedString<255>>,
|
email: Option<String>,
|
||||||
display_name: Option<LimitedString<255>>,
|
display_name: Option<String>,
|
||||||
avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
role: LimitedString<32>,
|
role: LimitedString<32>,
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
@@ -49,9 +49,9 @@ impl User {
|
|||||||
let mut user = Self {
|
let mut user = Self {
|
||||||
id: Auto::auto(),
|
id: Auto::auto(),
|
||||||
username: LimitedString::new(username).unwrap(),
|
username: LimitedString::new(username).unwrap(),
|
||||||
password: Some(hash),
|
password: Some(hash.into_string()),
|
||||||
email: email.map(|e| LimitedString::new(e).unwrap()),
|
email: email.map(str::to_owned),
|
||||||
display_name: display_name.map(|d| LimitedString::new(d).unwrap()),
|
display_name: display_name.map(str::to_owned),
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
role: LimitedString::new(role).unwrap(),
|
role: LimitedString::new(role).unwrap(),
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -72,10 +72,10 @@ impl User {
|
|||||||
role: &str,
|
role: &str,
|
||||||
) -> cot::db::Result<()> {
|
) -> cot::db::Result<()> {
|
||||||
self.username = LimitedString::new(username).unwrap();
|
self.username = LimitedString::new(username).unwrap();
|
||||||
self.email = email.map(|e| LimitedString::new(e).unwrap());
|
self.email = email.map(str::to_owned);
|
||||||
self.display_name = display_name.map(|d| LimitedString::new(d).unwrap());
|
self.display_name = display_name.map(str::to_owned);
|
||||||
if let Some(pw) = new_password {
|
if let Some(pw) = new_password {
|
||||||
self.password = Some(PasswordHash::from_password(&Password::new(pw)));
|
self.password = Some(PasswordHash::from_password(&Password::new(pw)).into_string());
|
||||||
}
|
}
|
||||||
self.role = LimitedString::new(role).unwrap();
|
self.role = LimitedString::new(role).unwrap();
|
||||||
self.save(db).await
|
self.save(db).await
|
||||||
@@ -95,8 +95,10 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the password hash, if set.
|
/// Return a reference to the password hash, if set.
|
||||||
pub fn password_ref(&self) -> Option<&PasswordHash> {
|
pub fn password_ref(&self) -> Option<PasswordHash> {
|
||||||
self.password.as_ref()
|
self.password
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|hash| PasswordHash::new(hash.clone()).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the stored role code into a `Role`, defaulting to `User`.
|
/// Parse the stored role code into a `Role`, defaulting to `User`.
|
||||||
@@ -142,8 +144,8 @@ impl User {
|
|||||||
id: Auto::auto(),
|
id: Auto::auto(),
|
||||||
username: LimitedString::new(username).unwrap(),
|
username: LimitedString::new(username).unwrap(),
|
||||||
password: None,
|
password: None,
|
||||||
email: email.map(|e| LimitedString::new(e).unwrap()),
|
email: email.map(str::to_owned),
|
||||||
display_name: display_name.map(|d| LimitedString::new(d).unwrap()),
|
display_name: display_name.map(str::to_owned),
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
role: LimitedString::new(role).unwrap(),
|
role: LimitedString::new(role).unwrap(),
|
||||||
is_active: true,
|
is_active: true,
|
||||||
@@ -160,10 +162,7 @@ impl User {
|
|||||||
|
|
||||||
/// Find a user by email address.
|
/// Find a user by email address.
|
||||||
pub async fn get_by_email(db: &Database, email: &str) -> cot::db::Result<Option<Self>> {
|
pub async fn get_by_email(db: &Database, email: &str) -> cot::db::Result<Option<Self>> {
|
||||||
let Ok(email) = LimitedString::<255>::new(email) else {
|
cot::db::query!(User, $email == Some(email.to_owned())).get(db).await
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
cot::db::query!(User, $email == Some(email)).get(db).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +178,8 @@ pub struct OidcLink {
|
|||||||
user_id: i64,
|
user_id: i64,
|
||||||
issuer: LimitedString<255>,
|
issuer: LimitedString<255>,
|
||||||
sub: LimitedString<255>,
|
sub: LimitedString<255>,
|
||||||
email: Option<LimitedString<255>>,
|
email: Option<String>,
|
||||||
name: Option<LimitedString<255>>,
|
name: Option<String>,
|
||||||
avatar_url: Option<String>,
|
avatar_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,8 +219,8 @@ impl OidcLink {
|
|||||||
user_id,
|
user_id,
|
||||||
issuer: LimitedString::new(issuer).unwrap(),
|
issuer: LimitedString::new(issuer).unwrap(),
|
||||||
sub: LimitedString::new(sub).unwrap(),
|
sub: LimitedString::new(sub).unwrap(),
|
||||||
email: email.map(|e| LimitedString::new(e).unwrap()),
|
email: email.map(str::to_owned),
|
||||||
name: name.map(|n| LimitedString::new(n).unwrap()),
|
name: name.map(str::to_owned),
|
||||||
avatar_url: None,
|
avatar_url: None,
|
||||||
};
|
};
|
||||||
link.insert(db).await?;
|
link.insert(db).await?;
|
||||||
@@ -235,8 +234,8 @@ impl OidcLink {
|
|||||||
email: Option<&str>,
|
email: Option<&str>,
|
||||||
name: Option<&str>,
|
name: Option<&str>,
|
||||||
) -> cot::db::Result<()> {
|
) -> cot::db::Result<()> {
|
||||||
self.email = email.map(|e| LimitedString::new(e).unwrap());
|
self.email = email.map(str::to_owned);
|
||||||
self.name = name.map(|n| LimitedString::new(n).unwrap());
|
self.name = name.map(str::to_owned);
|
||||||
self.save(db).await
|
self.save(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user