Create API endpoint for managing client configurations
This commit is contained in:
parent
01494f1770
commit
fc82c83421
8 changed files with 139 additions and 2 deletions
|
@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Created API endpoint for adding aliases.
|
- Created API endpoint for adding aliases.
|
||||||
- Populate `alsoKnownAs` property on actor object with declared aliases.
|
- Populate `alsoKnownAs` property on actor object with declared aliases.
|
||||||
- Support account migration from Mastodon.
|
- Support account migration from Mastodon.
|
||||||
|
- Created API endpoint for managing client configurations.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -780,6 +780,29 @@ paths:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Notification'
|
$ref: '#/components/schemas/Notification'
|
||||||
|
/api/v1/settings/client_config:
|
||||||
|
post:
|
||||||
|
summary: Update client configuration.
|
||||||
|
security:
|
||||||
|
- tokenAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
description: |
|
||||||
|
Client configuration.
|
||||||
|
Should contain a single key identifying type of client.
|
||||||
|
type: object
|
||||||
|
example: {"mitra-web":{"theme":"dark"}}
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CredentialAccount'
|
||||||
|
400:
|
||||||
|
description: Invalid request data.
|
||||||
/api/v1/settings/change_password:
|
/api/v1/settings/change_password:
|
||||||
post:
|
post:
|
||||||
summary: Set or change user's password.
|
summary: Set or change user's password.
|
||||||
|
@ -1504,6 +1527,10 @@ components:
|
||||||
role:
|
role:
|
||||||
description: The role assigned to the currently authorized user.
|
description: The role assigned to the currently authorized user.
|
||||||
$ref: '#/components/schemas/Role'
|
$ref: '#/components/schemas/Role'
|
||||||
|
client_config:
|
||||||
|
description: Client configurations.
|
||||||
|
type: object
|
||||||
|
example: {"mitra-web":{"theme":"dark"}}
|
||||||
ActivityParameters:
|
ActivityParameters:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE user_account ADD COLUMN client_config JSONB NOT NULL DEFAULT '{}';
|
|
@ -58,6 +58,7 @@ CREATE TABLE user_account (
|
||||||
private_key TEXT NOT NULL,
|
private_key TEXT NOT NULL,
|
||||||
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
||||||
user_role SMALLINT NOT NULL,
|
user_role SMALLINT NOT NULL,
|
||||||
|
client_config JSONB NOT NULL DEFAULT '{}',
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use serde_json::{Value as JsonValue};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use mitra_utils::{
|
use mitra_utils::{
|
||||||
|
@ -17,6 +18,8 @@ use crate::profiles::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::types::{
|
use super::types::{
|
||||||
|
ClientConfig,
|
||||||
|
DbClientConfig,
|
||||||
DbInviteCode,
|
DbInviteCode,
|
||||||
DbUser,
|
DbUser,
|
||||||
Role,
|
Role,
|
||||||
|
@ -189,6 +192,26 @@ pub async fn set_user_role(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_client_config(
|
||||||
|
db_client: &impl DatabaseClient,
|
||||||
|
user_id: &Uuid,
|
||||||
|
client_name: &str,
|
||||||
|
client_config_value: &JsonValue,
|
||||||
|
) -> Result<ClientConfig, DatabaseError> {
|
||||||
|
let maybe_row = db_client.query_opt(
|
||||||
|
"
|
||||||
|
UPDATE user_account
|
||||||
|
SET client_config = jsonb_set(client_config, ARRAY[$1], $2, true)
|
||||||
|
WHERE id = $3
|
||||||
|
RETURNING client_config
|
||||||
|
",
|
||||||
|
&[&client_name, &client_config_value, &user_id],
|
||||||
|
).await?;
|
||||||
|
let row = maybe_row.ok_or(DatabaseError::NotFound("user"))?;
|
||||||
|
let client_config: DbClientConfig = row.try_get("client_config")?;
|
||||||
|
Ok(client_config.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_user_by_id(
|
pub async fn get_user_by_id(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
user_id: &Uuid,
|
user_id: &Uuid,
|
||||||
|
@ -308,6 +331,7 @@ pub async fn get_user_count(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use crate::database::test_utils::create_test_database;
|
use crate::database::test_utils::create_test_database;
|
||||||
use crate::users::types::Role;
|
use crate::users::types::Role;
|
||||||
|
@ -362,4 +386,25 @@ mod tests {
|
||||||
let user = get_user_by_id(db_client, &user.id).await.unwrap();
|
let user = get_user_by_id(db_client, &user.id).await.unwrap();
|
||||||
assert_eq!(user.role, Role::ReadOnlyUser);
|
assert_eq!(user.role, Role::ReadOnlyUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_update_client_config() {
|
||||||
|
let db_client = &mut create_test_database().await;
|
||||||
|
let user_data = UserCreateData::default();
|
||||||
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
|
assert_eq!(user.client_config.is_empty(), true);
|
||||||
|
let client_name = "test";
|
||||||
|
let client_config_value = json!({"a": 1});
|
||||||
|
let client_config = update_client_config(
|
||||||
|
db_client,
|
||||||
|
&user.id,
|
||||||
|
client_name,
|
||||||
|
&client_config_value,
|
||||||
|
).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
client_config.get(client_name).unwrap(),
|
||||||
|
&client_config_value,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use postgres_types::FromSql;
|
use postgres_types::FromSql;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::{Value as JsonValue};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use mitra_utils::{
|
use mitra_utils::{
|
||||||
|
@ -9,6 +13,7 @@ use mitra_utils::{
|
||||||
|
|
||||||
use crate::database::{
|
use crate::database::{
|
||||||
int_enum::{int_enum_from_sql, int_enum_to_sql},
|
int_enum::{int_enum_from_sql, int_enum_to_sql},
|
||||||
|
json_macro::json_from_sql,
|
||||||
DatabaseTypeError,
|
DatabaseTypeError,
|
||||||
};
|
};
|
||||||
use crate::profiles::types::DbActorProfile;
|
use crate::profiles::types::DbActorProfile;
|
||||||
|
@ -100,6 +105,20 @@ impl TryFrom<i16> for Role {
|
||||||
int_enum_from_sql!(Role);
|
int_enum_from_sql!(Role);
|
||||||
int_enum_to_sql!(Role);
|
int_enum_to_sql!(Role);
|
||||||
|
|
||||||
|
pub type ClientConfig = HashMap<String, JsonValue>;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DbClientConfig(ClientConfig);
|
||||||
|
|
||||||
|
impl DbClientConfig {
|
||||||
|
pub fn into_inner(self) -> ClientConfig {
|
||||||
|
let Self(client_config) = self;
|
||||||
|
client_config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_from_sql!(DbClientConfig);
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(FromSql)]
|
#[derive(FromSql)]
|
||||||
#[postgres(name = "user_account")]
|
#[postgres(name = "user_account")]
|
||||||
|
@ -110,6 +129,7 @@ pub struct DbUser {
|
||||||
private_key: String,
|
private_key: String,
|
||||||
invite_code: Option<String>,
|
invite_code: Option<String>,
|
||||||
user_role: Role,
|
user_role: Role,
|
||||||
|
client_config: DbClientConfig,
|
||||||
created_at: DateTime<Utc>,
|
created_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +142,7 @@ pub struct User {
|
||||||
pub password_hash: Option<String>,
|
pub password_hash: Option<String>,
|
||||||
pub private_key: String,
|
pub private_key: String,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
|
pub client_config: ClientConfig,
|
||||||
pub profile: DbActorProfile,
|
pub profile: DbActorProfile,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +158,7 @@ impl User {
|
||||||
password_hash: db_user.password_hash,
|
password_hash: db_user.password_hash,
|
||||||
private_key: db_user.private_key,
|
private_key: db_user.private_key,
|
||||||
role: db_user.user_role,
|
role: db_user.user_role,
|
||||||
|
client_config: db_user.client_config.into_inner(),
|
||||||
profile: db_profile,
|
profile: db_profile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,9 @@ use mitra_models::{
|
||||||
},
|
},
|
||||||
subscriptions::types::Subscription,
|
subscriptions::types::Subscription,
|
||||||
users::types::{
|
users::types::{
|
||||||
Role,
|
ClientConfig,
|
||||||
Permission,
|
Permission,
|
||||||
|
Role,
|
||||||
User,
|
User,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -126,6 +127,7 @@ pub struct Account {
|
||||||
// CredentialAccount attributes
|
// CredentialAccount attributes
|
||||||
pub source: Option<Source>,
|
pub source: Option<Source>,
|
||||||
pub role: Option<ApiRole>,
|
pub role: Option<ApiRole>,
|
||||||
|
pub client_config: Option<ClientConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
|
@ -220,6 +222,7 @@ impl Account {
|
||||||
statuses_count: profile.post_count,
|
statuses_count: profile.post_count,
|
||||||
source: None,
|
source: None,
|
||||||
role: None,
|
role: None,
|
||||||
|
client_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +251,7 @@ impl Account {
|
||||||
);
|
);
|
||||||
account.source = Some(source);
|
account.source = Some(source);
|
||||||
account.role = Some(role);
|
account.role = Some(role);
|
||||||
|
account.client_config = Some(user.client_config);
|
||||||
account
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,11 @@ use mitra_models::{
|
||||||
update_profile,
|
update_profile,
|
||||||
},
|
},
|
||||||
profiles::types::ProfileUpdateData,
|
profiles::types::ProfileUpdateData,
|
||||||
users::queries::set_user_password,
|
users::queries::{
|
||||||
|
set_user_password,
|
||||||
|
update_client_config,
|
||||||
|
},
|
||||||
|
users::types::ClientConfig,
|
||||||
};
|
};
|
||||||
use mitra_utils::passwords::hash_password;
|
use mitra_utils::passwords::hash_password;
|
||||||
|
|
||||||
|
@ -48,6 +52,37 @@ use super::types::{
|
||||||
PasswordChangeRequest,
|
PasswordChangeRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Similar to Pleroma settings store
|
||||||
|
// https://docs-develop.pleroma.social/backend/development/API/differences_in_mastoapi_responses/#pleroma-settings-store
|
||||||
|
#[post("/client_config")]
|
||||||
|
async fn client_config_view(
|
||||||
|
auth: BearerAuth,
|
||||||
|
connection_info: ConnectionInfo,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
db_pool: web::Data<DbPool>,
|
||||||
|
request_data: web::Json<ClientConfig>,
|
||||||
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
|
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
if request_data.len() != 1 {
|
||||||
|
return Err(ValidationError("can't update more than one config").into());
|
||||||
|
};
|
||||||
|
let (client_name, client_config_value) =
|
||||||
|
request_data.iter().next().expect("hashmap entry should exist");
|
||||||
|
current_user.client_config = update_client_config(
|
||||||
|
db_client,
|
||||||
|
¤t_user.id,
|
||||||
|
client_name,
|
||||||
|
client_config_value,
|
||||||
|
).await?;
|
||||||
|
let account = Account::from_user(
|
||||||
|
&get_request_base_url(connection_info),
|
||||||
|
&config.instance_url(),
|
||||||
|
current_user,
|
||||||
|
);
|
||||||
|
Ok(HttpResponse::Ok().json(account))
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/change_password")]
|
#[post("/change_password")]
|
||||||
async fn change_password_view(
|
async fn change_password_view(
|
||||||
auth: BearerAuth,
|
auth: BearerAuth,
|
||||||
|
@ -231,6 +266,7 @@ async fn move_followers(
|
||||||
|
|
||||||
pub fn settings_api_scope() -> Scope {
|
pub fn settings_api_scope() -> Scope {
|
||||||
web::scope("/api/v1/settings")
|
web::scope("/api/v1/settings")
|
||||||
|
.service(client_config_view)
|
||||||
.service(change_password_view)
|
.service(change_password_view)
|
||||||
.service(add_alias_view)
|
.service(add_alias_view)
|
||||||
.service(export_followers_view)
|
.service(export_followers_view)
|
||||||
|
|
Loading…
Reference in a new issue