Adding activity table inserts.

This commit is contained in:
Dessalines 2020-04-27 18:17:02 -04:00
parent 9c30b37d57
commit 70060c27b2
15 changed files with 334 additions and 59 deletions

110
server/Cargo.lock generated vendored
View File

@ -390,9 +390,9 @@ dependencies = [
[[package]]
name = "arc-swap"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
checksum = "b585a98a234c46fc563103e9278c9391fde1f4e6850334da895d27edb9580f62"
[[package]]
name = "arrayvec"
@ -488,6 +488,16 @@ dependencies = [
"libc",
]
[[package]]
name = "base64"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
dependencies = [
"byteorder",
"safemem",
]
[[package]]
name = "base64"
version = "0.10.1"
@ -630,9 +640,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.50"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d"
[[package]]
name = "cfg-if"
@ -875,6 +885,7 @@ dependencies = [
"diesel_derives",
"pq-sys",
"r2d2",
"serde_json 1.0.51",
]
[[package]]
@ -945,16 +956,17 @@ checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "email"
version = "0.0.21"
version = "0.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65360503b8831670969621be3d3814c4c7be44c642de11f8c0f5aaa01f057b3e"
checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4"
dependencies = [
"base64 0.11.0",
"base64 0.9.3",
"chrono",
"encoding",
"lazy_static 1.4.0",
"rand 0.7.3",
"version_check 0.9.1",
"rand 0.4.6",
"time",
"version_check 0.1.5",
]
[[package]]
@ -1303,9 +1315,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15"
checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4"
dependencies = [
"libc",
]
@ -1362,9 +1374,9 @@ dependencies = [
[[package]]
name = "http-signature-normalization"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "257835255b5d40c6de712d90e56dc874ca5da2816121e7b9f3cfc7b3a55a5714"
checksum = "fde6b0321d465ea2cdc18b5d5ec73eee2ff20177ecee126cd8dd997f6c49e088"
dependencies = [
"chrono",
"thiserror",
@ -1571,6 +1583,7 @@ dependencies = [
"strum_macros",
"tokio",
"url",
"uuid 0.8.1",
]
[[package]]
@ -1593,16 +1606,16 @@ dependencies = [
[[package]]
name = "lettre_email"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c03656b85bea745db40d9e82a8a0c7236e0171dcdf1b98e95916956ec993f1"
checksum = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5"
dependencies = [
"base64 0.10.1",
"email",
"lettre",
"mime",
"time",
"uuid",
"uuid 0.7.4",
]
[[package]]
@ -1972,9 +1985,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.7.1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
"cfg-if",
"cloudabi",
@ -2072,9 +2085,9 @@ checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
[[package]]
name = "pin-utils"
version = "0.1.0-alpha.4"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
@ -2154,6 +2167,19 @@ dependencies = [
"scheduled-thread-pool",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi 0.3.8",
]
[[package]]
name = "rand"
version = "0.6.5"
@ -2426,9 +2452,15 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "schannel"
@ -2457,9 +2489,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a"
checksum = "3f331b9025654145cd425b9ded0caf8f5ae0df80d418b326e2dc1c3dc5eb0620"
dependencies = [
"bitflags",
"core-foundation",
@ -2470,9 +2502,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f"
checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
dependencies = [
"core-foundation-sys",
"libc",
@ -2661,9 +2693,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a"
checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "socket2"
@ -2727,9 +2759,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213"
dependencies = [
"proc-macro2",
"quote",
@ -2830,9 +2862,9 @@ dependencies = [
[[package]]
name = "threadpool"
version = "1.7.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
checksum = "e8dae184447c15d5a6916d973c642aec485105a13cd238192a6927ae3e077d66"
dependencies = [
"num_cpus",
]
@ -2849,9 +2881,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
checksum = "7d9c43f1bb96970e153bcbae39a65e249ccb942bd9d36dbdf086024920417c9c"
dependencies = [
"bytes",
"fnv",
@ -3062,6 +3094,16 @@ dependencies = [
"rand 0.6.5",
]
[[package]]
name = "uuid"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand 0.7.3",
"serde 1.0.106",
]
[[package]]
name = "v_escape"
version = "0.7.4"

3
server/Cargo.toml vendored
View File

@ -5,7 +5,7 @@ authors = ["Dessalines <happydooby@gmail.com>"]
edition = "2018"
[dependencies]
diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] }
diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
activitystreams = "0.5.0-alpha.16"
@ -45,3 +45,4 @@ base64 = "0.12.0"
tokio = "0.2.18"
futures = "0.3.4"
itertools = "0.9.0"
uuid = { version = "0.8", features = ["serde", "v4"] }

View File

@ -488,7 +488,7 @@ impl Perform for Oper<FollowCommunity> {
} else {
// TODO: still have to implement unfollow
let user = User_::read(&conn, user_id)?;
user.send_follow(&community.actor_id)?;
user.send_follow(&community.actor_id, &conn)?;
// TODO: this needs to return a "pending" state, until Accept is received from the remote server
}

View File

@ -104,6 +104,16 @@ impl ApubObjectType for Comment {
.create_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&create)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&create,
&creator.private_key.as_ref().unwrap(),
@ -128,6 +138,16 @@ impl ApubObjectType for Comment {
.update_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&update)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&update,
&creator.private_key.as_ref().unwrap(),

View File

@ -57,7 +57,7 @@ impl ActorType for Community {
}
/// As a local community, accept the follow request from a remote user.
fn send_accept_follow(&self, follow: &Follow) -> Result<(), Error> {
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
let actor_uri = follow
.follow_props
.get_actor_xsd_any_uri()
@ -65,22 +65,28 @@ impl ActorType for Community {
.to_string();
let mut accept = Accept::new();
// TODO using a fake accept id
let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
//follow
accept
.object_props
.set_context_xsd_any_uri(context())?
// TODO: needs proper id
.set_id(
follow
.follow_props
.get_actor_xsd_any_uri()
.unwrap()
.to_string(),
)?;
.set_id(id)?;
accept
.accept_props
.set_actor_xsd_any_uri(self.actor_id.to_owned())?
.set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
let to = format!("{}/inbox", actor_uri);
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: self.creator_id,
data: serde_json::to_value(&accept)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&accept,
&self.private_key.to_owned().unwrap(),

View File

@ -55,6 +55,15 @@ fn handle_follow(
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(&follow)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
user_id: user.id,
@ -63,7 +72,7 @@ fn handle_follow(
// This will fail if they're already a follower, but ignore the error.
CommunityFollower::follow(&conn, &community_follower_form).ok();
community.send_accept_follow(&follow)?;
community.send_accept_follow(&follow, &conn)?;
Ok(HttpResponse::Ok().finish())
}

View File

@ -46,6 +46,7 @@ use url::Url;
use crate::api::comment::CommentResponse;
use crate::api::post::PostResponse;
use crate::api::site::SearchResponse;
use crate::db::activity;
use crate::db::comment::{Comment, CommentForm};
use crate::db::comment_view::CommentView;
use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
@ -182,12 +183,12 @@ pub trait ActorType {
// These two have default impls, since currently a community can't follow anything,
// and a user can't be followed (yet)
#[allow(unused_variables)]
fn send_follow(&self, follow_actor_id: &str) -> Result<(), Error> {
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
Ok(())
}
#[allow(unused_variables)]
fn send_accept_follow(&self, follow: &Follow) -> Result<(), Error> {
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
Ok(())
}

View File

@ -108,6 +108,16 @@ impl ApubObjectType for Post {
.create_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(page)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&create)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&create,
&creator.private_key.as_ref().unwrap(),
@ -131,6 +141,16 @@ impl ApubObjectType for Post {
.update_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(page)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&update)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&update,
&creator.private_key.as_ref().unwrap(),

View File

@ -77,6 +77,15 @@ fn receive_create_post(
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(&create)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let post = PostForm::from_apub(&page, &conn)?;
let inserted_post = Post::create(conn, &post)?;
@ -110,6 +119,15 @@ fn receive_create_comment(
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(&create)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let comment = CommentForm::from_apub(&note, &conn)?;
let inserted_comment = Comment::create(conn, &comment)?;
@ -183,6 +201,15 @@ fn receive_update_post(
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(&update)?,
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;
Post::update(conn, post_id, &post)?;
@ -217,6 +244,15 @@ fn receive_update_comment(
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(&update)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let comment = CommentForm::from_apub(&note, &conn)?;
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
Comment::update(conn, comment_id, &comment)?;

View File

@ -54,20 +54,32 @@ impl ActorType for User_ {
self.public_key.to_owned().unwrap()
}
// TODO might be able to move this to a default trait fn
/// As a given local user, send out a follow request to a remote community.
fn send_follow(&self, follow_actor_id: &str) -> Result<(), Error> {
fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
let mut follow = Follow::new();
// TODO using a fake accept id
let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
follow
.object_props
.set_context_xsd_any_uri(context())?
// TODO: needs proper id
.set_id(self.actor_id.to_owned())?;
.set_id(id)?;
follow
.follow_props
.set_actor_xsd_any_uri(self.actor_id.to_owned())?
.set_object_xsd_any_uri(follow_actor_id)?;
let to = format!("{}/inbox", follow_actor_id);
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: self.id,
data: serde_json::to_value(&follow)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&follow,
&self.private_key.as_ref().unwrap(),

View File

@ -43,6 +43,15 @@ fn handle_accept(
let user = User_::read_from_name(&conn, username)?;
// Insert the received activity into the activity table
let activity_form = activity::ActivityForm {
user_id: community.creator_id,
data: serde_json::to_value(&accept)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
// Now you need to add this to the community follower
let community_follower_form = CommunityFollowerForm {
community_id: community.id,

123
server/src/db/activity.rs Normal file
View File

@ -0,0 +1,123 @@
use super::*;
use crate::schema::activity;
use crate::schema::activity::dsl::*;
use serde_json::Value;
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "activity"]
pub struct Activity {
pub id: i32,
pub user_id: i32,
pub data: Value,
pub local: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
}
#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
#[table_name = "activity"]
pub struct ActivityForm {
pub user_id: i32,
pub data: Value,
pub local: bool,
pub updated: Option<chrono::NaiveDateTime>,
}
impl Crud<ActivityForm> for Activity {
fn read(conn: &PgConnection, activity_id: i32) -> Result<Self, Error> {
activity.find(activity_id).first::<Self>(conn)
}
fn delete(conn: &PgConnection, activity_id: i32) -> Result<usize, Error> {
diesel::delete(activity.find(activity_id)).execute(conn)
}
fn create(conn: &PgConnection, new_activity: &ActivityForm) -> Result<Self, Error> {
insert_into(activity)
.values(new_activity)
.get_result::<Self>(conn)
}
fn update(
conn: &PgConnection,
activity_id: i32,
new_activity: &ActivityForm,
) -> Result<Self, Error> {
diesel::update(activity.find(activity_id))
.set(new_activity)
.get_result::<Self>(conn)
}
}
#[cfg(test)]
mod tests {
use super::super::user::*;
use super::*;
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let creator_form = UserForm {
name: "activity_creator_pm".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
matrix_user_id: None,
avatar: None,
admin: false,
banned: false,
updated: None,
show_nsfw: false,
theme: "darkly".into(),
default_sort_type: SortType::Hot as i16,
default_listing_type: ListingType::Subscribed as i16,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
actor_id: "changeme".into(),
bio: None,
local: true,
private_key: None,
public_key: None,
last_refreshed_at: None,
};
let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let test_json: Value = serde_json::from_str(
r#"{
"street": "Article Circle Expressway 1",
"city": "North Pole",
"postcode": "99705",
"state": "Alaska"
}"#,
)
.unwrap();
let activity_form = ActivityForm {
user_id: inserted_creator.id,
data: test_json.to_owned(),
local: true,
updated: None,
};
let inserted_activity = Activity::create(&conn, &activity_form).unwrap();
let expected_activity = Activity {
id: inserted_activity.id,
user_id: inserted_creator.id,
data: test_json,
local: true,
published: inserted_activity.published,
updated: None,
};
let read_activity = Activity::read(&conn, inserted_activity.id).unwrap();
let num_deleted = Activity::delete(&conn, inserted_activity.id).unwrap();
User_::delete(&conn, inserted_creator.id).unwrap();
assert_eq!(expected_activity, read_activity);
assert_eq!(expected_activity, inserted_activity);
assert_eq!(1, num_deleted);
}
}

View File

@ -4,6 +4,7 @@ use diesel::result::Error;
use diesel::*;
use serde::{Deserialize, Serialize};
pub mod activity;
pub mod category;
pub mod code_migrations;
pub mod comment;

View File

@ -25,10 +25,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.route("/feeds/all.xml", web::get().to(feeds::get_all_feed));
}
async fn get_all_feed(
info: web::Query<Params>,
db: DbPoolParam,
) -> Result<HttpResponse, Error> {
async fn get_all_feed(info: web::Query<Params>, db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
get_feed_all_data(&conn, &get_sort_type(info)?)

View File

@ -20,9 +20,7 @@ async fn node_info_well_known() -> Result<HttpResponse<Body>, failure::Error> {
Ok(HttpResponse::Ok().json(node_info))
}
async fn node_info(
db: DbPoolParam,
) -> Result<HttpResponse, Error> {
async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
let site_view = match SiteView::read(&conn) {