diff options
-rw-r--r-- | adenosine-pds/Cargo.toml | 2 | ||||
-rw-r--r-- | adenosine-pds/src/bsky.rs | 2 | ||||
-rw-r--r-- | adenosine-pds/src/lib.rs | 104 | ||||
-rw-r--r-- | adenosine-pds/src/models.rs | 2 | ||||
-rw-r--r-- | adenosine-pds/src/repo.rs | 2 | ||||
-rw-r--r-- | adenosine-pds/src/web.rs | 59 | ||||
-rw-r--r-- | adenosine-pds/templates/account.html | 36 | ||||
-rw-r--r-- | adenosine-pds/templates/adenosine.css | 74 | ||||
-rw-r--r-- | adenosine-pds/templates/at_collection.html | 7 | ||||
-rw-r--r-- | adenosine-pds/templates/at_record.html | 7 | ||||
-rw-r--r-- | adenosine-pds/templates/at_repo.html | 16 | ||||
-rw-r--r-- | adenosine-pds/templates/base.html | 10 | ||||
-rw-r--r-- | adenosine-pds/templates/macro.html | 41 | ||||
-rw-r--r-- | adenosine-pds/templates/post.html | 5 | ||||
-rw-r--r-- | adenosine-pds/templates/profile.html | 7 | ||||
-rw-r--r-- | adenosine-pds/templates/thread.html | 8 |
16 files changed, 276 insertions, 106 deletions
diff --git a/adenosine-pds/Cargo.toml b/adenosine-pds/Cargo.toml index 1637164..7118956 100644 --- a/adenosine-pds/Cargo.toml +++ b/adenosine-pds/Cargo.toml @@ -43,7 +43,7 @@ ucan = "0.7.0-alpha.1" bs58 = "*" async-trait = "*" dotenv = "*" -askama = "0.11" +askama = { version = "0.11", features = ["serde-json"] } time = { version = "*", features = ["formatting"] } # for vendored iroh-car diff --git a/adenosine-pds/src/bsky.rs b/adenosine-pds/src/bsky.rs index 66b05eb..a4d025b 100644 --- a/adenosine-pds/src/bsky.rs +++ b/adenosine-pds/src/bsky.rs @@ -248,7 +248,7 @@ pub fn bsky_get_thread( _srv: &mut AtpService, _uri: &AtUri, _depth: Option<u64>, -) -> Result<GenericFeed> { +) -> Result<PostThread> { // TODO: what is the best way to implement this? recurisvely? just first-level children to // start? unimplemented!() diff --git a/adenosine-pds/src/lib.rs b/adenosine-pds/src/lib.rs index 7b517f2..894f4a2 100644 --- a/adenosine-pds/src/lib.rs +++ b/adenosine-pds/src/lib.rs @@ -172,10 +172,10 @@ impl AtpService { (GET) ["/"] => { let host = request.header("Host").unwrap_or("localhost"); if Some(host.to_string()) == config.registration_domain { + web_wrap(account_view_handler(&srv, &host, request)) + } else { let view = GenericHomeView { domain: host.to_string() }; Response::html(view.render().unwrap()) - } else { - web_wrap(home_profile_handler(&srv, request)) } }, (GET) ["/about"] => { @@ -184,19 +184,19 @@ impl AtpService { Response::html(view.render().unwrap()) }, (GET) ["/u/{handle}", handle: String] => { - web_wrap(profile_handler(&srv, &handle, request)) + web_wrap(account_view_handler(&srv, &handle, request)) }, - (GET) ["/u/{did}/{collection}/{tid}", did: Did, collection: Nsid, tid: Tid] => { - web_wrap(post_handler(&srv, &did, &collection, &tid, request)) + (GET) ["/u/{did}/post/{tid}", did: Did, tid: Tid] => { + web_wrap(thread_view_handler(&srv, &did, &tid, request)) }, (GET) ["/at/{did}", did: Did] => { - web_wrap(repo_handler(&srv, &did, request)) + web_wrap(repo_view_handler(&srv, &did, request)) }, (GET) ["/at/{did}/{collection}", did: Did, collection: Nsid] => { - web_wrap(collection_handler(&srv, &did, &collection, request)) + web_wrap(collection_view_handler(&srv, &did, &collection, request)) }, (GET) ["/at/{did}/{collection}/{tid}", did: Did, collection: Nsid, tid: Tid] => { - web_wrap(record_handler(&srv, &did, &collection, &tid, request)) + web_wrap(record_view_handler(&srv, &did, &collection, &tid, request)) }, // ============ Static Files (compiled in to executable) (GET) ["/static/adenosine.css"] => { @@ -660,77 +660,57 @@ fn xrpc_post_handler( } } -fn home_profile_handler(srv: &Mutex<AtpService>, request: &Request) -> Result<String> { - let host = request.header("Host").unwrap_or("localhost"); - // XXX - let did = Did::from_str(host)?; - let mut _srv = srv.lock().expect("service mutex"); - - // TODO: get profile (bsky helper) - // TODO: get feed (bsky helper) - - Ok(ProfileView { - domain: host.to_string(), - did: did, - profile: json!({}), - feed: vec![], - } - .render()?) -} - // TODO: did, collection, tid have already been parsed by this point -fn profile_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result<String> { +fn account_view_handler( + srv: &Mutex<AtpService>, + handle: &str, + request: &Request, +) -> Result<String> { let host = request.header("Host").unwrap_or("localhost"); - let did = Did::from_str(did)?; - let mut _srv = srv.lock().expect("service mutex"); - - // TODO: get profile (bsky helper) - // TODO: get feed (bsky helper) + let mut srv = srv.lock().expect("service mutex"); + // TODO: unwrap as 404 + let did = srv + .atp_db + .resolve_handle(handle)? + .ok_or(XrpcError::NotFound(format!( + "no DID found for handle: {}", + handle + )))?; - Ok(ProfileView { + Ok(AccountView { domain: host.to_string(), - did: did, - profile: json!({}), - feed: vec![], + did: did.clone(), + profile: bsky_get_profile(&mut srv, &did)?, + feed: bsky_get_author_feed(&mut srv, &did)?.feed, } .render()?) } -fn post_handler( +fn thread_view_handler( srv: &Mutex<AtpService>, - did: &str, - collection: &str, - tid: &str, + handle: &str, + tid: &Tid, request: &Request, ) -> Result<String> { let host = request.header("Host").unwrap_or("localhost"); - let did = Did::from_str(did)?; - let collection = Nsid::from_str(collection)?; - let rkey = Tid::from_str(tid)?; + let collection = Nsid::from_str("app.bsky.feed.post")?; let mut srv = srv.lock().expect("service mutex"); + // TODO: not unwrap + let did = srv.atp_db.resolve_handle(handle)?.unwrap(); - let post = match srv.repo.get_atp_record(&did, &collection, &rkey) { - // TODO: format as JSON, not text debug - Ok(Some(ipld)) => ipld_into_json_value(ipld), - Ok(None) => Err(anyhow!(XrpcError::NotFound(format!( - "could not find record: /{}/{}", - collection, rkey - ))))?, - Err(e) => Err(e)?, - }; - - Ok(PostView { + // TODO: could construct URI directly + let uri = AtUri::from_str(&format!("at://{}/{}/{}", did, collection, tid))?; + Ok(ThreadView { domain: host.to_string(), - did: did, - collection: collection, - tid: rkey, - post_text: post["text"].as_str().unwrap().to_string(), // TODO: unwrap - post_created_at: "some-time".to_string(), + did, + collection, + tid: tid.clone(), + thread: bsky_get_thread(&mut srv, &uri, None)?.thread, } .render()?) } -fn repo_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result<String> { +fn repo_view_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result<String> { let host = request.header("Host").unwrap_or("localhost"); let did = Did::from_str(did)?; @@ -756,7 +736,7 @@ fn repo_handler(srv: &Mutex<AtpService>, did: &str, request: &Request) -> Result .render()?) } -fn collection_handler( +fn collection_view_handler( srv: &Mutex<AtpService>, did: &str, collection: &str, @@ -794,7 +774,7 @@ fn collection_handler( .render()?) } -fn record_handler( +fn record_view_handler( srv: &Mutex<AtpService>, did: &str, collection: &str, diff --git a/adenosine-pds/src/models.rs b/adenosine-pds/src/models.rs index 0f47c2d..3af780e 100644 --- a/adenosine-pds/src/models.rs +++ b/adenosine-pds/src/models.rs @@ -155,7 +155,7 @@ pub struct PostReply { #[allow(non_snake_case)] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)] pub struct PostThread { - pub thread: ThreadItem, + pub thread: Vec<ThreadItem>, } #[allow(non_snake_case)] diff --git a/adenosine-pds/src/repo.rs b/adenosine-pds/src/repo.rs index 1b24be8..9f5fca0 100644 --- a/adenosine-pds/src/repo.rs +++ b/adenosine-pds/src/repo.rs @@ -17,7 +17,7 @@ use std::collections::HashSet; use std::path::PathBuf; use std::str::FromStr; -#[derive(Debug)] +#[derive(Debug, serde::Serialize)] pub struct RepoCommit { pub sig: Box<[u8]>, pub commit_cid: Cid, diff --git a/adenosine-pds/src/web.rs b/adenosine-pds/src/web.rs index e783b5a..5b9d6de 100644 --- a/adenosine-pds/src/web.rs +++ b/adenosine-pds/src/web.rs @@ -17,23 +17,22 @@ pub struct AboutView { } #[derive(Template)] -#[template(path = "profile.html")] -pub struct ProfileView { +#[template(path = "account.html")] +pub struct AccountView { pub domain: String, pub did: Did, - pub profile: serde_json::Value, - pub feed: Vec<serde_json::Value>, + pub profile: Profile, + pub feed: Vec<FeedItem>, } #[derive(Template)] -#[template(path = "post.html")] -pub struct PostView { +#[template(path = "thread.html")] +pub struct ThreadView { pub domain: String, pub did: Did, pub collection: Nsid, pub tid: Tid, - pub post_text: String, - pub post_created_at: String, + pub thread: Vec<ThreadItem>, } #[derive(Template)] @@ -63,3 +62,47 @@ pub struct RecordView { pub tid: Tid, pub record: serde_json::Value, } + +mod filters { + use crate::AtUri; + use std::str::FromStr; + + pub fn aturi_to_path(aturi: &str) -> ::askama::Result<String> { + let aturi = AtUri::from_str(aturi).expect("aturi parse"); + if aturi.record.is_some() { + Ok(format!( + "/at/{}/{}/{}", + aturi.repository, + aturi.collection.unwrap(), + aturi.record.unwrap() + )) + } else if aturi.collection.is_some() { + Ok(format!( + "/at/{}/{}", + aturi.repository, + aturi.collection.unwrap() + )) + } else { + Ok(format!("/at/{}", aturi.repository)) + } + } + + pub fn aturi_to_thread_path(aturi: &str) -> ::askama::Result<String> { + let aturi = AtUri::from_str(aturi).expect("aturi parse"); + Ok(format!( + "/u/{}/post/{}", + aturi.repository, + aturi.record.unwrap() + )) + } + + pub fn aturi_to_tid(aturi: &str) -> ::askama::Result<String> { + let aturi = AtUri::from_str(aturi).expect("aturi parse"); + if aturi.record.is_some() { + Ok(aturi.record.unwrap()) + } else { + // TODO: raise an askama error here? + Ok("<MISSING>".to_string()) + } + } +} diff --git a/adenosine-pds/templates/account.html b/adenosine-pds/templates/account.html new file mode 100644 index 0000000..bd015d5 --- /dev/null +++ b/adenosine-pds/templates/account.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% import "macro.html" as macro %} + +{% block main %} + +<article class="profile"> +<h4> + {% if profile.displayName.is_some() %} + <span class="display_name">{{ profile.displayName.as_ref().unwrap() }}</span> + {% endif %} + <span class="handle">@{{ profile.handle }}</span> +</h4> +<a href="/at/{{ profile.did }}" class="ident">{{ profile.did }}</a> + +{% if profile.description.is_some() %} + <p>{{ profile.description.as_ref().unwrap() }} +{% endif %} + +<p class="counts"> + [<a href="#">{{ profile.followersCount}} followers</a> / + <a href="#">{{ profile.followsCount }} follows</a>] +</p> +</article> + +{% if feed.len() == 0 %} + <center><i>--- no posts yet! ---</i></center> +{% else %} + <center><i>--- showing {{ feed.len() }} of {{ profile.postsCount }} posts</i> ---</center> +{% endif %} + +{% for item in feed %} + {% call macro::feed_item(item) %} +{% endfor %} + + +{% endblock %} diff --git a/adenosine-pds/templates/adenosine.css b/adenosine-pds/templates/adenosine.css index 0686f9e..9a0f890 100644 --- a/adenosine-pds/templates/adenosine.css +++ b/adenosine-pds/templates/adenosine.css @@ -622,5 +622,75 @@ progress:indeterminate::-moz-progress-bar { } /********** adenosine tweaks **********/ -body { font-family: var(--mono-font); } -a { text-decoration: none; } +body { + font-family: var(--mono-font); +} +a { + text-decoration: none; +} +main { + font-size: smaller; + padding-top: 0px; +} +h2 { + margin-top: 0px; + margin-bottom: 1rem; + font-size: 2.5em; +} +nav.header { + border-bottom: 2px dashed var(--border); +} +nav.header img { + vertical-align: middle; +} +nav.header span { + vertical-align: middle; + color: var(--text); +} +nav.header h1 { + font-weight: normal; + font-size: 2rem; + margin-top: 1rem; + margin-bottom: 0.5rem; +} +.ident, a:hover.ident, a:visited.ident { + color: var(--text-light); +} +.smaller { + font-size: smaller; +} +body footer { + border-top: 2px dashed var(--border); + padding: 1rem 1rem 1rem 1rem; +} +.counts { + color: var(--text-light); +} +.counts a { + color: var(--text-light); +} +p.counts { + margin-bottom: 0px; +} +.display_name { + color: green; + font-weight: normal; +} +.handle { + color: green; + font-weight: bold; +} +.feed_item { + margin-top: 1rem; + margin-bottom: 1rem; +} +.profile h4 { + margin-top: 0px; + margin-bottom: 0px; +} +.repo_aturi, .repo_aturi a, .repo_aturi a:visited { + color: var(--code); + font-size: smaller; + overflow: auto; + font-weight: bold; +} diff --git a/adenosine-pds/templates/at_collection.html b/adenosine-pds/templates/at_collection.html index b4b7036..670cb0d 100644 --- a/adenosine-pds/templates/at_collection.html +++ b/adenosine-pds/templates/at_collection.html @@ -1,12 +1,13 @@ {% extends "base.html" %} {% block main %} -<p><b>at://{{ did }}/{{ collection }}/</b> +<h2>ATP Repository Explorer</h2> +<div class="repo_aturi">at://<a href="/at/{{ did }}">{{ did }}</a>/{{ collection }}/</div> -<p><b>records:</b> +<h4>Records</h4> <ul> {% for record in records %} - <li><a href="/at/{{did}}/{{collection}}/{{record["tid"].as_str().unwrap() }}">{{ record["tid"].as_str().unwrap() }}</a> + <li>/{{ collection }}/<a href="/at/{{did}}/{{collection}}/{{record["tid"].as_str().unwrap() }}">{{ record["tid"].as_str().unwrap() }}</a>/ {% endfor %} </ul> {% endblock %} diff --git a/adenosine-pds/templates/at_record.html b/adenosine-pds/templates/at_record.html index 10ed838..829ed4a 100644 --- a/adenosine-pds/templates/at_record.html +++ b/adenosine-pds/templates/at_record.html @@ -1,8 +1,9 @@ {% extends "base.html" %} {% block main %} -<p><b>at://{{ did }}/{{ collection }}/{{ tid }}</b> +<h2>ATP Repository Explorer</h2> +<div class="repo_aturi">at://<a href="/at/{{ did }}">{{ did }}</a>/<a href="/at/{{ did }}/{{ collection }}">{{ collection }}</a>/{{ tid }}</div> -<p><b>record json:</b> -<pre><code>{{ record }}</code></pre> +<h4>JSON</h4> +<pre><code>{{ record|json }}</code></pre> {% endblock %} diff --git a/adenosine-pds/templates/at_repo.html b/adenosine-pds/templates/at_repo.html index dcf4d87..384b8dc 100644 --- a/adenosine-pds/templates/at_repo.html +++ b/adenosine-pds/templates/at_repo.html @@ -1,18 +1,20 @@ {% extends "base.html" %} {% block main %} -<p><b>at://{{ did }}/</b> +<h2>ATP Repository Explorer</h2> +<div class="repo_aturi">at://{{ did }}/</div> -<p><b>collections:</b> +<h4>Collections</h4> <ul> {% for collection in describe.collections %} - <li><a href="/at/{{ did }}/{{ collection }}">{{collection}}/</a> + <li>/<a href="/at/{{ did }}/{{ collection }}">{{collection}}</a>/ {% endfor %} </ul> -<p>repo commit: -<pre><code>{{ "{:?}"|format(commit) }}</code></pre> +<h4>Describe</h4> +<pre><code>{{ describe|json }}</code></pre> + +<h4>Commit Node</h4> +<pre><code>{{ commit|json }}</code></pre> -<p>repo describe -<pre><code>{{ "{:?}"|format(describe) }}</code></pre> {% endblock %} diff --git a/adenosine-pds/templates/base.html b/adenosine-pds/templates/base.html index 0ae2061..cb88c0c 100644 --- a/adenosine-pds/templates/base.html +++ b/adenosine-pds/templates/base.html @@ -9,11 +9,11 @@ {% block head %}{% endblock %} </head> <body> - <nav> + <nav class="header"> <a href="/"> - <h3> - <img src="/static/logo_128.png" width="64" height="64">{{ domain }} - </h3> + <h1> + <img src="/static/logo_128.png" width="48" height="48"><span> {{ domain }}</span> + </h1> </a> </nav> <hr> @@ -25,7 +25,7 @@ <nav> <a href="/">home</a> - <a href="/about">about</a> - - <a href="https://gitlab.com/bnewbold/adenosine">source code</a> + <a href="https://gitlab.com/bnewbold/adenosine">adenosine v{{ env!("CARGO_PKG_VERSION") }}</a> </nav> </footer> </body> diff --git a/adenosine-pds/templates/macro.html b/adenosine-pds/templates/macro.html new file mode 100644 index 0000000..1d38482 --- /dev/null +++ b/adenosine-pds/templates/macro.html @@ -0,0 +1,41 @@ + +{% macro feed_item(item) %} + +<div class="feed_item"> +{% if item.repostedBy.is_some() %} + {% if item.author.displayName.is_some() %}{{ item.author.displayName.as_ref().unwrap() }}{% endif %} + <b>@{{ item.author.handle }}</b> +{% endif %} + +<div style="float: right;"> + <a class="item_timestamp" href="/u/{{ item.author.handle }}/post/{{ item.uri|aturi_to_tid }}"> + {% if item.record["createdAt"].as_str().is_some() %} + {{ item.record["createdAt"].as_str().unwrap() }} + {% else %} + {{ item.indexedAt }} + {% endif %} + </a> +</div> + + +{% if item.author.displayName.is_some() %} + <a href="/u/{{ item.author.handle }}"><span class="display_name">{{ item.author.displayName.as_ref().unwrap() }}</span></a> +{% endif %} +<a href="/u/{{ item.author.handle }}"><span class="handle">@{{ item.author.handle }}</span></a> +<br> +{{ item.record["text"].as_str().unwrap() }} +<br> +<span class="counts"> + [<a href="#">{{ item.likeCount }} like</a> / <a href="#">{{ item.repostCount }} repost</a> / <a href="#">{{ item.replyCount }} reply</a>] +</span> + +{% if item.record.get("reply").is_some() %} +<br> +<b style="color: orange;">reply to:</b> <a href="{{ item.record["reply"]["uri"].as_str().unwrap()|aturi_to_thread_path }}">{{ item.record["reply"]["uri"] }}</a> +{% endif %} + +<!-- TODO: "reposted by" --> +<!-- TODO: "reply to" --> + +</div> +{% endmacro %} diff --git a/adenosine-pds/templates/post.html b/adenosine-pds/templates/post.html deleted file mode 100644 index d7e6c85..0000000 --- a/adenosine-pds/templates/post.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base.html" %} - -{% block post %} -Post page (TODO) -{% endblock %} diff --git a/adenosine-pds/templates/profile.html b/adenosine-pds/templates/profile.html deleted file mode 100644 index 7c17951..0000000 --- a/adenosine-pds/templates/profile.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.html" %} - -{% block main %} -<p><b>at://{{ did }}/</b> - -<p>Profile page (TODO) -{% endblock %} diff --git a/adenosine-pds/templates/thread.html b/adenosine-pds/templates/thread.html new file mode 100644 index 0000000..e2e2e96 --- /dev/null +++ b/adenosine-pds/templates/thread.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% import "macro.html" as macro %} + +{% block post %} + +Post stuff will go here + +{% endblock %} |