Build and Publish / Build and Publish Docker Image (push) Successful in 1m26s
145 lines
4.3 KiB
Rust
145 lines
4.3 KiB
Rust
#[allow(dead_code)]
|
|
#[path = "../models.rs"]
|
|
mod models;
|
|
#[allow(dead_code)]
|
|
#[path = "../uploads.rs"]
|
|
mod uploads;
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use cot::db::{Database, Model};
|
|
use models::{Media, Testimonial};
|
|
|
|
fn database_url() -> String {
|
|
std::env::var("WEB_PETTING_DATABASE_URL")
|
|
.or_else(|_| std::env::var("DATABASE_URL"))
|
|
.unwrap_or_else(|_| "postgresql://postgres:postgres@localhost:5432/web_petting".to_string())
|
|
}
|
|
|
|
async fn add_thumbnail(
|
|
sources: &mut BTreeMap<String, bool>,
|
|
db_path: &str,
|
|
required: bool,
|
|
) -> bool {
|
|
if !uploads::supports_thumbnail(db_path) {
|
|
return true;
|
|
}
|
|
match uploads::ensure_local_thumbnail(db_path).await {
|
|
Ok(thumbnail_path) => {
|
|
sources
|
|
.entry(thumbnail_path)
|
|
.and_modify(|is_required| *is_required |= required)
|
|
.or_insert(required);
|
|
true
|
|
}
|
|
Err(error) => {
|
|
eprintln!("Could not create thumbnail for {db_path}: {error}");
|
|
!required
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
let db = Database::new(database_url()).await?;
|
|
let storage = uploads::Storage::load_configured_r2(&db).await?;
|
|
let mut sources: BTreeMap<String, bool> = BTreeMap::new();
|
|
let mut preparation_failed = false;
|
|
|
|
for media in Media::objects().all(&db).await? {
|
|
let required = media.status == "active";
|
|
sources
|
|
.entry(media.file_path.clone())
|
|
.and_modify(|is_required| *is_required |= required)
|
|
.or_insert(required);
|
|
if media.file_type == "photo"
|
|
&& !add_thumbnail(&mut sources, &media.file_path, required).await
|
|
{
|
|
preparation_failed = true;
|
|
}
|
|
}
|
|
|
|
for testimonial in Testimonial::objects().all(&db).await? {
|
|
let Some(image_path) = testimonial.image_path else {
|
|
continue;
|
|
};
|
|
let required = testimonial.status == "active";
|
|
sources
|
|
.entry(image_path.clone())
|
|
.and_modify(|is_required| *is_required |= required)
|
|
.or_insert(required);
|
|
// Testimonial thumbnails are retained too, even though the landing page
|
|
// currently displays the processed full-size image.
|
|
let _ = add_thumbnail(&mut sources, &image_path, false).await;
|
|
}
|
|
|
|
let mut uploaded = 0usize;
|
|
let mut skipped = 0usize;
|
|
let mut missing = 0usize;
|
|
let mut failed = 0usize;
|
|
|
|
for (db_path, required) in sources {
|
|
let local_path = uploads::resolve_db_path(&db_path);
|
|
match tokio::fs::try_exists(&local_path).await {
|
|
Ok(true) => {}
|
|
Ok(false) => {
|
|
eprintln!("Missing local file {}", local_path.display());
|
|
missing += 1;
|
|
if required {
|
|
failed += 1;
|
|
}
|
|
continue;
|
|
}
|
|
Err(error) => {
|
|
eprintln!(
|
|
"Could not inspect local file {}: {error}",
|
|
local_path.display()
|
|
);
|
|
missing += 1;
|
|
if required {
|
|
failed += 1;
|
|
}
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if storage.exists(&db_path).await? {
|
|
println!("Already in R2: {db_path}");
|
|
skipped += 1;
|
|
continue;
|
|
}
|
|
|
|
match storage.upload_local_copy(&db_path, &local_path).await {
|
|
Ok(()) => {
|
|
println!("Uploaded: {db_path}");
|
|
uploaded += 1;
|
|
}
|
|
Err(error) => {
|
|
eprintln!("Failed to upload {db_path}: {error}");
|
|
failed += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
db.close().await?;
|
|
println!(
|
|
"Migration summary: uploaded={uploaded}, already_present={skipped}, missing_local={missing}, failed={failed}"
|
|
);
|
|
println!("Local files were not deleted.");
|
|
|
|
if preparation_failed || failed > 0 {
|
|
return Err("R2 migration did not complete successfully".into());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn main() {
|
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("failed to build Tokio runtime");
|
|
if let Err(error) = runtime.block_on(run()) {
|
|
eprintln!("Migration failed: {error}");
|
|
std::process::exit(1);
|
|
}
|
|
}
|