1use crate::{
8 bundle_api::BundleIdResponse, certs_api::CertificatesResponse, AppStoreConnectClient, Result,
9};
10use serde::{Deserialize, Serialize};
11
12const APPLE_PROFILES_URL: &str = "https://api.appstoreconnect.apple.com/v1/profiles";
13
14impl AppStoreConnectClient {
15 pub fn create_profile(
16 &self,
17 name: &str,
18 profile_type: ProfileType,
19 bundle_id: &str,
20 certificates: &[String],
21 devices: Option<&[String]>,
22 ) -> Result<ProfileResponse> {
23 let token = self.get_token()?;
24 let body = ProfileCreateRequest {
25 data: ProfileCreateRequestData {
26 attributes: ProfileCreateRequestAttributes {
27 name: name.into(),
28 profile_type: profile_type.to_string(),
29 },
30 relationships: ProfileCreateRequestRelationships {
31 bundle_id: Ref {
32 data: RefData {
33 id: bundle_id.into(),
34 r#type: "bundleIds".into(),
35 },
36 },
37 certificates: Refs {
38 data: certificates
39 .iter()
40 .map(|certificate| RefData {
41 id: certificate.into(),
42 r#type: "certificates".into(),
43 })
44 .collect(),
45 },
46 devices: devices.map(|devices| Refs {
47 data: devices
48 .iter()
49 .map(|device| RefData {
50 id: device.into(),
51 r#type: "devices".into(),
52 })
53 .collect(),
54 }),
55 },
56 r#type: "profiles".into(),
57 },
58 };
59 let req = self
60 .client
61 .post(APPLE_PROFILES_URL)
62 .bearer_auth(token)
63 .header("Accept", "application/json")
64 .header("Content-Type", "application/json")
65 .json(&body);
66 Ok(self.send_request(req)?.json()?)
67 }
68
69 pub fn get_profile_bundle_id(&self, profile_id: &str) -> Result<BundleIdResponse> {
70 let token = self.get_token()?;
71 let req = self
72 .client
73 .get(format!("{APPLE_PROFILES_URL}/{profile_id}/bundleId"))
74 .bearer_auth(token)
75 .header("Accept", "application/json");
76 Ok(self.send_request(req)?.json()?)
77 }
78
79 pub fn list_profile_certificates(&self, profile_id: &str) -> Result<CertificatesResponse> {
80 let token = self.get_token()?;
81 let req = self
82 .client
83 .get(format!("{APPLE_PROFILES_URL}/{profile_id}/certificates"))
84 .bearer_auth(token)
85 .header("Accept", "application/json");
86 Ok(self.send_request(req)?.json()?)
87 }
88
89 pub fn list_profiles(&self) -> Result<ProfilesResponse> {
90 let token = self.get_token()?;
91 let req = self
92 .client
93 .get(APPLE_PROFILES_URL)
94 .bearer_auth(token)
95 .header("Accept", "application/json");
96 Ok(self.send_request(req)?.json()?)
97 }
98
99 pub fn get_profile(&self, id: &str) -> Result<ProfileResponse> {
100 let token = self.get_token()?;
101 let req = self
102 .client
103 .get(format!("{APPLE_PROFILES_URL}/{id}"))
104 .bearer_auth(token)
105 .header("Accept", "application/json");
106 Ok(self.send_request(req)?.json()?)
107 }
108
109 pub fn delete_profile(&self, id: &str) -> Result<()> {
110 let token = self.get_token()?;
111 let req = self
112 .client
113 .delete(format!("{APPLE_PROFILES_URL}/{id}"))
114 .bearer_auth(token);
115 self.send_request(req)?;
116 Ok(())
117 }
118}
119
120#[derive(Debug, Serialize)]
121#[serde(rename_all = "camelCase")]
122pub struct ProfileCreateRequest {
123 pub data: ProfileCreateRequestData,
124}
125
126#[derive(Debug, Serialize)]
127#[serde(rename_all = "camelCase")]
128pub struct ProfileCreateRequestData {
129 pub attributes: ProfileCreateRequestAttributes,
130 pub relationships: ProfileCreateRequestRelationships,
131 pub r#type: String,
132}
133
134#[derive(Debug, Serialize)]
135#[serde(rename_all = "camelCase")]
136pub struct ProfileCreateRequestAttributes {
137 pub name: String,
138 pub profile_type: String,
139}
140
141#[derive(Debug, Serialize)]
142#[serde(rename_all = "camelCase")]
143pub struct ProfileCreateRequestRelationships {
144 pub bundle_id: Ref,
145 pub certificates: Refs,
146 pub devices: Option<Refs>,
147}
148
149#[derive(Debug, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct Ref {
152 pub data: RefData,
153}
154
155#[derive(Debug, Serialize)]
156#[serde(rename_all = "camelCase")]
157pub struct Refs {
158 pub data: Vec<RefData>,
159}
160
161#[derive(Debug, Serialize)]
162#[serde(rename_all = "camelCase")]
163pub struct RefData {
164 pub id: String,
165 pub r#type: String,
166}
167
168#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
169pub enum ProfileType {
170 IosAppDevelopment,
171 MacAppDevelopment,
172 IosAppStore,
173 MacAppStore,
174 MacAppDirect,
175}
176
177impl std::fmt::Display for ProfileType {
178 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
179 let s = match self {
180 Self::IosAppDevelopment => "IOS_APP_DEVELOPMENT",
181 Self::MacAppDevelopment => "MAC_APP_DEVELOPMENT",
182 Self::IosAppStore => "IOS_APP_STORE",
183 Self::MacAppStore => "MAC_APP_STORE",
184 Self::MacAppDirect => "MAC_APP_DIRECT",
185 };
186 write!(f, "{s}")
187 }
188}
189
190#[derive(Debug, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct ProfileResponse {
193 pub data: Profile,
194}
195
196#[derive(Debug, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct ProfilesResponse {
199 pub data: Vec<Profile>,
200}
201
202#[derive(Debug, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct Profile {
205 pub attributes: ProfileAttributes,
206 pub id: String,
207}
208
209#[derive(Debug, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct ProfileAttributes {
212 pub name: String,
213 pub platform: String,
214 pub profile_content: String,
215 pub uuid: String,
216 pub created_date: Option<String>,
217 pub profile_state: String,
218 pub profile_type: String,
219 pub expiration_date: String,
220}