routes: Move pijul routes to controllers module As with the root routes, now the pijul routes are moved. The mounting of the routes is still done in main.rs though the controller module now collects them. This should DRY this code

zj
Sep 21, 2021, 8:44 AM
S6TFYMRGWC4PSRLQ4OAKE2Z3JFEVGF3RZ5Z3MEN5XSA4INPQILOAC

Dependencies

  • [2] HOAKL56V pijul: GET change works, now clone and pull works too Pull/Clone depends on routes already implemented for content negotiation, and thus was easy to get working. Again, this route lacks authentication and authorization.
  • [3] 3E77DEMD routes: Move root route to controllers module The routes were now in modules in the root, which created a messy situation for the future. For now the routes will be moved to the controllers module and later additional changes will be done to further clean this up.
  • [4] KULVODXD routes: User route mod exposes routes() now Other routes already did this to keep the routes clean when mounting. Now the user routes do too.
  • [5] W3M3C7CC Initial 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] ZGXZ2IRI Cleanup leftover files Recorded earlier, but uninteded. Now it's cleaned up again.
  • [7] 5UNA2DEA routes: 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] WSHUT37C 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.
  • [9] DSWQKJRH users: 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.
  • [10] KFVJ3KMW frontend: Introduce navigation bar Minor changes to the front-end mostly, to allow users to register, sign in, and sign out. The sign out route is changed to a GET endpoint, as links in HTML cannot DELETE.
  • [11] FS2NWBVN pijul: 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.
  • [12] TWIZ7QV4 db: 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.
  • [13] W2ZEVC64 pijul: 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.
  • [14] K4JNAJOF database: 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

  • file deletion: pijul.rs (----------)
    [4.179][4.367:399](),[4.399][4.400:400]()
    use crate::database::Database;
    use crate::models::projects;
    use crate::repoman::RepoMan;
    use rocket::{Route, State};
    channel: String, // TODO check if this breaks non-UTF-8 channels
    id: Option<String>,
    ) -> Option<String> {
    let p = match projects::find(db, org_path, proj_path).await {
    Some(p) => p,
    None => return None,
    };
    match p.repository(&repoman.storage_root) {
    Ok(r) => r.channel_remote_id(channel, id),
    Err(e) => {
    println!("{}", e);
    None
    }
    }
    }
    async fn changelist(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    ) -> Option<String> {
    Err(e) => {
    None
    }
    }
    }
    pub fn routes() -> Vec<Route> {
    }
    routes![remote_id, changelist, apply, identities, change]
    // TODO pijul: When this endpoint returns a 4XX or 5XX status code, it thinks the push still
    // succeeded.
    #[post("/<org_path>/<proj_path>/.pijul?<apply>", data = "<patch>")]
    async fn apply(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    apply: String,
    mut patch: TempFile<'_>,
    ) -> rocket::http::Status {
    let p = if let Some(p) = projects::find(db, org_path, proj_path).await {
    p
    } else {
    return rocket::http::Status::NotFound;
    };
    }
    }
    #[derive(FromForm)]
    identities: Vec<String>,
    }
    use rocket::serde::{json::Json, Serialize};
    id: Vec<String>,
    rev: u64,
    }
    // TODO figure out what I'm supposed to send here?
    }
    let hash = crate::models::pijul::changestores::Changestore::hash_from_string(change)?;
    let repo = p.repository(&repoman.storage_root).ok()?;
    let path = repo.changestore().change_file(hash);
    NamedFile::open(path).await.ok()
    #[get("/<org_path>/<proj_path>/.pijul?<identities>", rank = 4)]
    fn identities(org_path: String, proj_path: String, identities: Identities) -> Json<IdentitieRes> {
    Json(IdentitieRes { id: vec![], rev: 0 })
    }
    #[get("/<org_path>/<proj_path>/.pijul?<change>", rank = 3)]
    async fn change(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    change: String,
    ) -> Option<NamedFile> {
    let p = projects::find(db, org_path, proj_path).await?;
    #[derive(Serialize, FromForm)]
    struct IdentitieRes {
    struct Identities {
    if !repo.valid_change(hash) {
    // TODO clean up
    println!("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
    if patch.persist_to(&patch_path).await.is_err() {
    return rocket::http::Status::InternalServerError;
    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);
    use rocket::fs::TempFile;
    println!("error getting changelist: {}", e);
    match p
    .repository(&repoman.storage_root)
    .unwrap()
    .changelist(channel, changelist)
    {
    Ok(out) => Some(out),
    let p = projects::find(db, org_path, proj_path).await?;
    channel: String, // TODO check if this breaks non-UTF-8 channels
    changelist: u64,
    #[get("/<org_path>/<proj_path>/.pijul?<changelist>&<channel>")]
    /// Because of the overlap with other routes in parameters, this route has
    /// rank 2. This way the changelist matches first, and if it doesn't, this route
    /// will respond. Note that in the push flow this order is reversed.
    #[get("/<org_path>/<proj_path>/.pijul?<channel>&<id>", rank = 2)]
    async fn remote_id(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    use rocket::fs::NamedFile;
  • edit in src/main.rs at line 14
    [4.8067][4.5333:5344](),[4.5344][4.4093:4107](),[4.8067][4.4093:4107]()
    mod pijul;
    mod projects;
  • edit in src/main.rs at line 26
    [4.343][4.4108:4156](),[4.8399][4.4108:4156](),[4.4156][4.5507:5544]()
    .mount("/projects", projects::routes())
    .mount("/", pijul::routes())
  • file addition: pijul.rs (----------)
    [3.85]
    use crate::database::Database;
    use crate::models::projects;
    use crate::repoman::RepoMan;
    use rocket::fs::NamedFile;
    use rocket::{Route, State};
    /// Because of the overlap with other routes in parameters, this route has
    /// rank 2. This way the changelist matches first, and if it doesn't, this route
    /// will respond. Note that in the push flow this order is reversed.
    #[get("/<org_path>/<proj_path>/.pijul?<channel>&<id>", rank = 2)]
    async fn remote_id(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    channel: String, // TODO check if this breaks non-UTF-8 channels
    id: Option<String>,
    ) -> Option<String> {
    let p = match projects::find(db, org_path, proj_path).await {
    Some(p) => p,
    None => return None,
    };
    match p.repository(&repoman.storage_root) {
    Ok(r) => r.channel_remote_id(channel, id),
    Err(e) => {
    println!("{}", e);
    None
    }
    }
    }
    #[get("/<org_path>/<proj_path>/.pijul?<changelist>&<channel>")]
    async fn changelist(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    channel: String, // TODO check if this breaks non-UTF-8 channels
    changelist: u64,
    ) -> Option<String> {
    let p = projects::find(db, org_path, proj_path).await?;
    match p
    .repository(&repoman.storage_root)
    .unwrap()
    .changelist(channel, changelist)
    {
    Ok(out) => Some(out),
    Err(e) => {
    println!("error getting changelist: {}", e);
    None
    }
    }
    }
    use rocket::fs::TempFile;
    // TODO pijul: When this endpoint returns a 4XX or 5XX status code, it thinks the push still
    // succeeded.
    #[post("/<org_path>/<proj_path>/.pijul?<apply>", data = "<patch>")]
    async fn apply(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    apply: String,
    mut patch: TempFile<'_>,
    ) -> rocket::http::Status {
    let p = if let Some(p) = projects::find(db, org_path, proj_path).await {
    p
    } else {
    return rocket::http::Status::NotFound;
    };
    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);
    if patch.persist_to(&patch_path).await.is_err() {
    return rocket::http::Status::InternalServerError;
    }
    if !repo.valid_change(hash) {
    // TODO clean up
    println!("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
    }
    #[derive(FromForm)]
    struct Identities {
    identities: Vec<String>,
    }
    use rocket::serde::{json::Json, Serialize};
    #[derive(Serialize, FromForm)]
    struct IdentitieRes {
    id: Vec<String>,
    rev: u64,
    }
    // TODO figure out what I'm supposed to send here?
    #[get("/<org_path>/<proj_path>/.pijul?<identities>", rank = 4)]
    fn identities(org_path: String, proj_path: String, identities: Identities) -> Json<IdentitieRes> {
    Json(IdentitieRes { id: vec![], rev: 0 })
    }
    #[get("/<org_path>/<proj_path>/.pijul?<change>", rank = 3)]
    async fn change(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    change: String,
    ) -> Option<NamedFile> {
    let p = projects::find(db, org_path, proj_path).await?;
    let hash = crate::models::pijul::changestores::Changestore::hash_from_string(change)?;
    let repo = p.repository(&repoman.storage_root).ok()?;
    let path = repo.changestore().change_file(hash);
    NamedFile::open(path).await.ok()
    }
    pub fn routes() -> Vec<Route> {
    routes![remote_id, changelist, apply, identities, change]
    }
  • replacement in src/controllers/mod.rs at line 3
    [3.405][3.405:419]()
    pub mod root;
    [3.405]
    [3.419]
    mod pijul;
    mod root;
  • replacement in src/controllers/mod.rs at line 8
    [3.452][3.452:471]()
    root::routes()
    [3.452]
    [3.471]
    [root::routes(), pijul::routes()].concat()