Skip to main content

raps_cli/commands/
template.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! Project template management commands (v4.5+)
5//!
6//! Commands for managing project templates in ACC accounts.
7
8use anyhow::Result;
9use clap::Subcommand;
10use colored::Colorize;
11use serde::Serialize;
12
13use raps_acc::admin::AccountAdminClient;
14
15use crate::output::OutputFormat;
16
17#[derive(Debug, Subcommand)]
18pub enum TemplateCommands {
19    /// List project templates in an account
20    List {
21        /// Account ID (defaults to APS_ACCOUNT_ID env var)
22        #[arg(short, long)]
23        account: Option<String>,
24
25        /// Maximum templates to return
26        #[arg(long)]
27        limit: Option<usize>,
28    },
29
30    /// Get details of a specific template
31    Info {
32        /// Account ID
33        #[arg(short, long)]
34        account: Option<String>,
35
36        /// Template project ID
37        template_id: String,
38    },
39
40    /// Create a new project template
41    Create {
42        /// Account ID
43        #[arg(short, long)]
44        account: Option<String>,
45
46        /// Template name
47        #[arg(long)]
48        name: String,
49    },
50
51    /// Update an existing template
52    Update {
53        /// Account ID
54        #[arg(short, long)]
55        account: Option<String>,
56
57        /// Template project ID
58        template_id: String,
59
60        /// New template name
61        #[arg(long)]
62        name: Option<String>,
63    },
64
65    /// Archive a template
66    Archive {
67        /// Account ID
68        #[arg(short, long)]
69        account: Option<String>,
70
71        /// Template project ID
72        template_id: String,
73    },
74}
75
76#[derive(Serialize)]
77struct TemplateOutput {
78    id: String,
79    name: String,
80    status: String,
81}
82
83impl TemplateCommands {
84    pub async fn execute(
85        self,
86        admin_client: &AccountAdminClient,
87        output_format: OutputFormat,
88    ) -> Result<()> {
89        match self {
90            TemplateCommands::List { account, limit } => {
91                let account_id = get_account_id(account)?;
92                let limit = limit.unwrap_or(100);
93
94                if output_format.supports_colors() {
95                    println!("{}", "Fetching templates...".dimmed());
96                }
97
98                let templates = admin_client.list_all_templates(&account_id).await?;
99                let templates: Vec<_> = templates.into_iter().take(limit).collect();
100
101                let outputs: Vec<TemplateOutput> = templates
102                    .iter()
103                    .map(|t| TemplateOutput {
104                        id: t.id.clone(),
105                        name: t.name.clone(),
106                        status: t.status.clone().unwrap_or_else(|| "unknown".to_string()),
107                    })
108                    .collect();
109
110                if outputs.is_empty() {
111                    match output_format {
112                        OutputFormat::Table => {
113                            println!("{}", "No templates found.".yellow());
114                        }
115                        _ => output_format.write(&Vec::<TemplateOutput>::new())?,
116                    }
117                    return Ok(());
118                }
119
120                match output_format {
121                    OutputFormat::Table => {
122                        println!("\n{}", "Templates:".bold());
123                        println!("{}", "─".repeat(80));
124                        for t in &outputs {
125                            println!("{:<40} {:<30} {}", t.id.cyan(), t.name, t.status.green());
126                        }
127                        println!("{}", "─".repeat(80));
128                        println!("{} {} template(s)", "→".cyan(), outputs.len());
129                    }
130                    _ => output_format.write(&outputs)?,
131                }
132
133                Ok(())
134            }
135
136            TemplateCommands::Info {
137                account,
138                template_id,
139            } => {
140                let account_id = get_account_id(account)?;
141                let project = admin_client.get_project(&account_id, &template_id).await?;
142
143                let output = TemplateOutput {
144                    id: project.id.clone(),
145                    name: project.name.clone(),
146                    status: project
147                        .status
148                        .clone()
149                        .unwrap_or_else(|| "unknown".to_string()),
150                };
151
152                match output_format {
153                    OutputFormat::Table => {
154                        println!("\n{}", "Template Details:".bold());
155                        println!("{}", "─".repeat(60));
156                        println!("{:<15} {}", "ID:".bold(), output.id.cyan());
157                        println!("{:<15} {}", "Name:".bold(), output.name);
158                        println!("{:<15} {}", "Status:".bold(), output.status);
159                        println!("{}", "─".repeat(60));
160                    }
161                    _ => output_format.write(&output)?,
162                }
163
164                Ok(())
165            }
166
167            TemplateCommands::Create { account, name } => {
168                let account_id = get_account_id(account)?;
169
170                let request = raps_acc::admin::CreateProjectRequest {
171                    name,
172                    r#type: Some("Office".to_string()),
173                    // Note: APS API rejects classification:"template" on creation.
174                    // Template classification must be set via ACC admin UI after creation.
175                    ..Default::default()
176                };
177
178                let project = admin_client.create_project(&account_id, request).await?;
179
180                match output_format {
181                    OutputFormat::Table => {
182                        println!(
183                            "\n{} Template created: {} ({})",
184                            "✓".green().bold(),
185                            project.name,
186                            project.id.cyan()
187                        );
188                    }
189                    _ => {
190                        let output = TemplateOutput {
191                            id: project.id,
192                            name: project.name,
193                            status: project.status.unwrap_or_else(|| "pending".to_string()),
194                        };
195                        output_format.write(&output)?;
196                    }
197                }
198
199                Ok(())
200            }
201
202            TemplateCommands::Update {
203                account,
204                template_id,
205                name,
206            } => {
207                let account_id = get_account_id(account)?;
208
209                let request = raps_acc::admin::UpdateProjectRequest {
210                    name,
211                    ..Default::default()
212                };
213
214                let project = admin_client
215                    .update_project(&account_id, &template_id, request)
216                    .await?;
217
218                match output_format {
219                    OutputFormat::Table => {
220                        println!(
221                            "\n{} Template updated: {} ({})",
222                            "✓".green().bold(),
223                            project.name,
224                            project.id.cyan()
225                        );
226                    }
227                    _ => {
228                        let output = TemplateOutput {
229                            id: project.id,
230                            name: project.name,
231                            status: project.status.unwrap_or_else(|| "unknown".to_string()),
232                        };
233                        output_format.write(&output)?;
234                    }
235                }
236
237                Ok(())
238            }
239
240            TemplateCommands::Archive {
241                account,
242                template_id,
243            } => {
244                let account_id = get_account_id(account)?;
245                admin_client
246                    .archive_project(&account_id, &template_id)
247                    .await?;
248
249                match output_format {
250                    OutputFormat::Table => {
251                        println!(
252                            "\n{} Template archived: {}",
253                            "✓".green().bold(),
254                            template_id.cyan()
255                        );
256                    }
257                    _ => {
258                        let output = TemplateOutput {
259                            id: template_id,
260                            name: String::new(),
261                            status: "archived".to_string(),
262                        };
263                        output_format.write(&output)?;
264                    }
265                }
266
267                Ok(())
268            }
269        }
270    }
271}
272
273fn get_account_id(account: Option<String>) -> Result<String> {
274    let account_id = account.or_else(|| std::env::var("APS_ACCOUNT_ID").ok());
275    match account_id {
276        Some(id) if !id.is_empty() => Ok(id),
277        _ => {
278            anyhow::bail!(
279                "Account ID is required. Use --account or set APS_ACCOUNT_ID environment variable."
280            );
281        }
282    }
283}