mirror of
https://github.com/Blah-IM/blahrs.git
synced 2025-05-01 00:31:09 +00:00
feat(blahctl): add database commands to modify users and rooms
This commit is contained in:
parent
a972477171
commit
066061e2ec
3 changed files with 119 additions and 2 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -300,6 +300,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
"serde_jcs",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,6 +12,7 @@ humantime = "2"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
rusqlite = { version = "0.32", features = ["rusqlite-macros"] }
|
rusqlite = { version = "0.32", features = ["rusqlite-macros"] }
|
||||||
|
serde_jcs = "0.1.0"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tokio = { version = "1", features = ["rt", "macros"] }
|
tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,14 @@ use std::time::SystemTime;
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{ensure, Context, Result};
|
||||||
use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile};
|
use blah_types::identity::{IdUrl, UserActKeyDesc, UserIdentityDesc, UserProfile};
|
||||||
use blah_types::{bitflags, get_timestamp, PubKey, RoomAttrs, ServerPermission, SignExt};
|
use blah_types::{bitflags, get_timestamp, PubKey, RoomAttrs, ServerPermission, SignExt};
|
||||||
|
use clap::value_parser;
|
||||||
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
|
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
|
||||||
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey};
|
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey};
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
use humantime::Duration;
|
use humantime::Duration;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use rusqlite::{prepare_and_bind, Connection};
|
use rusqlite::{named_params, prepare_and_bind, Connection};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
const USER_AGENT: &str = concat!("blahctl/", env!("CARGO_PKG_VERSION"));
|
const USER_AGENT: &str = concat!("blahctl/", env!("CARGO_PKG_VERSION"));
|
||||||
|
@ -182,14 +183,52 @@ impl IdDescArgs {
|
||||||
enum DbCommand {
|
enum DbCommand {
|
||||||
/// Create and initialize database.
|
/// Create and initialize database.
|
||||||
Init,
|
Init,
|
||||||
|
/// Add a new user or update identity and act_keys of an existing user.
|
||||||
|
RegisterUser {
|
||||||
|
#[command(flatten)]
|
||||||
|
user: Box<IdDescArgs>,
|
||||||
|
|
||||||
|
/// User permission.
|
||||||
|
#[arg(long, value_parser = flag_parser::<ServerPermission>)]
|
||||||
|
permission: ServerPermission,
|
||||||
|
},
|
||||||
/// Set property of an existing user.
|
/// Set property of an existing user.
|
||||||
SetUser {
|
SetUser {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
user: Box<User>,
|
user: Box<User>,
|
||||||
|
|
||||||
|
/// User permission.
|
||||||
#[arg(long, value_parser = flag_parser::<ServerPermission>)]
|
#[arg(long, value_parser = flag_parser::<ServerPermission>)]
|
||||||
permission: ServerPermission,
|
permission: ServerPermission,
|
||||||
},
|
},
|
||||||
|
/// Create an empty room.
|
||||||
|
CreateRoom {
|
||||||
|
/// Room id.
|
||||||
|
#[arg(long, value_parser = value_parser!(i64).range(0..))]
|
||||||
|
rid: i64,
|
||||||
|
|
||||||
|
/// Room attributes.
|
||||||
|
#[arg(long, value_parser = flag_parser::<RoomAttrs>)]
|
||||||
|
attrs: Option<RoomAttrs>,
|
||||||
|
|
||||||
|
/// Room title.
|
||||||
|
#[arg(long)]
|
||||||
|
title: String,
|
||||||
|
},
|
||||||
|
/// Update attributes of an existing room.
|
||||||
|
SetRoom {
|
||||||
|
/// Room id.
|
||||||
|
#[arg(long, value_parser = value_parser!(i64).range(0..))]
|
||||||
|
rid: i64,
|
||||||
|
|
||||||
|
/// New attributes.
|
||||||
|
#[arg(long, value_parser = flag_parser::<RoomAttrs>)]
|
||||||
|
attrs: Option<RoomAttrs>,
|
||||||
|
|
||||||
|
/// New title.
|
||||||
|
#[arg(long)]
|
||||||
|
title: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flag_parser<T: bitflags::Flags>(s: &str) -> clap::error::Result<T> {
|
fn flag_parser<T: bitflags::Flags>(s: &str) -> clap::error::Result<T> {
|
||||||
|
@ -426,9 +465,59 @@ fn main_id(cmd: IdCommand) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_db(conn: Connection, command: DbCommand) -> Result<()> {
|
fn main_db(mut conn: Connection, command: DbCommand) -> Result<()> {
|
||||||
match command {
|
match command {
|
||||||
DbCommand::Init => {}
|
DbCommand::Init => {}
|
||||||
|
DbCommand::RegisterUser { user, permission } => {
|
||||||
|
let id_desc = user.load(&build_rt()?)?;
|
||||||
|
let fetch_time = get_timestamp();
|
||||||
|
id_desc
|
||||||
|
.verify(user.id_url.as_ref(), fetch_time)
|
||||||
|
.context("invalid identity description")?;
|
||||||
|
let id_desc_json = serde_jcs::to_string(&id_desc).expect("serialization cannot fail");
|
||||||
|
let id_key = &id_desc.id_key;
|
||||||
|
let txn = conn.transaction()?;
|
||||||
|
// TODO: These SQLs (partially?) duplicate with `blahd::database::Database`.
|
||||||
|
let uid = prepare_and_bind!(
|
||||||
|
txn,
|
||||||
|
r"
|
||||||
|
INSERT INTO `user` (`id_key`, `last_fetch_time`, `id_desc`)
|
||||||
|
VALUES (:id_key, :fetch_time, :id_desc_json)
|
||||||
|
ON CONFLICT (`id_key`) DO UPDATE SET
|
||||||
|
`last_fetch_time` = excluded.`last_fetch_time`,
|
||||||
|
`id_desc` = excluded.`id_desc`,
|
||||||
|
`permission` = :permission
|
||||||
|
RETURNING `uid`
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.raw_query()
|
||||||
|
.next()?
|
||||||
|
.expect("should insert or fail")
|
||||||
|
.get::<_, i64>(0)?;
|
||||||
|
prepare_and_bind!(
|
||||||
|
txn,
|
||||||
|
r"
|
||||||
|
DELETE FROM `user_act_key`
|
||||||
|
WHERE `uid` = :uid
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.raw_execute()?;
|
||||||
|
let mut stmt = txn.prepare(
|
||||||
|
r"
|
||||||
|
INSERT INTO `user_act_key` (`uid`, `act_key`, `expire_time`)
|
||||||
|
VALUES (:uid, :act_key, :expire_time)
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
for kdesc in &id_desc.act_keys {
|
||||||
|
stmt.execute(named_params! {
|
||||||
|
":uid": uid,
|
||||||
|
":act_key": kdesc.signee.payload.act_key,
|
||||||
|
":expire_time": i64::try_from(kdesc.signee.payload.expire_time).expect("verified timestamp"),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
stmt.finalize()?;
|
||||||
|
txn.commit()?;
|
||||||
|
}
|
||||||
DbCommand::SetUser { user, permission } => {
|
DbCommand::SetUser { user, permission } => {
|
||||||
let rt = build_rt()?;
|
let rt = build_rt()?;
|
||||||
let id_key = user.load(&rt)?;
|
let id_key = user.load(&rt)?;
|
||||||
|
@ -442,6 +531,32 @@ fn main_db(conn: Connection, command: DbCommand) -> Result<()> {
|
||||||
)
|
)
|
||||||
.raw_execute()?;
|
.raw_execute()?;
|
||||||
}
|
}
|
||||||
|
DbCommand::CreateRoom { rid, attrs, title } => {
|
||||||
|
assert!(rid >= 0, "checked by clap");
|
||||||
|
let attrs = attrs.unwrap_or_default();
|
||||||
|
prepare_and_bind!(
|
||||||
|
conn,
|
||||||
|
r"
|
||||||
|
INSERT INTO `room` (`rid`, `attrs`, `title`)
|
||||||
|
VALUES (:rid, :attrs, :title)
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.raw_execute()?;
|
||||||
|
}
|
||||||
|
DbCommand::SetRoom { rid, attrs, title } => {
|
||||||
|
assert!(rid >= 0, "checked by clap");
|
||||||
|
let updated = prepare_and_bind!(
|
||||||
|
conn,
|
||||||
|
r"
|
||||||
|
UPDATE `room` SET
|
||||||
|
`attrs` = COALESCE(:attrs, `attrs`),
|
||||||
|
`title` = COALESCE(:title, `title`)
|
||||||
|
WHERE `rid` = :rid
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.raw_execute()?;
|
||||||
|
ensure!(updated == 1, "room does not exist");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue