mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 02:22:21 +00:00
changes around extensions
This commit is contained in:
@@ -13,8 +13,7 @@ use actix_web::web;
|
|||||||
use actix_web::{web::Data, HttpRequest};
|
use actix_web::{web::Data, HttpRequest};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::derive::{From, Into, TryInto};
|
use derive_more::derive::{From, Into, TryInto};
|
||||||
use rustical_dav::extension::BoxedExtension;
|
use rustical_dav::extensions::CommonPropertiesProp;
|
||||||
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
|
||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
use rustical_dav::privileges::UserPrivilegeSet;
|
||||||
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
||||||
use rustical_store::auth::User;
|
use rustical_store::auth::User;
|
||||||
@@ -86,7 +85,7 @@ pub enum CalendarProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<CalendarResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -106,12 +105,7 @@ impl Resource for CalendarResource {
|
|||||||
type Prop = CalendarProp;
|
type Prop = CalendarProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use actix_web::{dev::ResourceMap, web::Data, HttpRequest};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::derive::{From, Into, TryInto};
|
use derive_more::derive::{From, Into, TryInto};
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
extension::BoxedExtension,
|
extensions::CommonPropertiesProp,
|
||||||
extensions::{CommonPropertiesExtension, CommonPropertiesProp},
|
|
||||||
privileges::UserPrivilegeSet,
|
privileges::UserPrivilegeSet,
|
||||||
resource::{InvalidProperty, Resource, ResourceService},
|
resource::{InvalidProperty, Resource, ResourceService},
|
||||||
};
|
};
|
||||||
@@ -44,7 +43,7 @@ pub enum CalendarObjectProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<CalendarObjectResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -73,14 +72,9 @@ impl Resource for CalendarObjectResource {
|
|||||||
type PropName = CalendarObjectPropName;
|
type PropName = CalendarObjectPropName;
|
||||||
type Prop = CalendarObjectProp;
|
type Prop = CalendarObjectProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
_rmap: &ResourceMap,
|
_rmap: &ResourceMap,
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use actix_web::web::Data;
|
|||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::derive::{From, TryInto};
|
use derive_more::derive::{From, TryInto};
|
||||||
use rustical_dav::extension::BoxedExtension;
|
use rustical_dav::extensions::CommonPropertiesProp;
|
||||||
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
|
||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
use rustical_dav::privileges::UserPrivilegeSet;
|
||||||
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
||||||
use rustical_dav::xml::HrefElement;
|
use rustical_dav::xml::HrefElement;
|
||||||
@@ -49,7 +48,7 @@ pub enum PrincipalProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<PrincipalResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -81,12 +80,7 @@ impl Resource for PrincipalResource {
|
|||||||
type Prop = PrincipalProp;
|
type Prop = PrincipalProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use actix_web::{dev::ResourceMap, web::Data, HttpRequest};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::derive::{From, Into, TryInto};
|
use derive_more::derive::{From, Into, TryInto};
|
||||||
use rustical_dav::{
|
use rustical_dav::{
|
||||||
extension::BoxedExtension,
|
extensions::CommonPropertiesProp,
|
||||||
extensions::{CommonPropertiesExtension, CommonPropertiesProp},
|
|
||||||
privileges::UserPrivilegeSet,
|
privileges::UserPrivilegeSet,
|
||||||
resource::{InvalidProperty, Resource, ResourceService},
|
resource::{InvalidProperty, Resource, ResourceService},
|
||||||
};
|
};
|
||||||
@@ -45,7 +44,7 @@ pub enum AddressObjectProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<AddressObjectResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -75,12 +74,7 @@ impl Resource for AddressObjectResource {
|
|||||||
type Prop = AddressObjectProp;
|
type Prop = AddressObjectProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ use actix_web::web;
|
|||||||
use actix_web::{web::Data, HttpRequest};
|
use actix_web::{web::Data, HttpRequest};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_more::derive::{From, Into, TryInto};
|
use derive_more::derive::{From, Into, TryInto};
|
||||||
use rustical_dav::extension::BoxedExtension;
|
use rustical_dav::extensions::CommonPropertiesProp;
|
||||||
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
|
||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
use rustical_dav::privileges::UserPrivilegeSet;
|
||||||
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
use rustical_dav::resource::{InvalidProperty, Resource, ResourceService};
|
||||||
use rustical_store::auth::User;
|
use rustical_store::auth::User;
|
||||||
@@ -71,7 +70,7 @@ pub enum AddressbookProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<AddressbookResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -91,12 +90,7 @@ impl Resource for AddressbookResource {
|
|||||||
type Prop = AddressbookProp;
|
type Prop = AddressbookProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub enum PrincipalProp {
|
|||||||
#[serde(skip_deserializing, untagged)]
|
#[serde(skip_deserializing, untagged)]
|
||||||
#[from]
|
#[from]
|
||||||
#[try_into]
|
#[try_into]
|
||||||
ExtCommonProperties(CommonPropertiesProp<PrincipalResource>),
|
ExtCommonProperties(CommonPropertiesProp<Resourcetype>),
|
||||||
|
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
Invalid,
|
Invalid,
|
||||||
@@ -81,12 +81,7 @@ impl Resource for PrincipalResource {
|
|||||||
type Prop = PrincipalProp;
|
type Prop = PrincipalProp;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PrincipalResource;
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
|
||||||
vec![BoxedExtension::from_ext(CommonPropertiesExtension::<
|
|
||||||
PrincipalResource,
|
|
||||||
>::default())]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -49,10 +49,8 @@ pub trait BoxableExtension<R: Resource> {
|
|||||||
fn list_props(&self) -> &'static [&'static str];
|
fn list_props(&self) -> &'static [&'static str];
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<
|
impl<R: Resource, RE: ResourceExtension<R, Prop: Into<R::Prop>, Error: Into<R::Error>>>
|
||||||
R: Resource,
|
BoxableExtension<R> for RE
|
||||||
RE: ResourceExtension<R, Prop: Into<R::Prop> + TryFrom<R::Prop>, Error: Into<R::Error>>,
|
|
||||||
> BoxableExtension<R> for RE
|
|
||||||
{
|
{
|
||||||
fn get_prop(
|
fn get_prop(
|
||||||
&self,
|
&self,
|
||||||
@@ -114,9 +112,7 @@ impl<
|
|||||||
pub struct BoxedExtension<R>(Box<dyn BoxableExtension<R>>);
|
pub struct BoxedExtension<R>(Box<dyn BoxableExtension<R>>);
|
||||||
|
|
||||||
impl<R: Resource> BoxedExtension<R> {
|
impl<R: Resource> BoxedExtension<R> {
|
||||||
pub fn from_ext<RE: ResourceExtension<R, Prop: Into<R::Prop> + TryFrom<R::Prop>> + 'static>(
|
pub fn from_ext<RE: ResourceExtension<R, Prop: Into<R::Prop>> + 'static>(ext: RE) -> Self {
|
||||||
ext: RE,
|
|
||||||
) -> Self {
|
|
||||||
let boxed_ext: Box<dyn BoxableExtension<R>> = Box::new(ext);
|
let boxed_ext: Box<dyn BoxableExtension<R>> = Box::new(ext);
|
||||||
BoxedExtension(boxed_ext)
|
BoxedExtension(boxed_ext)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
extension::ResourceExtension,
|
extension::ResourceExtension,
|
||||||
privileges::UserPrivilegeSet,
|
privileges::UserPrivilegeSet,
|
||||||
resource::{InvalidProperty, Resource},
|
resource::{InvalidProperty, Resource, ResourceType},
|
||||||
xml::HrefElement,
|
xml::HrefElement,
|
||||||
};
|
};
|
||||||
use actix_web::dev::ResourceMap;
|
use actix_web::dev::ResourceMap;
|
||||||
@@ -11,9 +11,9 @@ use std::marker::PhantomData;
|
|||||||
use strum::{EnumString, VariantNames};
|
use strum::{EnumString, VariantNames};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CommonPropertiesExtension<PR: Resource>(PhantomData<PR>);
|
pub struct CommonPropertiesExtension<R: Resource>(PhantomData<R>);
|
||||||
|
|
||||||
impl<PR: Resource> Default for CommonPropertiesExtension<PR> {
|
impl<R: Resource> Default for CommonPropertiesExtension<R> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(PhantomData)
|
Self(PhantomData)
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,10 @@ impl<PR: Resource> Default for CommonPropertiesExtension<PR> {
|
|||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum CommonPropertiesProp<R: Resource> {
|
pub enum CommonPropertiesProp<RT: ResourceType> {
|
||||||
// WebDAV (RFC 2518)
|
// WebDAV (RFC 2518)
|
||||||
Resourcetype(R::ResourceType),
|
#[serde(skip_deserializing)]
|
||||||
|
Resourcetype(RT),
|
||||||
|
|
||||||
// WebDAV Current Principal Extension (RFC 5397)
|
// WebDAV Current Principal Extension (RFC 5397)
|
||||||
CurrentUserPrincipal(HrefElement),
|
CurrentUserPrincipal(HrefElement),
|
||||||
@@ -36,7 +37,7 @@ pub enum CommonPropertiesProp<R: Resource> {
|
|||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Resource> InvalidProperty for CommonPropertiesProp<R> {
|
impl<RT: ResourceType> InvalidProperty for CommonPropertiesProp<RT> {
|
||||||
fn invalid_property(&self) -> bool {
|
fn invalid_property(&self) -> bool {
|
||||||
matches!(self, Self::Invalid)
|
matches!(self, Self::Invalid)
|
||||||
}
|
}
|
||||||
@@ -51,11 +52,11 @@ pub enum CommonPropertiesPropName {
|
|||||||
Owner,
|
Owner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Resource, PR: Resource> ResourceExtension<R> for CommonPropertiesExtension<PR>
|
impl<R: Resource> ResourceExtension<R> for CommonPropertiesExtension<R>
|
||||||
where
|
where
|
||||||
R::Prop: From<CommonPropertiesProp<R>>,
|
R::Prop: From<CommonPropertiesProp<R::ResourceType>>,
|
||||||
{
|
{
|
||||||
type Prop = CommonPropertiesProp<R>;
|
type Prop = CommonPropertiesProp<R::ResourceType>;
|
||||||
type PropName = CommonPropertiesPropName;
|
type PropName = CommonPropertiesPropName;
|
||||||
type Error = R::Error;
|
type Error = R::Error;
|
||||||
|
|
||||||
@@ -72,17 +73,21 @@ where
|
|||||||
}
|
}
|
||||||
CommonPropertiesPropName::CurrentUserPrincipal => {
|
CommonPropertiesPropName::CurrentUserPrincipal => {
|
||||||
CommonPropertiesProp::CurrentUserPrincipal(
|
CommonPropertiesProp::CurrentUserPrincipal(
|
||||||
PR::get_url(rmap, &[&user.id]).unwrap().into(),
|
R::PrincipalResource::get_url(rmap, &[&user.id])
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CommonPropertiesPropName::CurrentUserPrivilegeSet => {
|
CommonPropertiesPropName::CurrentUserPrivilegeSet => {
|
||||||
CommonPropertiesProp::CurrentUserPrivilegeSet(resource.get_user_privileges(user)?)
|
CommonPropertiesProp::CurrentUserPrivilegeSet(resource.get_user_privileges(user)?)
|
||||||
}
|
}
|
||||||
CommonPropertiesPropName::Owner => CommonPropertiesProp::Owner(
|
CommonPropertiesPropName::Owner => {
|
||||||
resource
|
CommonPropertiesProp::Owner(resource.get_owner().map(|owner| {
|
||||||
.get_owner()
|
R::PrincipalResource::get_url(rmap, &[owner])
|
||||||
.map(|owner| PR::get_url(rmap, &[owner]).unwrap().into()),
|
.unwrap()
|
||||||
),
|
.into()
|
||||||
|
}))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::extension::BoxedExtension;
|
use crate::extension::{BoxableExtension, BoxedExtension};
|
||||||
|
use crate::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
||||||
use crate::methods::{route_delete, route_propfind, route_proppatch};
|
use crate::methods::{route_delete, route_propfind, route_proppatch};
|
||||||
use crate::privileges::UserPrivilegeSet;
|
use crate::privileges::UserPrivilegeSet;
|
||||||
use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
|
use crate::xml::multistatus::{PropTagWrapper, PropstatElement, PropstatWrapper};
|
||||||
@@ -26,14 +27,20 @@ impl<T: ResourceReadProp + for<'de> Deserialize<'de>> ResourceProp for T {}
|
|||||||
pub trait ResourcePropName: FromStr + VariantNames {}
|
pub trait ResourcePropName: FromStr + VariantNames {}
|
||||||
impl<T: FromStr + VariantNames> ResourcePropName for T {}
|
impl<T: FromStr + VariantNames> ResourcePropName for T {}
|
||||||
|
|
||||||
|
pub trait ResourceType: Serialize + for<'de> Deserialize<'de> {}
|
||||||
|
impl<T: Serialize + for<'de> Deserialize<'de>> ResourceType for T {}
|
||||||
|
|
||||||
pub trait Resource: Clone + 'static {
|
pub trait Resource: Clone + 'static {
|
||||||
type PropName: ResourcePropName;
|
type PropName: ResourcePropName;
|
||||||
type Prop: ResourceProp;
|
type Prop: ResourceProp + From<CommonPropertiesProp<Self::ResourceType>>;
|
||||||
type Error: ResponseError + From<crate::Error>;
|
type Error: ResponseError + From<crate::Error>;
|
||||||
|
type PrincipalResource: Resource;
|
||||||
type ResourceType: Default + Serialize + for<'de> Deserialize<'de>;
|
type ResourceType: Default + Serialize + for<'de> Deserialize<'de>;
|
||||||
|
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
||||||
vec![]
|
vec![BoxedExtension::from_ext(
|
||||||
|
CommonPropertiesExtension::<Self>::default(),
|
||||||
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_props() -> Vec<&'static str> {
|
fn list_props() -> Vec<&'static str> {
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ impl<PR: Resource> Default for RootResource<PR> {
|
|||||||
|
|
||||||
impl<PR: Resource> Resource for RootResource<PR> {
|
impl<PR: Resource> Resource for RootResource<PR> {
|
||||||
type PropName = CommonPropertiesPropName;
|
type PropName = CommonPropertiesPropName;
|
||||||
type Prop = CommonPropertiesProp<RootResource<PR>>;
|
type Prop = CommonPropertiesProp<Self::ResourceType>;
|
||||||
type Error = PR::Error;
|
type Error = PR::Error;
|
||||||
type ResourceType = Resourcetype;
|
type ResourceType = Resourcetype;
|
||||||
|
type PrincipalResource = PR;
|
||||||
|
|
||||||
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
fn list_extensions() -> Vec<BoxedExtension<Self>> {
|
||||||
vec![BoxedExtension::from_ext(
|
vec![BoxedExtension::from_ext(
|
||||||
CommonPropertiesExtension::<PR>::default(),
|
CommonPropertiesExtension::<Self>::default(),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user