diff --git a/migrations/20260831100133_user-metadata-private.sql b/migrations/20260831100133_user-metadata-private.sql new file mode 100644 index 0000000..475d10e --- /dev/null +++ b/migrations/20260831100133_user-metadata-private.sql @@ -0,0 +1 @@ +ALTER TABLE user_metadata ADD COLUMN private BOOL DEFAULT false; diff --git a/src/db/metadata.rs b/src/db/metadata.rs index 6d0aac2..1df84db 100644 --- a/src/db/metadata.rs +++ b/src/db/metadata.rs @@ -8,6 +8,7 @@ pub struct UserMetadata { pub mantle_user_id: Uuid, pub key: String, pub value: serde_json::Value, + pub private: bool, pub created_at: DateTime, pub updated_at: DateTime, @@ -18,13 +19,15 @@ pub async fn set( mantle_user_id: Uuid, key: &str, value: &serde_json::Value, + private: Option, ) -> sqlx::Result { sqlx::query_as( r#" - INSERT INTO user_metadata (mantle_user_id, key, value) - VALUES ($1, $2, $3) + INSERT INTO user_metadata (mantle_user_id, key, value, private) + VALUES ($1, $2, $3, $4) ON CONFLICT (mantle_user_id, key) DO UPDATE SET value = excluded.value, + private = excluded.private, updated_at = NOW() RETURNING *; "#, @@ -32,6 +35,7 @@ pub async fn set( .bind(mantle_user_id) .bind(key) .bind(value) + .bind(private.unwrap_or(false)) .fetch_one(d) .await } diff --git a/src/services/account.rs b/src/services/account.rs index 82cfe63..2b8f686 100644 --- a/src/services/account.rs +++ b/src/services/account.rs @@ -107,6 +107,7 @@ impl AccountService { provider_user_id: &str, key: &str, value: &serde_json::Value, + private: Option, ) -> Result { let identity = db::identities::find_by_provider_id(&self.db, provider, provider_user_id) .await @@ -116,7 +117,7 @@ impl AccountService { provider_user_id: provider_user_id.into(), })?; - let metadata = db::metadata::set(&self.db, identity.mantle_user_id, key, value) + let metadata = db::metadata::set(&self.db, identity.mantle_user_id, key, value, private) .await .map_err(InternalError::from)?; diff --git a/src/web/dto/account.rs b/src/web/dto/account.rs new file mode 100644 index 0000000..70d748f --- /dev/null +++ b/src/web/dto/account.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +#[derive(Debug, Deserialize)] +pub struct MetadataRequestBody { + pub value: serde_json::Value, + pub private: Option, +} diff --git a/src/web/dto/mod.rs b/src/web/dto/mod.rs index 95ff93f..dda050e 100644 --- a/src/web/dto/mod.rs +++ b/src/web/dto/mod.rs @@ -1,3 +1,4 @@ +pub mod account; pub mod auth; pub mod health; diff --git a/src/web/dto/shared.rs b/src/web/dto/shared.rs index 3fb4cc0..e3cf79f 100644 --- a/src/web/dto/shared.rs +++ b/src/web/dto/shared.rs @@ -33,6 +33,7 @@ impl From for IdentityDto { #[derive(Debug, Serialize, Deserialize)] pub struct UserMetadataDto { value: serde_json::Value, + private: bool, created_at: DateTime, updated_at: DateTime, } @@ -41,6 +42,7 @@ impl From for UserMetadataDto { fn from(value: UserMetadata) -> Self { Self { value: value.value, + private: value.private, created_at: value.created_at, updated_at: value.updated_at, } diff --git a/src/web/routes/account.rs b/src/web/routes/account.rs index 7a6d547..d52a3ae 100644 --- a/src/web/routes/account.rs +++ b/src/web/routes/account.rs @@ -1,3 +1,4 @@ +use crate::web::dto::account::*; use crate::web::dto::{IdentityDto, UserMetadataDto, UserRequestQuery}; use crate::{ state::AppState, @@ -66,8 +67,8 @@ async fn get_linked_identities( ("key" = String, Path, description = "Metadata key to set") ), request_body( - content = serde_json::Value, - description = "Any JSON value to make metadata value", + content = MetadataRequestBody, + description = "Any JSON value to make metadata value and a private flag", ), responses( (status = 200, body = UserMetadataDto), @@ -80,12 +81,12 @@ async fn set_metadata( State(state): State, Query(req): Query, Path(key): Path, - Json(value): Json, + Json(body): Json, ) -> RouteResult { let metadata = UserMetadataDto::from( state .account - .metadata_set(req.provider, &req.id, &key, &value) + .metadata_set(req.provider, &req.id, &key, &body.value, body.private) .await?, ); Ok((StatusCode::OK, Json(metadata)))