lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

emit.rs (84053B)


      1 #[cfg(not(feature = "std"))]
      2 use alloc::format;
      3 #[cfg(not(feature = "std"))]
      4 use alloc::{
      5     collections::BTreeMap,
      6     string::{String, ToString},
      7     vec::Vec,
      8 };
      9 #[cfg(feature = "std")]
     10 use std::collections::BTreeMap;
     11 
     12 use radroots_events::farm::{
     13     RadrootsFarm, RadrootsFarmLocation, RadrootsFarmRef, RadrootsGcsLocation, RadrootsGeoJsonPoint,
     14     RadrootsGeoJsonPolygon,
     15 };
     16 use radroots_events::kinds::{KIND_FARM, KIND_LIST_SET_GENERIC, KIND_PLOT};
     17 use radroots_events::plot::RadrootsPlot;
     18 use radroots_events::profile::{
     19     RADROOTS_PROFILE_TYPE_TAG_KEY, RadrootsProfile, RadrootsProfileType,
     20     radroots_profile_type_from_tag_value, radroots_profile_type_tag_value,
     21 };
     22 use radroots_events_codec::farm::encode as farm_encode;
     23 use radroots_events_codec::farm::list_sets as farm_list_sets;
     24 use radroots_events_codec::list_set::encode as list_set_encode;
     25 use radroots_events_codec::plot::encode as plot_encode;
     26 use radroots_events_codec::wire::WireEventParts;
     27 use radroots_replica_db::{
     28     farm, farm_gcs_location, farm_member, farm_member_claim, farm_tag, gcs_location, nostr_profile,
     29     plot, plot_gcs_location, plot_tag,
     30 };
     31 use radroots_replica_db_schema::farm::{
     32     Farm, IFarmFieldsFilter, IFarmFindMany, IFarmFindOne, IFarmFindOneArgs,
     33 };
     34 use radroots_replica_db_schema::farm_gcs_location::{
     35     FarmGcsLocation, IFarmGcsLocationFieldsFilter, IFarmGcsLocationFindMany,
     36 };
     37 use radroots_replica_db_schema::farm_member::{
     38     FarmMember, IFarmMemberFieldsFilter, IFarmMemberFindMany,
     39 };
     40 use radroots_replica_db_schema::farm_member_claim::{
     41     FarmMemberClaim, IFarmMemberClaimFieldsFilter, IFarmMemberClaimFindMany,
     42 };
     43 use radroots_replica_db_schema::farm_tag::{IFarmTagFieldsFilter, IFarmTagFindMany};
     44 use radroots_replica_db_schema::gcs_location::{
     45     GcsLocation, GcsLocationQueryBindValues, IGcsLocationFindOne, IGcsLocationFindOneArgs,
     46 };
     47 use radroots_replica_db_schema::nostr_profile::{
     48     INostrProfileFindOne, INostrProfileFindOneArgs, NostrProfileQueryBindValues,
     49 };
     50 use radroots_replica_db_schema::plot::{IPlotFieldsFilter, IPlotFindMany, Plot};
     51 use radroots_replica_db_schema::plot_gcs_location::{
     52     IPlotGcsLocationFieldsFilter, IPlotGcsLocationFindMany, PlotGcsLocation,
     53 };
     54 use radroots_replica_db_schema::plot_tag::{IPlotTagFieldsFilter, IPlotTagFindMany};
     55 use radroots_sql_core::SqlExecutor;
     56 use serde_json::Value;
     57 
     58 use crate::canonical::canonical_json_string;
     59 use crate::error::RadrootsReplicaEventsError;
     60 use crate::geo::{geojson_point_from_lat_lng, geojson_polygon_circle_wgs84};
     61 use crate::types::{
     62     RADROOTS_REPLICA_TRANSFER_VERSION, RadrootsReplicaEventDraft, RadrootsReplicaFarmSelector,
     63     RadrootsReplicaSyncBundle, RadrootsReplicaSyncOptions, RadrootsReplicaSyncRequest,
     64 };
     65 
     66 const ROLE_PRIMARY: &str = "primary";
     67 const ROLE_MEMBER: &str = "member";
     68 const ROLE_OWNER: &str = "owner";
     69 const ROLE_WORKER: &str = "worker";
     70 
     71 #[cfg(test)]
     72 pub(crate) mod failpoints {
     73     use std::cell::Cell;
     74 
     75     thread_local! {
     76         static FORCE_LIST_SET_TO_WIRE_ERROR: Cell<bool> = const { Cell::new(false) };
     77         static FORCE_GCS_LOCATION_TO_EVENT_ERROR: Cell<bool> = const { Cell::new(false) };
     78     }
     79 
     80     pub(crate) fn set_list_set_to_wire_error() {
     81         FORCE_LIST_SET_TO_WIRE_ERROR.with(|flag| flag.set(true));
     82     }
     83 
     84     pub(crate) fn take_list_set_to_wire_error() -> bool {
     85         FORCE_LIST_SET_TO_WIRE_ERROR.with(|flag| {
     86             let value = flag.get();
     87             flag.set(false);
     88             value
     89         })
     90     }
     91 
     92     pub(crate) fn set_gcs_location_to_event_error() {
     93         FORCE_GCS_LOCATION_TO_EVENT_ERROR.with(|flag| flag.set(true));
     94     }
     95 
     96     pub(crate) fn take_gcs_location_to_event_error() -> bool {
     97         FORCE_GCS_LOCATION_TO_EVENT_ERROR.with(|flag| {
     98             let value = flag.get();
     99             flag.set(false);
    100             value
    101         })
    102     }
    103 }
    104 
    105 pub fn radroots_replica_sync_all(
    106     exec: &dyn SqlExecutor,
    107     request: &RadrootsReplicaSyncRequest,
    108 ) -> Result<RadrootsReplicaSyncBundle, RadrootsReplicaEventsError> {
    109     radroots_replica_sync_all_with_options(exec, &request.farm, request.options.as_ref())
    110 }
    111 
    112 pub fn radroots_replica_sync_all_with_options(
    113     exec: &dyn SqlExecutor,
    114     farm_selector: &RadrootsReplicaFarmSelector,
    115     options: Option<&RadrootsReplicaSyncOptions>,
    116 ) -> Result<RadrootsReplicaSyncBundle, RadrootsReplicaEventsError> {
    117     let farm = resolve_farm(exec, farm_selector)?;
    118     let include_profiles = options.and_then(|opt| opt.include_profiles).unwrap_or(true);
    119     let include_list_sets = options
    120         .and_then(|opt| opt.include_list_sets)
    121         .unwrap_or(true);
    122     let include_claims = options
    123         .and_then(|opt| opt.include_membership_claims)
    124         .unwrap_or(true);
    125 
    126     let mut events = Vec::new();
    127 
    128     if include_profiles {
    129         let profiles = radroots_replica_profile_events(exec, &farm)?;
    130         events.extend(profiles);
    131     }
    132 
    133     events.push(radroots_replica_farm_event(exec, &farm)?);
    134 
    135     let plots = radroots_replica_plot_events(exec, &farm)?;
    136     events.extend(plots);
    137 
    138     if include_list_sets {
    139         let list_sets = radroots_replica_list_set_events(exec, &farm)?;
    140         events.extend(list_sets);
    141     }
    142 
    143     if include_claims {
    144         let claims = radroots_replica_membership_claim_events(exec, &farm.pubkey)?;
    145         events.extend(claims);
    146     }
    147 
    148     Ok(RadrootsReplicaSyncBundle {
    149         version: RADROOTS_REPLICA_TRANSFER_VERSION,
    150         events,
    151     })
    152 }
    153 
    154 pub fn radroots_replica_profile_events(
    155     exec: &dyn SqlExecutor,
    156     farm: &Farm,
    157 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> {
    158     let mut pubkeys = collect_profile_pubkeys(exec, farm)?;
    159     pubkeys.sort();
    160     pubkeys.dedup();
    161 
    162     let mut events = Vec::new();
    163     for pubkey in pubkeys {
    164         if let Some(profile) = load_profile(exec, &pubkey)? {
    165             events.push(profile_event(&pubkey, profile)?);
    166         }
    167     }
    168     Ok(events)
    169 }
    170 
    171 pub fn radroots_replica_farm_event(
    172     exec: &dyn SqlExecutor,
    173     farm: &Farm,
    174 ) -> Result<RadrootsReplicaEventDraft, RadrootsReplicaEventsError> {
    175     let tags = collect_farm_tags(exec, &farm.id)?;
    176     let location = load_farm_location(exec, farm)?;
    177     let farm_event = RadrootsFarm {
    178         d_tag: farm.d_tag.clone(),
    179         name: farm.name.clone(),
    180         about: farm.about.clone(),
    181         website: farm.website.clone(),
    182         picture: farm.picture.clone(),
    183         banner: farm.banner.clone(),
    184         location,
    185         tags: if tags.is_empty() { None } else { Some(tags) },
    186     };
    187     let tags = farm_encode::farm_build_tags(&farm_event)?;
    188     let content = canonical_json_string(&farm_event)?;
    189     let parts = WireEventParts {
    190         kind: KIND_FARM,
    191         content,
    192         tags,
    193     };
    194     Ok(parts_to_draft(&farm.pubkey, parts))
    195 }
    196 
    197 pub fn radroots_replica_plot_events(
    198     exec: &dyn SqlExecutor,
    199     farm: &Farm,
    200 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> {
    201     let plots = load_plots(exec, &farm.id)?;
    202     let mut events = Vec::new();
    203     for plot_row in plots {
    204         let tags = collect_plot_tags(exec, &plot_row.id)?;
    205         let location = load_plot_location(exec, &plot_row)?;
    206         let plot_event = RadrootsPlot {
    207             d_tag: plot_row.d_tag.clone(),
    208             farm: RadrootsFarmRef {
    209                 pubkey: farm.pubkey.clone(),
    210                 d_tag: farm.d_tag.clone(),
    211             },
    212             name: plot_row.name.clone(),
    213             about: plot_row.about.clone(),
    214             location,
    215             tags: if tags.is_empty() { None } else { Some(tags) },
    216         };
    217         let tags = plot_encode::plot_build_tags(&plot_event)?;
    218         let content = canonical_json_string(&plot_event)?;
    219         let parts = WireEventParts {
    220             kind: KIND_PLOT,
    221             content,
    222             tags,
    223         };
    224         events.push(parts_to_draft(&farm.pubkey, parts));
    225     }
    226     Ok(events)
    227 }
    228 
    229 pub fn radroots_replica_list_set_events(
    230     exec: &dyn SqlExecutor,
    231     farm: &Farm,
    232 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> {
    233     let members = load_farm_members(exec, &farm.id)?;
    234     let plots = load_plots(exec, &farm.id)?;
    235 
    236     let members_list =
    237         farm_list_sets::farm_members_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_MEMBER))?;
    238     let owners_list =
    239         farm_list_sets::farm_owners_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_OWNER))?;
    240     let workers_list =
    241         farm_list_sets::farm_workers_list_set(&farm.d_tag, role_pubkeys(&members, ROLE_WORKER))?;
    242 
    243     let plot_ids = sorted_plot_ids(&plots);
    244     let plots_list = farm_list_sets::farm_plots_list_set(&farm.d_tag, &farm.pubkey, plot_ids)?;
    245 
    246     let list_sets = [members_list, owners_list, workers_list, plots_list];
    247     let mut events = Vec::new();
    248     for list_set in list_sets {
    249         let parts = list_set_to_wire_parts(&list_set)?;
    250         events.push(parts_to_draft(&farm.pubkey, parts));
    251     }
    252     Ok(events)
    253 }
    254 
    255 pub fn radroots_replica_membership_claim_events(
    256     exec: &dyn SqlExecutor,
    257     farm_pubkey: &str,
    258 ) -> Result<Vec<RadrootsReplicaEventDraft>, RadrootsReplicaEventsError> {
    259     let claims = load_member_claims(exec, farm_pubkey)?;
    260     let mut by_member: BTreeMap<String, Vec<String>> = BTreeMap::new();
    261     for claim in claims {
    262         by_member
    263             .entry(claim.member_pubkey.clone())
    264             .or_default()
    265             .push(claim.farm_pubkey.clone());
    266     }
    267 
    268     let mut events = Vec::new();
    269     for (member_pubkey, _) in by_member.iter() {
    270         let all_claims = load_member_claims_for_member(exec, member_pubkey)?;
    271         let mut farm_pubkeys = all_claims
    272             .into_iter()
    273             .map(|claim| claim.farm_pubkey)
    274             .collect::<Vec<String>>();
    275         farm_pubkeys.sort();
    276         farm_pubkeys.dedup();
    277         let list_set = farm_list_sets::member_of_farms_list_set(farm_pubkeys)?;
    278         let parts = list_set_to_wire_parts(&list_set)?;
    279         events.push(parts_to_draft(member_pubkey, parts));
    280     }
    281 
    282     Ok(events)
    283 }
    284 
    285 fn resolve_farm(
    286     exec: &dyn SqlExecutor,
    287     selector: &RadrootsReplicaFarmSelector,
    288 ) -> Result<Farm, RadrootsReplicaEventsError> {
    289     if let Some(id) = selector.id.as_ref().filter(|v| !v.trim().is_empty()) {
    290         let result_query = farm::find_one(
    291             exec,
    292             &IFarmFindOne::On(IFarmFindOneArgs {
    293                 on: radroots_replica_db_schema::farm::FarmQueryBindValues::Id { id: id.clone() },
    294             }),
    295         );
    296         let result = result_query?;
    297         return result.result.ok_or_else(|| {
    298             RadrootsReplicaEventsError::InvalidSelector(format!("farm not found: {id}"))
    299         });
    300     }
    301 
    302     let d_tag = selector
    303         .d_tag
    304         .as_ref()
    305         .map(|v| v.trim())
    306         .filter(|v| !v.is_empty());
    307     let pubkey = selector
    308         .pubkey
    309         .as_ref()
    310         .map(|v| v.trim())
    311         .filter(|v| !v.is_empty());
    312 
    313     let (d_tag, pubkey) = match (d_tag, pubkey) {
    314         (Some(d_tag), Some(pubkey)) => (d_tag, pubkey),
    315         _ => {
    316             return Err(RadrootsReplicaEventsError::InvalidSelector(
    317                 "farm selector requires id or (d_tag + pubkey)".to_string(),
    318             ));
    319         }
    320     };
    321 
    322     let filter = IFarmFieldsFilter {
    323         id: None,
    324         created_at: None,
    325         updated_at: None,
    326         d_tag: Some(d_tag.to_string()),
    327         pubkey: Some(pubkey.to_string()),
    328         name: None,
    329         about: None,
    330         website: None,
    331         picture: None,
    332         banner: None,
    333         location_primary: None,
    334         location_city: None,
    335         location_region: None,
    336         location_country: None,
    337     };
    338     let result_query = farm::find_many(
    339         exec,
    340         &IFarmFindMany {
    341             filter: Some(filter),
    342         },
    343     );
    344     let result = result_query?;
    345     if result.results.len() == 1 {
    346         return Ok(result.results.into_iter().next().expect("farm result"));
    347     }
    348     Err(RadrootsReplicaEventsError::InvalidSelector(
    349         "farm selector did not resolve to a single farm".to_string(),
    350     ))
    351 }
    352 
    353 fn collect_farm_tags(
    354     exec: &dyn SqlExecutor,
    355     farm_id: &str,
    356 ) -> Result<Vec<String>, RadrootsReplicaEventsError> {
    357     let filter = IFarmTagFieldsFilter {
    358         id: None,
    359         created_at: None,
    360         updated_at: None,
    361         farm_id: Some(farm_id.to_string()),
    362         tag: None,
    363     };
    364     let result_query = farm_tag::find_many(
    365         exec,
    366         &IFarmTagFindMany {
    367             filter: Some(filter),
    368         },
    369     );
    370     let result = result_query?;
    371     let mut tags = result
    372         .results
    373         .into_iter()
    374         .map(|row| row.tag)
    375         .collect::<Vec<_>>();
    376     tags.sort();
    377     tags.dedup();
    378     Ok(tags)
    379 }
    380 
    381 fn collect_plot_tags(
    382     exec: &dyn SqlExecutor,
    383     plot_id: &str,
    384 ) -> Result<Vec<String>, RadrootsReplicaEventsError> {
    385     let filter = IPlotTagFieldsFilter {
    386         id: None,
    387         created_at: None,
    388         updated_at: None,
    389         plot_id: Some(plot_id.to_string()),
    390         tag: None,
    391     };
    392     let result_query = plot_tag::find_many(
    393         exec,
    394         &IPlotTagFindMany {
    395             filter: Some(filter),
    396         },
    397     );
    398     let result = result_query?;
    399     let mut tags = result
    400         .results
    401         .into_iter()
    402         .map(|row| row.tag)
    403         .collect::<Vec<_>>();
    404     tags.sort();
    405     tags.dedup();
    406     Ok(tags)
    407 }
    408 
    409 fn load_farm_members(
    410     exec: &dyn SqlExecutor,
    411     farm_id: &str,
    412 ) -> Result<Vec<FarmMember>, RadrootsReplicaEventsError> {
    413     let filter = IFarmMemberFieldsFilter {
    414         id: None,
    415         created_at: None,
    416         updated_at: None,
    417         farm_id: Some(farm_id.to_string()),
    418         member_pubkey: None,
    419         role: None,
    420     };
    421     let result_query = farm_member::find_many(
    422         exec,
    423         &IFarmMemberFindMany {
    424             filter: Some(filter),
    425         },
    426     );
    427     let result = result_query?;
    428     Ok(result.results)
    429 }
    430 
    431 fn role_pubkeys(members: &[FarmMember], role: &str) -> Vec<String> {
    432     let mut values = members
    433         .iter()
    434         .filter(|member| member.role == role)
    435         .map(|member| member.member_pubkey.clone())
    436         .collect::<Vec<_>>();
    437     values.sort();
    438     values.dedup();
    439     values
    440 }
    441 
    442 fn sorted_plot_ids(plots: &[Plot]) -> Vec<String> {
    443     let mut ids = plots
    444         .iter()
    445         .map(|plot| plot.d_tag.clone())
    446         .collect::<Vec<_>>();
    447     ids.sort();
    448     ids.dedup();
    449     ids
    450 }
    451 
    452 fn load_plots(
    453     exec: &dyn SqlExecutor,
    454     farm_id: &str,
    455 ) -> Result<Vec<Plot>, RadrootsReplicaEventsError> {
    456     let filter = IPlotFieldsFilter {
    457         id: None,
    458         created_at: None,
    459         updated_at: None,
    460         d_tag: None,
    461         farm_id: Some(farm_id.to_string()),
    462         name: None,
    463         about: None,
    464         location_primary: None,
    465         location_city: None,
    466         location_region: None,
    467         location_country: None,
    468     };
    469     let result_query = plot::find_many(
    470         exec,
    471         &IPlotFindMany {
    472             filter: Some(filter),
    473         },
    474     );
    475     let result = result_query?;
    476     let mut plots = result.results;
    477     plots.sort_by(|a, b| a.d_tag.cmp(&b.d_tag));
    478     Ok(plots)
    479 }
    480 
    481 fn load_farm_location(
    482     exec: &dyn SqlExecutor,
    483     farm: &Farm,
    484 ) -> Result<Option<RadrootsFarmLocation>, RadrootsReplicaEventsError> {
    485     let gcs = load_gcs_location_for_farm(exec, &farm.id)?;
    486     let has_strings = [
    487         farm.location_primary.as_deref(),
    488         farm.location_city.as_deref(),
    489         farm.location_region.as_deref(),
    490         farm.location_country.as_deref(),
    491     ]
    492     .into_iter()
    493     .flatten()
    494     .any(|value| !value.trim().is_empty());
    495     if !has_strings && gcs.is_none() {
    496         return Ok(None);
    497     }
    498     Ok(Some(RadrootsFarmLocation {
    499         primary: farm.location_primary.clone(),
    500         city: farm.location_city.clone(),
    501         region: farm.location_region.clone(),
    502         country: farm.location_country.clone(),
    503         gcs,
    504     }))
    505 }
    506 
    507 fn load_plot_location(
    508     exec: &dyn SqlExecutor,
    509     plot: &Plot,
    510 ) -> Result<Option<radroots_events::plot::RadrootsPlotLocation>, RadrootsReplicaEventsError> {
    511     let location = load_gcs_location_for_plot(exec, &plot.id)?;
    512     Ok(
    513         location.map(|gcs| radroots_events::plot::RadrootsPlotLocation {
    514             primary: plot.location_primary.clone(),
    515             city: plot.location_city.clone(),
    516             region: plot.location_region.clone(),
    517             country: plot.location_country.clone(),
    518             gcs,
    519         }),
    520     )
    521 }
    522 
    523 fn load_gcs_location_for_farm(
    524     exec: &dyn SqlExecutor,
    525     farm_id: &str,
    526 ) -> Result<Option<RadrootsGcsLocation>, RadrootsReplicaEventsError> {
    527     let primary = load_relation_by_role(exec, farm_id, ROLE_PRIMARY, RelationType::Farm)?;
    528     match primary {
    529         Some(gcs) => Ok(Some(gcs)),
    530         None => load_relation_by_role(exec, farm_id, "", RelationType::Farm),
    531     }
    532 }
    533 
    534 fn load_gcs_location_for_plot(
    535     exec: &dyn SqlExecutor,
    536     plot_id: &str,
    537 ) -> Result<Option<RadrootsGcsLocation>, RadrootsReplicaEventsError> {
    538     let primary = load_relation_by_role(exec, plot_id, ROLE_PRIMARY, RelationType::Plot)?;
    539     match primary {
    540         Some(gcs) => Ok(Some(gcs)),
    541         None => load_relation_by_role(exec, plot_id, "", RelationType::Plot),
    542     }
    543 }
    544 
    545 enum RelationType {
    546     Farm,
    547     Plot,
    548 }
    549 
    550 fn load_relation_by_role(
    551     exec: &dyn SqlExecutor,
    552     id: &str,
    553     role: &str,
    554     relation: RelationType,
    555 ) -> Result<Option<RadrootsGcsLocation>, RadrootsReplicaEventsError> {
    556     let mut rels = match relation {
    557         RelationType::Farm => {
    558             let filter = IFarmGcsLocationFieldsFilter {
    559                 id: None,
    560                 created_at: None,
    561                 updated_at: None,
    562                 farm_id: Some(id.to_string()),
    563                 gcs_location_id: None,
    564                 role: if role.is_empty() {
    565                     None
    566                 } else {
    567                     Some(role.to_string())
    568                 },
    569             };
    570             let result_query = farm_gcs_location::find_many(
    571                 exec,
    572                 &IFarmGcsLocationFindMany {
    573                     filter: Some(filter),
    574                 },
    575             );
    576             let result = result_query?;
    577             result
    578                 .results
    579                 .into_iter()
    580                 .map(RelationRow::Farm)
    581                 .collect::<Vec<_>>()
    582         }
    583         RelationType::Plot => {
    584             let filter = IPlotGcsLocationFieldsFilter {
    585                 id: None,
    586                 created_at: None,
    587                 updated_at: None,
    588                 plot_id: Some(id.to_string()),
    589                 gcs_location_id: None,
    590                 role: if role.is_empty() {
    591                     None
    592                 } else {
    593                     Some(role.to_string())
    594                 },
    595             };
    596             let result_query = plot_gcs_location::find_many(
    597                 exec,
    598                 &IPlotGcsLocationFindMany {
    599                     filter: Some(filter),
    600                 },
    601             );
    602             let result = result_query?;
    603             result
    604                 .results
    605                 .into_iter()
    606                 .map(RelationRow::Plot)
    607                 .collect::<Vec<_>>()
    608         }
    609     };
    610 
    611     if rels.is_empty() {
    612         return Ok(None);
    613     }
    614 
    615     rels.sort_by(compare_relation_rows);
    616     let gcs_id = rels[0].gcs_location_id().to_string();
    617     let gcs_result = gcs_location::find_one(
    618         exec,
    619         &IGcsLocationFindOne::On(IGcsLocationFindOneArgs {
    620             on: GcsLocationQueryBindValues::Id { id: gcs_id },
    621         }),
    622     );
    623     let gcs = match gcs_result?.result {
    624         Some(gcs) => gcs,
    625         None => {
    626             return Err(RadrootsReplicaEventsError::InvalidData(
    627                 "gcs_location not found".to_string(),
    628             ));
    629         }
    630     };
    631     Ok(Some(gcs_location_to_event(&gcs)?))
    632 }
    633 
    634 fn compare_relation_rows(a: &RelationRow, b: &RelationRow) -> core::cmp::Ordering {
    635     let rank = location_role_rank(a.role()).cmp(&location_role_rank(b.role()));
    636     rank.then_with(|| a.gcs_location_id().cmp(b.gcs_location_id()))
    637 }
    638 
    639 fn list_set_to_wire_parts(
    640     list_set: &radroots_events::list_set::RadrootsListSet,
    641 ) -> Result<WireEventParts, RadrootsReplicaEventsError> {
    642     #[cfg(test)]
    643     if failpoints::take_list_set_to_wire_error() {
    644         return Err(RadrootsReplicaEventsError::InvalidData(
    645             "list_set_to_wire".to_string(),
    646         ));
    647     }
    648     Ok(list_set_encode::to_wire_parts_with_kind(
    649         list_set,
    650         KIND_LIST_SET_GENERIC,
    651     )?)
    652 }
    653 
    654 enum RelationRow {
    655     Farm(FarmGcsLocation),
    656     Plot(PlotGcsLocation),
    657 }
    658 
    659 impl RelationRow {
    660     fn gcs_location_id(&self) -> &str {
    661         match self {
    662             Self::Farm(row) => row.gcs_location_id.as_str(),
    663             Self::Plot(row) => row.gcs_location_id.as_str(),
    664         }
    665     }
    666 
    667     fn role(&self) -> &str {
    668         match self {
    669             Self::Farm(row) => row.role.as_str(),
    670             Self::Plot(row) => row.role.as_str(),
    671         }
    672     }
    673 }
    674 
    675 fn location_role_rank(role: &str) -> u8 {
    676     u8::from(role != ROLE_PRIMARY)
    677 }
    678 
    679 fn gcs_location_to_event(
    680     gcs: &GcsLocation,
    681 ) -> Result<RadrootsGcsLocation, RadrootsReplicaEventsError> {
    682     #[cfg(test)]
    683     if failpoints::take_gcs_location_to_event_error() {
    684         return Err(RadrootsReplicaEventsError::InvalidData(
    685             "gcs_location_to_event".to_string(),
    686         ));
    687     }
    688     let point = parse_point(&gcs.point, gcs.lat, gcs.lng);
    689     let polygon = parse_polygon(&gcs.polygon, gcs.lat, gcs.lng);
    690     Ok(RadrootsGcsLocation {
    691         lat: gcs.lat,
    692         lng: gcs.lng,
    693         geohash: gcs.geohash.clone(),
    694         point,
    695         polygon,
    696         accuracy: gcs.accuracy,
    697         altitude: gcs.altitude,
    698         tag_0: gcs.tag_0.clone(),
    699         label: gcs.label.clone(),
    700         area: gcs.area,
    701         elevation: gcs.elevation,
    702         soil: gcs.soil.clone(),
    703         climate: gcs.climate.clone(),
    704         gc_id: gcs.gc_id.clone(),
    705         gc_name: gcs.gc_name.clone(),
    706         gc_admin1_id: gcs.gc_admin1_id.clone(),
    707         gc_admin1_name: gcs.gc_admin1_name.clone(),
    708         gc_country_id: gcs.gc_country_id.clone(),
    709         gc_country_name: gcs.gc_country_name.clone(),
    710     })
    711 }
    712 
    713 fn parse_point(value: &str, lat: f64, lng: f64) -> RadrootsGeoJsonPoint {
    714     if !value.trim().is_empty()
    715         && let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPoint>(value)
    716     {
    717         return parsed;
    718     }
    719     geojson_point_from_lat_lng(lat, lng)
    720 }
    721 
    722 fn parse_polygon(value: &str, lat: f64, lng: f64) -> RadrootsGeoJsonPolygon {
    723     if !value.trim().is_empty()
    724         && let Ok(parsed) = serde_json::from_str::<RadrootsGeoJsonPolygon>(value)
    725         && !parsed.coordinates.is_empty()
    726         && !parsed.coordinates[0].is_empty()
    727     {
    728         return parsed;
    729     }
    730     geojson_polygon_circle_wgs84(lat, lng, 100.0, 64)
    731 }
    732 
    733 fn load_profile(
    734     exec: &dyn SqlExecutor,
    735     pubkey: &str,
    736 ) -> Result<
    737     Option<radroots_replica_db_schema::nostr_profile::NostrProfile>,
    738     RadrootsReplicaEventsError,
    739 > {
    740     let result_query = nostr_profile::find_one(
    741         exec,
    742         &INostrProfileFindOne::On(INostrProfileFindOneArgs {
    743             on: NostrProfileQueryBindValues::PublicKey {
    744                 public_key: pubkey.to_string(),
    745             },
    746         }),
    747     );
    748     let result = result_query?;
    749     Ok(result.result)
    750 }
    751 
    752 fn profile_event(
    753     pubkey: &str,
    754     profile: radroots_replica_db_schema::nostr_profile::NostrProfile,
    755 ) -> Result<RadrootsReplicaEventDraft, RadrootsReplicaEventsError> {
    756     let profile_type = match profile.profile_type.as_str() {
    757         "individual" | "farmer" => Some(RadrootsProfileType::Individual),
    758         "farm" => Some(RadrootsProfileType::Farm),
    759         "coop" => Some(RadrootsProfileType::Coop),
    760         "any" => Some(RadrootsProfileType::Any),
    761         other => radroots_profile_type_from_tag_value(other),
    762     };
    763     let profile_event = RadrootsProfile {
    764         name: profile.name,
    765         display_name: profile.display_name,
    766         nip05: profile.nip05,
    767         about: profile.about,
    768         website: profile.website,
    769         picture: profile.picture,
    770         banner: profile.banner,
    771         lud06: profile.lud06,
    772         lud16: profile.lud16,
    773         bot: None,
    774     };
    775     let content = serialize_profile_content(&profile_event)?;
    776     let mut tags = Vec::new();
    777     if let Some(profile_type) = profile_type {
    778         let tag = vec![
    779             RADROOTS_PROFILE_TYPE_TAG_KEY.to_string(),
    780             radroots_profile_type_tag_value(profile_type).to_string(),
    781         ];
    782         tags.push(tag);
    783     }
    784     Ok(RadrootsReplicaEventDraft {
    785         kind: radroots_events::kinds::KIND_PROFILE,
    786         author: pubkey.to_string(),
    787         content,
    788         tags,
    789     })
    790 }
    791 
    792 fn serialize_profile_content(
    793     profile: &RadrootsProfile,
    794 ) -> Result<String, RadrootsReplicaEventsError> {
    795     let mut obj = serde_json::Map::new();
    796     obj.insert("name".to_string(), Value::from(profile.name.clone()));
    797     if let Some(value) = profile.display_name.as_ref() {
    798         obj.insert("display_name".to_string(), Value::from(value.clone()));
    799     }
    800     if let Some(value) = profile.nip05.as_ref() {
    801         obj.insert("nip05".to_string(), Value::from(value.clone()));
    802     }
    803     if let Some(value) = profile.about.as_ref() {
    804         obj.insert("about".to_string(), Value::from(value.clone()));
    805     }
    806     if let Some(value) = profile.website.as_ref() {
    807         obj.insert("website".to_string(), Value::from(value.clone()));
    808     }
    809     if let Some(value) = profile.picture.as_ref() {
    810         obj.insert("picture".to_string(), Value::from(value.clone()));
    811     }
    812     if let Some(value) = profile.banner.as_ref() {
    813         obj.insert("banner".to_string(), Value::from(value.clone()));
    814     }
    815     if let Some(value) = profile.lud06.as_ref() {
    816         obj.insert("lud06".to_string(), Value::from(value.clone()));
    817     }
    818     if let Some(value) = profile.lud16.as_ref() {
    819         obj.insert("lud16".to_string(), Value::from(value.clone()));
    820     }
    821     canonical_json_string(&Value::Object(obj))
    822 }
    823 
    824 fn collect_member_pubkeys(
    825     exec: &dyn SqlExecutor,
    826     farm_id: &str,
    827 ) -> Result<Vec<String>, RadrootsReplicaEventsError> {
    828     let members = load_farm_members(exec, farm_id)?;
    829     let mut pubkeys = members
    830         .into_iter()
    831         .map(|row| row.member_pubkey)
    832         .collect::<Vec<_>>();
    833     pubkeys.sort();
    834     pubkeys.dedup();
    835     Ok(pubkeys)
    836 }
    837 
    838 fn collect_profile_pubkeys(
    839     exec: &dyn SqlExecutor,
    840     farm: &Farm,
    841 ) -> Result<Vec<String>, RadrootsReplicaEventsError> {
    842     let mut pubkeys = collect_member_pubkeys(exec, &farm.id)?;
    843     let claims = load_member_claims(exec, &farm.pubkey)?;
    844     pubkeys.extend(claims.into_iter().map(|claim| claim.member_pubkey));
    845     pubkeys.push(farm.pubkey.clone());
    846     Ok(pubkeys)
    847 }
    848 
    849 fn load_member_claims(
    850     exec: &dyn SqlExecutor,
    851     farm_pubkey: &str,
    852 ) -> Result<Vec<FarmMemberClaim>, RadrootsReplicaEventsError> {
    853     let filter = IFarmMemberClaimFieldsFilter {
    854         id: None,
    855         created_at: None,
    856         updated_at: None,
    857         member_pubkey: None,
    858         farm_pubkey: Some(farm_pubkey.to_string()),
    859     };
    860     let result_query = farm_member_claim::find_many(
    861         exec,
    862         &IFarmMemberClaimFindMany {
    863             filter: Some(filter),
    864         },
    865     );
    866     let result = result_query?;
    867     Ok(result.results)
    868 }
    869 
    870 fn load_member_claims_for_member(
    871     exec: &dyn SqlExecutor,
    872     member_pubkey: &str,
    873 ) -> Result<Vec<FarmMemberClaim>, RadrootsReplicaEventsError> {
    874     let filter = IFarmMemberClaimFieldsFilter {
    875         id: None,
    876         created_at: None,
    877         updated_at: None,
    878         member_pubkey: Some(member_pubkey.to_string()),
    879         farm_pubkey: None,
    880     };
    881     let result_query = farm_member_claim::find_many(
    882         exec,
    883         &IFarmMemberClaimFindMany {
    884             filter: Some(filter),
    885         },
    886     );
    887     let result = result_query?;
    888     Ok(result.results)
    889 }
    890 
    891 fn parts_to_draft(author: &str, parts: WireEventParts) -> RadrootsReplicaEventDraft {
    892     RadrootsReplicaEventDraft {
    893         kind: parts.kind,
    894         author: author.to_string(),
    895         content: parts.content,
    896         tags: parts.tags,
    897     }
    898 }
    899 
    900 #[cfg(test)]
    901 mod tests {
    902     use super::*;
    903     use radroots_replica_db::{
    904         farm, farm_gcs_location, farm_member, farm_member_claim, farm_tag, gcs_location,
    905         migrations, nostr_profile, plot, plot_gcs_location, plot_tag,
    906     };
    907     use radroots_replica_db_schema::farm::{IFarmFields, IFarmFieldsFilter, IFarmFindMany};
    908     use radroots_replica_db_schema::farm_gcs_location::{
    909         IFarmGcsLocationFields, IFarmGcsLocationFindMany,
    910     };
    911     use radroots_replica_db_schema::farm_member::IFarmMemberFields;
    912     use radroots_replica_db_schema::farm_member_claim::IFarmMemberClaimFields;
    913     use radroots_replica_db_schema::farm_tag::IFarmTagFields;
    914     use radroots_replica_db_schema::gcs_location::IGcsLocationFields;
    915     use radroots_replica_db_schema::nostr_profile::INostrProfileFields;
    916     use radroots_replica_db_schema::plot::{IPlotFields, IPlotFindMany};
    917     use radroots_replica_db_schema::plot_gcs_location::{
    918         IPlotGcsLocationFields, IPlotGcsLocationFindMany,
    919     };
    920     use radroots_replica_db_schema::plot_tag::IPlotTagFields;
    921     use radroots_sql_core::{ExecOutcome, SqlError, SqlExecutor, SqliteExecutor};
    922 
    923     struct ErrorExecutor;
    924 
    925     impl SqlExecutor for ErrorExecutor {
    926         fn exec(&self, _sql: &str, _params_json: &str) -> Result<ExecOutcome, SqlError> {
    927             Err(SqlError::Internal)
    928         }
    929 
    930         fn query_raw(&self, _sql: &str, _params_json: &str) -> Result<String, SqlError> {
    931             Err(SqlError::Internal)
    932         }
    933 
    934         fn begin(&self) -> Result<(), SqlError> {
    935             Ok(())
    936         }
    937 
    938         fn commit(&self) -> Result<(), SqlError> {
    939             Ok(())
    940         }
    941 
    942         fn rollback(&self) -> Result<(), SqlError> {
    943             Ok(())
    944         }
    945     }
    946 
    947     struct QueryFailExecutor<'a> {
    948         inner: &'a SqliteExecutor,
    949         needle: &'static str,
    950         err: SqlError,
    951     }
    952 
    953     impl SqlExecutor for QueryFailExecutor<'_> {
    954         fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> {
    955             self.inner.exec(sql, params_json)
    956         }
    957 
    958         fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> {
    959             if sql.to_ascii_lowercase().contains(self.needle) {
    960                 return Err(self.err.clone());
    961             }
    962             self.inner.query_raw(sql, params_json)
    963         }
    964 
    965         fn begin(&self) -> Result<(), SqlError> {
    966             self.inner.begin()
    967         }
    968 
    969         fn commit(&self) -> Result<(), SqlError> {
    970             self.inner.commit()
    971         }
    972 
    973         fn rollback(&self) -> Result<(), SqlError> {
    974             self.inner.rollback()
    975         }
    976     }
    977 
    978     struct DuplicateFarmSelectorExecutor<'a> {
    979         inner: &'a SqliteExecutor,
    980         duplicated_rows_json: String,
    981     }
    982 
    983     impl SqlExecutor for DuplicateFarmSelectorExecutor<'_> {
    984         fn exec(&self, sql: &str, params_json: &str) -> Result<ExecOutcome, SqlError> {
    985             self.inner.exec(sql, params_json)
    986         }
    987 
    988         fn query_raw(&self, sql: &str, params_json: &str) -> Result<String, SqlError> {
    989             let _ = (sql, params_json);
    990             Ok(self.duplicated_rows_json.clone())
    991         }
    992 
    993         fn begin(&self) -> Result<(), SqlError> {
    994             self.inner.begin()
    995         }
    996 
    997         fn commit(&self) -> Result<(), SqlError> {
    998             self.inner.commit()
    999         }
   1000 
   1001         fn rollback(&self) -> Result<(), SqlError> {
   1002             self.inner.rollback()
   1003         }
   1004     }
   1005 
   1006     fn seed(exec: &SqliteExecutor) -> (Farm, Plot, Plot) {
   1007         migrations::run_all_up(exec).expect("migrations");
   1008         let farm = farm::create(
   1009             exec,
   1010             &IFarmFields {
   1011                 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
   1012                 pubkey: "f".repeat(64),
   1013                 name: "farm".to_string(),
   1014                 about: Some("about".to_string()),
   1015                 website: Some("https://farm.example.com".to_string()),
   1016                 picture: Some("https://farm.example.com/p.png".to_string()),
   1017                 banner: Some("https://farm.example.com/b.png".to_string()),
   1018                 location_primary: Some("primary".to_string()),
   1019                 location_city: Some("city".to_string()),
   1020                 location_region: Some("region".to_string()),
   1021                 location_country: Some("country".to_string()),
   1022             },
   1023         )
   1024         .expect("farm")
   1025         .result;
   1026 
   1027         let gcs_primary = gcs_location::create(
   1028             exec,
   1029             &IGcsLocationFields {
   1030                 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
   1031                 lat: 10.0,
   1032                 lng: 20.0,
   1033                 geohash: "s0".to_string(),
   1034                 point: "{\"type\":\"Point\",\"coordinates\":[20.0,10.0]}".to_string(),
   1035                 polygon:
   1036                     "{\"type\":\"Polygon\",\"coordinates\":[[[20.0,10.0],[20.1,10.1],[19.9,10.1],[20.0,10.0]]]}".to_string(),
   1037                 accuracy: None,
   1038                 altitude: None,
   1039                 tag_0: None,
   1040                 label: None,
   1041                 area: None,
   1042                 elevation: None,
   1043                 soil: None,
   1044                 climate: None,
   1045                 gc_id: None,
   1046                 gc_name: None,
   1047                 gc_admin1_id: None,
   1048                 gc_admin1_name: None,
   1049                 gc_country_id: None,
   1050                 gc_country_name: None,
   1051             },
   1052         )
   1053         .expect("gcs primary")
   1054         .result;
   1055         let gcs_secondary = gcs_location::create(
   1056             exec,
   1057             &IGcsLocationFields {
   1058                 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(),
   1059                 lat: 11.0,
   1060                 lng: 21.0,
   1061                 geohash: "s1".to_string(),
   1062                 point: "{".to_string(),
   1063                 polygon: "{\"type\":\"Polygon\",\"coordinates\":[[]]}".to_string(),
   1064                 accuracy: None,
   1065                 altitude: None,
   1066                 tag_0: None,
   1067                 label: None,
   1068                 area: None,
   1069                 elevation: None,
   1070                 soil: None,
   1071                 climate: None,
   1072                 gc_id: None,
   1073                 gc_name: None,
   1074                 gc_admin1_id: None,
   1075                 gc_admin1_name: None,
   1076                 gc_country_id: None,
   1077                 gc_country_name: None,
   1078             },
   1079         )
   1080         .expect("gcs secondary")
   1081         .result;
   1082 
   1083         let _ = farm_gcs_location::create(
   1084             exec,
   1085             &IFarmGcsLocationFields {
   1086                 farm_id: farm.id.clone(),
   1087                 gcs_location_id: gcs_secondary.id.clone(),
   1088                 role: "".to_string(),
   1089             },
   1090         )
   1091         .expect("farm gcs secondary");
   1092         let _ = farm_gcs_location::create(
   1093             exec,
   1094             &IFarmGcsLocationFields {
   1095                 farm_id: farm.id.clone(),
   1096                 gcs_location_id: gcs_primary.id.clone(),
   1097                 role: "primary".to_string(),
   1098             },
   1099         )
   1100         .expect("farm gcs primary");
   1101 
   1102         let plot_primary = plot::create(
   1103             exec,
   1104             &IPlotFields {
   1105                 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
   1106                 farm_id: farm.id.clone(),
   1107                 name: "plot-primary".to_string(),
   1108                 about: Some("plot about".to_string()),
   1109                 location_primary: Some("plot primary".to_string()),
   1110                 location_city: Some("plot city".to_string()),
   1111                 location_region: Some("plot region".to_string()),
   1112                 location_country: Some("plot country".to_string()),
   1113             },
   1114         )
   1115         .expect("plot primary")
   1116         .result;
   1117         let plot_secondary = plot::create(
   1118             exec,
   1119             &IPlotFields {
   1120                 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(),
   1121                 farm_id: farm.id.clone(),
   1122                 name: "plot-secondary".to_string(),
   1123                 about: Some("plot secondary about".to_string()),
   1124                 location_primary: Some("plot secondary primary".to_string()),
   1125                 location_city: None,
   1126                 location_region: None,
   1127                 location_country: None,
   1128             },
   1129         )
   1130         .expect("plot secondary")
   1131         .result;
   1132 
   1133         let _ = plot_gcs_location::create(
   1134             exec,
   1135             &IPlotGcsLocationFields {
   1136                 plot_id: plot_primary.id.clone(),
   1137                 gcs_location_id: gcs_secondary.id.clone(),
   1138                 role: "secondary".to_string(),
   1139             },
   1140         )
   1141         .expect("plot primary secondary relation");
   1142         let _ = plot_gcs_location::create(
   1143             exec,
   1144             &IPlotGcsLocationFields {
   1145                 plot_id: plot_primary.id.clone(),
   1146                 gcs_location_id: gcs_primary.id.clone(),
   1147                 role: "primary".to_string(),
   1148             },
   1149         )
   1150         .expect("plot primary relation");
   1151         let _ = plot_gcs_location::create(
   1152             exec,
   1153             &IPlotGcsLocationFields {
   1154                 plot_id: plot_secondary.id.clone(),
   1155                 gcs_location_id: gcs_secondary.id.clone(),
   1156                 role: "secondary".to_string(),
   1157             },
   1158         )
   1159         .expect("plot secondary relation");
   1160 
   1161         let _ = farm_tag::create(
   1162             exec,
   1163             &IFarmTagFields {
   1164                 farm_id: farm.id.clone(),
   1165                 tag: "coffee".to_string(),
   1166             },
   1167         )
   1168         .expect("farm tag");
   1169         let _ = plot_tag::create(
   1170             exec,
   1171             &IPlotTagFields {
   1172                 plot_id: plot_primary.id.clone(),
   1173                 tag: "orchard".to_string(),
   1174             },
   1175         )
   1176         .expect("plot tag");
   1177 
   1178         let _ = farm_member::create(
   1179             exec,
   1180             &IFarmMemberFields {
   1181                 farm_id: farm.id.clone(),
   1182                 member_pubkey: "6".repeat(64),
   1183                 role: "member".to_string(),
   1184             },
   1185         )
   1186         .expect("member");
   1187         let _ = farm_member::create(
   1188             exec,
   1189             &IFarmMemberFields {
   1190                 farm_id: farm.id.clone(),
   1191                 member_pubkey: "8".repeat(64),
   1192                 role: "owner".to_string(),
   1193             },
   1194         )
   1195         .expect("owner");
   1196         let _ = farm_member::create(
   1197             exec,
   1198             &IFarmMemberFields {
   1199                 farm_id: farm.id.clone(),
   1200                 member_pubkey: "e".repeat(64),
   1201                 role: "worker".to_string(),
   1202             },
   1203         )
   1204         .expect("worker");
   1205         let _ = farm_member::create(
   1206             exec,
   1207             &IFarmMemberFields {
   1208                 farm_id: farm.id.clone(),
   1209                 member_pubkey: "1".repeat(64),
   1210                 role: "member".to_string(),
   1211             },
   1212         )
   1213         .expect("member no profile");
   1214 
   1215         let _ = farm_member_claim::create(
   1216             exec,
   1217             &IFarmMemberClaimFields {
   1218                 member_pubkey: "6".repeat(64),
   1219                 farm_pubkey: farm.pubkey.clone(),
   1220             },
   1221         )
   1222         .expect("claim member");
   1223         let _ = farm_member_claim::create(
   1224             exec,
   1225             &IFarmMemberClaimFields {
   1226                 member_pubkey: "1".repeat(64),
   1227                 farm_pubkey: farm.pubkey.clone(),
   1228             },
   1229         )
   1230         .expect("claim member no profile");
   1231 
   1232         let _ = nostr_profile::create(
   1233             exec,
   1234             &INostrProfileFields {
   1235                 public_key: farm.pubkey.clone(),
   1236                 profile_type: "farm".to_string(),
   1237                 name: "farm profile".to_string(),
   1238                 display_name: None,
   1239                 about: None,
   1240                 website: None,
   1241                 picture: None,
   1242                 banner: None,
   1243                 nip05: None,
   1244                 lud06: None,
   1245                 lud16: None,
   1246             },
   1247         )
   1248         .expect("farm profile");
   1249         let _ = nostr_profile::create(
   1250             exec,
   1251             &INostrProfileFields {
   1252                 public_key: "6".repeat(64),
   1253                 profile_type: "legacy".to_string(),
   1254                 name: "member profile".to_string(),
   1255                 display_name: Some("member".to_string()),
   1256                 about: Some("about".to_string()),
   1257                 website: Some("https://member.example.com".to_string()),
   1258                 picture: Some("https://member.example.com/p.png".to_string()),
   1259                 banner: Some("https://member.example.com/b.png".to_string()),
   1260                 nip05: Some("member@example.com".to_string()),
   1261                 lud06: Some("lud06".to_string()),
   1262                 lud16: Some("lud16".to_string()),
   1263             },
   1264         )
   1265         .expect("member profile");
   1266 
   1267         (farm, plot_primary, plot_secondary)
   1268     }
   1269 
   1270     fn create_farm_record(exec: &SqliteExecutor, d_tag: &str, pubkey: &str, name: &str) -> Farm {
   1271         farm::create(
   1272             exec,
   1273             &IFarmFields {
   1274                 d_tag: d_tag.to_string(),
   1275                 pubkey: pubkey.to_string(),
   1276                 name: name.to_string(),
   1277                 about: None,
   1278                 website: None,
   1279                 picture: None,
   1280                 banner: None,
   1281                 location_primary: None,
   1282                 location_city: None,
   1283                 location_region: None,
   1284                 location_country: None,
   1285             },
   1286         )
   1287         .expect("farm")
   1288         .result
   1289     }
   1290 
   1291     fn create_plot_record(exec: &SqliteExecutor, farm_id: &str, d_tag: &str, name: &str) {
   1292         let _ = plot::create(
   1293             exec,
   1294             &IPlotFields {
   1295                 d_tag: d_tag.to_string(),
   1296                 farm_id: farm_id.to_string(),
   1297                 name: name.to_string(),
   1298                 about: None,
   1299                 location_primary: None,
   1300                 location_city: None,
   1301                 location_region: None,
   1302                 location_country: None,
   1303             },
   1304         )
   1305         .expect("plot");
   1306     }
   1307 
   1308     fn add_member_record(exec: &SqliteExecutor, farm_id: &str, member_pubkey: &str, role: &str) {
   1309         let _ = farm_member::create(
   1310             exec,
   1311             &IFarmMemberFields {
   1312                 farm_id: farm_id.to_string(),
   1313                 member_pubkey: member_pubkey.to_string(),
   1314                 role: role.to_string(),
   1315             },
   1316         )
   1317         .expect("member");
   1318     }
   1319 
   1320     #[test]
   1321     fn emit_paths_cover_private_and_public_helpers() {
   1322         let exec = SqliteExecutor::open_memory().expect("db");
   1323         let (farm_row, plot_primary, plot_secondary) = seed(&exec);
   1324 
   1325         let by_id = resolve_farm(
   1326             &exec,
   1327             &RadrootsReplicaFarmSelector {
   1328                 id: Some(farm_row.id.clone()),
   1329                 d_tag: None,
   1330                 pubkey: None,
   1331             },
   1332         )
   1333         .expect("resolve by id");
   1334         assert_eq!(by_id.id, farm_row.id);
   1335 
   1336         assert!(
   1337             resolve_farm(
   1338                 &exec,
   1339                 &RadrootsReplicaFarmSelector {
   1340                     id: Some("00000000-0000-0000-0000-000000000000".to_string()),
   1341                     d_tag: None,
   1342                     pubkey: None,
   1343                 },
   1344             )
   1345             .is_err()
   1346         );
   1347         assert!(
   1348             radroots_replica_sync_all_with_options(
   1349                 &exec,
   1350                 &RadrootsReplicaFarmSelector {
   1351                     id: Some("00000000-0000-0000-0000-000000000000".to_string()),
   1352                     d_tag: None,
   1353                     pubkey: None,
   1354                 },
   1355                 None,
   1356             )
   1357             .is_err()
   1358         );
   1359         assert!(
   1360             resolve_farm(
   1361                 &exec,
   1362                 &RadrootsReplicaFarmSelector {
   1363                     id: None,
   1364                     d_tag: None,
   1365                     pubkey: None,
   1366                 },
   1367             )
   1368             .is_err()
   1369         );
   1370 
   1371         assert!(
   1372             farm::create(
   1373                 &exec,
   1374                 &IFarmFields {
   1375                     d_tag: farm_row.d_tag.clone(),
   1376                     pubkey: farm_row.pubkey.clone(),
   1377                     name: "duplicate".to_string(),
   1378                     about: None,
   1379                     website: None,
   1380                     picture: None,
   1381                     banner: None,
   1382                     location_primary: None,
   1383                     location_city: None,
   1384                     location_region: None,
   1385                     location_country: None,
   1386                 },
   1387             )
   1388             .is_err()
   1389         );
   1390         let by_identity = resolve_farm(
   1391             &exec,
   1392             &RadrootsReplicaFarmSelector {
   1393                 id: None,
   1394                 d_tag: Some(farm_row.d_tag.clone()),
   1395                 pubkey: Some(farm_row.pubkey.clone()),
   1396             },
   1397         )
   1398         .expect("resolve unique farm");
   1399         assert_eq!(by_identity.id, farm_row.id);
   1400 
   1401         let tags = collect_farm_tags(&exec, &farm_row.id).expect("farm tags");
   1402         assert_eq!(tags, vec!["coffee".to_string()]);
   1403         let plot_tags = collect_plot_tags(&exec, &plot_primary.id).expect("plot tags");
   1404         assert_eq!(plot_tags, vec!["orchard".to_string()]);
   1405 
   1406         let members = load_farm_members(&exec, &farm_row.id).expect("members");
   1407         assert_eq!(role_pubkeys(&members, ROLE_MEMBER).len(), 2);
   1408         assert_eq!(role_pubkeys(&members, ROLE_OWNER).len(), 1);
   1409         assert_eq!(role_pubkeys(&members, ROLE_WORKER).len(), 1);
   1410         let plots = load_plots(&exec, &farm_row.id).expect("plots");
   1411         assert_eq!(sorted_plot_ids(&plots).len(), 2);
   1412 
   1413         let farm_location = load_farm_location(&exec, &farm_row).expect("farm location");
   1414         assert!(farm_location.is_some());
   1415         let plot_location_primary = load_plot_location(&exec, &plot_primary).expect("plot primary");
   1416         assert!(plot_location_primary.is_some());
   1417         let plot_location_secondary =
   1418             load_plot_location(&exec, &plot_secondary).expect("plot secondary");
   1419         assert!(plot_location_secondary.is_some());
   1420 
   1421         assert!(
   1422             load_relation_by_role(&exec, &farm_row.id, "primary", RelationType::Farm)
   1423                 .expect("farm primary")
   1424                 .is_some()
   1425         );
   1426         assert!(
   1427             load_relation_by_role(&exec, &farm_row.id, "", RelationType::Farm)
   1428                 .expect("farm fallback")
   1429                 .is_some()
   1430         );
   1431         assert!(
   1432             load_relation_by_role(&exec, &plot_secondary.id, "", RelationType::Plot)
   1433                 .expect("plot fallback")
   1434                 .is_some()
   1435         );
   1436 
   1437         let mut farm_rel =
   1438             farm_gcs_location::find_many(&exec, &IFarmGcsLocationFindMany { filter: None })
   1439                 .expect("farm rels")
   1440                 .results;
   1441         let mut plot_rel =
   1442             plot_gcs_location::find_many(&exec, &IPlotGcsLocationFindMany { filter: None })
   1443                 .expect("plot rels")
   1444                 .results;
   1445         let farm_row_role = RelationRow::Farm(farm_rel.remove(0)).role().to_string();
   1446         let plot_row_role = RelationRow::Plot(plot_rel.remove(0)).role().to_string();
   1447         let _ = farm_row_role;
   1448         let _ = plot_row_role;
   1449         assert_eq!(location_role_rank(ROLE_PRIMARY), 0);
   1450         assert_eq!(location_role_rank("secondary"), 1);
   1451 
   1452         let point_valid = parse_point("{\"type\":\"Point\",\"coordinates\":[1.0,2.0]}", 3.0, 4.0);
   1453         assert_eq!(point_valid.coordinates, [1.0, 2.0]);
   1454         let point_invalid = parse_point("{", 3.0, 4.0);
   1455         assert_eq!(point_invalid.coordinates, [4.0, 3.0]);
   1456         let point_empty = parse_point("", 3.0, 4.0);
   1457         assert_eq!(point_empty.coordinates, [4.0, 3.0]);
   1458 
   1459         let polygon_valid = parse_polygon(
   1460             "{\"type\":\"Polygon\",\"coordinates\":[[[1.0,2.0],[1.1,2.1],[1.0,2.0]]]}",
   1461             3.0,
   1462             4.0,
   1463         );
   1464         assert!(!polygon_valid.coordinates[0].is_empty());
   1465         let polygon_empty_outer =
   1466             parse_polygon("{\"type\":\"Polygon\",\"coordinates\":[]}", 3.0, 4.0);
   1467         assert!(!polygon_empty_outer.coordinates[0].is_empty());
   1468         let polygon_empty_inner =
   1469             parse_polygon("{\"type\":\"Polygon\",\"coordinates\":[[]]}", 3.0, 4.0);
   1470         assert!(!polygon_empty_inner.coordinates[0].is_empty());
   1471         let polygon_invalid = parse_polygon("{", 3.0, 4.0);
   1472         assert!(!polygon_invalid.coordinates[0].is_empty());
   1473         let polygon_blank = parse_polygon("", 3.0, 4.0);
   1474         assert!(!polygon_blank.coordinates[0].is_empty());
   1475 
   1476         assert!(
   1477             load_profile(&exec, &farm_row.pubkey)
   1478                 .expect("farm profile")
   1479                 .is_some()
   1480         );
   1481         assert!(
   1482             load_profile(&exec, &"3".repeat(64))
   1483                 .expect("missing profile")
   1484                 .is_none()
   1485         );
   1486 
   1487         let profile_event_farm = profile_event(
   1488             &farm_row.pubkey,
   1489             radroots_replica_db_schema::nostr_profile::NostrProfile {
   1490                 id: "00000000-0000-0000-0000-000000000001".to_string(),
   1491                 created_at: "2024-01-01T00:00:00.000Z".to_string(),
   1492                 updated_at: "2024-01-01T00:00:00.000Z".to_string(),
   1493                 public_key: farm_row.pubkey.clone(),
   1494                 profile_type: "farm".to_string(),
   1495                 name: "farm".to_string(),
   1496                 display_name: None,
   1497                 about: None,
   1498                 website: None,
   1499                 picture: None,
   1500                 banner: None,
   1501                 nip05: None,
   1502                 lud06: None,
   1503                 lud16: None,
   1504             },
   1505         )
   1506         .expect("profile farm");
   1507         assert!(!profile_event_farm.tags.is_empty());
   1508         let profile_event_unknown = profile_event(
   1509             &"6".repeat(64),
   1510             radroots_replica_db_schema::nostr_profile::NostrProfile {
   1511                 id: "00000000-0000-0000-0000-000000000002".to_string(),
   1512                 created_at: "2024-01-01T00:00:00.000Z".to_string(),
   1513                 updated_at: "2024-01-01T00:00:00.000Z".to_string(),
   1514                 public_key: "6".repeat(64),
   1515                 profile_type: "legacy".to_string(),
   1516                 name: "legacy".to_string(),
   1517                 display_name: None,
   1518                 about: None,
   1519                 website: None,
   1520                 picture: None,
   1521                 banner: None,
   1522                 nip05: None,
   1523                 lud06: None,
   1524                 lud16: None,
   1525             },
   1526         )
   1527         .expect("profile legacy");
   1528         assert!(profile_event_unknown.tags.is_empty());
   1529 
   1530         let profile_content = serialize_profile_content(&RadrootsProfile {
   1531             name: "name".to_string(),
   1532             display_name: Some("display".to_string()),
   1533             nip05: Some("nip05".to_string()),
   1534             about: Some("about".to_string()),
   1535             website: Some("website".to_string()),
   1536             picture: Some("picture".to_string()),
   1537             banner: Some("banner".to_string()),
   1538             lud06: Some("lud06".to_string()),
   1539             lud16: Some("lud16".to_string()),
   1540             bot: None,
   1541         })
   1542         .expect("serialize profile");
   1543         assert!(profile_content.contains("\"name\":\"name\""));
   1544 
   1545         let member_pubkeys = collect_member_pubkeys(&exec, &farm_row.id).expect("member pubkeys");
   1546         assert!(!member_pubkeys.is_empty());
   1547         let profile_pubkeys = collect_profile_pubkeys(&exec, &farm_row).expect("profile pubkeys");
   1548         assert!(!profile_pubkeys.is_empty());
   1549         let claims = load_member_claims(&exec, &farm_row.pubkey).expect("claims");
   1550         assert!(!claims.is_empty());
   1551         let member_claims =
   1552             load_member_claims_for_member(&exec, &"6".repeat(64)).expect("claims by member");
   1553         assert!(!member_claims.is_empty());
   1554 
   1555         let profile_events = radroots_replica_profile_events(&exec, &farm_row).expect("profiles");
   1556         assert!(!profile_events.is_empty());
   1557         let farm_event = radroots_replica_farm_event(&exec, &farm_row).expect("farm event");
   1558         assert_eq!(farm_event.kind, KIND_FARM);
   1559         let plot_events = radroots_replica_plot_events(&exec, &farm_row).expect("plot events");
   1560         assert_eq!(plot_events.len(), 2);
   1561         let list_sets = radroots_replica_list_set_events(&exec, &farm_row).expect("list sets");
   1562         assert_eq!(list_sets.len(), 4);
   1563         let membership_claims =
   1564             radroots_replica_membership_claim_events(&exec, &farm_row.pubkey).expect("membership");
   1565         assert!(!membership_claims.is_empty());
   1566         let bundle = radroots_replica_sync_all_with_options(
   1567             &exec,
   1568             &RadrootsReplicaFarmSelector {
   1569                 id: Some(farm_row.id.clone()),
   1570                 d_tag: None,
   1571                 pubkey: None,
   1572             },
   1573             Some(&RadrootsReplicaSyncOptions {
   1574                 include_profiles: Some(true),
   1575                 include_list_sets: Some(true),
   1576                 include_membership_claims: Some(true),
   1577             }),
   1578         )
   1579         .expect("sync all");
   1580         assert!(!bundle.events.is_empty());
   1581 
   1582         let _ = exec.exec("PRAGMA foreign_keys = OFF", "[]");
   1583         let _ = plot_gcs_location::create(
   1584             &exec,
   1585             &IPlotGcsLocationFields {
   1586                 plot_id: plot_secondary.id.clone(),
   1587                 gcs_location_id: "00000000-0000-0000-0000-000000000000".to_string(),
   1588                 role: "".to_string(),
   1589             },
   1590         );
   1591         assert!(load_relation_by_role(&exec, &plot_secondary.id, "", RelationType::Plot).is_err());
   1592 
   1593         let by_pair = farm::find_many(
   1594             &exec,
   1595             &IFarmFindMany {
   1596                 filter: Some(IFarmFieldsFilter {
   1597                     id: None,
   1598                     created_at: None,
   1599                     updated_at: None,
   1600                     d_tag: Some("AAAAAAAAAAAAAAAAAAAAAA".to_string()),
   1601                     pubkey: Some("f".repeat(64)),
   1602                     name: None,
   1603                     about: None,
   1604                     website: None,
   1605                     picture: None,
   1606                     banner: None,
   1607                     location_primary: None,
   1608                     location_city: None,
   1609                     location_region: None,
   1610                     location_country: None,
   1611                 }),
   1612             },
   1613         )
   1614         .expect("by pair");
   1615         assert!(!by_pair.results.is_empty());
   1616 
   1617         let plots_lookup = plot::find_many(
   1618             &exec,
   1619             &IPlotFindMany {
   1620                 filter: Some(IPlotFieldsFilter {
   1621                     id: None,
   1622                     created_at: None,
   1623                     updated_at: None,
   1624                     d_tag: None,
   1625                     farm_id: Some(farm_row.id),
   1626                     name: None,
   1627                     about: None,
   1628                     location_primary: None,
   1629                     location_city: None,
   1630                     location_region: None,
   1631                     location_country: None,
   1632                 }),
   1633             },
   1634         )
   1635         .expect("plots lookup");
   1636         assert_eq!(plots_lookup.results.len(), 2);
   1637     }
   1638 
   1639     #[test]
   1640     fn emit_option_toggles_and_empty_rows_cover_branches() {
   1641         let exec = SqliteExecutor::open_memory().expect("db");
   1642         migrations::run_all_up(&exec).expect("migrations");
   1643 
   1644         let farm = farm::create(
   1645             &exec,
   1646             &IFarmFields {
   1647                 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
   1648                 pubkey: "b".repeat(64),
   1649                 name: "farm-empty".to_string(),
   1650                 about: None,
   1651                 website: None,
   1652                 picture: None,
   1653                 banner: None,
   1654                 location_primary: None,
   1655                 location_city: None,
   1656                 location_region: None,
   1657                 location_country: None,
   1658             },
   1659         )
   1660         .expect("farm")
   1661         .result;
   1662 
   1663         let _plot = plot::create(
   1664             &exec,
   1665             &IPlotFields {
   1666                 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(),
   1667                 farm_id: farm.id.clone(),
   1668                 name: "plot-empty".to_string(),
   1669                 about: None,
   1670                 location_primary: None,
   1671                 location_city: None,
   1672                 location_region: None,
   1673                 location_country: None,
   1674             },
   1675         )
   1676         .expect("plot");
   1677 
   1678         let selector = RadrootsReplicaFarmSelector {
   1679             id: Some(farm.id.clone()),
   1680             d_tag: None,
   1681             pubkey: None,
   1682         };
   1683         let bundle = radroots_replica_sync_all_with_options(
   1684             &exec,
   1685             &selector,
   1686             Some(&RadrootsReplicaSyncOptions {
   1687                 include_profiles: Some(false),
   1688                 include_list_sets: Some(false),
   1689                 include_membership_claims: Some(false),
   1690             }),
   1691         )
   1692         .expect("sync");
   1693         assert_eq!(bundle.events.len(), 2);
   1694 
   1695         let farm_event = radroots_replica_farm_event(&exec, &farm).expect("farm event");
   1696         assert_eq!(farm_event.kind, KIND_FARM);
   1697         let plot_events = radroots_replica_plot_events(&exec, &farm).expect("plot events");
   1698         assert_eq!(plot_events.len(), 1);
   1699 
   1700         let claims =
   1701             radroots_replica_membership_claim_events(&exec, &"3".repeat(64)).expect("empty claims");
   1702         assert!(claims.is_empty());
   1703 
   1704         let by_pair = resolve_farm(
   1705             &exec,
   1706             &RadrootsReplicaFarmSelector {
   1707                 id: None,
   1708                 d_tag: Some(farm.d_tag.clone()),
   1709                 pubkey: Some(farm.pubkey.clone()),
   1710             },
   1711         )
   1712         .expect("resolve by pair");
   1713         assert_eq!(by_pair.id, farm.id);
   1714     }
   1715 
   1716     #[test]
   1717     fn emit_profile_variants_and_missing_profiles_are_handled() {
   1718         let exec = SqliteExecutor::open_memory().expect("db");
   1719         let (farm_row, _, _) = seed(&exec);
   1720 
   1721         let _ = farm_member::create(
   1722             &exec,
   1723             &IFarmMemberFields {
   1724                 farm_id: farm_row.id.clone(),
   1725                 member_pubkey: "3".repeat(64),
   1726                 role: ROLE_MEMBER.to_string(),
   1727             },
   1728         )
   1729         .expect("member");
   1730 
   1731         let profiles = radroots_replica_profile_events(&exec, &farm_row).expect("profiles");
   1732         assert!(!profiles.is_empty());
   1733 
   1734         let profile_coop = profile_event(
   1735             &"c".repeat(64),
   1736             radroots_replica_db_schema::nostr_profile::NostrProfile {
   1737                 id: "00000000-0000-0000-0000-0000000000c0".to_string(),
   1738                 created_at: "2024-01-01T00:00:00.000Z".to_string(),
   1739                 updated_at: "2024-01-01T00:00:00.000Z".to_string(),
   1740                 public_key: "c".repeat(64),
   1741                 profile_type: "coop".to_string(),
   1742                 name: "coop".to_string(),
   1743                 display_name: None,
   1744                 about: None,
   1745                 website: None,
   1746                 picture: None,
   1747                 banner: None,
   1748                 nip05: None,
   1749                 lud06: None,
   1750                 lud16: None,
   1751             },
   1752         )
   1753         .expect("profile coop");
   1754         assert!(!profile_coop.tags.is_empty());
   1755 
   1756         let profile_any = profile_event(
   1757             &"a".repeat(64),
   1758             radroots_replica_db_schema::nostr_profile::NostrProfile {
   1759                 id: "00000000-0000-0000-0000-0000000000a0".to_string(),
   1760                 created_at: "2024-01-01T00:00:00.000Z".to_string(),
   1761                 updated_at: "2024-01-01T00:00:00.000Z".to_string(),
   1762                 public_key: "a".repeat(64),
   1763                 profile_type: "any".to_string(),
   1764                 name: "any".to_string(),
   1765                 display_name: None,
   1766                 about: None,
   1767                 website: None,
   1768                 picture: None,
   1769                 banner: None,
   1770                 nip05: None,
   1771                 lud06: None,
   1772                 lud16: None,
   1773             },
   1774         )
   1775         .expect("profile any");
   1776         assert!(!profile_any.tags.is_empty());
   1777 
   1778         let profile_sparse = serialize_profile_content(&RadrootsProfile {
   1779             name: "sparse".to_string(),
   1780             display_name: None,
   1781             nip05: None,
   1782             about: None,
   1783             website: None,
   1784             picture: None,
   1785             banner: None,
   1786             lud06: None,
   1787             lud16: None,
   1788             bot: None,
   1789         })
   1790         .expect("serialize");
   1791         assert!(profile_sparse.contains("\"name\""));
   1792     }
   1793 
   1794     #[test]
   1795     fn emit_query_error_paths_are_reported() {
   1796         let exec = SqliteExecutor::open_memory().expect("db");
   1797         migrations::run_all_up(&exec).expect("migrations");
   1798 
   1799         let farm = Farm {
   1800             id: "farm".to_string(),
   1801             created_at: "now".to_string(),
   1802             updated_at: "now".to_string(),
   1803             d_tag: "d".to_string(),
   1804             pubkey: "9".repeat(64),
   1805             name: "farm".to_string(),
   1806             about: None,
   1807             website: None,
   1808             picture: None,
   1809             banner: None,
   1810             location_primary: None,
   1811             location_city: None,
   1812             location_region: None,
   1813             location_country: None,
   1814         };
   1815         let plot = Plot {
   1816             id: "plot".to_string(),
   1817             created_at: "now".to_string(),
   1818             updated_at: "now".to_string(),
   1819             d_tag: "plot".to_string(),
   1820             farm_id: farm.id.clone(),
   1821             name: "plot".to_string(),
   1822             about: None,
   1823             location_primary: None,
   1824             location_city: None,
   1825             location_region: None,
   1826             location_country: None,
   1827         };
   1828 
   1829         let tags_fail = QueryFailExecutor {
   1830             inner: &exec,
   1831             needle: "farm_tag",
   1832             err: SqlError::Internal,
   1833         };
   1834         assert!(collect_farm_tags(&tags_fail, "id").is_err());
   1835 
   1836         let plot_tags_fail = QueryFailExecutor {
   1837             inner: &exec,
   1838             needle: "plot_tag",
   1839             err: SqlError::Internal,
   1840         };
   1841         assert!(collect_plot_tags(&plot_tags_fail, "id").is_err());
   1842 
   1843         let members_fail = QueryFailExecutor {
   1844             inner: &exec,
   1845             needle: "farm_member",
   1846             err: SqlError::Internal,
   1847         };
   1848         assert!(load_farm_members(&members_fail, "id").is_err());
   1849 
   1850         let plots_fail = QueryFailExecutor {
   1851             inner: &exec,
   1852             needle: "from plot",
   1853             err: SqlError::Internal,
   1854         };
   1855         assert!(load_plots(&plots_fail, "id").is_err());
   1856 
   1857         let farm_location_fail = QueryFailExecutor {
   1858             inner: &exec,
   1859             needle: "farm_gcs_location",
   1860             err: SqlError::Internal,
   1861         };
   1862         assert!(load_farm_location(&farm_location_fail, &farm).is_err());
   1863 
   1864         let plot_location_fail = QueryFailExecutor {
   1865             inner: &exec,
   1866             needle: "plot_gcs_location",
   1867             err: SqlError::Internal,
   1868         };
   1869         assert!(load_plot_location(&plot_location_fail, &plot).is_err());
   1870 
   1871         let profile_fail = QueryFailExecutor {
   1872             inner: &exec,
   1873             needle: "nostr_profile",
   1874             err: SqlError::Internal,
   1875         };
   1876         assert!(load_profile(&profile_fail, "p").is_err());
   1877 
   1878         let claims_fail = QueryFailExecutor {
   1879             inner: &exec,
   1880             needle: "farm_member_claim",
   1881             err: SqlError::Internal,
   1882         };
   1883         assert!(load_member_claims(&claims_fail, "p").is_err());
   1884         assert!(load_member_claims_for_member(&claims_fail, "p").is_err());
   1885         assert!(collect_profile_pubkeys(&claims_fail, &farm).is_err());
   1886     }
   1887 
   1888     #[test]
   1889     fn load_farm_location_preserves_string_only_locations() {
   1890         let exec = SqliteExecutor::open_memory().expect("db");
   1891         migrations::run_all_up(&exec).expect("migrations");
   1892         let farm_row = farm::create(
   1893             &exec,
   1894             &IFarmFields {
   1895                 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(),
   1896                 pubkey: "f".repeat(64),
   1897                 name: "string-only farm".to_string(),
   1898                 about: None,
   1899                 website: None,
   1900                 picture: None,
   1901                 banner: None,
   1902                 location_primary: Some("San Francisco, CA".to_string()),
   1903                 location_city: Some("San Francisco".to_string()),
   1904                 location_region: Some("CA".to_string()),
   1905                 location_country: Some("US".to_string()),
   1906             },
   1907         )
   1908         .expect("farm")
   1909         .result;
   1910 
   1911         let location = load_farm_location(&exec, &farm_row)
   1912             .expect("location query")
   1913             .expect("string-only location");
   1914         assert_eq!(location.primary.as_deref(), Some("San Francisco, CA"));
   1915         assert_eq!(location.city.as_deref(), Some("San Francisco"));
   1916         assert_eq!(location.region.as_deref(), Some("CA"));
   1917         assert_eq!(location.country.as_deref(), Some("US"));
   1918         assert!(location.gcs.is_none());
   1919     }
   1920 
   1921     #[test]
   1922     fn emit_propagates_queryfail_and_builder_errors() {
   1923         let exec = SqliteExecutor::open_memory().expect("db");
   1924         let (farm_row, _, _) = seed(&exec);
   1925         let selector = RadrootsReplicaFarmSelector {
   1926             id: Some(farm_row.id.clone()),
   1927             d_tag: None,
   1928             pubkey: None,
   1929         };
   1930 
   1931         let profile_query_fail = QueryFailExecutor {
   1932             inner: &exec,
   1933             needle: "farm_member_claim",
   1934             err: SqlError::Internal,
   1935         };
   1936         assert!(radroots_replica_profile_events(&profile_query_fail, &farm_row).is_err());
   1937         let profile_load_fail = QueryFailExecutor {
   1938             inner: &exec,
   1939             needle: "nostr_profile",
   1940             err: SqlError::Internal,
   1941         };
   1942         assert!(radroots_replica_profile_events(&profile_load_fail, &farm_row).is_err());
   1943 
   1944         let farm_tag_fail = QueryFailExecutor {
   1945             inner: &exec,
   1946             needle: "farm_tag",
   1947             err: SqlError::Internal,
   1948         };
   1949         assert!(radroots_replica_farm_event(&farm_tag_fail, &farm_row).is_err());
   1950 
   1951         let plot_tag_fail = QueryFailExecutor {
   1952             inner: &exec,
   1953             needle: "plot_tag",
   1954             err: SqlError::Internal,
   1955         };
   1956         assert!(radroots_replica_plot_events(&plot_tag_fail, &farm_row).is_err());
   1957 
   1958         let list_set_fail = QueryFailExecutor {
   1959             inner: &exec,
   1960             needle: "farm_member",
   1961             err: SqlError::Internal,
   1962         };
   1963         assert!(radroots_replica_list_set_events(&list_set_fail, &farm_row).is_err());
   1964 
   1965         let claims_fail = QueryFailExecutor {
   1966             inner: &exec,
   1967             needle: "farm_member_claim",
   1968             err: SqlError::Internal,
   1969         };
   1970         assert!(radroots_replica_membership_claim_events(&claims_fail, &farm_row.pubkey).is_err());
   1971 
   1972         let sync_profiles_fail = QueryFailExecutor {
   1973             inner: &exec,
   1974             needle: "nostr_profile",
   1975             err: SqlError::Internal,
   1976         };
   1977         assert!(
   1978             radroots_replica_sync_all_with_options(
   1979                 &sync_profiles_fail,
   1980                 &selector,
   1981                 Some(&RadrootsReplicaSyncOptions {
   1982                     include_profiles: Some(true),
   1983                     include_list_sets: Some(false),
   1984                     include_membership_claims: Some(false),
   1985                 }),
   1986             )
   1987             .is_err()
   1988         );
   1989         let sync_farm_fail = QueryFailExecutor {
   1990             inner: &exec,
   1991             needle: "farm_tag",
   1992             err: SqlError::Internal,
   1993         };
   1994         assert!(
   1995             radroots_replica_sync_all_with_options(
   1996                 &sync_farm_fail,
   1997                 &selector,
   1998                 Some(&RadrootsReplicaSyncOptions {
   1999                     include_profiles: Some(false),
   2000                     include_list_sets: Some(false),
   2001                     include_membership_claims: Some(false),
   2002                 }),
   2003             )
   2004             .is_err()
   2005         );
   2006         let sync_plot_fail = QueryFailExecutor {
   2007             inner: &exec,
   2008             needle: "plot_tag",
   2009             err: SqlError::Internal,
   2010         };
   2011         assert!(
   2012             radroots_replica_sync_all_with_options(
   2013                 &sync_plot_fail,
   2014                 &selector,
   2015                 Some(&RadrootsReplicaSyncOptions {
   2016                     include_profiles: Some(false),
   2017                     include_list_sets: Some(false),
   2018                     include_membership_claims: Some(false),
   2019                 }),
   2020             )
   2021             .is_err()
   2022         );
   2023         let sync_list_set_fail = QueryFailExecutor {
   2024             inner: &exec,
   2025             needle: "farm_member",
   2026             err: SqlError::Internal,
   2027         };
   2028         assert!(
   2029             radroots_replica_sync_all_with_options(
   2030                 &sync_list_set_fail,
   2031                 &selector,
   2032                 Some(&RadrootsReplicaSyncOptions {
   2033                     include_profiles: Some(false),
   2034                     include_list_sets: Some(true),
   2035                     include_membership_claims: Some(false),
   2036                 }),
   2037             )
   2038             .is_err()
   2039         );
   2040         let sync_claims_fail = QueryFailExecutor {
   2041             inner: &exec,
   2042             needle: "farm_member_claim",
   2043             err: SqlError::Internal,
   2044         };
   2045         assert!(
   2046             radroots_replica_sync_all_with_options(
   2047                 &sync_claims_fail,
   2048                 &selector,
   2049                 Some(&RadrootsReplicaSyncOptions {
   2050                     include_profiles: Some(false),
   2051                     include_list_sets: Some(false),
   2052                     include_membership_claims: Some(true),
   2053                 }),
   2054             )
   2055             .is_err()
   2056         );
   2057 
   2058         assert!(radroots_replica_farm_event(&exec, &farm_row).is_ok());
   2059         assert!(radroots_replica_plot_events(&exec, &farm_row).is_ok());
   2060     }
   2061 
   2062     #[test]
   2063     fn emit_additional_error_branches_are_reported() {
   2064         let exec = SqliteExecutor::open_memory().expect("db");
   2065         let (farm_row, _, _) = seed(&exec);
   2066 
   2067         crate::canonical::failpoints::set_error();
   2068         assert!(radroots_replica_profile_events(&exec, &farm_row).is_err());
   2069         let farm_profile = load_profile(&exec, &farm_row.pubkey)
   2070             .expect("load profile")
   2071             .expect("farm profile");
   2072         crate::canonical::failpoints::set_error();
   2073         assert!(profile_event(&farm_row.pubkey, farm_profile).is_err());
   2074 
   2075         let farm_location_fail = QueryFailExecutor {
   2076             inner: &exec,
   2077             needle: "farm_gcs_location",
   2078             err: SqlError::Internal,
   2079         };
   2080         assert!(radroots_replica_farm_event(&farm_location_fail, &farm_row).is_err());
   2081         let invalid_farm = Farm {
   2082             id: farm_row.id.clone(),
   2083             created_at: farm_row.created_at.clone(),
   2084             updated_at: farm_row.updated_at.clone(),
   2085             d_tag: "invalid".to_string(),
   2086             pubkey: farm_row.pubkey.clone(),
   2087             name: farm_row.name.clone(),
   2088             about: farm_row.about.clone(),
   2089             website: farm_row.website.clone(),
   2090             picture: farm_row.picture.clone(),
   2091             banner: farm_row.banner.clone(),
   2092             location_primary: farm_row.location_primary.clone(),
   2093             location_city: farm_row.location_city.clone(),
   2094             location_region: farm_row.location_region.clone(),
   2095             location_country: farm_row.location_country.clone(),
   2096         };
   2097         assert!(radroots_replica_farm_event(&exec, &invalid_farm).is_err());
   2098         crate::canonical::failpoints::set_error();
   2099         assert!(radroots_replica_farm_event(&exec, &farm_row).is_err());
   2100 
   2101         let plots_fail = QueryFailExecutor {
   2102             inner: &exec,
   2103             needle: "from plot",
   2104             err: SqlError::Internal,
   2105         };
   2106         assert!(radroots_replica_plot_events(&plots_fail, &farm_row).is_err());
   2107         let plot_location_fail = QueryFailExecutor {
   2108             inner: &exec,
   2109             needle: "plot_gcs_location",
   2110             err: SqlError::Internal,
   2111         };
   2112         assert!(radroots_replica_plot_events(&plot_location_fail, &farm_row).is_err());
   2113         crate::canonical::failpoints::set_error();
   2114         assert!(radroots_replica_plot_events(&exec, &farm_row).is_err());
   2115         create_plot_record(&exec, &farm_row.id, "invalid", "plot-invalid");
   2116         assert!(radroots_replica_plot_events(&exec, &farm_row).is_err());
   2117 
   2118         let resolve_id_fail = QueryFailExecutor {
   2119             inner: &exec,
   2120             needle: "from farm",
   2121             err: SqlError::Internal,
   2122         };
   2123         assert!(
   2124             resolve_farm(
   2125                 &resolve_id_fail,
   2126                 &RadrootsReplicaFarmSelector {
   2127                     id: Some(farm_row.id.clone()),
   2128                     d_tag: None,
   2129                     pubkey: None,
   2130                 }
   2131             )
   2132             .is_err()
   2133         );
   2134         let resolve_pair_fail = QueryFailExecutor {
   2135             inner: &exec,
   2136             needle: "from farm",
   2137             err: SqlError::Internal,
   2138         };
   2139         assert!(
   2140             resolve_farm(
   2141                 &resolve_pair_fail,
   2142                 &RadrootsReplicaFarmSelector {
   2143                     id: None,
   2144                     d_tag: Some(farm_row.d_tag.clone()),
   2145                     pubkey: Some(farm_row.pubkey.clone()),
   2146                 }
   2147             )
   2148             .is_err()
   2149         );
   2150 
   2151         let gcs_query_fail = QueryFailExecutor {
   2152             inner: &exec,
   2153             needle: "from gcs_location",
   2154             err: SqlError::Internal,
   2155         };
   2156         assert!(
   2157             load_relation_by_role(
   2158                 &gcs_query_fail,
   2159                 &farm_row.id,
   2160                 ROLE_PRIMARY,
   2161                 RelationType::Farm
   2162             )
   2163             .is_err()
   2164         );
   2165         super::failpoints::set_gcs_location_to_event_error();
   2166         assert!(
   2167             load_relation_by_role(&exec, &farm_row.id, ROLE_PRIMARY, RelationType::Farm).is_err()
   2168         );
   2169 
   2170         let member_fail = QueryFailExecutor {
   2171             inner: &exec,
   2172             needle: "farm_member",
   2173             err: SqlError::Internal,
   2174         };
   2175         assert!(collect_member_pubkeys(&member_fail, &farm_row.id).is_err());
   2176         assert!(collect_profile_pubkeys(&member_fail, &farm_row).is_err());
   2177 
   2178         let list_plot_fail = QueryFailExecutor {
   2179             inner: &exec,
   2180             needle: "from plot",
   2181             err: SqlError::Internal,
   2182         };
   2183         assert!(radroots_replica_list_set_events(&list_plot_fail, &farm_row).is_err());
   2184 
   2185         let list_member_error_farm = create_farm_record(
   2186             &exec,
   2187             "AAAAAAAAAAAAAAAAAAAAAA",
   2188             &"1".repeat(64),
   2189             "list-member-error",
   2190         );
   2191         add_member_record(
   2192             &exec,
   2193             &list_member_error_farm.id,
   2194             &" ".repeat(64),
   2195             ROLE_MEMBER,
   2196         );
   2197         create_plot_record(
   2198             &exec,
   2199             &list_member_error_farm.id,
   2200             "AAAAAAAAAAAAAAAAAAAAAQ",
   2201             "plot-member-error",
   2202         );
   2203         assert!(radroots_replica_list_set_events(&exec, &list_member_error_farm).is_err());
   2204 
   2205         let list_owner_error_farm = create_farm_record(
   2206             &exec,
   2207             "AAAAAAAAAAAAAAAAAAAAAA",
   2208             &"2".repeat(64),
   2209             "list-owner-error",
   2210         );
   2211         add_member_record(
   2212             &exec,
   2213             &list_owner_error_farm.id,
   2214             &"a".repeat(64),
   2215             ROLE_MEMBER,
   2216         );
   2217         add_member_record(
   2218             &exec,
   2219             &list_owner_error_farm.id,
   2220             &" ".repeat(64),
   2221             ROLE_OWNER,
   2222         );
   2223         add_member_record(
   2224             &exec,
   2225             &list_owner_error_farm.id,
   2226             &"b".repeat(64),
   2227             ROLE_WORKER,
   2228         );
   2229         create_plot_record(
   2230             &exec,
   2231             &list_owner_error_farm.id,
   2232             "AAAAAAAAAAAAAAAAAAAAAQ",
   2233             "plot-owner-error",
   2234         );
   2235         assert!(radroots_replica_list_set_events(&exec, &list_owner_error_farm).is_err());
   2236 
   2237         let list_worker_error_farm = create_farm_record(
   2238             &exec,
   2239             "AAAAAAAAAAAAAAAAAAAAAA",
   2240             &"3".repeat(64),
   2241             "list-worker-error",
   2242         );
   2243         add_member_record(
   2244             &exec,
   2245             &list_worker_error_farm.id,
   2246             &"c".repeat(64),
   2247             ROLE_MEMBER,
   2248         );
   2249         add_member_record(
   2250             &exec,
   2251             &list_worker_error_farm.id,
   2252             &"d".repeat(64),
   2253             ROLE_OWNER,
   2254         );
   2255         add_member_record(
   2256             &exec,
   2257             &list_worker_error_farm.id,
   2258             &" ".repeat(64),
   2259             ROLE_WORKER,
   2260         );
   2261         create_plot_record(
   2262             &exec,
   2263             &list_worker_error_farm.id,
   2264             "AAAAAAAAAAAAAAAAAAAAAQ",
   2265             "plot-worker-error",
   2266         );
   2267         assert!(radroots_replica_list_set_events(&exec, &list_worker_error_farm).is_err());
   2268 
   2269         let list_plot_error_farm = create_farm_record(
   2270             &exec,
   2271             "AAAAAAAAAAAAAAAAAAAAAA",
   2272             &"4".repeat(64),
   2273             "list-plot-error",
   2274         );
   2275         add_member_record(
   2276             &exec,
   2277             &list_plot_error_farm.id,
   2278             &"e".repeat(64),
   2279             ROLE_MEMBER,
   2280         );
   2281         add_member_record(&exec, &list_plot_error_farm.id, &"f".repeat(64), ROLE_OWNER);
   2282         add_member_record(
   2283             &exec,
   2284             &list_plot_error_farm.id,
   2285             &"7".repeat(64),
   2286             ROLE_WORKER,
   2287         );
   2288         create_plot_record(&exec, &list_plot_error_farm.id, "", "plot-list-error");
   2289         assert!(radroots_replica_list_set_events(&exec, &list_plot_error_farm).is_err());
   2290 
   2291         let clean_exec = SqliteExecutor::open_memory().expect("db clean");
   2292         let (clean_farm, _, _) = seed(&clean_exec);
   2293         super::failpoints::set_list_set_to_wire_error();
   2294         assert!(radroots_replica_list_set_events(&clean_exec, &clean_farm).is_err());
   2295 
   2296         let invalid_list_set = radroots_events::list_set::RadrootsListSet {
   2297             d_tag: String::new(),
   2298             content: String::new(),
   2299             entries: Vec::new(),
   2300             title: None,
   2301             description: None,
   2302             image: None,
   2303         };
   2304         assert!(list_set_to_wire_parts(&invalid_list_set).is_err());
   2305 
   2306         let claims_member_query_fail = QueryFailExecutor {
   2307             inner: &exec,
   2308             needle: "where member_pubkey",
   2309             err: SqlError::Internal,
   2310         };
   2311         assert!(
   2312             radroots_replica_membership_claim_events(&claims_member_query_fail, &farm_row.pubkey)
   2313                 .is_err()
   2314         );
   2315 
   2316         let _ = farm_member_claim::create(
   2317             &exec,
   2318             &IFarmMemberClaimFields {
   2319                 member_pubkey: "a".repeat(64),
   2320                 farm_pubkey: " ".repeat(64),
   2321             },
   2322         )
   2323         .expect("empty-farm-pubkey claim");
   2324         assert!(radroots_replica_membership_claim_events(&exec, &" ".repeat(64)).is_err());
   2325 
   2326         super::failpoints::set_list_set_to_wire_error();
   2327         assert!(radroots_replica_membership_claim_events(&clean_exec, &clean_farm.pubkey).is_err());
   2328     }
   2329 
   2330     #[test]
   2331     fn emit_list_set_wire_error_paths_are_reported() {
   2332         let exec = SqliteExecutor::open_memory().expect("db");
   2333         let (farm_row, _, _) = seed(&exec);
   2334 
   2335         super::failpoints::set_list_set_to_wire_error();
   2336         assert!(radroots_replica_list_set_events(&exec, &farm_row).is_err());
   2337 
   2338         let invalid_list_set = radroots_events::list_set::RadrootsListSet {
   2339             d_tag: String::new(),
   2340             content: String::new(),
   2341             entries: Vec::new(),
   2342             title: None,
   2343             description: None,
   2344             image: None,
   2345         };
   2346         assert!(list_set_to_wire_parts(&invalid_list_set).is_err());
   2347     }
   2348 
   2349     #[test]
   2350     fn emit_pass_through_executor_instantiation_paths_are_covered() {
   2351         let exec = SqliteExecutor::open_memory().expect("db");
   2352         let (farm_row, _, plot_secondary) = seed(&exec);
   2353 
   2354         let pass = QueryFailExecutor {
   2355             inner: &exec,
   2356             needle: "__never_match__",
   2357             err: SqlError::Internal,
   2358         };
   2359 
   2360         let selector = RadrootsReplicaFarmSelector {
   2361             id: Some(farm_row.id.clone()),
   2362             d_tag: None,
   2363             pubkey: None,
   2364         };
   2365         let request = RadrootsReplicaSyncRequest {
   2366             farm: selector.clone(),
   2367             options: None,
   2368         };
   2369         let bundle = radroots_replica_sync_all(&pass, &request).expect("sync via pass executor");
   2370         assert!(!bundle.events.is_empty());
   2371 
   2372         let resolved_by_id = resolve_farm(&pass, &selector).expect("resolve by id");
   2373         assert_eq!(resolved_by_id.id, farm_row.id);
   2374         let resolved_by_pair = resolve_farm(
   2375             &pass,
   2376             &RadrootsReplicaFarmSelector {
   2377                 id: None,
   2378                 d_tag: Some(farm_row.d_tag.clone()),
   2379                 pubkey: Some(farm_row.pubkey.clone()),
   2380             },
   2381         )
   2382         .expect("resolve by pair");
   2383         assert_eq!(resolved_by_pair.id, farm_row.id);
   2384         let duplicate_pair = DuplicateFarmSelectorExecutor {
   2385             inner: &exec,
   2386             duplicated_rows_json: {
   2387                 let farm_json = serde_json::to_string(&farm_row).expect("farm json");
   2388                 format!("[{farm_json},{farm_json}]")
   2389             },
   2390         };
   2391         duplicate_pair.begin().expect("duplicate begin");
   2392         duplicate_pair.rollback().expect("duplicate rollback");
   2393         duplicate_pair.begin().expect("duplicate begin");
   2394         duplicate_pair.commit().expect("duplicate commit");
   2395         duplicate_pair
   2396             .exec("CREATE TABLE duplicate_probe (id INTEGER)", "[]")
   2397             .expect("duplicate exec");
   2398         let duplicate_err = resolve_farm(
   2399             &duplicate_pair,
   2400             &RadrootsReplicaFarmSelector {
   2401                 id: None,
   2402                 d_tag: Some(farm_row.d_tag.clone()),
   2403                 pubkey: Some(farm_row.pubkey.clone()),
   2404             },
   2405         )
   2406         .map(|_| ())
   2407         .unwrap_err();
   2408         assert!(
   2409             duplicate_err
   2410                 .to_string()
   2411                 .contains("did not resolve to a single farm")
   2412         );
   2413         assert!(
   2414             resolve_farm(
   2415                 &pass,
   2416                 &RadrootsReplicaFarmSelector {
   2417                     id: Some("00000000-0000-0000-0000-000000000000".to_string()),
   2418                     d_tag: None,
   2419                     pubkey: None,
   2420                 },
   2421             )
   2422             .is_err()
   2423         );
   2424 
   2425         let member_pubkeys = collect_member_pubkeys(&pass, &farm_row.id).expect("member pubkeys");
   2426         assert!(!member_pubkeys.is_empty());
   2427         let profile_pubkeys = collect_profile_pubkeys(&pass, &farm_row).expect("profile pubkeys");
   2428         assert!(!profile_pubkeys.is_empty());
   2429 
   2430         assert!(
   2431             load_relation_by_role(&pass, &farm_row.id, ROLE_PRIMARY, RelationType::Farm)
   2432                 .expect("farm primary relation")
   2433                 .is_some()
   2434         );
   2435         assert!(
   2436             load_relation_by_role(&pass, &farm_row.id, "", RelationType::Farm)
   2437                 .expect("farm fallback relation")
   2438                 .is_some()
   2439         );
   2440         assert!(
   2441             load_relation_by_role(&pass, &plot_secondary.id, "", RelationType::Plot)
   2442                 .expect("plot fallback relation")
   2443                 .is_some()
   2444         );
   2445     }
   2446 
   2447     #[test]
   2448     fn emit_executor_trait_method_paths_are_covered() {
   2449         let sqlite = SqliteExecutor::open_memory().expect("db");
   2450         migrations::run_all_up(&sqlite).expect("migrations");
   2451 
   2452         let err_exec = ErrorExecutor;
   2453         assert!(err_exec.exec("SELECT 1", "[]").is_err());
   2454         assert!(err_exec.query_raw("SELECT 1", "[]").is_err());
   2455         assert!(err_exec.begin().is_ok());
   2456         assert!(err_exec.commit().is_ok());
   2457         assert!(err_exec.rollback().is_ok());
   2458 
   2459         let pass = QueryFailExecutor {
   2460             inner: &sqlite,
   2461             needle: "__never_match__",
   2462             err: SqlError::Internal,
   2463         };
   2464         assert!(pass.exec("PRAGMA foreign_keys = ON", "[]").is_ok());
   2465         assert!(pass.query_raw("SELECT 1", "[]").is_ok());
   2466         assert!(pass.begin().is_ok());
   2467         assert!(pass.commit().is_ok());
   2468 
   2469         let pass_rollback = QueryFailExecutor {
   2470             inner: &sqlite,
   2471             needle: "__never_match__",
   2472             err: SqlError::Internal,
   2473         };
   2474         assert!(pass_rollback.begin().is_ok());
   2475         assert!(pass_rollback.rollback().is_ok());
   2476 
   2477         let fail_query = QueryFailExecutor {
   2478             inner: &sqlite,
   2479             needle: "select 1",
   2480             err: SqlError::Internal,
   2481         };
   2482         assert!(fail_query.query_raw("SELECT 1", "[]").is_err());
   2483     }
   2484 }