add webserver, sbot follow and subscription logic for database
This commit is contained in:
parent
51521a57f9
commit
12b4165029
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "lykin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-std = "1.10"
|
||||
bincode = "1.3"
|
||||
env_logger = "0.9"
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
|
||||
log = "0.4"
|
||||
rocket = "0.5.0-rc.1"
|
||||
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["tera"] }
|
||||
sled = "0.34"
|
|
@ -0,0 +1,84 @@
|
|||
// Key-value store using sled.
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
fmt::{Display, Formatter},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use bincode::Options;
|
||||
use log::{debug, info, warn};
|
||||
use sled::{Db, IVec, Result, Tree};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct IVecString {
|
||||
pub bytes: IVec,
|
||||
pub string: String,
|
||||
}
|
||||
|
||||
impl Display for IVecString {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.string)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IVec> for IVecString {
|
||||
fn from(bytes: IVec) -> Self {
|
||||
Self {
|
||||
string: String::from_utf8_lossy(&bytes).into_owned(),
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Database {
|
||||
/// Stores the sled database instance.
|
||||
db: Db,
|
||||
/// Stores the public keys of all the feeds we are subscribed to.
|
||||
feed_tree: Tree,
|
||||
/// Stores the messages (content and metadata) for all the feeds we are subscribed to.
|
||||
message_tree: Tree,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
// TODO: return Result<Self> and use try operators
|
||||
// implement simple custom error type
|
||||
pub fn init(path: &Path) -> Self {
|
||||
// Open the database at the given path.
|
||||
// The database will be created if it does not yet exist.
|
||||
// This code will panic if an IO error is encountered.
|
||||
info!("initialising the sled database");
|
||||
let db = sled::open(path).expect("failed to open database");
|
||||
debug!("opening the 'feeds' database tree");
|
||||
let feed_tree = db
|
||||
.open_tree("feeds")
|
||||
.expect("failed to open database feeds tree");
|
||||
debug!("opening the 'messages' database tree");
|
||||
let message_tree = db
|
||||
.open_tree("messages")
|
||||
.expect("failed to open database messages tree");
|
||||
|
||||
Database {
|
||||
db,
|
||||
feed_tree,
|
||||
message_tree,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_feed(&self, public_key: &str) -> Result<Option<IVec>> {
|
||||
self.feed_tree.insert(&public_key, vec![0])
|
||||
}
|
||||
|
||||
pub fn remove_feed(&self, public_key: &str) -> Result<Option<IVec>> {
|
||||
self.feed_tree.remove(&public_key)
|
||||
}
|
||||
|
||||
pub fn get_feeds(&self) -> Vec<String> {
|
||||
self.feed_tree
|
||||
.iter()
|
||||
.keys()
|
||||
.map(|bytes| IVecString::from(bytes.unwrap()))
|
||||
.map(|ivec_string| ivec_string.string)
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
mod db;
|
||||
mod sbot;
|
||||
mod utils;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use log::{debug, info, warn};
|
||||
use rocket::{form::Form, get, launch, post, response::Redirect, routes, uri, FromForm, State};
|
||||
use rocket_dyn_templates::{tera::Context, Template};
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
#[derive(FromForm)]
|
||||
struct Peer {
|
||||
public_key: String,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
fn index(db: &State<Database>) -> Template {
|
||||
let mut context = Context::new();
|
||||
let feeds = db.get_feeds();
|
||||
context.insert("feeds", &feeds);
|
||||
|
||||
Template::render("index", &context.into_json())
|
||||
}
|
||||
|
||||
#[post("/subscribe", data = "<peer>")]
|
||||
fn subscribe_form(db: &State<Database>, peer: Form<Peer>) -> Redirect {
|
||||
// validate the public key
|
||||
if let Ok(_) = utils::validate_public_key(&peer.public_key) {
|
||||
debug!("public key {} is valid", &peer.public_key);
|
||||
match db.add_feed(&peer.public_key) {
|
||||
Ok(_) => {
|
||||
debug!("added {} to feed tree in database", &peer.public_key);
|
||||
// check if we already follow the peer
|
||||
// - if not, follow the peer and create a tree for the peer
|
||||
}
|
||||
Err(_e) => warn!(
|
||||
"failed to add {} to feed tree in database",
|
||||
&peer.public_key
|
||||
),
|
||||
}
|
||||
} else {
|
||||
warn!("{} is invalid", &peer.public_key);
|
||||
}
|
||||
|
||||
Redirect::to(uri!(index))
|
||||
}
|
||||
|
||||
#[post("/unsubscribe", data = "<peer>")]
|
||||
fn unsubscribe_form(db: &State<Database>, peer: Form<Peer>) -> Redirect {
|
||||
// validate the public key
|
||||
match utils::validate_public_key(&peer.public_key) {
|
||||
Ok(_) => {
|
||||
debug!("public key {} is valid", &peer.public_key);
|
||||
match db.remove_feed(&peer.public_key) {
|
||||
Ok(_) => debug!("removed {} from feed tree in database", &peer.public_key),
|
||||
Err(_e) => warn!(
|
||||
"failed to remove {} from feed tree in database",
|
||||
&peer.public_key
|
||||
),
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("{} is invalid: {}", &peer.public_key, e),
|
||||
}
|
||||
|
||||
Redirect::to(uri!(index))
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
env_logger::init();
|
||||
|
||||
info!("launching the web server");
|
||||
rocket::build()
|
||||
.manage(Database::init(Path::new("lykin")))
|
||||
.mount("/", routes![index, subscribe_form, unsubscribe_form])
|
||||
.attach(Template::fairing())
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Scuttlebutt functionality.
|
||||
|
||||
use async_std::task;
|
||||
use golgi::Sbot;
|
||||
|
||||
/// Follow a peer.
|
||||
pub fn follow_peer(public_key: &str) -> Result<String, String> {
|
||||
task::block_on(async {
|
||||
let mut sbot_client = Sbot::init(None, None).await.map_err(|e| e.to_string())?;
|
||||
|
||||
match sbot_client.follow(public_key).await {
|
||||
Ok(_) => Ok("Followed peer".to_string()),
|
||||
Err(e) => Err(format!("Failed to follow peer: {}", e)),
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/// Ensure that the given public key is a valid ed25519 key.
|
||||
///
|
||||
/// Return an error string if the key is invalid.
|
||||
pub fn validate_public_key(public_key: &str) -> Result<(), String> {
|
||||
// ensure the id starts with the correct sigil link
|
||||
if !public_key.starts_with('@') {
|
||||
return Err("expected '@' sigil as first character".to_string());
|
||||
}
|
||||
|
||||
// find the dot index denoting the start of the algorithm definition tag
|
||||
let dot_index = match public_key.rfind('.') {
|
||||
Some(index) => index,
|
||||
None => return Err("no dot index was found".to_string()),
|
||||
};
|
||||
|
||||
// check hashing algorithm (must end with ".ed25519")
|
||||
if !&public_key.ends_with(".ed25519") {
|
||||
return Err("hashing algorithm must be ed25519".to_string());
|
||||
}
|
||||
|
||||
// obtain the base64 portion (substring) of the public key
|
||||
let base64_str = &public_key[1..dot_index];
|
||||
|
||||
// length of a base64 encoded ed25519 public key
|
||||
if base64_str.len() != 44 {
|
||||
return Err("base64 data length is incorrect".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<!doctype html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>lykin</title>
|
||||
<meta name="description" content="lykin: an SSB tutorial application">
|
||||
<meta name="author" content="glyph">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<h1>lykin</h1>
|
||||
<form action="/subscribe" method="post" style="width: 500px;">
|
||||
<fieldset>
|
||||
<legend>Subscription Management</legend>
|
||||
<input type="text" id="public_key" name="public_key" size=53 maxlength=53><br>
|
||||
<label for="public_key">Public Key</label><br>
|
||||
<br>
|
||||
<input type="submit" value="Subscribe">
|
||||
<input type="submit" value="Unsubscribe" formaction="/unsubscribe">
|
||||
</fieldset>
|
||||
</form>
|
||||
<div>
|
||||
<h2>Subscriptions</h2>
|
||||
<ul>
|
||||
{% for feed in feeds -%}
|
||||
<li>{{ feed }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue