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:
| M | src/html.rs | | | 76 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | src/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\":[", ¬e.json()?)?;
+
+ if let Ok(blocks) = ndb.get_blocks_by_key(&txn, note_key) {
+ for (i, block) in blocks.iter(¬e).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, ¬e_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, ¬e_rd, r),