Skip to main content

fn0_deploy/
project.rs

1use crate::name::MAX_PROJECT_NAME_LEN;
2use anyhow::{Result, anyhow};
3use serde::{Deserialize, Serialize};
4
5#[derive(Serialize)]
6struct NewProjectInput<'a> {
7    name: &'a str,
8}
9
10#[derive(Deserialize)]
11#[serde(tag = "t", rename_all_fields = "camelCase")]
12enum NewProject {
13    Ok { project_id: String },
14    NotLoggedIn,
15    InvalidName,
16    InternalError,
17}
18
19pub async fn ensure_project_id(
20    client: &reqwest::Client,
21    control_url: &str,
22    token: &str,
23    project_name: &str,
24    project_id: &mut Option<String>,
25) -> Result<String> {
26    if let Some(id) = project_id.as_ref() {
27        return Ok(id.clone());
28    }
29    let url = format!(
30        "{}/__forte_action/new_project",
31        control_url.trim_end_matches('/')
32    );
33    let resp = client
34        .post(&url)
35        .bearer_auth(token)
36        .json(&NewProjectInput { name: project_name })
37        .send()
38        .await?
39        .error_for_status()
40        .map_err(|e| anyhow!("new_project failed: {e}"))?;
41    let raw: NewProject = resp.json().await?;
42    let id = match raw {
43        NewProject::Ok { project_id } => project_id,
44        NewProject::NotLoggedIn => {
45            return Err(anyhow!("control rejected token; run `fn0 login` again."));
46        }
47        NewProject::InvalidName => {
48            return Err(anyhow!(
49                "control rejected project name '{project_name}': must be 1-{MAX_PROJECT_NAME_LEN} chars of letters, digits, '.', '_', '-'"
50            ));
51        }
52        NewProject::InternalError => {
53            return Err(anyhow!(
54                "new_project: server error; check fn0-control logs"
55            ));
56        }
57    };
58    *project_id = Some(id.clone());
59    Ok(id)
60}
61
62#[derive(Serialize)]
63struct RenameProjectInput<'a> {
64    project_id: &'a str,
65    new_name: &'a str,
66}
67
68#[derive(Deserialize)]
69#[serde(tag = "t", rename_all_fields = "camelCase")]
70enum RenameProject {
71    Ok,
72    NotLoggedIn,
73    NotFound,
74    InvalidName,
75    InternalError,
76}
77
78#[derive(Serialize)]
79struct DeleteProjectInput<'a> {
80    project_id: &'a str,
81}
82
83#[derive(Deserialize)]
84#[serde(tag = "t", rename_all_fields = "camelCase")]
85enum DeleteProject {
86    Ok,
87    NotLoggedIn,
88    NotFound,
89    InternalError,
90}
91
92pub async fn delete_project(project_id: &str) -> Result<()> {
93    let creds = crate::credentials::require()?;
94    let client = reqwest::Client::new();
95    let url = format!(
96        "{}/__forte_action/delete_project",
97        creds.control_url.trim_end_matches('/')
98    );
99    let resp = client
100        .post(&url)
101        .bearer_auth(&creds.token)
102        .json(&DeleteProjectInput { project_id })
103        .send()
104        .await?
105        .error_for_status()?;
106    let raw: DeleteProject = resp.json().await?;
107    match raw {
108        DeleteProject::Ok => Ok(()),
109        DeleteProject::NotLoggedIn => {
110            Err(anyhow!("control rejected token; run `fn0 login` again."))
111        }
112        DeleteProject::NotFound => Err(anyhow!(
113            "project '{project_id}' not found or not owned by you."
114        )),
115        DeleteProject::InternalError => Err(anyhow!(
116            "delete_project: server error; check fn0-control logs"
117        )),
118    }
119}
120
121pub async fn rename_project(project_id: &str, new_name: &str) -> Result<()> {
122    let creds = crate::credentials::require()?;
123    let client = reqwest::Client::new();
124    let url = format!(
125        "{}/__forte_action/rename_project",
126        creds.control_url.trim_end_matches('/')
127    );
128    let resp = client
129        .post(&url)
130        .bearer_auth(&creds.token)
131        .json(&RenameProjectInput {
132            project_id,
133            new_name,
134        })
135        .send()
136        .await?
137        .error_for_status()?;
138    let raw: RenameProject = resp.json().await?;
139    match raw {
140        RenameProject::Ok => Ok(()),
141        RenameProject::NotLoggedIn => Err(anyhow!("control rejected token; run `fn0 login` again.")),
142        RenameProject::NotFound => Err(anyhow!(
143            "project '{project_id}' not found or not owned by you."
144        )),
145        RenameProject::InvalidName => Err(anyhow!(
146            "control rejected name '{new_name}': must be 1-{MAX_PROJECT_NAME_LEN} chars of letters, digits, '.', '_', '-'"
147        )),
148        RenameProject::InternalError => Err(anyhow!(
149            "rename_project: server error; check fn0-control logs"
150        )),
151    }
152}