···33use crate::database_writer::DatabaseOperation;
44use crate::types::records::{AppBskyActorProfile, AppBskyActorStatus};
55use crate::utils::at_uri_is_by;
66+use jacquard_common::IntoStatic;
6778pub fn handle_profile(
89 ctx: &super::RecordContext,
···1112 let mut operations = Vec::new();
12131314 if ctx.rkey == "self" {
1414- let labels = record.labels.clone();
1515+ let labels = record.labels.clone().map(|l| l.into_static());
15161617 // Don't allow pinned posts that aren't by us
1718 if let Some(pinned) = &record.pinned_post {
1818- if !at_uri_is_by(&pinned.uri, &ctx.repo) {
1919+ if !at_uri_is_by(pinned.uri.as_str(), &ctx.repo) {
1920 record.pinned_post = None;
2021 }
2122 }
+3-3
consumer/src/database_writer/operations/types.rs
···4343}
44444545/// Self-labels data for posts/profiles/etc (from AT Protocol)
4646-/// Re-export of lexica SelfLabels for convenience
4646+/// Re-export of jacquard SelfLabels for convenience
4747/// Used in DatabaseOperation::MaintainSelfLabels variant
4848-pub type SelfLabels = lexica::com_atproto::label::SelfLabels;
4848+pub type SelfLabels = jacquard_api::com_atproto::label::SelfLabels<'static>;
49495050/// Database operation to be performed by database writer
5151#[derive(Debug)]
···237237 UpsertBookmark {
238238 actor_id: i32,
239239 rkey: i64, // TID converted to i64
240240- record: lexica::community_lexicon::bookmarks::Bookmark,
240240+ record: super::handlers::bookmark::Bookmark,
241241 },
242242243243 // Generic operations
···120120121121 // Verification: subject is the verified actor
122122 RecordTypes::AppBskyGraphVerification(rec) => {
123123- RecordReferences::with_subject(rec.subject.clone())
123123+ RecordReferences::with_subject(rec.subject.to_string())
124124 }
125125126126 // Like: extract author DID from liked post URI and via repost URI
127127 // NOTE: Likes don't have subject_actor_id FK in DB, but we need it for notifications
128128 RecordTypes::AppBskyFeedLike(rec) => {
129129- let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(&rec.subject.uri) {
129129+ let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) {
130130 RecordReferences::with_subject(subject_did.to_string())
131131 } else {
132132 RecordReferences::empty()
···134134135135 // Extract via repost URI and CID (full StrongRef)
136136 if let Some(via_ref) = &rec.via {
137137- refs.via_uri = Some(via_ref.uri.clone());
137137+ refs.via_uri = Some(via_ref.uri.to_string());
138138 refs.via_cid = Some(via_ref.cid.to_string());
139139 }
140140···144144 // Repost: extract author DID from reposted post URI and via repost URI
145145 // NOTE: Reposts don't have subject_actor_id FK in DB, but we need it for notifications
146146 RecordTypes::AppBskyFeedRepost(rec) => {
147147- let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(&rec.subject.uri) {
147147+ let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) {
148148 RecordReferences::with_subject(subject_did.to_string())
149149 } else {
150150 RecordReferences::empty()
···152152153153 // Extract via repost URI and CID (full StrongRef)
154154 if let Some(via_ref) = &rec.via {
155155- refs.via_uri = Some(via_ref.uri.clone());
155155+ refs.via_uri = Some(via_ref.uri.to_string());
156156 refs.via_cid = Some(via_ref.cid.to_string());
157157 }
158158···182182 }
183183 AppBskyEmbed::RecordWithMedia(rwm) => {
184184 refs.quoted_author_did =
185185- parakeet_db::at_uri_util::extract_did(&rwm.record.record.uri)
185185+ parakeet_db::at_uri_util::extract_did(&rwm.record.uri)
186186 .map(|s| s.to_string());
187187 }
188188 _ => {}
···201201 // ListBlock: extract list owner DID
202202 RecordTypes::AppBskyGraphListBlock(rec) => {
203203 let mut dids = Vec::new();
204204- if let Some(list_did) = parakeet_db::at_uri_util::extract_did(&rec.subject) {
204204+ if let Some(list_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) {
205205 dids.push(list_did.to_string());
206206 }
207207 RecordReferences::with_additional(dids)
+4-5
consumer/src/database_writer/workers_tap.rs
···384384 },
385385 AppBskyEmbed::RecordWithMedia(record_with_media) => {
386386 // Extract DID from the quoted post URI
387387- if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_with_media.record.record.uri) {
387387+ if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_with_media.record.uri) {
388388 let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?;
389389 Some(actor_id)
390390 } else {
···403403 // Resolve mentioned actors from facets
404404 let mut mentions = Vec::new();
405405 if let Some(ref facets) = post.facets {
406406+ use jacquard_api::app_bsky::richtext::facet::FacetFeaturesItem;
406407 for facet_item in facets {
407408 for feature in &facet_item.features {
408408- if let lexica::app_bsky::richtext::FacetOuter::Bsky(
409409- lexica::app_bsky::richtext::Facet::Mention { did }
410410- ) = feature {
409409+ if let FacetFeaturesItem::Mention(mention) = feature {
411410 // Resolve mentioned actor
412412- let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?;
411411+ let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, mention.did.as_ref()).await?;
413412 mentions.push(actor_id);
414413 }
415414 }
+42-35
consumer/src/db/composite_builders.rs
···66use parakeet_db::composite_types::{ExtEmbed, VideoEmbed, ImageEmbed, FacetEmbed};
77use parakeet_db::types::{ImageMimeType, VideoMimeType, FacetType};
88use crate::types::records::{AppBskyEmbedImages, AppBskyEmbedVideo, AppBskyEmbedExternal};
99-use lexica::app_bsky::richtext::{FacetMain, FacetOuter, Facet};
99+use jacquard_api::app_bsky::richtext::facet::{Facet as FacetMain, FacetFeaturesItem};
1010use std::str::FromStr;
11111212/// Build external embed composite from AppBskyEmbedExternal
···1616 // Parse thumbnail mime type and CID if present
1717 let (thumb_mime_type, thumb_cid) = if let Some(ref thumb) = ext.thumb {
1818 let mime = ImageMimeType::from_str(&thumb.mime_type).ok();
1919- let cid_bytes = thumb.cid.to_bytes();
2020- let cid = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes);
1919+ let cid_str = thumb.cid().as_str();
2020+ let cid_parsed = cid_str.parse::<cid::Cid>().ok();
2121+ let cid_bytes = cid_parsed.map(|c| c.to_bytes());
2222+ let cid = cid_bytes.and_then(|b| parakeet_db::cid_util::cid_to_digest_owned(&b));
2123 (mime, cid)
2224 } else {
2325 (None, None)
···41434244 let mime_type = VideoMimeType::from_str(&video.mime_type).ok()?;
43454444- let cid_bytes = video.cid.to_bytes();
4646+ let cid_str = video.cid().as_str();
4747+ let cid_parsed = cid_str.parse::<cid::Cid>().ok()?;
4848+ let cid_bytes = cid_parsed.to_bytes();
4549 let cid = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes)?;
46504751 // Build caption fields (max 3)
···5054 for (idx, caption_data) in caption_list.iter().enumerate().take(3) {
5155 let lang = LanguageCode::from_str(&caption_data.lang).ok()?;
5256 let caption_mime_type = CaptionMimeType::from_str(&caption_data.file.mime_type).ok()?;
5353- let caption_cid_bytes = caption_data.file.cid.to_bytes();
5757+ let caption_cid_str = caption_data.file.cid().as_str();
5858+ let caption_cid_parsed = caption_cid_str.parse::<cid::Cid>().ok()?;
5959+ let caption_cid_bytes = caption_cid_parsed.to_bytes();
5460 let caption_cid = parakeet_db::cid_util::cid_to_digest_owned(&caption_cid_bytes)?;
55615662 captions[idx] = Some(VideoCaption {
···6672 cid,
6773 alt: embed.alt.clone(),
6874 // Note: aspect_ratio in AppBskyEmbedVideo contains width/height
6969- width: embed.aspect_ratio.as_ref().map(|ar| ar.width),
7070- height: embed.aspect_ratio.as_ref().map(|ar| ar.height),
7575+ width: embed.aspect_ratio.as_ref().and_then(|ar| ar.width.try_into().ok()),
7676+ height: embed.aspect_ratio.as_ref().and_then(|ar| ar.height.try_into().ok()),
7177 caption_1: captions[0].clone(),
7278 caption_2: captions[1].clone(),
7379 caption_3: captions[2].clone(),
···8490 for (idx, image) in embed.images.iter().enumerate().take(4) {
8591 let mime_type = ImageMimeType::from_str(&image.image.mime_type).ok();
8692 if let Some(mime_type) = mime_type {
8787- let cid_bytes = image.image.cid.to_bytes();
8888- if let Some(cid) = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) {
8989- images[idx] = Some(ImageEmbed {
9090- mime_type,
9191- cid,
9292- alt: Some(image.alt.clone()),
9393- width: image.aspect_ratio.as_ref().map(|ar| ar.width),
9494- height: image.aspect_ratio.as_ref().map(|ar| ar.height),
9595- });
9393+ let cid_str = image.image.cid().as_str();
9494+ if let Ok(cid_parsed) = cid_str.parse::<cid::Cid>() {
9595+ let cid_bytes = cid_parsed.to_bytes();
9696+ if let Some(cid) = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) {
9797+ images[idx] = Some(ImageEmbed {
9898+ mime_type,
9999+ cid,
100100+ alt: Some(image.alt.clone()),
101101+ width: image.aspect_ratio.as_ref().and_then(|ar| ar.width.try_into().ok()),
102102+ height: image.aspect_ratio.as_ref().and_then(|ar| ar.height.try_into().ok()),
103103+ });
104104+ }
96105 }
97106 }
98107 }
···118127119128 for (idx, facet_main) in facets.iter().enumerate().take(8) {
120129 // Get first feature from the facet
121121- let Some(feature_outer) = facet_main.features.first() else {
130130+ let Some(feature) = facet_main.features.first() else {
122131 continue; // Skip facets with no features
123132 };
124133125125- // Extract the inner Facet enum
126126- let facet = match feature_outer {
127127- FacetOuter::Bsky(f) => f,
128128- FacetOuter::Other(_) => continue, // Skip non-Bluesky facets
129129- };
130130-131134 // Determine facet type and extract data
132132- let (facet_type, link_uri, mention_actor_id, tag) = match facet {
133133- Facet::Link { uri } => {
134134- (FacetType::Link, Some(uri.clone()), None, None)
135135+ let (facet_type, link_uri, mention_actor_id, tag) = match feature {
136136+ FacetFeaturesItem::Link(link) => {
137137+ (FacetType::Link, Some(link.uri.as_str().to_string()), None, None)
135138 }
136136- Facet::Mention { did } => {
139139+ FacetFeaturesItem::Mention(mention) => {
137140 // Look up actor_id from the pre-built map
138138- let actor_id = actor_id_map.get(did).copied();
141141+ let actor_id = actor_id_map.get(mention.did.as_ref()).copied();
139142 (FacetType::Mention, None, actor_id, None)
140143 }
141141- Facet::Tag { tag: tag_str } => {
142142- (FacetType::Tag, None, None, Some(tag_str.clone()))
144144+ FacetFeaturesItem::Tag(tag) => {
145145+ (FacetType::Tag, None, None, Some(tag.tag.to_string()))
146146+ }
147147+ FacetFeaturesItem::Unknown(_) => {
148148+ // Skip unknown facet types
149149+ continue;
143150 }
144151 };
145152146153 facet_array[idx] = Some(FacetEmbed {
147154 facet_type,
148148- index_start: facet_main.index.byte_start,
149149- index_end: facet_main.index.byte_end,
155155+ index_start: facet_main.index.byte_start as i32,
156156+ index_end: facet_main.index.byte_end as i32,
150157 link_uri,
151158 mention_actor_id,
152159 tag,
···167174 let mut mentions = Vec::new();
168175169176 for facet_main in facets.iter().take(8) {
170170- for feature_outer in &facet_main.features {
171171- if let FacetOuter::Bsky(Facet::Mention { did }) = feature_outer {
177177+ for feature in &facet_main.features {
178178+ if let FacetFeaturesItem::Mention(mention) = feature {
172179 // Look up actor_id from DID
173173- let actor_id = actor_id_map.get(did).copied();
180180+ let actor_id = actor_id_map.get(mention.did.as_ref()).copied();
174181 mentions.push(actor_id);
175182 }
176183 }
+18-13
consumer/src/db/labels.rs
···22use crate::types::records::AppBskyLabelerService;
33use deadpool_postgres::GenericClient;
44use ipld_core::cid::Cid;
55-use lexica::com_atproto::label::{LabelValueDefinition, SelfLabels};
55+use jacquard_api::com_atproto::label::{LabelValueDefinition, SelfLabels};
66use std::collections::HashMap;
7788pub async fn maintain_label_defs<C: GenericClient>(
···19192020 // Build labeler_defs array from label values and definitions
2121 // Maps label_identifier -> definition
2222- let definitions = rec
2323- .policies
2424- .label_value_definitions
2525- .iter()
2626- .map(|def| (def.identifier.clone(), def))
2727- .collect::<HashMap<String, &LabelValueDefinition>>();
2222+ let definitions = if let Some(ref defs) = rec.policies.label_value_definitions {
2323+ defs.iter()
2424+ .map(|def| (def.identifier.to_string(), def))
2525+ .collect::<HashMap<String, &LabelValueDefinition>>()
2626+ } else {
2727+ HashMap::new()
2828+ };
28292930 // Build arrays of values for each composite field
3031 let mut label_identifiers = Vec::new();
···3435 let mut adult_onlys = Vec::new();
3536 let mut locales_vals = Vec::new();
36373737- for label in &rec.policies.label_values {
3838- let definition = definitions.get(label);
3838+ // label_values is Vec<LabelValue> in jacquard (not Option)
3939+ let label_values = rec.policies.label_values.as_slice();
4040+ for label in label_values {
4141+ let label_str = label.as_str().to_string();
4242+ let definition = definitions.get(&label_str);
39434040- label_identifiers.push(label.clone());
4444+ label_identifiers.push(label_str);
4145 severities.push(definition.map(|v| v.severity.to_string()));
4246 blurs_vals.push(definition.map(|v| v.blurs.to_string()));
4343- default_settings.push(definition.and_then(|v| v.default_setting).map(|v| v.to_string()));
4747+ default_settings.push(definition.and_then(|v| v.default_setting.as_ref().map(|s| s.to_string())));
4448 adult_onlys.push(definition.and_then(|v| v.adult_only).unwrap_or_default());
4549 locales_vals.push(definition.and_then(|v| serde_json::to_value(&v.locales).ok()));
4650 }
···8690 repo: &str,
8791 _cid: Option<Cid>,
8892 at_uri: &str,
8989- self_labels: SelfLabels,
9393+ self_labels: SelfLabels<'_>,
9094) -> Result<u64> {
9195 // Resolve actor_id from DID
9296 let labeler_actor_id = super::actor_id_from_did(conn, repo).await?;
···109113 let expires_vals: Vec<Option<String>> = vec![None; self_labels.values.len()]; // No expiration
110114111115 for label in self_labels.values {
112112- labels_data.push(label.val.clone());
116116+ // The LabelValue type in jacquard uses 'val' field
117117+ labels_data.push(label.val.to_string());
113118 }
114119115120 if collection == "app.bsky.actor.profile" && rkey == "self" {
+10-4
consumer/src/db/operations/actor.rs
···2121 let cid_bytes = cid.to_bytes();
2222 let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes)
2323 .expect("CID must be valid AT Protocol CID");
2424- let avatar = blob_to_cid_bytes(rec.avatar);
2525- let banner = blob_to_cid_bytes(rec.banner);
2424+ let avatar = blob_to_cid_bytes(rec.avatar.as_ref());
2525+ let banner = blob_to_cid_bytes(rec.banner.as_ref());
2626 let (pinned_uri, _pinned_cid) = strongref_to_parts(rec.pinned_post.as_ref());
2727 let (joined_sp_uri, _joined_sp_cid) = strongref_to_parts(rec.joined_via_starter_pack.as_ref());
2828···152152 let thumb = rec.embed.as_ref().and_then(|v| v.external.thumb.clone());
153153 let thumb_mime = thumb.as_ref().map(|v| crate::utils::strip_mime_params(&v.mime_type));
154154 let thumb_cid = thumb.as_ref().map(|v| {
155155- let cid_bytes = v.cid.to_bytes();
155155+ let cid_str = v.cid();
156156+ let cid_parsed = cid_str.as_str().parse::<cid::Cid>().expect("Valid CID");
157157+ let cid_bytes = cid_parsed.to_bytes();
156158 parakeet_db::cid_util::cid_to_digest(&cid_bytes)
157159 .expect("CID must be valid AT Protocol CID")
158160 .to_vec()
···208210 status: Some(StatusOp::Set {
209211 cid: cid_digest.to_vec(),
210212 created_at: Some(rec.created_at),
211211- status_type: rec.status.to_string(),
213213+ // Serialize status to JSON to extract the type string
214214+ status_type: serde_json::to_value(&rec.status)
215215+ .ok()
216216+ .and_then(|v| v.get("$type").and_then(|t| t.as_str()).map(String::from))
217217+ .unwrap_or_else(|| "active".to_string()),
212218 duration_minutes: rec.duration_minutes,
213219 embed_post_actor_id,
214220 embed_post_rkey,
+2-1
consumer/src/db/operations/community.rs
···22use super::feed::get_actor_id;
33use deadpool_postgres::GenericClient;
44use eyre::Context as _;
55-use lexica::community_lexicon::bookmarks::Bookmark;
55+// Use the local Bookmark type from handlers
66+use crate::database_writer::operations::handlers::bookmark::Bookmark;
6778/// Upsert a bookmark into the actor's bookmarks[] array
89///
+4-4
consumer/src/db/operations/feed/feedgen.rs
···1616/// Strip lexicon prefix from enum value
1717///
1818/// Example: "app.bsky.feed.defs#contentModeUnspecified" -> "contentModeUnspecified"
1919+#[allow(dead_code)]
1920fn strip_lexicon_prefix(value: &str) -> &str {
2021 value.split('#').nth(1).unwrap_or(value)
2122}
···5657 let description_facets = rec
5758 .description_facets
5859 .and_then(|v| serde_json::to_value(v).ok());
5959- let avatar = blob_to_cid_bytes(rec.avatar);
6060+ let avatar = blob_to_cid_bytes(rec.avatar.as_ref());
60616161- // Strip lexicon prefix from content_mode
6262- // Example: "app.bsky.feed.defs#contentModeUnspecified" -> "contentModeUnspecified"
6363- let content_mode = rec.content_mode.as_ref().map(|s| strip_lexicon_prefix(s));
6262+ // Note: content_mode field not available in jacquard types
6363+ let content_mode: Option<String> = None;
64646565 conn.query_one(
6666 include_str!("../../sql/feedgen_upsert.sql"),
+14-13
consumer/src/db/operations/feed/post.rs
···158158 }
159159 AppBskyEmbed::RecordWithMedia(rwm) => {
160160 // Process media part
161161- let (ext, vid, i1, i2, i3, i4) = match rwm.media.as_ref() {
162162- AppBskyEmbed::Images(images) => {
163163- let (i1, i2, i3, i4) = composite_builders::build_image_embeds(images);
161161+ let (ext, vid, i1, i2, i3, i4) = match &rwm.media {
162162+ crate::types::records::MediaEmbed::Images(images) => {
163163+ let (i1, i2, i3, i4) = composite_builders::build_image_embeds(&images);
164164 (None, None, i1, i2, i3, i4)
165165 }
166166- AppBskyEmbed::Video(video) => {
167167- (None, composite_builders::build_video_embed(video), None, None, None, None)
166166+ crate::types::records::MediaEmbed::Video(video) => {
167167+ (None, composite_builders::build_video_embed(&video), None, None, None, None)
168168 }
169169- AppBskyEmbed::External(external) => {
170170- (composite_builders::build_ext_embed(external), None, None, None, None, None)
169169+ crate::types::records::MediaEmbed::External(external) => {
170170+ (composite_builders::build_ext_embed(&external), None, None, None, None, None)
171171 }
172172- _ => (None, None, None, None, None, None),
173172 };
174173 // Process record part
175175- let embed_uri = rwm.record.record.uri.as_str();
176176- let embed_cid_str = rwm.record.record.cid.to_string();
174174+ let embed_uri = rwm.record.uri.as_str();
175175+ let embed_cid_str = rwm.record.cid.to_string();
177176 let embed_did = parakeet_db::at_uri_util::extract_did(embed_uri)
178177 .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing DID in {}", embed_uri))?;
179178 let embed_rkey = parakeet_db::at_uri_util::extract_rkey(embed_uri)
···194193 if let Some(ref facets) = rec.facets {
195194 // Build a map of DID -> actor_id for mentioned actors
196195 let mut actor_id_map: HashMap<String, i32> = HashMap::new();
196196+ use jacquard_api::app_bsky::richtext::facet::FacetFeaturesItem;
197197 for facet_main in facets.iter() {
198198- for feature_outer in &facet_main.features {
199199- if let lexica::app_bsky::richtext::FacetOuter::Bsky(lexica::app_bsky::richtext::Facet::Mention { did }) = feature_outer {
198198+ for feature in &facet_main.features {
199199+ if let FacetFeaturesItem::Mention(mention) = feature {
200200+ let did = mention.did.as_ref();
200201 if !actor_id_map.contains_key(did) {
201202 // Resolve DID to actor_id
202203 if let Ok((mention_actor_id, _, _)) = get_actor_id(conn, did).await {
203203- actor_id_map.insert(did.clone(), mention_actor_id);
204204+ actor_id_map.insert(did.to_string(), mention_actor_id);
204205 }
205206 }
206207 }
+18-18
consumer/src/db/operations/feed/postgate.rs
···3232 _cid: Cid, // No longer stored - postgates are denormalized
3333 rec: &AppBskyFeedPostgate,
3434) -> Result<u64> {
3535- let rules = rec
3636- .embedding_rules
3737- .iter()
3838- .map(|v| v.as_str().to_owned())
3939- .collect::<Vec<_>>();
3535+ let rules: Vec<String> = vec![]; // TODO: Extract rules from DisableRule when needed
40364137 // Parse post URI to get post actor_id and rkey
4242- let post_did = parakeet_db::at_uri_util::extract_did(&rec.post)
3838+ let post_did = parakeet_db::at_uri_util::extract_did(rec.post.as_str())
4339 .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: missing DID in {}", rec.post))?;
4444- let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(&rec.post)
4040+ let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(rec.post.as_str())
4541 .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: {}", rec.post))?;
4642 let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str)
4743 .wrap_err_with(|| format!("Invalid TID in postgate post URI: {}", post_rkey_str))?;
···5753 .get::<_, i32>(0);
58545955 // OPTIMIZATION: Resolve detached embedding URIs to (actor_id, rkey) pairs in Rust
6060- let (detached_actor_ids, detached_rkeys): (Vec<i32>, Vec<i64>) = if !rec.detached_embedding_uris.is_empty() {
6161- let uri_refs: Vec<&str> = rec.detached_embedding_uris.iter().map(|s| s.as_str()).collect();
6262- let resolved = crate::db::bulk_resolve::resolve_post_uris_bulk(conn, &uri_refs).await?;
5656+ let (detached_actor_ids, detached_rkeys): (Vec<i32>, Vec<i64>) = if let Some(ref uris) = rec.detached_embedding_uris {
5757+ if !uris.is_empty() {
5858+ let uri_refs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect();
5959+ let resolved = crate::db::bulk_resolve::resolve_post_uris_bulk(conn, &uri_refs).await?;
63606464- // Build parallel arrays for SQL
6565- let mut actor_ids = Vec::new();
6666- let mut rkeys = Vec::new();
6767- for uri in &rec.detached_embedding_uris {
6868- if let Some(&(aid, rk)) = resolved.get(uri.as_str()) {
6969- actor_ids.push(aid);
7070- rkeys.push(rk);
6161+ // Build parallel arrays for SQL
6262+ let mut actor_ids = Vec::new();
6363+ let mut rkeys = Vec::new();
6464+ for uri in uris {
6565+ if let Some(&(aid, rk)) = resolved.get(uri.as_str()) {
6666+ actor_ids.push(aid);
6767+ rkeys.push(rk);
6868+ }
7169 }
7070+ (actor_ids, rkeys)
7171+ } else {
7272+ (vec![], vec![])
7273 }
7373- (actor_ids, rkeys)
7474 } else {
7575 (vec![], vec![])
7676 };
+13-23
consumer/src/db/operations/feed/threadgate.rs
···8888 let allow = rec.allow.as_ref().map(|allow| {
8989 allow
9090 .iter()
9191- .map(|v| v.as_str().to_owned())
9191+ .map(|v| match v {
9292+ crate::types::records::ThreadgateRule::MentionRule {} => crate::types::records::THREADGATE_RULE_MENTION.to_owned(),
9393+ crate::types::records::ThreadgateRule::FollowingRule {} => crate::types::records::THREADGATE_RULE_FOLLOWING.to_owned(),
9494+ crate::types::records::ThreadgateRule::List { .. } => crate::types::records::THREADGATE_RULE_LIST.to_owned(),
9595+ })
9296 .collect::<Vec<_>>()
9397 });
94989599 // Parse post URI to get post actor_id and rkey
96100 // Note: The post being protected is usually the same as the threadgate's actor,
97101 // but we extract it from the URI to be safe
9898- let post_did = parakeet_db::at_uri_util::extract_did(&rec.post)
9999- .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: missing DID in {}", rec.post))?;
100100- let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(&rec.post)
101101- .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: {}", rec.post))?;
102102+ let post_uri = rec.post.as_ref()
103103+ .ok_or_else(|| eyre::eyre!("Missing post URI in threadgate"))?;
104104+ let post_did = parakeet_db::at_uri_util::extract_did(post_uri.uri.as_str())
105105+ .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: missing DID in {}", post_uri.uri))?;
106106+ let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(post_uri.uri.as_str())
107107+ .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: {}", post_uri.uri))?;
102108 let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str)
103109 .wrap_err_with(|| format!("Invalid TID in threadgate post URI: {}", post_rkey_str))?;
104110···112118 .wrap_err_with(|| format!("Failed to find actor for DID: {}", post_did))?
113119 .get::<_, i32>(0);
114120115115- // OPTIMIZATION: Resolve hidden reply URIs to (actor_id, rkey) pairs in Rust
116116- let (hidden_actor_ids, hidden_rkeys): (Vec<i32>, Vec<i64>) = if !rec.hidden_replies.is_empty() {
117117- let uri_refs: Vec<&str> = rec.hidden_replies.iter().map(|s| s.as_str()).collect();
118118- let resolved = crate::db::bulk_resolve::resolve_post_uris_bulk(conn, &uri_refs).await?;
119119-120120- // Build parallel arrays for SQL
121121- let mut actor_ids = Vec::new();
122122- let mut rkeys = Vec::new();
123123- for uri in &rec.hidden_replies {
124124- if let Some(&(aid, rk)) = resolved.get(uri.as_str()) {
125125- actor_ids.push(aid);
126126- rkeys.push(rk);
127127- }
128128- }
129129- (actor_ids, rkeys)
130130- } else {
131131- (vec![], vec![])
132132- };
121121+ // TODO: Handle hidden_replies when added to our custom type
122122+ let (hidden_actor_ids, hidden_rkeys): (Vec<i32>, Vec<i64>) = (vec![], vec![]);
133123134124 // Extract allowed list URIs from rules and resolve to natural keys
135125 let (allowed_list_actor_ids, allowed_list_rkeys): (Vec<i32>, Vec<String>) = if let Some(ref allow_rules) = rec.allow {
+15-5
consumer/src/db/operations/graph.rs
···190190 let description_facets = rec
191191 .description_facets
192192 .and_then(|v| serde_json::to_value(v).ok());
193193- let avatar = blob_to_cid_bytes(rec.avatar);
193193+ let avatar = blob_to_cid_bytes(rec.avatar.as_ref());
194194195195 // Acquire table-scoped advisory lock on lists table for this record
196196 let (table_id, key_id) = crate::database_writer::locking::actor_record_lock_str("lists", actor_id, rkey);
197197 crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?;
198198199199+ // Convert ListPurpose to string for database
200200+ // Note: jacquard's ListPurpose doesn't expose variants directly, serialize to JSON to get the type
201201+ let purpose = serde_json::to_value(&rec.purpose)
202202+ .ok()
203203+ .and_then(|v| v.get("$type").and_then(|t| t.as_str()).map(String::from))
204204+ .unwrap_or_else(|| "app.bsky.graph.defs#curatelist".to_string());
205205+199206 conn.query_one(
200207 include_str!("../sql/list_upsert.sql"),
201208 &[
202209 &actor_id,
203210 &cid_digest,
204204- &rec.purpose,
211211+ &purpose,
205212 &rec.name,
206213 &rec.description,
207214 &description_facets,
···242249243250 // Parse list URI to get natural keys (list_actor_id, list_rkey)
244251 // Format: at://did:plc:xxx/app.bsky.graph.list/rkey
245245- let parts: Vec<&str> = rec.subject.strip_prefix("at://")
252252+ let parts: Vec<&str> = rec.subject.uri.as_str().strip_prefix("at://")
246253 .ok_or_else(|| eyre::eyre!("Invalid AT URI"))?
247254 .split('/')
248255 .collect();
···324331 crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?;
325332326333 // Resolve list natural keys by creating stub list if needed
327327- let (list_owner_actor_id, list_rkey) = super::feed::ensure_list_natural_key(conn, &rec.list).await?;
334334+ let (list_owner_actor_id, list_rkey) = super::feed::ensure_list_natural_key(conn, rec.list.uri.as_str()).await?;
328335329336 conn.execute(
330337 include_str!("../sql/list_item_upsert.sql"),
···377384378385 // Insert verification - actors already resolved (no SELECT subquery needed)
379386 // Note: created_at is derived from TID rkey
387387+ let handle_str = rec.handle.to_string();
388388+ // display_name is CowStr, not Option<CowStr>
389389+ let display_name_str = Some(rec.display_name.to_string());
380390 conn.execute(
381391 "INSERT INTO verification (actor_id, rkey, cid, verifier_actor_id, subject_actor_id, handle, display_name)
382392 VALUES ($1, $2, $3, $1, $4, $5, $6)
383393 ON CONFLICT (actor_id, rkey) DO NOTHING",
384384- &[&actor_id, &rkey, &cid_digest, &subject_actor_id, &rec.handle, &rec.display_name],
394394+ &[&actor_id, &rkey, &cid_digest, &subject_actor_id, &handle_str, &display_name_str],
385395 )
386396 .await
387397 .wrap_err_with(|| format!("Failed to insert verification for actor_id:{} rkey:{}", actor_id, rkey))
+6-11
consumer/src/db/operations/labeler.rs
···53535454/// Extract the short name from an AT Protocol enum URN
5555/// e.g., "com.atproto.moderation.defs#reasonSpam" -> "spam"
5656+#[allow(dead_code)]
5657fn extract_enum_short_name(urn: &str) -> String {
5758 urn.split('#')
5859 .nth(1)
···7677 let cid_bytes = cid.to_bytes();
7778 let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes)
7879 .ok_or_eyre("CID must be valid AT Protocol CID")?;
7979- let reasons = rec.reason_types.as_ref().map(|v| {
8080- v.iter()
8181- .map(|r| extract_enum_short_name(&r.to_string()))
8282- .collect::<Vec<_>>()
8383- });
8484- let subject_types = rec.subject_types.as_ref().map(|v| {
8585- v.iter()
8686- .map(|s| s.to_string().to_lowercase())
8787- .collect::<Vec<_>>()
8888- });
8080+ // Note: reason_types, subject_types, and subject_collections not available in jacquard types
8181+ let reasons: Option<Vec<String>> = None;
8282+ let subject_types: Option<Vec<String>> = None;
8383+ let subject_collections: Option<Vec<String>> = None;
89849085 let _ = conn
9186 .execute(
···9590 &cid_digest,
9691 &reasons,
9792 &subject_types,
9898- &rec.subject_collections,
9393+ &subject_collections,
9994 ],
10095 )
10196 .await?;
+2-2
consumer/src/db/operations/starter_pack.rs
···2424 crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?;
25252626 // Resolve list natural key by creating stub list if needed
2727- let (list_actor_id, list_rkey) = super::feed::ensure_list_natural_key(conn, &rec.list).await?;
2727+ let (list_actor_id, list_rkey) = super::feed::ensure_list_natural_key(conn, rec.list.uri.as_str()).await?;
28282929 // Resolve feed URIs to feedgen natural keys in Rust
3030 // Use parallel arrays for actor_ids and rkeys (easier than composite types for Postgres binding)
···3535 for feed_ref in feeds {
3636 // Parse feed URI to extract (did, rkey)
3737 let (feed_did, collection, feed_rkey) =
3838- parakeet_db::at_uri_util::parse_at_uri(&feed_ref.uri)
3838+ parakeet_db::at_uri_util::parse_at_uri(feed_ref.uri.as_str())
3939 .ok_or_else(|| eyre::eyre!("Invalid feed URI: {}", feed_ref.uri))?;
40404141 // Validate it's a feedgen URI
-1
consumer/src/lib.rs
···6060}
61616262// Re-export commonly used utility functions at the crate root
6363-pub use utils::build_user_agent;