Skip to content

Resources

The ResourceRepository manages modality resources (whiteboards, worksheets, lesson plans, assessments) in PostgreSQL.

Full resource with snapshot bytes (used for single-resource fetches):

pub struct ResourceRow {
pub id: String,
pub name: String,
pub description: Option<String>,
pub modality: String,
pub snapshot: Option<Vec<u8>>,
pub folder_id: Option<String>,
pub cover_image_url: Option<String>,
pub is_public: bool,
pub is_replicable: bool,
pub forked_from: Option<String>,
pub created_by: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub last_viewed_at: Option<DateTime<Utc>>,
}

Without snapshot bytes (used for listings):

pub struct ResourceSummaryRow {
// Same fields as ResourceRow minus `snapshot`
}
pub struct CreateResource {
pub name: String,
pub description: Option<String>,
pub modality: String,
pub snapshot: Option<Vec<u8>>,
pub folder_id: Option<String>,
}
MethodPurpose
get(id)Fetch single resource with snapshot
list(ids)Fetch multiple resources
create(input)Insert new resource
create_with_id(id, input)Insert with specific ID
push_update(id, snapshot)Merge snapshot bytes (Loro document merge)
fork(id, user_id)Fork resource (copy with forked_from reference)
forks_of(id)List all forks of a resource
duplicate_many(ids, user_id)Batch duplicate with new IDs
delete(id)Delete single resource
delete_many(ids)Batch delete
update_last_viewed(id)Touch last-viewed timestamp
list_connection(params)Cursor-based paginated listing

push_update doesn’t replace the snapshot — it merges the incoming bytes with the existing Loro document:

pub async fn push_update(&self, id: &str, snapshot: &[u8]) -> Result<()> {
let existing = self.get(id).await?;
let doc = LoroDoc::new();
if let Some(existing_bytes) = existing.snapshot {
doc.import(&existing_bytes)?;
}
doc.import(snapshot)?;
let merged = doc.export_snapshot();
// UPDATE resources SET snapshot = $1 WHERE id = $2
}

This ensures concurrent edits from multiple clients are merged via Loro CRDT, not overwritten.

list_connection uses keyset pagination for stable cursor-based results:

  1. Decode cursor → (sort_field_value, id)
  2. Apply filter WHERE (sort_field, id) > (cursor_value, cursor_id)
  3. Fetch N+1 rows to determine has_next_page
  4. Return N rows + page info
pub struct ResourceConnectionParams {
pub first: Option<i32>,
pub after: Option<String>,
pub last: Option<i32>,
pub before: Option<String>,
pub sort: ResourceSort,
pub direction: PaginationDirection,
pub filter: Option<ResourceFilterInput>,
}
pub enum ResourceSort {
CreatedAt,
UpdatedAt,
Name,
LastViewed,
Modality,
}
pub struct ResourceConnectionResult {
pub rows: Vec<ResourceSummaryRow>,
pub has_next_page: bool,
pub has_previous_page: bool,
pub total_count: i64,
}
MigrationPurpose
001Create resources table
003Add folder_id to resources
004Add cover_image_url, last_viewed_at
006Add is_public, is_replicable, forked_from
  • Folders — folder organization for resources
  • Bundles — assessment algorithm data
  • GraphQL — resolvers that use this repository
  • Auth — Oso Cloud fact management alongside writes