mirror of
https://git.dirksys.ovh/dirk/bankserver.git
synced 2025-12-20 02:59:20 +01:00
key based user data
This commit is contained in:
parent
ac695a4b16
commit
e8eef372aa
@ -1,6 +1,8 @@
|
|||||||
create table user_data(
|
create table user_data(
|
||||||
owner uuid not null references users(id) on delete cascade,
|
owner uuid not null references users(id) on delete cascade,
|
||||||
"user" uuid not null references users(id) on delete cascade,
|
"user" uuid not null references users(id) on delete cascade,
|
||||||
|
"key" text not null,
|
||||||
data jsonb not null,
|
data jsonb not null,
|
||||||
primary key (owner, "user")
|
unique (owner, "user"),
|
||||||
|
primary key (owner, "user", "key")
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,7 +21,8 @@ pub(super) fn router() -> Router<Arc<AppState>> {
|
|||||||
Router::new()
|
Router::new()
|
||||||
.route("/{target}", get(user_info))
|
.route("/{target}", get(user_info))
|
||||||
.route("/@me/balance", get(user_balance))
|
.route("/@me/balance", get(user_balance))
|
||||||
.route("/@me/data", get(get_user_data).post(set_user_data))
|
.route("/@me/data", get(list_user_data_keys))
|
||||||
|
.route("/@me/data/{key}", get(get_user_data).post(set_user_data))
|
||||||
.route("/@me/accounts", get(user_accounts))
|
.route("/@me/accounts", get(user_accounts))
|
||||||
.route("/@me/transactions", get(me_transaction_history))
|
.route("/@me/transactions", get(me_transaction_history))
|
||||||
.route("/", get(list_users))
|
.route("/", get(list_users))
|
||||||
@ -101,14 +102,26 @@ pub async fn user_accounts(EState(state): State, auth: Auth) -> Result<Json<User
|
|||||||
Ok(Json(UserAccounts { result }))
|
Ok(Json(UserAccounts { result }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(state))]
|
||||||
|
pub async fn list_user_data_keys(
|
||||||
|
EState(state): State,
|
||||||
|
auth: Auth,
|
||||||
|
) -> Result<Json<Vec<String>>, Error> {
|
||||||
|
let user = auth.user_id();
|
||||||
|
let conn = state.conn().await?;
|
||||||
|
let data = Users::list_data(&conn, user).await?;
|
||||||
|
Ok(Json(data))
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn get_user_data(
|
pub async fn get_user_data(
|
||||||
EState(state): State,
|
EState(state): State,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
|
Path(key): Path<String>,
|
||||||
) -> Result<Json<Option<serde_json::Value>>, Error> {
|
) -> Result<Json<Option<serde_json::Value>>, Error> {
|
||||||
let user = auth.user_id();
|
let user = auth.user_id();
|
||||||
let conn = state.conn().await?;
|
let conn = state.conn().await?;
|
||||||
let data = Users::get_data(&conn, user).await?;
|
let data = Users::get_data(&conn, user, &key).await?;
|
||||||
Ok(Json(data))
|
Ok(Json(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +129,14 @@ pub async fn get_user_data(
|
|||||||
pub async fn set_user_data(
|
pub async fn set_user_data(
|
||||||
EState(state): State,
|
EState(state): State,
|
||||||
auth: Auth,
|
auth: Auth,
|
||||||
|
Path(key): Path<String>,
|
||||||
Json(body): Json<Option<serde_json::Value>>,
|
Json(body): Json<Option<serde_json::Value>>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let user = auth.user_id();
|
let user = auth.user_id();
|
||||||
let mut conn = state.conn().await?;
|
let mut conn = state.conn().await?;
|
||||||
match body {
|
match body {
|
||||||
Some(data) => Users::set_data(&mut conn, user, data).await?,
|
Some(data) => Users::set_data(&mut conn, user, &key, data).await?,
|
||||||
None => Users::delete_data(&mut conn, user).await?,
|
None => Users::delete_data(&mut conn, user, &key).await?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,37 +121,64 @@ impl Users {
|
|||||||
Ok(info)
|
Ok(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(client))]
|
||||||
|
pub async fn list_data(
|
||||||
|
client: &impl GenericClient,
|
||||||
|
user: Uuid,
|
||||||
|
) -> Result<Vec<String>, tokio_postgres::Error> {
|
||||||
|
let stmt = client
|
||||||
|
.prepare_cached("select \"key\" from user_data where owner = $1 and \"user\" = $1")
|
||||||
|
.await?;
|
||||||
|
let res = client
|
||||||
|
.query(&stmt, &[&user])
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| row.get(0))
|
||||||
|
.collect();
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(client))]
|
||||||
pub async fn get_data(
|
pub async fn get_data(
|
||||||
client: &impl GenericClient,
|
client: &impl GenericClient,
|
||||||
user: Uuid,
|
user: Uuid,
|
||||||
|
key: &str,
|
||||||
) -> Result<Option<serde_json::Value>, tokio_postgres::Error> {
|
) -> Result<Option<serde_json::Value>, tokio_postgres::Error> {
|
||||||
let stmt = client
|
let stmt = client
|
||||||
.prepare_cached("select data from user_data where owner = $1 and \"user\" = $1")
|
.prepare_cached(
|
||||||
|
"select data from user_data where owner = $1 and \"user\" = $1 and \"key\" = $2 ",
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let res = client.query_opt(&stmt, &[&user]).await?;
|
let res = client.query_opt(&stmt, &[&user, &key]).await?;
|
||||||
Ok(res.map(|row| row.get(0)))
|
Ok(res.map(|row| row.get(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(client, data))]
|
||||||
pub async fn set_data(
|
pub async fn set_data(
|
||||||
client: &mut impl GenericClient,
|
client: &mut impl GenericClient,
|
||||||
user: Uuid,
|
user: Uuid,
|
||||||
|
key: &str,
|
||||||
data: serde_json::Value,
|
data: serde_json::Value,
|
||||||
) -> Result<(), tokio_postgres::Error> {
|
) -> Result<(), tokio_postgres::Error> {
|
||||||
let stmt = client
|
let stmt = client
|
||||||
.prepare_cached("insert into user_data(owner, \"user\", data) values ($1, $1, $2) on conflict (owner, \"user\") do update set data = $2")
|
.prepare_cached("insert into user_data(owner, \"user\", \"key\", data) values ($1, $1, $2, $3) on conflict (owner, \"user\") do update set data = $3")
|
||||||
.await?;
|
.await?;
|
||||||
client.execute(&stmt, &[&user, &data]).await?;
|
client.execute(&stmt, &[&user, &key, &data]).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(client))]
|
||||||
pub async fn delete_data(
|
pub async fn delete_data(
|
||||||
client: &mut impl GenericClient,
|
client: &mut impl GenericClient,
|
||||||
user: Uuid,
|
user: Uuid,
|
||||||
|
key: &str,
|
||||||
) -> Result<(), tokio_postgres::Error> {
|
) -> Result<(), tokio_postgres::Error> {
|
||||||
let stmt = client
|
let stmt = client
|
||||||
.prepare_cached("delete from user_data where \"owner\" = $1 and user = $2")
|
.prepare_cached(
|
||||||
|
"delete from user_data where \"owner\" = $1 and \"user\" = $1 and \"key\" = $2",
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
client.execute(&stmt, &[&user]).await?;
|
client.execute(&stmt, &[&user, &key]).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,16 +8,60 @@ HTTP 200
|
|||||||
[Captures]
|
[Captures]
|
||||||
token: jsonpath "$.token"
|
token: jsonpath "$.token"
|
||||||
|
|
||||||
POST {{host}}/api/users/@me/data
|
GET {{host}}/api/users/@me/data
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
[Asserts]
|
||||||
|
jsonpath "$" isCollection
|
||||||
|
jsonpath "$" isEmpty
|
||||||
|
|
||||||
|
GET {{host}}/api/users/@me/data/test
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
[Asserts]
|
||||||
|
jsonpath "$" == null
|
||||||
|
|
||||||
|
POST {{host}}/api/users/@me/data/test
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
{
|
{
|
||||||
"hello": "world"
|
"hello": "world"
|
||||||
}
|
}
|
||||||
HTTP 200
|
HTTP 200
|
||||||
|
|
||||||
GET {{host}}/api/users/@me/data
|
GET {{host}}/api/users/@me/data/test
|
||||||
Authorization: Bearer {{token}}
|
Authorization: Bearer {{token}}
|
||||||
HTTP 200
|
HTTP 200
|
||||||
|
|
||||||
[Asserts]
|
[Asserts]
|
||||||
jsonpath "$.hello" == "world"
|
jsonpath "$.hello" == "world"
|
||||||
|
|
||||||
|
GET {{host}}/api/users/@me/data
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
[Asserts]
|
||||||
|
jsonpath "$" isCollection
|
||||||
|
jsonpath "$" count == 1
|
||||||
|
jsonpath "$[0]" == "test"
|
||||||
|
|
||||||
|
POST {{host}}/api/users/@me/data/test
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
null
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
GET {{host}}/api/users/@me/data/test
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
[Asserts]
|
||||||
|
jsonpath "$" == null
|
||||||
|
|
||||||
|
GET {{host}}/api/users/@me/data
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
HTTP 200
|
||||||
|
|
||||||
|
[Asserts]
|
||||||
|
jsonpath "$" isCollection
|
||||||
|
jsonpath "$" isEmpty
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user