Add payment_options field to actor profile
This commit is contained in:
parent
cef026f89a
commit
e573ecb27b
10 changed files with 137 additions and 10 deletions
1
migrations/V0026__actor_profile__add_payment_options.sql
Normal file
1
migrations/V0026__actor_profile__add_payment_options.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE actor_profile ADD COLUMN payment_options JSONB NOT NULL DEFAULT '[]';
|
|
@ -8,6 +8,7 @@ CREATE TABLE actor_profile (
|
|||
avatar_file_name VARCHAR(100),
|
||||
banner_file_name VARCHAR(100),
|
||||
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
||||
payment_options JSONB NOT NULL DEFAULT '[]',
|
||||
extra_fields JSONB NOT NULL DEFAULT '[]',
|
||||
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
|
||||
following_count INTEGER NOT NULL CHECK (following_count >= 0) DEFAULT 0,
|
||||
|
|
|
@ -78,6 +78,7 @@ async fn prepare_remote_profile_data(
|
|||
avatar,
|
||||
banner,
|
||||
identity_proofs,
|
||||
payment_options: vec![],
|
||||
extra_fields,
|
||||
actor_json: Some(actor),
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ pub async fn update_remote_profile(
|
|||
avatar,
|
||||
banner,
|
||||
identity_proofs,
|
||||
payment_options: vec![],
|
||||
extra_fields,
|
||||
actor_json: Some(actor),
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::models::profiles::types::{
|
|||
DbActorProfile,
|
||||
ExtraField,
|
||||
IdentityProof,
|
||||
PaymentOption,
|
||||
ProfileUpdateData,
|
||||
};
|
||||
use crate::models::profiles::validators::validate_username;
|
||||
|
@ -194,6 +195,7 @@ impl AccountUpdateData {
|
|||
current_avatar: &Option<String>,
|
||||
current_banner: &Option<String>,
|
||||
current_identity_proofs: &[IdentityProof],
|
||||
current_payment_options: &[PaymentOption],
|
||||
media_dir: &Path,
|
||||
) -> Result<ProfileUpdateData, FileError> {
|
||||
let avatar = process_b64_image_field_value(
|
||||
|
@ -203,6 +205,7 @@ impl AccountUpdateData {
|
|||
self.header, current_banner.clone(), media_dir,
|
||||
)?;
|
||||
let identity_proofs = current_identity_proofs.to_vec();
|
||||
let payment_options = current_payment_options.to_vec();
|
||||
let extra_fields = self.fields_attributes.unwrap_or(vec![]);
|
||||
let profile_data = ProfileUpdateData {
|
||||
display_name: self.display_name,
|
||||
|
@ -211,6 +214,7 @@ impl AccountUpdateData {
|
|||
avatar,
|
||||
banner,
|
||||
identity_proofs,
|
||||
payment_options,
|
||||
extra_fields,
|
||||
actor_json: None, // always None for local profiles
|
||||
};
|
||||
|
|
|
@ -28,7 +28,10 @@ use crate::models::profiles::queries::{
|
|||
get_profile_by_id,
|
||||
update_profile,
|
||||
};
|
||||
use crate::models::profiles::types::{IdentityProof, ProfileUpdateData};
|
||||
use crate::models::profiles::types::{
|
||||
IdentityProof,
|
||||
ProfileUpdateData,
|
||||
};
|
||||
use crate::models::relationships::queries::{
|
||||
create_follow_request,
|
||||
follow,
|
||||
|
@ -184,6 +187,7 @@ async fn update_credentials(
|
|||
¤t_user.profile.avatar_file_name,
|
||||
¤t_user.profile.banner_file_name,
|
||||
¤t_user.profile.identity_proofs.into_inner(),
|
||||
¤t_user.profile.payment_options.into_inner(),
|
||||
&config.media_dir(),
|
||||
)
|
||||
.map_err(|err| {
|
||||
|
|
|
@ -16,6 +16,7 @@ use super::types::{
|
|||
DbActorProfile,
|
||||
ExtraFields,
|
||||
IdentityProofs,
|
||||
PaymentOptions,
|
||||
ProfileCreateData,
|
||||
ProfileUpdateData,
|
||||
};
|
||||
|
@ -31,10 +32,10 @@ pub async fn create_profile(
|
|||
INSERT INTO actor_profile (
|
||||
id, username, display_name, acct, bio, bio_source,
|
||||
avatar_file_name, banner_file_name,
|
||||
identity_proofs, extra_fields,
|
||||
identity_proofs, payment_options, extra_fields,
|
||||
actor_json
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
RETURNING actor_profile
|
||||
",
|
||||
&[
|
||||
|
@ -47,6 +48,7 @@ pub async fn create_profile(
|
|||
&profile_data.avatar,
|
||||
&profile_data.banner,
|
||||
&IdentityProofs(profile_data.identity_proofs),
|
||||
&PaymentOptions(profile_data.payment_options),
|
||||
&ExtraFields(profile_data.extra_fields),
|
||||
&profile_data.actor_json,
|
||||
],
|
||||
|
@ -70,10 +72,11 @@ pub async fn update_profile(
|
|||
avatar_file_name = $4,
|
||||
banner_file_name = $5,
|
||||
identity_proofs = $6,
|
||||
extra_fields = $7,
|
||||
actor_json = $8,
|
||||
payment_options = $7,
|
||||
extra_fields = $8,
|
||||
actor_json = $9,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $9
|
||||
WHERE id = $10
|
||||
RETURNING actor_profile
|
||||
",
|
||||
&[
|
||||
|
@ -83,6 +86,7 @@ pub async fn update_profile(
|
|||
&data.avatar,
|
||||
&data.banner,
|
||||
&IdentityProofs(data.identity_proofs),
|
||||
&PaymentOptions(data.payment_options),
|
||||
&ExtraFields(data.extra_fields),
|
||||
&data.actor_json,
|
||||
&profile_id,
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use postgres_types::FromSql;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::Error as DeserializerError,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::activitypub::actors::types::Actor;
|
||||
use crate::activitypub::identifiers::local_actor_id;
|
||||
use crate::database::json_macro::{json_from_sql, json_to_sql};
|
||||
use crate::errors::ValidationError;
|
||||
use crate::errors::{ConversionError, ValidationError};
|
||||
use crate::ethereum::identity::DidPkh;
|
||||
use super::validators::{
|
||||
validate_username,
|
||||
validate_display_name,
|
||||
validate_payment_options,
|
||||
clean_bio,
|
||||
clean_extra_fields,
|
||||
};
|
||||
|
@ -35,6 +41,72 @@ impl IdentityProofs {
|
|||
json_from_sql!(IdentityProofs);
|
||||
json_to_sql!(IdentityProofs);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PaymentType {
|
||||
Link,
|
||||
EthereumSubscription,
|
||||
}
|
||||
|
||||
impl From<&PaymentType> for i16 {
|
||||
fn from(payment_type: &PaymentType) -> i16 {
|
||||
match payment_type {
|
||||
PaymentType::Link => 1,
|
||||
PaymentType::EthereumSubscription => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i16> for PaymentType {
|
||||
type Error = ConversionError;
|
||||
|
||||
fn try_from(value: i16) -> Result<Self, Self::Error> {
|
||||
let payment_type = match value {
|
||||
1 => Self::Link,
|
||||
2 => Self::EthereumSubscription,
|
||||
_ => return Err(ConversionError),
|
||||
};
|
||||
Ok(payment_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PaymentType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
let value: i16 = self.into();
|
||||
serializer.serialize_i16(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PaymentType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
let value: i16 = Deserialize::deserialize(deserializer)?;
|
||||
Self::try_from(value).map_err(DeserializerError::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct PaymentOption {
|
||||
pub payment_type: PaymentType,
|
||||
pub name: Option<String>,
|
||||
pub href: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct PaymentOptions(pub Vec<PaymentOption>);
|
||||
|
||||
impl PaymentOptions {
|
||||
pub fn into_inner(self) -> Vec<PaymentOption> {
|
||||
let Self(payment_options) = self;
|
||||
payment_options
|
||||
}
|
||||
}
|
||||
|
||||
json_from_sql!(PaymentOptions);
|
||||
json_to_sql!(PaymentOptions);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtraField {
|
||||
pub name: String,
|
||||
|
@ -70,6 +142,7 @@ pub struct DbActorProfile {
|
|||
pub avatar_file_name: Option<String>,
|
||||
pub banner_file_name: Option<String>,
|
||||
pub identity_proofs: IdentityProofs,
|
||||
pub payment_options: PaymentOptions,
|
||||
pub extra_fields: ExtraFields,
|
||||
pub follower_count: i32,
|
||||
pub following_count: i32,
|
||||
|
@ -143,6 +216,7 @@ impl Default for DbActorProfile {
|
|||
avatar_file_name: None,
|
||||
banner_file_name: None,
|
||||
identity_proofs: IdentityProofs(vec![]),
|
||||
payment_options: PaymentOptions(vec![]),
|
||||
extra_fields: ExtraFields(vec![]),
|
||||
follower_count: 0,
|
||||
following_count: 0,
|
||||
|
@ -164,6 +238,7 @@ pub struct ProfileCreateData {
|
|||
pub avatar: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub identity_proofs: Vec<IdentityProof>,
|
||||
pub payment_options: Vec<PaymentOption>,
|
||||
pub extra_fields: Vec<ExtraField>,
|
||||
pub actor_json: Option<Actor>,
|
||||
}
|
||||
|
@ -178,6 +253,7 @@ impl ProfileCreateData {
|
|||
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
||||
self.bio = Some(cleaned_bio);
|
||||
};
|
||||
validate_payment_options(&self.payment_options)?;
|
||||
self.extra_fields = clean_extra_fields(&self.extra_fields)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -190,6 +266,7 @@ pub struct ProfileUpdateData {
|
|||
pub avatar: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub identity_proofs: Vec<IdentityProof>,
|
||||
pub payment_options: Vec<PaymentOption>,
|
||||
pub extra_fields: Vec<ExtraField>,
|
||||
pub actor_json: Option<Actor>,
|
||||
}
|
||||
|
@ -204,7 +281,7 @@ impl ProfileUpdateData {
|
|||
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
||||
self.bio = Some(cleaned_bio);
|
||||
};
|
||||
// Clean extra fields and remove fields with empty labels
|
||||
validate_payment_options(&self.payment_options)?;
|
||||
self.extra_fields = clean_extra_fields(&self.extra_fields)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -220,6 +297,7 @@ impl From<&DbActorProfile> for ProfileUpdateData {
|
|||
avatar: profile.avatar_file_name,
|
||||
banner: profile.banner_file_name,
|
||||
identity_proofs: profile.identity_proofs.into_inner(),
|
||||
payment_options: profile.payment_options.into_inner(),
|
||||
extra_fields: profile.extra_fields.into_inner(),
|
||||
actor_json: profile.actor_json,
|
||||
}
|
||||
|
@ -242,6 +320,18 @@ mod tests {
|
|||
assert_eq!(serialized, json_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_payment_option_serialization() {
|
||||
let json_data = r#"{"payment_type":2,"name":null,"href":null}"#;
|
||||
let payment_option: PaymentOption = serde_json::from_str(json_data).unwrap();
|
||||
assert!(matches!(
|
||||
payment_option.payment_type,
|
||||
PaymentType::EthereumSubscription,
|
||||
));
|
||||
let serialized = serde_json::to_string(&payment_option).unwrap();
|
||||
assert_eq!(serialized, json_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_actor_address() {
|
||||
let local_profile = DbActorProfile {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use regex::Regex;
|
||||
use crate::errors::ValidationError;
|
||||
use crate::utils::html::{clean_html, clean_html_strict};
|
||||
use super::types::ExtraField;
|
||||
use super::types::{ExtraField, PaymentOption, PaymentType};
|
||||
|
||||
const USERNAME_RE: &str = r"^[a-zA-Z0-9_\.-]+$";
|
||||
|
||||
|
@ -46,6 +46,26 @@ pub fn clean_bio(bio: &str, is_remote: bool) -> Result<String, ValidationError>
|
|||
Ok(cleaned_bio)
|
||||
}
|
||||
|
||||
pub fn validate_payment_options(payment_options: &[PaymentOption])
|
||||
-> Result<(), ValidationError>
|
||||
{
|
||||
for option in payment_options {
|
||||
match option.payment_type {
|
||||
PaymentType::Link => {
|
||||
if option.name.is_none() || option.href.is_none() {
|
||||
return Err(ValidationError("invalid payment option"));
|
||||
};
|
||||
},
|
||||
PaymentType::EthereumSubscription => {
|
||||
if option.name.is_some() || option.href.is_some() {
|
||||
return Err(ValidationError("invalid payment option"));
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const FIELD_NAME_MAX_SIZE: usize = 500;
|
||||
const FIELD_VALUE_MAX_SIZE: usize = 5000;
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ pub async fn create_user(
|
|||
avatar: None,
|
||||
banner: None,
|
||||
identity_proofs: vec![],
|
||||
payment_options: vec![],
|
||||
extra_fields: vec![],
|
||||
actor_json: None,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue