2020-04-27 16:57:00 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[serde(untagged)]
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub enum SharedAcceptedObjects {
|
|
|
|
Create(Create),
|
|
|
|
Update(Update),
|
2020-04-28 04:16:02 +00:00
|
|
|
Like(Like),
|
|
|
|
Dislike(Dislike),
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
impl SharedAcceptedObjects {
|
|
|
|
fn object(&self) -> Option<&BaseBox> {
|
|
|
|
match self {
|
|
|
|
SharedAcceptedObjects::Create(c) => c.create_props.get_object_base_box(),
|
|
|
|
SharedAcceptedObjects::Update(u) => u.update_props.get_object_base_box(),
|
2020-04-28 04:16:02 +00:00
|
|
|
SharedAcceptedObjects::Like(l) => l.like_props.get_object_base_box(),
|
|
|
|
SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_object_base_box(),
|
2020-04-27 23:21:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
/// Handler for all incoming activities to user inboxes.
|
|
|
|
pub async fn shared_inbox(
|
|
|
|
request: HttpRequest,
|
|
|
|
input: web::Json<SharedAcceptedObjects>,
|
|
|
|
db: DbPoolParam,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
// TODO: would be nice if we could do the signature check here, but we cant access the actor property
|
2020-04-27 23:21:41 +00:00
|
|
|
let activity = input.into_inner();
|
2020-04-27 16:57:00 +00:00
|
|
|
let conn = &db.get().unwrap();
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
let json = serde_json::to_string(&activity)?;
|
|
|
|
debug!("Shared inbox received activity: {}", json);
|
|
|
|
|
|
|
|
let object = activity.object().cloned().unwrap();
|
|
|
|
|
|
|
|
match (activity, object.kind()) {
|
|
|
|
(SharedAcceptedObjects::Create(c), Some("Page")) => {
|
|
|
|
receive_create_post(&c, &request, &conn, chat_server)
|
|
|
|
}
|
2020-04-28 04:16:02 +00:00
|
|
|
(SharedAcceptedObjects::Update(u), Some("Page")) => {
|
|
|
|
receive_update_post(&u, &request, &conn, chat_server)
|
|
|
|
}
|
|
|
|
(SharedAcceptedObjects::Like(l), Some("Page")) => {
|
|
|
|
receive_like_post(&l, &request, &conn, chat_server)
|
|
|
|
}
|
|
|
|
(SharedAcceptedObjects::Dislike(d), Some("Page")) => {
|
|
|
|
receive_dislike_post(&d, &request, &conn, chat_server)
|
|
|
|
}
|
|
|
|
(SharedAcceptedObjects::Create(c), Some("Note")) => {
|
|
|
|
receive_create_comment(&c, &request, &conn, chat_server)
|
|
|
|
}
|
2020-04-27 23:21:41 +00:00
|
|
|
(SharedAcceptedObjects::Update(u), Some("Note")) => {
|
|
|
|
receive_update_comment(&u, &request, &conn, chat_server)
|
|
|
|
}
|
2020-04-28 04:16:02 +00:00
|
|
|
(SharedAcceptedObjects::Like(l), Some("Note")) => {
|
|
|
|
receive_like_comment(&l, &request, &conn, chat_server)
|
|
|
|
}
|
|
|
|
(SharedAcceptedObjects::Dislike(d), Some("Note")) => {
|
|
|
|
receive_dislike_comment(&d, &request, &conn, chat_server)
|
2020-04-27 23:21:41 +00:00
|
|
|
}
|
|
|
|
_ => Err(format_err!("Unknown incoming activity type.")),
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
fn receive_create_post(
|
2020-04-27 16:57:00 +00:00
|
|
|
create: &Create,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
2020-04-27 23:21:41 +00:00
|
|
|
let page = create
|
|
|
|
.create_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Page>()?;
|
2020-04-27 16:57:00 +00:00
|
|
|
|
|
|
|
let user_uri = create
|
|
|
|
.create_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
2020-04-27 22:17:02 +00:00
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&create)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let post = PostForm::from_apub(&page, &conn)?;
|
|
|
|
let inserted_post = Post::create(conn, &post)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let post_view = PostView::read(&conn, inserted_post.id, None)?;
|
|
|
|
|
|
|
|
let res = PostResponse { post: post_view };
|
|
|
|
|
|
|
|
chat_server.do_send(SendPost {
|
|
|
|
op: UserOperation::CreatePost,
|
|
|
|
post: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
Ok(HttpResponse::Ok().finish())
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn receive_create_comment(
|
|
|
|
create: &Create,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
2020-04-27 23:21:41 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let note = create
|
|
|
|
.create_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Note>()?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let user_uri = create
|
|
|
|
.create_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
2020-04-27 22:17:02 +00:00
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&create)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let comment = CommentForm::from_apub(¬e, &conn)?;
|
|
|
|
let inserted_comment = Comment::create(conn, &comment)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let comment_view = CommentView::read(&conn, inserted_comment.id, None)?;
|
|
|
|
|
|
|
|
// TODO get those recipient actor ids from somewhere
|
|
|
|
let recipient_ids = vec![];
|
|
|
|
let res = CommentResponse {
|
|
|
|
comment: comment_view,
|
|
|
|
recipient_ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
chat_server.do_send(SendComment {
|
|
|
|
op: UserOperation::CreateComment,
|
|
|
|
comment: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn receive_update_post(
|
|
|
|
update: &Update,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
2020-04-27 23:21:41 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let page = update
|
|
|
|
.update_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Page>()?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let user_uri = update
|
|
|
|
.update_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
2020-04-27 22:17:02 +00:00
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&update)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let post = PostForm::from_apub(&page, conn)?;
|
|
|
|
let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
|
|
|
|
Post::update(conn, post_id, &post)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let post_view = PostView::read(&conn, post_id, None)?;
|
|
|
|
|
|
|
|
let res = PostResponse { post: post_view };
|
|
|
|
|
|
|
|
chat_server.do_send(SendPost {
|
|
|
|
op: UserOperation::EditPost,
|
|
|
|
post: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
Ok(HttpResponse::Ok().finish())
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 04:16:02 +00:00
|
|
|
fn receive_like_post(
|
|
|
|
like: &Like,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let page = like
|
|
|
|
.like_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Page>()?;
|
|
|
|
|
|
|
|
let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&like)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
|
|
|
let post = PostForm::from_apub(&page, conn)?;
|
|
|
|
let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
|
|
|
|
|
|
|
|
let like_form = PostLikeForm {
|
|
|
|
post_id,
|
|
|
|
user_id: user.id,
|
|
|
|
score: 1,
|
|
|
|
};
|
|
|
|
PostLike::remove(&conn, &like_form)?;
|
|
|
|
PostLike::like(&conn, &like_form)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let post_view = PostView::read(&conn, post_id, None)?;
|
|
|
|
|
|
|
|
let res = PostResponse { post: post_view };
|
|
|
|
|
|
|
|
chat_server.do_send(SendPost {
|
|
|
|
op: UserOperation::CreatePostLike,
|
|
|
|
post: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn receive_dislike_post(
|
|
|
|
dislike: &Dislike,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let page = dislike
|
|
|
|
.dislike_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Page>()?;
|
|
|
|
|
|
|
|
let user_uri = dislike
|
|
|
|
.dislike_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&dislike)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
|
|
|
let post = PostForm::from_apub(&page, conn)?;
|
|
|
|
let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
|
|
|
|
|
|
|
|
let like_form = PostLikeForm {
|
|
|
|
post_id,
|
|
|
|
user_id: user.id,
|
|
|
|
score: -1,
|
|
|
|
};
|
|
|
|
PostLike::remove(&conn, &like_form)?;
|
|
|
|
PostLike::like(&conn, &like_form)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let post_view = PostView::read(&conn, post_id, None)?;
|
|
|
|
|
|
|
|
let res = PostResponse { post: post_view };
|
|
|
|
|
|
|
|
chat_server.do_send(SendPost {
|
|
|
|
op: UserOperation::CreatePostLike,
|
|
|
|
post: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
|
|
}
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
fn receive_update_comment(
|
|
|
|
update: &Update,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
2020-04-27 23:21:41 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let note = update
|
|
|
|
.update_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Note>()?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let user_uri = update
|
|
|
|
.update_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
2020-04-27 22:17:02 +00:00
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&update)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
2020-04-27 16:57:00 +00:00
|
|
|
let comment = CommentForm::from_apub(¬e, &conn)?;
|
|
|
|
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
|
|
|
|
Comment::update(conn, comment_id, &comment)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let comment_view = CommentView::read(&conn, comment_id, None)?;
|
|
|
|
|
|
|
|
// TODO get those recipient actor ids from somewhere
|
|
|
|
let recipient_ids = vec![];
|
|
|
|
let res = CommentResponse {
|
|
|
|
comment: comment_view,
|
|
|
|
recipient_ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
chat_server.do_send(SendComment {
|
|
|
|
op: UserOperation::EditComment,
|
|
|
|
comment: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
2020-04-27 23:21:41 +00:00
|
|
|
Ok(HttpResponse::Ok().finish())
|
2020-04-27 16:57:00 +00:00
|
|
|
}
|
2020-04-28 04:16:02 +00:00
|
|
|
|
|
|
|
fn receive_like_comment(
|
|
|
|
like: &Like,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let note = like
|
|
|
|
.like_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Note>()?;
|
|
|
|
|
|
|
|
let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&like)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
|
|
|
let comment = CommentForm::from_apub(¬e, &conn)?;
|
|
|
|
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
|
|
|
|
let like_form = CommentLikeForm {
|
|
|
|
comment_id,
|
|
|
|
post_id: comment.post_id,
|
|
|
|
user_id: user.id,
|
|
|
|
score: 1,
|
|
|
|
};
|
|
|
|
CommentLike::remove(&conn, &like_form)?;
|
|
|
|
CommentLike::like(&conn, &like_form)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let comment_view = CommentView::read(&conn, comment_id, None)?;
|
|
|
|
|
|
|
|
// TODO get those recipient actor ids from somewhere
|
|
|
|
let recipient_ids = vec![];
|
|
|
|
let res = CommentResponse {
|
|
|
|
comment: comment_view,
|
|
|
|
recipient_ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
chat_server.do_send(SendComment {
|
|
|
|
op: UserOperation::CreateCommentLike,
|
|
|
|
comment: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn receive_dislike_comment(
|
|
|
|
dislike: &Dislike,
|
|
|
|
request: &HttpRequest,
|
|
|
|
conn: &PgConnection,
|
|
|
|
chat_server: ChatServerParam,
|
|
|
|
) -> Result<HttpResponse, Error> {
|
|
|
|
let note = dislike
|
|
|
|
.dislike_props
|
|
|
|
.get_object_base_box()
|
|
|
|
.to_owned()
|
|
|
|
.unwrap()
|
|
|
|
.to_owned()
|
|
|
|
.to_concrete::<Note>()?;
|
|
|
|
|
|
|
|
let user_uri = dislike
|
|
|
|
.dislike_props
|
|
|
|
.get_actor_xsd_any_uri()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
|
|
|
|
verify(request, &user.public_key.unwrap())?;
|
|
|
|
|
|
|
|
// Insert the received activity into the activity table
|
|
|
|
let activity_form = activity::ActivityForm {
|
|
|
|
user_id: user.id,
|
|
|
|
data: serde_json::to_value(&dislike)?,
|
|
|
|
local: false,
|
|
|
|
updated: None,
|
|
|
|
};
|
|
|
|
activity::Activity::create(&conn, &activity_form)?;
|
|
|
|
|
|
|
|
let comment = CommentForm::from_apub(¬e, &conn)?;
|
|
|
|
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
|
|
|
|
let like_form = CommentLikeForm {
|
|
|
|
comment_id,
|
|
|
|
post_id: comment.post_id,
|
|
|
|
user_id: user.id,
|
|
|
|
score: -1,
|
|
|
|
};
|
|
|
|
CommentLike::remove(&conn, &like_form)?;
|
|
|
|
CommentLike::like(&conn, &like_form)?;
|
|
|
|
|
|
|
|
// Refetch the view
|
|
|
|
let comment_view = CommentView::read(&conn, comment_id, None)?;
|
|
|
|
|
|
|
|
// TODO get those recipient actor ids from somewhere
|
|
|
|
let recipient_ids = vec![];
|
|
|
|
let res = CommentResponse {
|
|
|
|
comment: comment_view,
|
|
|
|
recipient_ids,
|
|
|
|
};
|
|
|
|
|
|
|
|
chat_server.do_send(SendComment {
|
|
|
|
op: UserOperation::CreateCommentLike,
|
|
|
|
comment: res,
|
|
|
|
my_id: None,
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(HttpResponse::Ok().finish())
|
|
|
|
}
|