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!("new_project: server error; check fn0-control logs"));
54        }
55    };
56    *project_id = Some(id.clone());
57    Ok(id)
58}
59
60#[derive(Serialize)]
61struct RenameProjectInput<'a> {
62    project_id: &'a str,
63    new_name: &'a str,
64}
65
66#[derive(Deserialize)]
67#[serde(tag = "t", rename_all_fields = "camelCase")]
68enum RenameProject {
69    Ok,
70    NotLoggedIn,
71    NotFound,
72    InvalidName,
73    InternalError,
74}
75
76#[derive(Serialize)]
77struct DeleteProjectInput<'a> {
78    project_id: &'a str,
79}
80
81#[derive(Deserialize)]
82#[serde(tag = "t", rename_all_fields = "camelCase")]
83enum DeleteProject {
84    Ok,
85    NotLoggedIn,
86    NotFound,
87    InternalError,
88}
89
90pub async fn delete_project(project_id: &str) -> Result<()> {
91    let creds = crate::credentials::require()?;
92    let client = reqwest::Client::new();
93    let url = format!(
94        "{}/__forte_action/delete_project",
95        creds.control_url.trim_end_matches('/')
96    );
97    let resp = client
98        .post(&url)
99        .bearer_auth(&creds.token)
100        .json(&DeleteProjectInput { project_id })
101        .send()
102        .await?
103        .error_for_status()?;
104    let raw: DeleteProject = resp.json().await?;
105    match raw {
106        DeleteProject::Ok => Ok(()),
107        DeleteProject::NotLoggedIn => {
108            Err(anyhow!("control rejected token; run `fn0 login` again."))
109        }
110        DeleteProject::NotFound => Err(anyhow!(
111            "project '{project_id}' not found or not owned by you."
112        )),
113        DeleteProject::InternalError => Err(anyhow!(
114            "delete_project: server error; check fn0-control logs"
115        )),
116    }
117}
118
119pub async fn rename_project(project_id: &str, new_name: &str) -> Result<()> {
120    let creds = crate::credentials::require()?;
121    let client = reqwest::Client::new();
122    let url = format!(
123        "{}/__forte_action/rename_project",
124        creds.control_url.trim_end_matches('/')
125    );
126    let resp = client
127        .post(&url)
128        .bearer_auth(&creds.token)
129        .json(&RenameProjectInput {
130            project_id,
131            new_name,
132        })
133        .send()
134        .await?
135        .error_for_status()?;
136    let raw: RenameProject = resp.json().await?;
137    match raw {
138        RenameProject::Ok => Ok(()),
139        RenameProject::NotLoggedIn => {
140            Err(anyhow!("control rejected token; run `fn0 login` again."))
141        }
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}