implement custom data

This commit is contained in:
DSeeLP 2025-03-24 15:24:53 +01:00
parent efa8332f33
commit 975fc1d567
6 changed files with 127 additions and 1 deletions

View File

@ -0,0 +1,6 @@
create table user_data(
owner uuid not null references users(id) on delete cascade,
"user" uuid not null references users(id) on delete cascade,
data jsonb not null,
primary key (owner, "user")
);

View File

@ -125,6 +125,42 @@ paths:
$ref: '#/components/responses/Unauthorized'
default:
$ref: '#/components/responses/Default'
/api/users/@me/data:
get:
operationId: self-get-data
summary: User data
tags:
- Users
security:
- bearer: []
responses:
200:
description: Ok
content:
application/json:
schema: {}
401:
$ref: '#/components/responses/Unauthorized'
default:
$ref: '#/components/responses/Default'
post:
operationId: self-set-data
summary: Set User data
tags:
- Users
security:
- bearer: []
requestBody:
content:
application/json:
schema: {}
responses:
200:
description: Ok
401:
$ref: '#/components/responses/Unauthorized'
default:
$ref: '#/components/responses/Default'
/api/users/@me/balance:
get:
operationId: self-get-balance

View File

@ -1,4 +1,4 @@
use std::{borrow::Cow, sync::Arc};
use std::sync::Arc;
use axum::{
Router,

View File

@ -21,6 +21,7 @@ pub(super) fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/{target}", get(user_info))
.route("/@me/balance", get(user_balance))
.route("/@me/data", get(get_user_data).post(set_user_data))
.route("/@me/accounts", get(user_accounts))
.route("/@me/transactions", get(me_transaction_history))
.route("/", get(list_users))
@ -100,6 +101,32 @@ pub async fn user_accounts(EState(state): State, auth: Auth) -> Result<Json<User
Ok(Json(UserAccounts { result }))
}
#[instrument(skip(state))]
pub async fn get_user_data(
EState(state): State,
auth: Auth,
) -> Result<Json<Option<serde_json::Value>>, Error> {
let user = auth.user_id();
let conn = state.conn().await?;
let data = Users::get_data(&conn, user).await?;
Ok(Json(data))
}
#[instrument(skip(state))]
pub async fn set_user_data(
EState(state): State,
auth: Auth,
Json(body): Json<Option<serde_json::Value>>,
) -> Result<(), Error> {
let user = auth.user_id();
let mut conn = state.conn().await?;
match body {
Some(data) => Users::set_data(&mut conn, user, data).await?,
None => Users::delete_data(&mut conn, user).await?,
}
Ok(())
}
#[cfg(test)]
mod tests {
use uuid::Uuid;

View File

@ -120,4 +120,38 @@ impl Users {
let info = client.query_opt(&stmt, &[&id]).await?.map(user_from_row);
Ok(info)
}
pub async fn get_data(
client: &impl GenericClient,
user: Uuid,
) -> Result<Option<serde_json::Value>, tokio_postgres::Error> {
let stmt = client
.prepare_cached("select data from user_data where owner = $1 and \"user\" = $1")
.await?;
let res = client.query_opt(&stmt, &[&user]).await?;
Ok(res.map(|row| row.get(0)))
}
pub async fn set_data(
client: &mut impl GenericClient,
user: Uuid,
data: serde_json::Value,
) -> Result<(), tokio_postgres::Error> {
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")
.await?;
client.execute(&stmt, &[&user, &data]).await?;
Ok(())
}
pub async fn delete_data(
client: &mut impl GenericClient,
user: Uuid,
) -> Result<(), tokio_postgres::Error> {
let stmt = client
.prepare_cached("delete from user_data where \"owner\" = $1 and user = $2")
.await?;
client.execute(&stmt, &[&user]).await?;
Ok(())
}
}

View File

@ -0,0 +1,23 @@
POST {{host}}/api/login
{
"name": "user1",
"password": "this-is-a-password"
}
HTTP 200
[Captures]
token: jsonpath "$.token"
POST {{host}}/api/users/@me/data
Authorization: Bearer {{token}}
{
"hello": "world"
}
HTTP 200
GET {{host}}/api/users/@me/data
Authorization: Bearer {{token}}
HTTP 200
[Asserts]
jsonpath "$.hello" == "world"