llm_registry_api/graphql/
mutation.rs1use async_graphql::{Context, InputObject, Object, Result};
6use llm_registry_core::{
7 AssetId, Checksum, HashAlgorithm, StorageBackend, StorageLocation,
8};
9use llm_registry_service::{RegisterAssetRequest, ServiceRegistry, UpdateAssetRequest};
10use semver::Version;
11use std::sync::Arc;
12
13use super::types::{
14 GqlAsset, GqlAssetStatus, GqlAssetType, GqlDeleteResult, GqlRegisterResult, GqlUpdateResult,
15};
16use crate::auth::AuthUser;
17use crate::error::ApiError;
18
19pub struct Mutation;
21
22#[derive(InputObject)]
24pub struct RegisterAssetInput {
25 pub asset_type: GqlAssetType,
27 pub name: String,
29 pub version: String,
31 pub description: Option<String>,
33 pub license: Option<String>,
35 #[graphql(default)]
37 pub tags: Vec<String>,
38 #[graphql(default)]
40 pub annotations: Vec<AnnotationInput>,
41 pub storage_path: String,
43 #[graphql(default = "local")]
45 pub storage_backend: String,
46 pub storage_uri: Option<String>,
48 pub checksum: String,
50 #[graphql(default = "SHA256")]
52 pub checksum_algorithm: String,
53 pub size_bytes: Option<u64>,
55 pub content_type: Option<String>,
57}
58
59#[derive(InputObject)]
61pub struct UpdateAssetInput {
62 pub asset_id: String,
64 pub status: Option<GqlAssetStatus>,
66 pub description: Option<String>,
68 pub license: Option<String>,
70 #[graphql(default)]
72 pub add_tags: Vec<String>,
73 #[graphql(default)]
75 pub remove_tags: Vec<String>,
76 #[graphql(default)]
78 pub add_annotations: Vec<AnnotationInput>,
79 #[graphql(default)]
81 pub remove_annotations: Vec<String>,
82}
83
84#[derive(InputObject)]
86pub struct AnnotationInput {
87 pub key: String,
89 pub value: String,
91}
92
93#[Object]
94impl Mutation {
95 async fn register_asset(
97 &self,
98 ctx: &Context<'_>,
99 input: RegisterAssetInput,
100 ) -> Result<GqlRegisterResult> {
101 let services = ctx.data::<Arc<ServiceRegistry>>()?;
102
103 let _user = ctx.data_opt::<AuthUser>();
105
106 let version = Version::parse(&input.version)
108 .map_err(|e| ApiError::bad_request(format!("Invalid version: {}", e)))?;
109
110 let algorithm = match input.checksum_algorithm.to_uppercase().as_str() {
112 "SHA256" => HashAlgorithm::SHA256,
113 "SHA3_256" | "SHA3-256" => HashAlgorithm::SHA3_256,
114 "BLAKE3" => HashAlgorithm::BLAKE3,
115 _ => return Err(ApiError::bad_request("Invalid checksum algorithm"))?
116 };
117
118 let backend = match input.storage_backend.to_lowercase().as_str() {
120 "filesystem" | "local" => StorageBackend::FileSystem {
121 base_path: "/var/lib/llm-registry".to_string(),
122 },
123 _ => StorageBackend::FileSystem {
124 base_path: "/var/lib/llm-registry".to_string(),
125 },
126 };
127
128 let storage = StorageLocation {
130 backend,
131 path: input.storage_path,
132 uri: input.storage_uri,
133 };
134
135 let checksum = Checksum {
137 algorithm,
138 value: input.checksum,
139 };
140
141 let request = RegisterAssetRequest {
143 asset_type: input.asset_type.to_core(),
144 name: input.name,
145 version,
146 description: input.description,
147 license: input.license,
148 tags: input.tags,
149 annotations: input
150 .annotations
151 .into_iter()
152 .map(|a| (a.key, a.value))
153 .collect(),
154 storage,
155 checksum,
156 provenance: None,
157 dependencies: vec![],
158 size_bytes: input.size_bytes,
159 content_type: input.content_type,
160 };
161
162 let response = services
163 .registration()
164 .register_asset(request)
165 .await
166 .map_err(|e| ApiError::from(e))?;
167
168 Ok(GqlRegisterResult {
169 asset: GqlAsset(response.asset),
170 message: "Asset registered successfully".to_string(),
171 })
172 }
173
174 async fn update_asset(
176 &self,
177 ctx: &Context<'_>,
178 input: UpdateAssetInput,
179 ) -> Result<GqlUpdateResult> {
180 let services = ctx.data::<Arc<ServiceRegistry>>()?;
181
182 let _user = ctx.data_opt::<AuthUser>();
184
185 let asset_id = input
187 .asset_id
188 .parse::<AssetId>()
189 .map_err(|e| ApiError::bad_request(format!("Invalid asset ID: {}", e)))?;
190
191 let request = UpdateAssetRequest {
193 asset_id,
194 status: input.status.map(|s| s.to_core()),
195 description: input.description,
196 license: input.license,
197 add_tags: input.add_tags,
198 remove_tags: input.remove_tags,
199 add_annotations: input
200 .add_annotations
201 .into_iter()
202 .map(|a| (a.key, a.value))
203 .collect(),
204 remove_annotations: input.remove_annotations,
205 };
206
207 let response = services
208 .registration()
209 .update_asset(request)
210 .await
211 .map_err(|e| ApiError::from(e))?;
212
213 Ok(GqlUpdateResult {
214 asset: GqlAsset(response.asset),
215 message: "Asset updated successfully".to_string(),
216 })
217 }
218
219 async fn delete_asset(&self, ctx: &Context<'_>, id: String) -> Result<GqlDeleteResult> {
221 let services = ctx.data::<Arc<ServiceRegistry>>()?;
222
223 let _user = ctx.data_opt::<AuthUser>();
225
226 let asset_id = id
228 .parse::<AssetId>()
229 .map_err(|e| ApiError::bad_request(format!("Invalid asset ID: {}", e)))?;
230
231 services
232 .registration()
233 .delete_asset(&asset_id)
234 .await
235 .map_err(|e| ApiError::from(e))?;
236
237 Ok(GqlDeleteResult {
238 asset_id: id,
239 message: "Asset deleted successfully".to_string(),
240 })
241 }
242}