1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 connect_builder::{IsUnset, SetApiToken, State},
8 ConnectBuilder,
9};
10
11#[derive(Debug, Clone, Default, Deserialize, Serialize)]
12pub struct Credentials {
13 pub api_token: Option<String>,
14}
15
16impl Credentials {
17 pub fn path() -> Result<PathBuf> {
18 let config_dir = dirs::config_dir()
19 .ok_or_else(|| anyhow::anyhow!("Could not get user config directory"))?
20 .join("edgee");
21 if !config_dir.exists() {
22 std::fs::create_dir_all(&config_dir).context("Could not create Edgee config dir")?;
23 }
24
25 Ok(config_dir.join("credentials.toml"))
26 }
27
28 pub fn load() -> Result<Self> {
29 let creds_path = Self::path()?;
30 if !creds_path.exists() {
31 return Ok(Self::default());
32 }
33
34 let content =
35 std::fs::read_to_string(creds_path).context("Could not read credentials file")?;
36 toml::from_str(&content).context("Could not load credentials file")
37 }
38
39 pub fn save(&self) -> Result<()> {
40 use std::io::Write;
41
42 let content =
43 toml::to_string_pretty(self).context("Could not serialize credentials data")?;
44
45 let creds_path = Self::path()?;
46
47 let mut file = {
48 use std::fs::OpenOptions;
49
50 let mut options = OpenOptions::new();
51 options.write(true).create(true).truncate(true);
52
53 #[cfg(unix)]
54 {
55 use std::os::unix::fs::OpenOptionsExt;
56
57 options.mode(0o0600);
60 }
61
62 options.open(creds_path)?
63 };
64
65 file.write_all(content.as_bytes())
66 .context("Could not write credentials data")
67 }
68
69 pub fn check_api_token(&self) -> Result<()> {
70 let Some(_api_token) = self.api_token.as_deref() else {
71 anyhow::bail!("Not logged in");
72 };
73
74 Ok(())
77 }
78}
79
80impl<'a, S: State> ConnectBuilder<'a, S> {
81 pub fn credentials(self, creds: &Credentials) -> ConnectBuilder<'a, SetApiToken<S>>
82 where
83 S::ApiToken: IsUnset,
84 {
85 let api_token = creds.api_token.as_deref().unwrap();
86 self.api_token(api_token)
87 }
88}