pijul: Pushes now work completely While still missing authn and authz, the flow of pushing now works. This enables validation of at least storing data. Later changes will show the data on the project page.
Dependencies
- [2]
W2ZEVC64pijul: HTTP push now works While missing Auth{n,z}, one can now push to the main channel. The patches are stored and normal Pijul command can be run in the repository on disk. - [3]
FS2NWBVNpijul: Start of push/pull work This change includes one API endpoint, .pijul. It allows for getting a channels remote ID. A lot of plumbing around repositories is added too, from init to opening pristine and actions like it. - [4]
TWIZ7QV4db: Add interface to add a project Right now a project has a name, and an owner which is hardcoded to 1. This is because basically I'm speedrunning to implement push/pull of Pijul and then revisit to add depth to features and tests. Model code is now split into files properly too. - [5]
W3M3C7CCInitial commit This change includes a very small hello world application server written in Rust using Rocket.rs. Managing dependencies is done with Nix as that works well between Linux and Mac for me. - [6]
DSWQKJRHusers: Introduce User guard for routes Rockets guards are very powerful to disallow users for certain routes. This far this wasn't implemented, and allowed no-one other than the first user to sign up. This change introduces the User guard and employs it for a few routes. The guard works by checking the encrypted cookie for the user_id, and perform a database lookup on it. - [7]
5UNA2DEAroutes: Register and authenticate users Allow users to sign up, and sign in/sign out. The routes are added, though the design of the pages is very bare bones still, it's hard to go through the full flow to demo. On the server side: Passwords are stored encrypted in the database with salts. This uses the PG encrypt tooling to prevent against bugs and maintainance costs on this project. When a user is signed in, the user ID is set in a private cookie. Rocket has Guards for routes, which has not been implemented yet for this project. - [8]
K4JNAJOFdatabase: Connect to postgres on Rocket boot As database I've chosen PostgreSQL, as my personal experience has been good with it. This change allows Rocket to connect to the database on booting the server. It depends on the DATABASE_URL being set, and for now circumvents the Rocket config helpers as it seemed faster to be up and running this way.
Change contents
- replacement in src/pijul.rs at line 41
let p = match projects::find(db, org_path, proj_path).await {Some(p) => p,None => return None,};let p = projects::find(db, org_path, proj_path).await?; - replacement in src/pijul.rs at line 43
match p.repository(&repoman.storage_root) {Ok(r) => r.changelist(channel, changelist).ok(),println!("changelist");match p.repository(&repoman.storage_root).unwrap().changelist(channel, changelist){Ok(out) => Some(out), - replacement in src/pijul.rs at line 76
let patch_path = p.repository(&repoman.storage_root).unwrap().changestore().change_file(apply).unwrap();let repo = p.repository(&repoman.storage_root).unwrap();let hash =if let Some(h) = crate::models::pijul::changestores::Changestore::hash_from_string(apply) {h} else {return rocket::http::Status::InternalServerError;};let patch_path = repo.changestore().change_file(hash);repo.changestore().ensure_parent_dirs(hash); - replacement in src/pijul.rs at line 87
match patch.persist_to(patch_path).await {Ok(_) => rocket::http::Status::Accepted,Err(_e) => rocket::http::Status::InternalServerError,if patch.persist_to(&patch_path).await.is_err() {return rocket::http::Status::InternalServerError; - edit in src/pijul.rs at line 90
if !repo.valid_change(hash) {// TODO clean upprintln!("Not valid as object");return rocket::http::Status::InternalServerError;}if repo.apply_change_to_channel("main", hash).is_ok() {return rocket::http::Status::Accepted;};rocket::http::Status::InternalServerError - edit in src/pijul.rs at line 103
#[derive(FromForm)]struct IdentitiesReq {identities: Vec<String>,}use rocket::serde::{json::Json, Serialize};#[derive(Serialize)]struct Identities {id: Vec<String>,rev: u64,}// TODO figure out what I'm supposed to send here?#[get("/<org_path>/<proj_path>/.pijul?<id_req>", rank = 3)]fn identities(org_path: String, proj_path: String, id_req: IdentitiesReq) -> Json<Identities> {println!("{:?}", id_req.identities);Json(Identities { id: vec![], rev: 0 })} - replacement in src/pijul.rs at line 125
routes![remote_id, changelist, apply]routes![remote_id, changelist, apply, identities] - edit in src/models/pijul/repositories.rs at line 7
use libpijul::Base32;use libpijul::MutTxnT; - edit in src/models/pijul/repositories.rs at line 9
use libpijul::{Base32, Hash};use libpijul::{MutTxnT, MutTxnTExt}; - edit in src/models/pijul/repositories.rs at line 26
- edit in src/models/pijul/repositories.rs at line 37
}// Checks if the change is on disk, and if we can deserializepub fn valid_change(&self, hash: Hash) -> bool {libpijul::change::Change::deserialize(&self.changestore().change_file(hash).to_string_lossy(),Some(&hash),).is_ok()}// TODO maybe make a proper channel struct etc laterpub fn apply_change_to_channel(&self, chan: &str, change: Hash) -> Result<(), anyhow::Error> {let mut txn = self.pristine()?.mut_txn_begin()?;let chan = if let Some(chan) = txn.load_channel(chan)? {chan} else {bail!("channel not found")};{let mut write_channel = chan.write();txn.apply_change(&libpijul::changestore::filesystem::FileSystem::from_changes(self.changestore().dir(),),&mut write_channel,&change,)?;}txn.commit()?;Ok(()) - replacement in src/models/pijul/repositories.rs at line 77
full_path: full_path,full_path: full_path.join(libpijul::DOT_DIR), - edit in src/models/pijul/repositories.rs at line 143
// TODO validate the txn needs closing?// TODO validate the txn needs closing?//// Or does the drop function do that? - edit in src/models/pijul/repositories.rs at line 150
println!("loaded channel, changes for: {}, from: {}", channel, from); - edit in src/models/pijul/repositories.rs at line 152
println!("found change"); - replacement in src/models/pijul/repositories.rs at line 154
let h: libpijul::Hash = hash.into();let h: Hash = hash.into(); - edit in src/models/pijul/repositories.rs at line 158
println!("{}.{}.{}", offset, h.to_base32(), m.to_base32()); - edit in src/models/pijul/changestores.rs at line 5
use anyhow::bail; - edit in src/models/pijul/changestores.rs at line 13
pub fn hash_from_string(hash: String) -> Option<Hash> {Hash::from_base32(hash.as_bytes())} - edit in src/models/pijul/changestores.rs at line 20
// change_file will ensure parent directories are created thus has side-effects on diskpub fn change_file(&self, hash: String) -> Result<PathBuf, anyhow::Error> {let h = if let Some(h) = Hash::from_base32(hash.as_bytes()) {h} else {bail!("invalid hash to store a change")}; - edit in src/models/pijul/changestores.rs at line 21
pub fn change_file(&self, hash: Hash) -> PathBuf { - replacement in src/models/pijul/changestores.rs at line 23
libpijul::changestore::filesystem::push_filename(&mut path, &h);libpijul::changestore::filesystem::push_filename(&mut path, &hash); - replacement in src/models/pijul/changestores.rs at line 25
std::fs::create_dir_all(path.parent().unwrap())?;path} - replacement in src/models/pijul/changestores.rs at line 28
Ok(path)pub fn ensure_parent_dirs(&self, hash: Hash) -> Result<(), anyhow::Error> {println!("parent dir: {}",self.change_file(hash).parent().unwrap().display());Ok(std::fs::create_dir_all(self.change_file(hash).parent().unwrap(),)?) - replacement in src/models/pijul/changestores.rs at line 38
fn dir(&self) -> PathBuf {// Full path the the changes directory, last path elements are thus .pijul/changespub fn dir(&self) -> PathBuf { - file addition: changes.rs[3.2530]
- edit in Cargo.toml at line 9
serde = "1.0" - replacement in Cargo.toml at line 18
features = ["secrets"]features = ["secrets", "json"] - edit in Cargo.lock at line 1572
"serde", - edit in Cargo.lock at line 2160
"serde_json",