Skip to main content

wowlab_cli/commands/snapshot/assisted_rotas/
output.rs

1use anyhow::{Context, Result};
2use serde::Serialize;
3use uuid::Uuid;
4use wowlab_fs::{atomic, directory, path::Path};
5use wowlab_parsers::apply_rotation_overlay;
6use wowlab_types::game::SpecId;
7
8use super::{super::db, preparation::PreparedRotation};
9
10#[derive(Debug, Serialize)]
11struct DumpIndexEntry {
12    spec_id: i32,
13    name: String,
14    slug: String,
15    file: String,
16}
17
18pub(super) async fn upsert_rotations(
19    user_id: &str,
20    is_public: bool,
21    rotations: &[PreparedRotation],
22) -> Result<()> {
23    let user_id = Uuid::parse_str(user_id).context("invalid --user-id UUID")?;
24    let database_url = std::env::var("DATABASE_URL").context("DATABASE_URL not set")?;
25    let pool = db::connect(&database_url).await?;
26    let mut transaction = pool.begin().await?;
27
28    for rotation in rotations {
29        let script = match u32::try_from(rotation.spec_id)
30            .ok()
31            .and_then(SpecId::from_wow_spec_id)
32        {
33            Some(spec) => apply_rotation_overlay(spec.slug(), &rotation.script)?,
34            None => rotation.script.clone(),
35        };
36
37        sqlx::query_file!(
38            "queries/snapshot_assisted_rotas_upsert.sql",
39            user_id,
40            &rotation.slug,
41            &rotation.name,
42            &script,
43            &rotation.description,
44            is_public,
45            rotation.spec_id
46        )
47        .execute(&mut *transaction)
48        .await?;
49    }
50
51    transaction.commit().await?;
52    println!(
53        "Upserted {} assisted rotations to public.rotations",
54        rotations.len()
55    );
56
57    Ok(())
58}
59
60pub(super) fn write_rotation_dumps(out_dir: &Path, rotations: &[PreparedRotation]) -> Result<()> {
61    directory::ensure(out_dir)?;
62    let mut index = Vec::with_capacity(rotations.len());
63
64    for rotation in rotations {
65        let file = match u32::try_from(rotation.spec_id)
66            .ok()
67            .and_then(SpecId::from_wow_spec_id)
68        {
69            Some(spec) => format!("{}_assisted.json", spec.slug()),
70            None => format!("spec_{}_assisted.json", rotation.spec_id),
71        };
72        let path = out_dir.join(&file);
73
74        let contents = serde_json::to_string_pretty(&rotation.script)?;
75
76        atomic::replace(&path, contents)?;
77        println!("{}", path.display());
78        index.push(DumpIndexEntry {
79            spec_id: rotation.spec_id,
80            name: rotation.name.clone(),
81            slug: rotation.slug.clone(),
82            file,
83        });
84    }
85
86    let index_path = out_dir.join("index.json");
87    let contents = serde_json::to_string_pretty(&index)?;
88
89    atomic::replace(&index_path, contents)?;
90    println!(
91        "Wrote {} engine rotation dumps to {}",
92        rotations.len(),
93        out_dir.display()
94    );
95
96    Ok(())
97}
98
99#[cfg(test)]
100mod tests {
101    use googletest::prelude::*;
102    use serde_json::json;
103    use wowlab_fs::{directory, file, temporary::Directory};
104
105    use super::{PreparedRotation, write_rotation_dumps};
106
107    #[gtest]
108    fn rotation_dump_preserves_json_bytes_and_writes_index_atomically() -> Result<()> {
109        let output = Directory::new().or_fail()?;
110        let rotations = [PreparedRotation {
111            spec_id: 999_999,
112            name: "Test".to_string(),
113            slug: "test".to_string(),
114            description: "test rotation".to_string(),
115            script: json!({ "spell": "fireball" }),
116        }];
117
118        write_rotation_dumps(output.path(), &rotations).or_fail()?;
119
120        verify_that!(
121            file::read_text(&output.path().join("spec_999999_assisted.json")).or_fail()?,
122            eq("{\n  \"spell\": \"fireball\"\n}")
123        )?;
124        verify_that!(
125            file::read_text(&output.path().join("index.json")).or_fail()?,
126            eq(
127                "[\n  {\n    \"spec_id\": 999999,\n    \"name\": \"Test\",\n    \
128                 \"slug\": \"test\",\n    \"file\": \"spec_999999_assisted.json\"\n  }\n]"
129            )
130        )?;
131
132        verify_that!(directory::entries(output.path()).or_fail()?, len(eq(2)))
133    }
134}