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),
|
avatar_file_name VARCHAR(100),
|
||||||
banner_file_name VARCHAR(100),
|
banner_file_name VARCHAR(100),
|
||||||
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
identity_proofs JSONB NOT NULL DEFAULT '[]',
|
||||||
|
payment_options JSONB NOT NULL DEFAULT '[]',
|
||||||
extra_fields JSONB NOT NULL DEFAULT '[]',
|
extra_fields JSONB NOT NULL DEFAULT '[]',
|
||||||
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
|
follower_count INTEGER NOT NULL CHECK (follower_count >= 0) DEFAULT 0,
|
||||||
following_count INTEGER NOT NULL CHECK (following_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,
|
avatar,
|
||||||
banner,
|
banner,
|
||||||
identity_proofs,
|
identity_proofs,
|
||||||
|
payment_options: vec![],
|
||||||
extra_fields,
|
extra_fields,
|
||||||
actor_json: Some(actor),
|
actor_json: Some(actor),
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,6 +64,7 @@ pub async fn update_remote_profile(
|
||||||
avatar,
|
avatar,
|
||||||
banner,
|
banner,
|
||||||
identity_proofs,
|
identity_proofs,
|
||||||
|
payment_options: vec![],
|
||||||
extra_fields,
|
extra_fields,
|
||||||
actor_json: Some(actor),
|
actor_json: Some(actor),
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::models::profiles::types::{
|
||||||
DbActorProfile,
|
DbActorProfile,
|
||||||
ExtraField,
|
ExtraField,
|
||||||
IdentityProof,
|
IdentityProof,
|
||||||
|
PaymentOption,
|
||||||
ProfileUpdateData,
|
ProfileUpdateData,
|
||||||
};
|
};
|
||||||
use crate::models::profiles::validators::validate_username;
|
use crate::models::profiles::validators::validate_username;
|
||||||
|
@ -194,6 +195,7 @@ impl AccountUpdateData {
|
||||||
current_avatar: &Option<String>,
|
current_avatar: &Option<String>,
|
||||||
current_banner: &Option<String>,
|
current_banner: &Option<String>,
|
||||||
current_identity_proofs: &[IdentityProof],
|
current_identity_proofs: &[IdentityProof],
|
||||||
|
current_payment_options: &[PaymentOption],
|
||||||
media_dir: &Path,
|
media_dir: &Path,
|
||||||
) -> Result<ProfileUpdateData, FileError> {
|
) -> Result<ProfileUpdateData, FileError> {
|
||||||
let avatar = process_b64_image_field_value(
|
let avatar = process_b64_image_field_value(
|
||||||
|
@ -203,6 +205,7 @@ impl AccountUpdateData {
|
||||||
self.header, current_banner.clone(), media_dir,
|
self.header, current_banner.clone(), media_dir,
|
||||||
)?;
|
)?;
|
||||||
let identity_proofs = current_identity_proofs.to_vec();
|
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 extra_fields = self.fields_attributes.unwrap_or(vec![]);
|
||||||
let profile_data = ProfileUpdateData {
|
let profile_data = ProfileUpdateData {
|
||||||
display_name: self.display_name,
|
display_name: self.display_name,
|
||||||
|
@ -211,6 +214,7 @@ impl AccountUpdateData {
|
||||||
avatar,
|
avatar,
|
||||||
banner,
|
banner,
|
||||||
identity_proofs,
|
identity_proofs,
|
||||||
|
payment_options,
|
||||||
extra_fields,
|
extra_fields,
|
||||||
actor_json: None, // always None for local profiles
|
actor_json: None, // always None for local profiles
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,10 @@ use crate::models::profiles::queries::{
|
||||||
get_profile_by_id,
|
get_profile_by_id,
|
||||||
update_profile,
|
update_profile,
|
||||||
};
|
};
|
||||||
use crate::models::profiles::types::{IdentityProof, ProfileUpdateData};
|
use crate::models::profiles::types::{
|
||||||
|
IdentityProof,
|
||||||
|
ProfileUpdateData,
|
||||||
|
};
|
||||||
use crate::models::relationships::queries::{
|
use crate::models::relationships::queries::{
|
||||||
create_follow_request,
|
create_follow_request,
|
||||||
follow,
|
follow,
|
||||||
|
@ -184,6 +187,7 @@ async fn update_credentials(
|
||||||
¤t_user.profile.avatar_file_name,
|
¤t_user.profile.avatar_file_name,
|
||||||
¤t_user.profile.banner_file_name,
|
¤t_user.profile.banner_file_name,
|
||||||
¤t_user.profile.identity_proofs.into_inner(),
|
¤t_user.profile.identity_proofs.into_inner(),
|
||||||
|
¤t_user.profile.payment_options.into_inner(),
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
|
|
@ -16,6 +16,7 @@ use super::types::{
|
||||||
DbActorProfile,
|
DbActorProfile,
|
||||||
ExtraFields,
|
ExtraFields,
|
||||||
IdentityProofs,
|
IdentityProofs,
|
||||||
|
PaymentOptions,
|
||||||
ProfileCreateData,
|
ProfileCreateData,
|
||||||
ProfileUpdateData,
|
ProfileUpdateData,
|
||||||
};
|
};
|
||||||
|
@ -31,10 +32,10 @@ pub async fn create_profile(
|
||||||
INSERT INTO actor_profile (
|
INSERT INTO actor_profile (
|
||||||
id, username, display_name, acct, bio, bio_source,
|
id, username, display_name, acct, bio, bio_source,
|
||||||
avatar_file_name, banner_file_name,
|
avatar_file_name, banner_file_name,
|
||||||
identity_proofs, extra_fields,
|
identity_proofs, payment_options, extra_fields,
|
||||||
actor_json
|
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
|
RETURNING actor_profile
|
||||||
",
|
",
|
||||||
&[
|
&[
|
||||||
|
@ -47,6 +48,7 @@ pub async fn create_profile(
|
||||||
&profile_data.avatar,
|
&profile_data.avatar,
|
||||||
&profile_data.banner,
|
&profile_data.banner,
|
||||||
&IdentityProofs(profile_data.identity_proofs),
|
&IdentityProofs(profile_data.identity_proofs),
|
||||||
|
&PaymentOptions(profile_data.payment_options),
|
||||||
&ExtraFields(profile_data.extra_fields),
|
&ExtraFields(profile_data.extra_fields),
|
||||||
&profile_data.actor_json,
|
&profile_data.actor_json,
|
||||||
],
|
],
|
||||||
|
@ -70,10 +72,11 @@ pub async fn update_profile(
|
||||||
avatar_file_name = $4,
|
avatar_file_name = $4,
|
||||||
banner_file_name = $5,
|
banner_file_name = $5,
|
||||||
identity_proofs = $6,
|
identity_proofs = $6,
|
||||||
extra_fields = $7,
|
payment_options = $7,
|
||||||
actor_json = $8,
|
extra_fields = $8,
|
||||||
|
actor_json = $9,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $9
|
WHERE id = $10
|
||||||
RETURNING actor_profile
|
RETURNING actor_profile
|
||||||
",
|
",
|
||||||
&[
|
&[
|
||||||
|
@ -83,6 +86,7 @@ pub async fn update_profile(
|
||||||
&data.avatar,
|
&data.avatar,
|
||||||
&data.banner,
|
&data.banner,
|
||||||
&IdentityProofs(data.identity_proofs),
|
&IdentityProofs(data.identity_proofs),
|
||||||
|
&PaymentOptions(data.payment_options),
|
||||||
&ExtraFields(data.extra_fields),
|
&ExtraFields(data.extra_fields),
|
||||||
&data.actor_json,
|
&data.actor_json,
|
||||||
&profile_id,
|
&profile_id,
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use postgres_types::FromSql;
|
use postgres_types::FromSql;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
|
de::Error as DeserializerError,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::activitypub::actors::types::Actor;
|
use crate::activitypub::actors::types::Actor;
|
||||||
use crate::activitypub::identifiers::local_actor_id;
|
use crate::activitypub::identifiers::local_actor_id;
|
||||||
use crate::database::json_macro::{json_from_sql, json_to_sql};
|
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 crate::ethereum::identity::DidPkh;
|
||||||
use super::validators::{
|
use super::validators::{
|
||||||
validate_username,
|
validate_username,
|
||||||
validate_display_name,
|
validate_display_name,
|
||||||
|
validate_payment_options,
|
||||||
clean_bio,
|
clean_bio,
|
||||||
clean_extra_fields,
|
clean_extra_fields,
|
||||||
};
|
};
|
||||||
|
@ -35,6 +41,72 @@ impl IdentityProofs {
|
||||||
json_from_sql!(IdentityProofs);
|
json_from_sql!(IdentityProofs);
|
||||||
json_to_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)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ExtraField {
|
pub struct ExtraField {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -70,6 +142,7 @@ pub struct DbActorProfile {
|
||||||
pub avatar_file_name: Option<String>,
|
pub avatar_file_name: Option<String>,
|
||||||
pub banner_file_name: Option<String>,
|
pub banner_file_name: Option<String>,
|
||||||
pub identity_proofs: IdentityProofs,
|
pub identity_proofs: IdentityProofs,
|
||||||
|
pub payment_options: PaymentOptions,
|
||||||
pub extra_fields: ExtraFields,
|
pub extra_fields: ExtraFields,
|
||||||
pub follower_count: i32,
|
pub follower_count: i32,
|
||||||
pub following_count: i32,
|
pub following_count: i32,
|
||||||
|
@ -143,6 +216,7 @@ impl Default for DbActorProfile {
|
||||||
avatar_file_name: None,
|
avatar_file_name: None,
|
||||||
banner_file_name: None,
|
banner_file_name: None,
|
||||||
identity_proofs: IdentityProofs(vec![]),
|
identity_proofs: IdentityProofs(vec![]),
|
||||||
|
payment_options: PaymentOptions(vec![]),
|
||||||
extra_fields: ExtraFields(vec![]),
|
extra_fields: ExtraFields(vec![]),
|
||||||
follower_count: 0,
|
follower_count: 0,
|
||||||
following_count: 0,
|
following_count: 0,
|
||||||
|
@ -164,6 +238,7 @@ pub struct ProfileCreateData {
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
pub identity_proofs: Vec<IdentityProof>,
|
pub identity_proofs: Vec<IdentityProof>,
|
||||||
|
pub payment_options: Vec<PaymentOption>,
|
||||||
pub extra_fields: Vec<ExtraField>,
|
pub extra_fields: Vec<ExtraField>,
|
||||||
pub actor_json: Option<Actor>,
|
pub actor_json: Option<Actor>,
|
||||||
}
|
}
|
||||||
|
@ -178,6 +253,7 @@ impl ProfileCreateData {
|
||||||
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
||||||
self.bio = Some(cleaned_bio);
|
self.bio = Some(cleaned_bio);
|
||||||
};
|
};
|
||||||
|
validate_payment_options(&self.payment_options)?;
|
||||||
self.extra_fields = clean_extra_fields(&self.extra_fields)?;
|
self.extra_fields = clean_extra_fields(&self.extra_fields)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -190,6 +266,7 @@ pub struct ProfileUpdateData {
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
pub identity_proofs: Vec<IdentityProof>,
|
pub identity_proofs: Vec<IdentityProof>,
|
||||||
|
pub payment_options: Vec<PaymentOption>,
|
||||||
pub extra_fields: Vec<ExtraField>,
|
pub extra_fields: Vec<ExtraField>,
|
||||||
pub actor_json: Option<Actor>,
|
pub actor_json: Option<Actor>,
|
||||||
}
|
}
|
||||||
|
@ -204,7 +281,7 @@ impl ProfileUpdateData {
|
||||||
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
||||||
self.bio = Some(cleaned_bio);
|
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)?;
|
self.extra_fields = clean_extra_fields(&self.extra_fields)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -220,6 +297,7 @@ impl From<&DbActorProfile> for ProfileUpdateData {
|
||||||
avatar: profile.avatar_file_name,
|
avatar: profile.avatar_file_name,
|
||||||
banner: profile.banner_file_name,
|
banner: profile.banner_file_name,
|
||||||
identity_proofs: profile.identity_proofs.into_inner(),
|
identity_proofs: profile.identity_proofs.into_inner(),
|
||||||
|
payment_options: profile.payment_options.into_inner(),
|
||||||
extra_fields: profile.extra_fields.into_inner(),
|
extra_fields: profile.extra_fields.into_inner(),
|
||||||
actor_json: profile.actor_json,
|
actor_json: profile.actor_json,
|
||||||
}
|
}
|
||||||
|
@ -242,6 +320,18 @@ mod tests {
|
||||||
assert_eq!(serialized, json_data);
|
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]
|
#[test]
|
||||||
fn test_local_actor_address() {
|
fn test_local_actor_address() {
|
||||||
let local_profile = DbActorProfile {
|
let local_profile = DbActorProfile {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::utils::html::{clean_html, clean_html_strict};
|
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_\.-]+$";
|
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)
|
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_NAME_MAX_SIZE: usize = 500;
|
||||||
const FIELD_VALUE_MAX_SIZE: usize = 5000;
|
const FIELD_VALUE_MAX_SIZE: usize = 5000;
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ pub async fn create_user(
|
||||||
avatar: None,
|
avatar: None,
|
||||||
banner: None,
|
banner: None,
|
||||||
identity_proofs: vec![],
|
identity_proofs: vec![],
|
||||||
|
payment_options: vec![],
|
||||||
extra_fields: vec![],
|
extra_fields: vec![],
|
||||||
actor_json: None,
|
actor_json: None,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue