diff --git a/src/possum/tangled.gleam b/src/possum/tangled.gleam index 1a0005f..4fc3365 100644 --- a/src/possum/tangled.gleam +++ b/src/possum/tangled.gleam @@ -1,17 +1,17 @@ import gleam/http/request import gleam/int import gleam/option -import gleam/order import gleam/string import possum/did const api = "api.tangled.org" /// Namespaced Identifiers (NSIDs) are used to reference Lexicon schemas for -/// records, XRPC endpoints, and more. For example, com.atproto.sync.getRecord. +/// records, XRPC endpoints, and more pub type Nsid { Issue PullRequest + Repo String } @@ -20,6 +20,7 @@ pub fn nsid_to_string(nsid: Nsid) -> String { case nsid { Issue -> "sh.tangled.repo.issue" PullRequest -> "sh.tangled.repo.pull" + Repo -> "sh.tangled.repo" String -> "sh.tangled.string" } } @@ -34,16 +35,12 @@ pub fn nsid_to_string(nsid: Nsid) -> String { /// import possum/tangled /// /// // DID of the user that created the record -/// let assert Ok(did) = did.parse("did:plc:xu5apv6kmu5jp7g5hwdnej42") +/// let assert Ok(did) = did.parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz") +/// let rkey = "3mp2gobkoy722" +/// let nsid = tangled.PullRequest /// /// request.new() -/// |> tangled.list_comments( -/// did, -/// tangled.PullRequest, -/// "3mp2gobkoy722", -/// option.None, -/// option.None, -/// ) +/// |> tangled.list_comments(did, nsid, rkey, option.None, option.None) /// ``` pub fn list_comments( request: request.Request(_), @@ -53,6 +50,8 @@ pub fn list_comments( cursor: option.Option(String), limit: option.Option(Int), ) -> request.Request(_) { + let endpoint = ".feed.listComments" + let subject = string.join([did.to_string(did), nsid_to_string(nsid), rkey], "/") @@ -71,6 +70,138 @@ pub fn list_comments( request |> request.set_host(api) |> request.set_header("accept", "application/json") - |> request.set_path("/xrpc/sh.tangled.feed.listComments") + |> request.set_path("/xrpc/sh.tangled" <> endpoint) + |> request.set_query(query) +} + +/// List reactions attached to a specific record +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/http/request +/// import possum/did +/// import possum/tangled +/// +/// // DID of the user that created the record +/// let assert Ok(did) = did.parse("did:plc:qfpnj4og54vl56wngdriaxug") +/// let rkey = "3mq2pk76ge222" +/// let nsid = tangled.PullRequest +/// +/// request.new() +/// |> tangled.list_reactions(did, nsid, rkey, option.None, option.None) +/// ``` +pub fn list_reactions( + request: request.Request(_), + did: did.Did, + nsid: Nsid, + rkey: String, + cursor: option.Option(String), + limit: option.Option(Int), +) -> request.Request(_) { + let endpoint = ".feed.listReactions" + + let subject = + string.join([did.to_string(did), nsid_to_string(nsid), rkey], "/") + + let query = [#("subject", "at://" <> subject)] + + let query = case cursor { + option.Some(value) -> [#("cursor", value), ..query] + option.None -> query + } + + let query = case limit { + option.Some(value) -> [#("limit", int.to_string(value)), ..query] + option.None -> query + } + + request + |> request.set_host(api) + |> request.set_header("accept", "application/json") + |> request.set_path("/xrpc/sh.tangled" <> endpoint) + |> request.set_query(query) +} + +/// List stars attached to a specific repository. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/http/request +/// import possum/did +/// import possum/tangled +/// +/// // DID of the user that created the record +/// let assert Ok(did) = did.parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz") +/// +/// request.new() +/// |> tangled.list_stars(did, option.None, option.None) +/// ``` +pub fn list_stars( + request: request.Request(_), + did: did.Did, + cursor: option.Option(String), + limit: option.Option(Int), +) -> request.Request(_) { + let endpoint = ".feed.listStars" + let query = [#("subject", did.to_string(did))] + + let query = case cursor { + option.Some(value) -> [#("cursor", value), ..query] + option.None -> query + } + + let query = case limit { + option.Some(value) -> [#("limit", int.to_string(value)), ..query] + option.None -> query + } + + request + |> request.set_host(api) + |> request.set_header("accept", "application/json") + |> request.set_path("/xrpc/sh.tangled" <> endpoint) + |> request.set_query(query) +} + +/// List ref-updates attached to a specific repository. +/// +/// ## Examples +/// +/// ```gleam +/// import gleam/http/request +/// import possum/did +/// import possum/tangled +/// +/// // DID of the repository +/// let assert Ok(did) = did.parse("did:plc:f2s2nd3tnw75so75ndrns6s2") +/// +/// request.new() +/// |> tangled.list_ref_updates(did, option.None, option.None) +/// ``` +pub fn list_ref_updates( + request: request.Request(_), + did: did.Did, + cursor: option.Option(String), + limit: option.Option(Int), +) -> request.Request(_) { + let endpoint = ".git.listRefUpdates" + + let query = [#("subject", did.to_string(did))] + + let query = case cursor { + option.Some(value) -> [#("cursor", value), ..query] + option.None -> query + } + + let query = case limit { + option.Some(value) -> [#("limit", int.to_string(value)), ..query] + option.None -> query + } + + request + |> request.set_host(api) + |> request.set_header("accept", "application/json") + |> request.set_path("/xrpc/sh.tangled" <> endpoint) |> request.set_query(query) } diff --git a/test/possum_tangled_test.gleam b/test/possum_tangled_test.gleam index b82bddc..4b04b4c 100644 --- a/test/possum_tangled_test.gleam +++ b/test/possum_tangled_test.gleam @@ -2,7 +2,7 @@ import gleam/dynamic/decode import gleam/http/request import gleam/httpc import gleam/json -import gleam/result +import gleam/option import gleeunit import possum/did import possum/tangled @@ -11,27 +11,74 @@ pub fn main() { gleeunit.main() } +fn lexicon_decoder(lexicon: String) -> decode.Decoder(List(Nil)) { + let list_decoder = + decode.list({ + use _ <- decode.field("uri", decode.string) + use _ <- decode.field("cid", decode.string) + + use string <- decode.subfield(["value", "$type"], decode.string) + case string { + string if string == lexicon -> decode.success(Nil) + _ -> decode.failure(Nil, "$type") + } + }) + + use items <- decode.field("items", list_decoder) + case items { + [] -> decode.failure([], "non-empty list") + some -> decode.success(some) + } +} + /// at://did:plc:xu5apv6kmu5jp7g5hwdnej42/sh.tangled.repo.pull/3mp2gobkoy722 pub fn list_comments_test() -> Nil { let assert Ok(did) = did.parse("did:plc:xu5apv6kmu5jp7g5hwdnej42") + let rkey = "3mp2gobkoy722" + let nsid = tangled.PullRequest let assert Ok(response) = request.new() - |> tangled.list_comments(did, tangled.PullRequest, "3mp2gobkoy722") + |> tangled.list_comments(did, nsid, rkey, option.None, option.None) |> httpc.send - let lexicon_decoder = { - use lexicon <- decode.subfield(["value", "$type"], decode.string) - case lexicon { - "sh.tangled.feed.comment" -> decode.success(Nil) - _ -> decode.failure(Nil, "$type") - } - } + let assert Ok(_) = + response.body + |> json.parse(lexicon_decoder("sh.tangled.feed.comment")) - let decoder = { - use _ <- decode.field("items", decode.list(lexicon_decoder)) - decode.success(Nil) - } + Nil +} + +// at://did:plc:qfpnj4og54vl56wngdriaxug/sh.tangled.repo.pull/3mq2pk76ge222 +// pub fn list_reactions_test() -> Nil { +// let assert Ok(did) = did.parse("did:plc:qfpnj4og54vl56wngdriaxug") +// let rkey = "3mq2pk76ge222" +// let nsid = tangled.PullRequest +// +// let assert Ok(response) = +// request.new() +// |> tangled.list_reactions(did, nsid, rkey, option.None, option.None) +// |> httpc.send +// +// let assert Ok(_) = +// response.body +// |> json.parse(lexicon_decoder("sh.tangled.feed.reaction")) +// +// Nil +// } + +/// https://api.tangled.org/xrpc/sh.tangled.feed.listStars?subject=at://did:plc:f2s2nd3tnw75so75ndrns6s2 +pub fn list_stars_test() -> Nil { + let assert Ok(did) = did.parse("did:plc:f2s2nd3tnw75so75ndrns6s2") + + let assert Ok(response) = + request.new() + |> tangled.list_stars(did, option.None, option.None) + |> httpc.send + + let assert Ok(_) = + response.body + |> json.parse(lexicon_decoder("sh.tangled.feed.star")) - assert json.parse(response.body, decoder) |> result.is_ok + Nil }