notecrumbs

a nostr opengraph server build on nostrdb and egui
git clone git://jb55.com/notecrumbs
Log | Files | Refs | README | LICENSE

commit e70a0bf661fd3c67a759ef2df384f968c07c58bb
parent 48c7d9e8cfef67598eb67992af41a1cb46b8548d
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 31 Dec 2024 10:00:26 -0800

api: add note json endpoint

Add a note json endpoint that includes parsed note contents. This
will be useful for external applications, as well as quick-loading
pages when we get to it.

Changelog-Added: Add note json endpoint
Fixes: #19

Diffstat:
Msrc/html.rs | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/main.rs | 18+++++++++++++++++-
2 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/src/html.rs b/src/html.rs @@ -7,10 +7,84 @@ use crate::{ use http_body_util::Full; use hyper::{body::Bytes, header, Request, Response, StatusCode}; use nostr_sdk::prelude::{Nip19, ToBech32}; -use nostrdb::{BlockType, Blocks, Mention, Note, Transaction}; +use nostrdb::{BlockType, Blocks, Filter, Mention, Ndb, Note, Transaction}; use std::io::Write; use tracing::{error, warn}; +fn blocktype_name(blocktype: &BlockType) -> &'static str { + match blocktype { + BlockType::MentionBech32 => "mention", + BlockType::Hashtag => "hashtag", + BlockType::Url => "url", + BlockType::Text => "text", + BlockType::MentionIndex => "indexed_mention", + BlockType::Invoice => "invoice", + } +} + +pub fn serve_note_json( + ndb: &Ndb, + note_rd: &NoteAndProfileRenderData, +) -> Result<Response<Full<Bytes>>, Error> { + let mut body: Vec<u8> = vec![]; + + let note_key = match note_rd.note_rd { + NoteRenderData::Note(note_key) => note_key, + NoteRenderData::Missing(note_id) => { + warn!("missing note_id {}", hex::encode(note_id)); + return Err(Error::NotFound); + } + }; + + let txn = Transaction::new(ndb)?; + + let note = if let Ok(note) = ndb.get_note_by_key(&txn, note_key) { + note + } else { + // 404 + return Err(Error::NotFound); + }; + + write!(body, "{{\"note\":{},\"parsed_content\":[", &note.json()?)?; + + if let Ok(blocks) = ndb.get_blocks_by_key(&txn, note_key) { + for (i, block) in blocks.iter(&note).enumerate() { + if i != 0 { + write!(body, ",")?; + } + write!( + body, + "{{\"{}\":{}}}", + blocktype_name(&block.blocktype()), + serde_json::to_string(block.as_str())? + )?; + } + }; + + write!(body, "]")?; + + if let Ok(results) = ndb.query( + &txn, + &[Filter::new() + .authors([note.pubkey()]) + .kinds([0]) + .limit(1) + .build()], + 1, + ) { + if let Some(profile_note) = results.first() { + write!(body, ",\"profile\":{}", profile_note.note.json()?)?; + } + } + + writeln!(body, "}}")?; + + Ok(Response::builder() + .header(header::CONTENT_TYPE, "application/json; charset=utf-8") + .status(StatusCode::OK) + .body(Full::new(Bytes::from(body)))?) +} + pub fn render_note_content(body: &mut Vec<u8>, note: &Note, blocks: &Blocks) { for block in blocks.iter(note) { match block.blocktype() { diff --git a/src/main.rs b/src/main.rs @@ -122,7 +122,14 @@ async fn serve( r: Request<hyper::body::Incoming>, ) -> Result<Response<Full<Bytes>>, Error> { let is_png = r.uri().path().ends_with(".png"); - let until = if is_png { 4 } else { 0 }; + let is_json = r.uri().path().ends_with(".json"); + let until = if is_png { + 4 + } else if is_json { + 5 + } else { + 0 + }; let path_len = r.uri().path().len(); let nip19 = match Nip19::from_bech32(&r.uri().path()[1..path_len - until]) { @@ -166,6 +173,15 @@ async fn serve( .header(header::CONTENT_TYPE, "image/png") .status(StatusCode::OK) .body(Full::new(Bytes::from(data)))?) + } else if is_json { + match render_data { + RenderData::Note(note_rd) => html::serve_note_json(&app.ndb, &note_rd), + RenderData::Profile(_profile_rd) => { + return Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("todo: profile json")))?); + } + } } else { match render_data { RenderData::Note(note_rd) => html::serve_note_html(app, &nip19, &note_rd, r),