Select the types of activity you want to include in your feed.
refactor api endpoints to more clear xrpc pattern, add procedure and query lexicons for slice endpoints, update codegen for query and procedure lexicons, update frontend to use the new codegen
···11-pub mod actors;
22-pub mod jetstream;
33-pub mod jobs;
44-pub mod logs;
55-pub mod oauth;
61pub mod openapi;
77-pub mod records;
88-pub mod sparkline;
99-pub mod stats;
1010-pub mod sync;
1111-pub mod upload_blob;
122pub mod xrpc_dynamic;
-524
api/src/api/oauth.rs
···11-use axum::{
22- extract::{Query, State},
33- http::HeaderMap,
44- response::Json,
55- Json as ExtractJson,
66-};
77-use serde::{Deserialize, Serialize};
88-use reqwest::Client;
99-1010-use crate::{
1111- AppState,
1212- auth,
1313- errors::AppError,
1414- models::{
1515- CreateOAuthClientRequest, OAuthClientDetails, ListOAuthClientsResponse,
1616- UpdateOAuthClientRequest, DeleteOAuthClientResponse
1717- },
1818-};
1919-2020-#[derive(Deserialize)]
2121-#[serde(rename_all = "camelCase")]
2222-pub struct GetOAuthClientsQuery {
2323- pub slice: String,
2424-}
2525-2626-#[derive(Deserialize)]
2727-#[serde(rename_all = "camelCase")]
2828-pub struct DeleteOAuthClientRequest {
2929- pub client_id: String,
3030-}
3131-3232-#[derive(Debug, Serialize, Deserialize)]
3333-#[serde(rename_all = "snake_case")]
3434-struct AipClientRegistrationRequest {
3535- pub client_name: String,
3636- pub redirect_uris: Vec<String>,
3737- #[serde(skip_serializing_if = "Option::is_none")]
3838- pub grant_types: Option<Vec<String>>,
3939- #[serde(skip_serializing_if = "Option::is_none")]
4040- pub response_types: Option<Vec<String>>,
4141- #[serde(skip_serializing_if = "Option::is_none")]
4242- pub scope: Option<String>,
4343- #[serde(skip_serializing_if = "Option::is_none")]
4444- pub client_uri: Option<String>,
4545- #[serde(skip_serializing_if = "Option::is_none")]
4646- pub logo_uri: Option<String>,
4747- #[serde(skip_serializing_if = "Option::is_none")]
4848- pub tos_uri: Option<String>,
4949- #[serde(skip_serializing_if = "Option::is_none")]
5050- pub policy_uri: Option<String>,
5151-}
5252-5353-#[derive(Serialize, Deserialize)]
5454-#[serde(rename_all = "snake_case")]
5555-struct AipClientRegistrationResponse {
5656- pub client_id: String,
5757- #[serde(skip_serializing_if = "Option::is_none")]
5858- pub client_secret: Option<String>,
5959- #[serde(skip_serializing_if = "Option::is_none")]
6060- pub registration_access_token: Option<String>,
6161- pub client_name: String,
6262- pub redirect_uris: Vec<String>,
6363- pub grant_types: Vec<String>,
6464- pub response_types: Vec<String>,
6565- #[serde(skip_serializing_if = "Option::is_none")]
6666- pub scope: Option<String>,
6767- #[serde(skip_serializing_if = "Option::is_none")]
6868- pub client_uri: Option<String>,
6969- #[serde(skip_serializing_if = "Option::is_none")]
7070- pub logo_uri: Option<String>,
7171- #[serde(skip_serializing_if = "Option::is_none")]
7272- pub tos_uri: Option<String>,
7373- #[serde(skip_serializing_if = "Option::is_none")]
7474- pub policy_uri: Option<String>,
7575- // Additional fields that AIP returns but we don't need to send
7676- #[serde(skip_serializing_if = "Option::is_none")]
7777- pub token_endpoint_auth_method: Option<String>,
7878- #[serde(skip_serializing_if = "Option::is_none")]
7979- pub registration_client_uri: Option<String>,
8080- #[serde(skip_serializing_if = "Option::is_none")]
8181- pub client_id_issued_at: Option<i64>,
8282- #[serde(skip_serializing_if = "Option::is_none")]
8383- pub client_secret_expires_at: Option<i64>,
8484-}
8585-8686-pub async fn create_oauth_client(
8787- State(state): State<AppState>,
8888- headers: HeaderMap,
8989- ExtractJson(request): ExtractJson<CreateOAuthClientRequest>,
9090-) -> Result<Json<serde_json::Value>, AppError> {
9191- // Debug log the incoming request
9292- tracing::debug!("Incoming OAuth client registration request: {:?}", request);
9393-9494- // Validate request
9595- if request.client_name.trim().is_empty() {
9696- return Err(AppError::BadRequest("Client name cannot be empty".to_string()));
9797- }
9898-9999- if request.redirect_uris.is_empty() {
100100- return Err(AppError::BadRequest("At least one redirect URI is required".to_string()));
101101- }
102102-103103- // Validate redirect URIs have basic URL format
104104- for uri in &request.redirect_uris {
105105- if !uri.starts_with("http://") && !uri.starts_with("https://") {
106106- return Err(AppError::BadRequest(format!("Redirect URI must use HTTP or HTTPS: {}", uri)));
107107- }
108108- if uri.trim().is_empty() {
109109- return Err(AppError::BadRequest("Redirect URI cannot be empty".to_string()));
110110- }
111111- }
112112-113113- // Extract and verify authentication
114114- let token = auth::extract_bearer_token(&headers)
115115- .map_err(|_| AppError::BadRequest("Missing or invalid Authorization header".to_string()))?;
116116- let user_info = auth::verify_oauth_token(&token, &state.config.auth_base_url).await
117117- .map_err(|_| AppError::BadRequest("Invalid or expired access token".to_string()))?;
118118-119119- let user_did = user_info.sub;
120120-121121- // Register client with AIP
122122- let aip_base_url = &state.config.auth_base_url;
123123-124124- let client = Client::new();
125125- let aip_request = AipClientRegistrationRequest {
126126- client_name: request.client_name.clone(),
127127- redirect_uris: request.redirect_uris.clone(),
128128- grant_types: request.grant_types.clone(),
129129- response_types: request.response_types.clone(),
130130- scope: request.scope.clone(),
131131- client_uri: request.client_uri.clone(),
132132- logo_uri: request.logo_uri.clone(),
133133- tos_uri: request.tos_uri.clone(),
134134- policy_uri: request.policy_uri.clone(),
135135- };
136136-137137- let registration_url = format!("{}/oauth/clients/register", aip_base_url);
138138- tracing::debug!("Attempting to register OAuth client at: {}", registration_url);
139139- tracing::debug!("Sending AIP request: {:?}", aip_request);
140140-141141- let aip_response = client
142142- .post(®istration_url)
143143- .json(&aip_request)
144144- .send()
145145- .await
146146- .map_err(|e| AppError::Internal(format!("Failed to register client with AIP: {}", e)))?;
147147-148148- let status = aip_response.status();
149149- tracing::debug!("AIP registration response status: {}", status);
150150-151151- if !status.is_success() {
152152- let error_text = aip_response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
153153- tracing::error!("AIP registration failed with status {}: {}", status, error_text);
154154-155155- // Try to parse the error response as JSON to get more details
156156- let detailed_error = if let Ok(error_json) = serde_json::from_str::<serde_json::Value>(&error_text) {
157157- if let Some(error_desc) = error_json.get("error_description").and_then(|v| v.as_str()) {
158158- error_desc.to_string()
159159- } else if let Some(error) = error_json.get("error").and_then(|v| v.as_str()) {
160160- error.to_string()
161161- } else {
162162- error_text
163163- }
164164- } else {
165165- error_text
166166- };
167167-168168- // Return success=false with detailed error message instead of HTTP error
169169- return Ok(Json(serde_json::json!({
170170- "success": false,
171171- "message": detailed_error
172172- })));
173173- }
174174-175175- tracing::debug!("Parsing AIP response JSON...");
176176-177177- // Get the response body as text first to debug what we're receiving
178178- let response_body = aip_response
179179- .text()
180180- .await
181181- .map_err(|e| AppError::Internal(format!("Failed to get response body: {}", e)))?;
182182-183183- tracing::debug!("AIP response body: {}", response_body);
184184-185185- // Try to parse the JSON from the text
186186- let aip_client: AipClientRegistrationResponse = serde_json::from_str(&response_body)
187187- .map_err(|e| {
188188- tracing::error!("Failed to parse AIP response JSON: {}", e);
189189- tracing::error!("Raw response was: {}", response_body);
190190- AppError::Internal(format!("Failed to parse AIP response: {}", e))
191191- })?;
192192-193193- tracing::debug!("Successfully parsed AIP response, client_id: {}", aip_client.client_id);
194194-195195- // Store the client info in our database
196196- tracing::debug!("Storing OAuth client in database...");
197197- let oauth_client = state.database
198198- .create_oauth_client(
199199- &request.slice_uri,
200200- &aip_client.client_id,
201201- aip_client.registration_access_token.as_deref(),
202202- &user_did,
203203- )
204204- .await
205205- .map_err(|e| {
206206- tracing::error!("Failed to store OAuth client in database: {}", e);
207207- AppError::Internal(format!("Failed to store OAuth client: {}", e))
208208- })?;
209209-210210- tracing::debug!("Successfully stored OAuth client in database");
211211-212212- // Return the full client details from AIP
213213- let response = OAuthClientDetails {
214214- client_id: aip_client.client_id,
215215- client_secret: aip_client.client_secret,
216216- client_name: aip_client.client_name,
217217- redirect_uris: aip_client.redirect_uris,
218218- grant_types: aip_client.grant_types,
219219- response_types: aip_client.response_types,
220220- scope: aip_client.scope,
221221- client_uri: aip_client.client_uri,
222222- logo_uri: aip_client.logo_uri,
223223- tos_uri: aip_client.tos_uri,
224224- policy_uri: aip_client.policy_uri,
225225- created_at: oauth_client.created_at,
226226- created_by_did: oauth_client.created_by_did,
227227- };
228228-229229- Ok(Json(serde_json::to_value(response).unwrap()))
230230-}
231231-232232-pub async fn get_oauth_clients(
233233- State(state): State<AppState>,
234234- headers: HeaderMap,
235235- Query(params): Query<GetOAuthClientsQuery>,
236236-) -> Result<Json<ListOAuthClientsResponse>, AppError> {
237237- tracing::debug!("get_oauth_clients called with slice parameter: {}", params.slice);
238238-239239- // Log all headers for debugging
240240- tracing::debug!("Request headers: {:?}", headers);
241241-242242- // Extract and verify authentication
243243- let token = auth::extract_bearer_token(&headers)
244244- .map_err(|e| {
245245- tracing::error!("Failed to extract bearer token: {:?}", e);
246246- AppError::BadRequest("Missing or invalid Authorization header".to_string())
247247- })?;
248248-249249- tracing::debug!("Extracted bearer token (first 20 chars): {}...",
250250- if token.len() > 20 { &token[..20] } else { &token });
251251-252252- auth::verify_oauth_token(&token, &state.config.auth_base_url).await
253253- .map_err(|e| {
254254- tracing::error!("OAuth token verification failed: {:?}", e);
255255- AppError::BadRequest("Invalid or expired access token".to_string())
256256- })?;
257257-258258- tracing::debug!("OAuth token verification successful");
259259-260260- // Get clients from our database
261261- let clients = state.database
262262- .get_oauth_clients_for_slice(¶ms.slice)
263263- .await
264264- .map_err(|e| AppError::Internal(format!("Failed to fetch OAuth clients: {}", e)))?;
265265-266266- tracing::debug!("Found {} OAuth clients in database for slice: {}", clients.len(), params.slice);
267267-268268- if clients.is_empty() {
269269- return Ok(Json(ListOAuthClientsResponse { clients: vec![] }));
270270- }
271271-272272- // Fetch detailed info from AIP for each client
273273- let aip_base_url = &state.config.auth_base_url;
274274- let client = Client::new();
275275- let mut client_details = Vec::new();
276276-277277- for oauth_client in clients {
278278- // Fetch client details from AIP
279279- let aip_url = format!("{}/oauth/clients/{}", aip_base_url, oauth_client.client_id);
280280- tracing::debug!("Fetching client details from AIP: {}", aip_url);
281281-282282- // Use registration access token if available for authentication
283283- let mut request_builder = client.get(&aip_url);
284284- if let Some(token) = &oauth_client.registration_access_token {
285285- request_builder = request_builder.bearer_auth(token);
286286- tracing::debug!("Using registration access token for authentication");
287287- } else {
288288- tracing::debug!("No registration access token available");
289289- }
290290-291291- let aip_response = request_builder.send().await;
292292-293293- match aip_response {
294294- Ok(response) => {
295295- let status = response.status();
296296- tracing::debug!("AIP response status for {}: {}", oauth_client.client_id, status);
297297-298298- if status.is_success() {
299299- // Get the response body as text first to log it
300300- match response.text().await {
301301- Ok(response_text) => {
302302- tracing::debug!("AIP response body for {}: {}", oauth_client.client_id, response_text);
303303-304304- // Try to parse the JSON
305305- match serde_json::from_str::<AipClientRegistrationResponse>(&response_text) {
306306- Ok(aip_client) => {
307307- tracing::debug!("Successfully parsed AIP client details for {}", oauth_client.client_id);
308308- client_details.push(OAuthClientDetails {
309309- client_id: aip_client.client_id,
310310- client_secret: aip_client.client_secret,
311311- client_name: aip_client.client_name,
312312- redirect_uris: aip_client.redirect_uris,
313313- grant_types: aip_client.grant_types,
314314- response_types: aip_client.response_types,
315315- scope: aip_client.scope,
316316- client_uri: aip_client.client_uri,
317317- logo_uri: aip_client.logo_uri,
318318- tos_uri: aip_client.tos_uri,
319319- policy_uri: aip_client.policy_uri,
320320- created_at: oauth_client.created_at,
321321- created_by_did: oauth_client.created_by_did,
322322- });
323323- }
324324- Err(parse_error) => {
325325- tracing::error!("Failed to parse AIP client JSON for {}: {}", oauth_client.client_id, parse_error);
326326- }
327327- }
328328- }
329329- Err(text_error) => {
330330- tracing::error!("Failed to get AIP response text for {}: {}", oauth_client.client_id, text_error);
331331- }
332332- }
333333- } else {
334334- // Handle non-success status codes
335335- match response.text().await {
336336- Ok(error_text) => {
337337- tracing::error!("AIP client fetch failed with status {} for {}: {}", status, oauth_client.client_id, error_text);
338338- }
339339- Err(_) => {
340340- tracing::error!("AIP client fetch failed with status {} for {}", status, oauth_client.client_id);
341341- }
342342- }
343343- }
344344- }
345345- Err(e) => {
346346- tracing::error!("AIP client fetch error for {}: {}", oauth_client.client_id, e);
347347- // If we can't fetch from AIP, create a minimal response
348348- client_details.push(OAuthClientDetails {
349349- client_id: oauth_client.client_id.clone(),
350350- client_secret: None,
351351- client_name: "Unknown".to_string(),
352352- redirect_uris: vec![],
353353- grant_types: vec!["authorization_code".to_string()],
354354- response_types: vec!["code".to_string()],
355355- scope: None,
356356- client_uri: None,
357357- logo_uri: None,
358358- tos_uri: None,
359359- policy_uri: None,
360360- created_at: oauth_client.created_at,
361361- created_by_did: oauth_client.created_by_did,
362362- });
363363- }
364364- }
365365- }
366366-367367- Ok(Json(ListOAuthClientsResponse { clients: client_details }))
368368-}
369369-370370-pub async fn update_oauth_client(
371371- State(state): State<AppState>,
372372- headers: HeaderMap,
373373- ExtractJson(request): ExtractJson<UpdateOAuthClientRequest>,
374374-) -> Result<Json<serde_json::Value>, AppError> {
375375- let client_id = request.client_id.clone();
376376-377377- // Extract and verify authentication
378378- let token = auth::extract_bearer_token(&headers)
379379- .map_err(|_| AppError::BadRequest("Missing or invalid Authorization header".to_string()))?;
380380- auth::verify_oauth_token(&token, &state.config.auth_base_url).await
381381- .map_err(|_| AppError::BadRequest("Invalid or expired access token".to_string()))?;
382382-383383- // Get the client from our database to get the registration access token
384384- let oauth_client = state.database
385385- .get_oauth_client_by_id(&client_id)
386386- .await
387387- .map_err(|e| AppError::Internal(format!("Failed to fetch OAuth client: {}", e)))?
388388- .ok_or_else(|| AppError::NotFound("OAuth client not found".to_string()))?;
389389-390390- let registration_token = oauth_client.registration_access_token
391391- .ok_or_else(|| AppError::Internal("Client missing registration access token".to_string()))?;
392392-393393- // Build AIP update request
394394- let aip_request = AipClientRegistrationRequest {
395395- client_name: request.client_name.unwrap_or_default(),
396396- redirect_uris: request.redirect_uris.unwrap_or_default(),
397397- grant_types: None, // Keep existing
398398- response_types: None, // Keep existing
399399- scope: request.scope,
400400- client_uri: request.client_uri,
401401- logo_uri: request.logo_uri,
402402- tos_uri: request.tos_uri,
403403- policy_uri: request.policy_uri,
404404- };
405405-406406- let aip_base_url = &state.config.auth_base_url;
407407- let client = Client::new();
408408- let update_url = format!("{}/oauth/clients/{}", aip_base_url, client_id);
409409-410410- tracing::debug!("Updating OAuth client at: {}", update_url);
411411- tracing::debug!("Sending AIP update request: {:?}", aip_request);
412412-413413- let aip_response = client
414414- .put(&update_url)
415415- .bearer_auth(®istration_token)
416416- .json(&aip_request)
417417- .send()
418418- .await
419419- .map_err(|e| AppError::Internal(format!("Failed to update client with AIP: {}", e)))?;
420420-421421- let status = aip_response.status();
422422- tracing::debug!("AIP update response status: {}", status);
423423-424424- if !status.is_success() {
425425- let error_text = aip_response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
426426- tracing::error!("AIP update failed with status {}: {}", status, error_text);
427427-428428- // Try to parse the error response as JSON to get more details
429429- let detailed_error = if let Ok(error_json) = serde_json::from_str::<serde_json::Value>(&error_text) {
430430- if let Some(error_desc) = error_json.get("error_description").and_then(|v| v.as_str()) {
431431- error_desc.to_string()
432432- } else if let Some(error) = error_json.get("error").and_then(|v| v.as_str()) {
433433- error.to_string()
434434- } else {
435435- error_text
436436- }
437437- } else {
438438- error_text
439439- };
440440-441441- // Return success=false with detailed error message instead of HTTP error
442442- return Ok(Json(serde_json::json!({
443443- "success": false,
444444- "message": detailed_error
445445- })));
446446- }
447447-448448- // Parse the response
449449- let response_body = aip_response
450450- .text()
451451- .await
452452- .map_err(|e| AppError::Internal(format!("Failed to get response body: {}", e)))?;
453453-454454- tracing::debug!("AIP update response body: {}", response_body);
455455-456456- let aip_client: AipClientRegistrationResponse = serde_json::from_str(&response_body)
457457- .map_err(|e| {
458458- tracing::error!("Failed to parse AIP response JSON: {}", e);
459459- AppError::Internal(format!("Failed to parse AIP response: {}", e))
460460- })?;
461461-462462- // Return the updated client details
463463- let response = OAuthClientDetails {
464464- client_id: aip_client.client_id,
465465- client_secret: aip_client.client_secret,
466466- client_name: aip_client.client_name,
467467- redirect_uris: aip_client.redirect_uris,
468468- grant_types: aip_client.grant_types,
469469- response_types: aip_client.response_types,
470470- scope: aip_client.scope,
471471- client_uri: aip_client.client_uri,
472472- logo_uri: aip_client.logo_uri,
473473- tos_uri: aip_client.tos_uri,
474474- policy_uri: aip_client.policy_uri,
475475- created_at: oauth_client.created_at,
476476- created_by_did: oauth_client.created_by_did,
477477- };
478478-479479- Ok(Json(serde_json::to_value(response).unwrap()))
480480-}
481481-482482-pub async fn delete_oauth_client(
483483- State(state): State<AppState>,
484484- headers: HeaderMap,
485485- ExtractJson(request): ExtractJson<DeleteOAuthClientRequest>,
486486-) -> Result<Json<DeleteOAuthClientResponse>, AppError> {
487487- let client_id = request.client_id;
488488- // Extract and verify authentication
489489- let token = auth::extract_bearer_token(&headers)
490490- .map_err(|_| AppError::BadRequest("Missing or invalid Authorization header".to_string()))?;
491491- auth::verify_oauth_token(&token, &state.config.auth_base_url).await
492492- .map_err(|_| AppError::BadRequest("Invalid or expired access token".to_string()))?;
493493-494494- // Get the client from our database first
495495- let oauth_client = state.database
496496- .get_oauth_client_by_id(&client_id)
497497- .await
498498- .map_err(|e| AppError::Internal(format!("Failed to fetch OAuth client: {}", e)))?
499499- .ok_or_else(|| AppError::NotFound("OAuth client not found".to_string()))?;
500500-501501- // Delete from AIP if we have a registration access token
502502- if let Some(registration_token) = &oauth_client.registration_access_token {
503503- let aip_base_url = &state.config.auth_base_url;
504504-505505- let client = Client::new();
506506- let _aip_response = client
507507- .delete(&format!("{}/oauth/clients/{}", aip_base_url, client_id))
508508- .bearer_auth(registration_token)
509509- .send()
510510- .await;
511511- // We continue even if AIP deletion fails, as we want to clean up our database
512512- }
513513-514514- // Delete from our database
515515- state.database
516516- .delete_oauth_client(&client_id)
517517- .await
518518- .map_err(|e| AppError::Internal(format!("Failed to delete OAuth client: {}", e)))?;
519519-520520- Ok(Json(DeleteOAuthClientResponse {
521521- success: true,
522522- message: format!("OAuth client {} deleted successfully", client_id),
523523- }))
524524-}
+14-24
api/src/api/openapi.rs
···787787fn create_record_schema_from_lexicon(lexicon_data: Option<&serde_json::Value>, slice_uri: &str) -> OpenApiSchema {
788788 if let Some(lexicon) = lexicon_data {
789789 // Get the definitions object directly (it's already parsed JSON, not a string)
790790- if let Some(definitions) = lexicon.get("defs") {
791791- if let Some(main_def) = definitions.get("main") {
792792- if let Some(record_def) = main_def.get("record") {
793793- if let Some(properties) = record_def.get("properties") {
790790+ if let Some(definitions) = lexicon.get("defs")
791791+ && let Some(main_def) = definitions.get("main")
792792+ && let Some(record_def) = main_def.get("record")
793793+ && let Some(properties) = record_def.get("properties") {
794794 // Convert lexicon properties to OpenAPI schema properties
795795 let mut openapi_props = HashMap::new();
796796 let mut required_fields = Vec::new();
797797798798 // Get required fields from record level
799799- if let Some(required_array) = record_def.get("required") {
800800- if let Some(required_list) = required_array.as_array() {
799799+ if let Some(required_array) = record_def.get("required")
800800+ && let Some(required_list) = required_array.as_array() {
801801 for req_field in required_list {
802802 if let Some(field_name) = req_field.as_str() {
803803 required_fields.push(field_name.to_string());
804804 }
805805 }
806806 }
807807- }
808807809808 let default_example = if let Some(props_obj) = properties.as_object() {
810809 for (prop_name, prop_def) in props_obj {
···839838 default: default_example,
840839 };
841840 }
842842- }
843843- }
844844- }
845841 }
846842847843 // Fallback to generic object schema (without rkey - that's a separate request parameter)
···938934 default: None,
939935 }),
940936 "array" => {
941941- if let Some(items_def) = prop_def.get("items") {
942942- if let Some(items_schema) = convert_lexicon_property_to_openapi(items_def) {
937937+ if let Some(items_def) = prop_def.get("items")
938938+ && let Some(items_schema) = convert_lexicon_property_to_openapi(items_def) {
943939 return Some(OpenApiSchema {
944940 schema_type: "array".to_string(),
945941 format: None,
···949945 default: None,
950946 });
951947 }
952952- }
953948 Some(OpenApiSchema {
954949 schema_type: "array".to_string(),
955950 format: None,
···11301125 where_conditions.insert("indexedAt".to_string(), where_condition_schema.clone());
1131112611321127 // Extract fields from lexicon if available
11331133- if let Some(lexicon) = lexicon_data {
11341134- if let Some(definitions) = lexicon.get("definitions").or_else(|| lexicon.get("defs")) {
11351135- if let Some(main_def) = definitions.get("main") {
11361136- if let Some(record_def) = main_def.get("record") {
11371137- if let Some(record_properties) = record_def.get("properties") {
11381138- if let Some(props_obj) = record_properties.as_object() {
11281128+ if let Some(lexicon) = lexicon_data
11291129+ && let Some(definitions) = lexicon.get("definitions").or_else(|| lexicon.get("defs"))
11301130+ && let Some(main_def) = definitions.get("main")
11311131+ && let Some(record_def) = main_def.get("record")
11321132+ && let Some(record_properties) = record_def.get("properties")
11331133+ && let Some(props_obj) = record_properties.as_object() {
11391134 for field_name in props_obj.keys() {
11401135 where_conditions.insert(field_name.clone(), where_condition_schema.clone());
11411136 }
11421137 }
11431143- }
11441144- }
11451145- }
11461146- }
11471147- }
1148113811491139 properties.insert("where".to_string(), OpenApiSchema {
11501140 schema_type: "object".to_string(),
···192192 for collection in &all_collections {
193193 requests_by_pds
194194 .entry(pds_url.clone())
195195- .or_insert_with(Vec::new)
195195+ .or_default()
196196 .push((repo.clone(), collection.clone()));
197197 }
198198 }
···535535536536 for atproto_record in list_response.records {
537537 // Check if we already have this record with the same CID
538538- if let Some(existing_cid) = existing_cids.get(&atproto_record.uri) {
539539- if existing_cid == &atproto_record.cid {
538538+ if let Some(existing_cid) = existing_cids.get(&atproto_record.uri)
539539+ && existing_cid == &atproto_record.cid {
540540 // Record unchanged, skip it
541541 skipped_count += 1;
542542 continue;
543543 }
544544- }
545544546545 // Record is new or changed, include it
547546 let record = Record {
···11+pub mod create_oauth_client;
22+pub mod delete_oauth_client;
33+pub mod get_actors;
44+pub mod get_jetstream_logs;
55+pub mod get_jetstream_status;
66+pub mod get_job_history;
77+pub mod get_job_logs;
88+pub mod get_job_status;
99+pub mod get_oauth_clients;
1010+pub mod get_slice_records;
1111+pub mod get_sparklines;
1212+pub mod openapi;
1313+pub mod start_sync;
1414+pub mod stats;
1515+pub mod sync_user_collections;
1616+pub mod update_oauth_client;
+1
api/src/xrpc/network/slices/slice/openapi.rs
···11+pub use crate::api::openapi::get_openapi_spec as handler;
···77import { Database } from "lucide-preact";
88import { buildSliceUrlFromView } from "../../../../utils/slice-params.ts";
99import type { AuthenticatedUser } from "../../../../routes/middleware.ts";
1010-import type { NetworkSlicesSliceDefsSliceView } from "../../../../client.ts";
1111-import { IndexedRecord } from "@slices/client";
1010+import type { NetworkSlicesSliceDefsSliceView, NetworkSlicesSliceGetSliceRecordsIndexedRecord } from "../../../../client.ts";
12111313-interface Record extends IndexedRecord {
1212+interface Record extends NetworkSlicesSliceGetSliceRecordsIndexedRecord {
1413 pretty_value?: string;
1514}
1615
···11-import { IndexedRecord } from "@slices/client";
21import { Card } from "../../../../../shared/fragments/Card.tsx";
32import { Text } from "../../../../../shared/fragments/Text.tsx";
33+import type { NetworkSlicesSliceGetSliceRecordsIndexedRecord } from "../../../../../client.ts";
4455-interface Record extends IndexedRecord {
55+interface Record extends NetworkSlicesSliceGetSliceRecordsIndexedRecord {
66 pretty_value?: string;
77}
88