~vpzom/lotide

4d4220ad3f429dfa06247d5cf17c62196acd97ae — Colin Reeder 2 months ago 2289ae1
Distinguish between html & text in user & community descriptions
M Cargo.lock => Cargo.lock +64 -6
@@ 220,6 220,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"

[[package]]
name = "buf-min"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa17aa1cf56bdd6bb30518767d00e58019d326f3f05d8c3e0730b549d332ea83"
dependencies = [
 "bytes",
]

[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 291,7 300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
 "lazy_static",
 "nom",
 "nom 5.1.2",
 "serde",
]



@@ 469,7 478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [
 "backtrace",
 "version_check",
 "version_check 0.9.2",
]

[[package]]


@@ 713,7 722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
dependencies = [
 "typenum",
 "version_check",
 "version_check 0.9.2",
]

[[package]]


@@ 1063,7 1072,7 @@ dependencies = [
 "idna",
 "mime",
 "native-tls",
 "nom",
 "nom 5.1.2",
 "once_cell",
 "quoted_printable",
 "r2d2",


@@ 1176,6 1185,7 @@ dependencies = [
 "unic-langid",
 "url",
 "uuid",
 "v_htmlescape",
]

[[package]]


@@ 1397,13 1407,23 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"

[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
 "memchr",
 "version_check 0.1.5",
]

[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
 "lexical-core",
 "memchr",
 "version_check",
 "version_check 0.9.2",
]

[[package]]


@@ 2417,7 2437,7 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
 "version_check",
 "version_check 0.9.2",
]

[[package]]


@@ 2478,6 2498,38 @@ dependencies = [
]

[[package]]
name = "v_escape"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3e0ab5fab1db278a9413d2ea794cb66f471f898c5b020c3c394f6447625d9d4"
dependencies = [
 "buf-min",
 "v_escape_derive",
]

[[package]]
name = "v_escape_derive"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668"
dependencies = [
 "nom 4.2.3",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "v_htmlescape"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f9a8af610ad6f7fc9989c9d2590d9764bc61f294884e9ee93baa58795174572"
dependencies = [
 "cfg-if 1.0.0",
 "v_escape",
]

[[package]]
name = "vcpkg"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 2485,6 2537,12 @@ checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"

[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"

[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"

M Cargo.toml => Cargo.toml +1 -0
@@ 54,6 54,7 @@ migrant_lib = { version = "0.30.0", features = ["d-postgres"] }
pdcm-linkify = "0.1.0"
log = "0.4"
env_logger = "0.8"
v_htmlescape = "0.12.0"

[dev-dependencies]
rand = "0.7.3"

A migrations/20201231175007_description-html/down.sql => migrations/20201231175007_description-html/down.sql +4 -0
@@ 0,0 1,4 @@
BEGIN;
	ALTER TABLE person DROP COLUMN description_html;
	ALTER TABLE community DROP COLUMN description_html;
COMMIT;

A migrations/20201231175007_description-html/up.sql => migrations/20201231175007_description-html/up.sql +4 -0
@@ 0,0 1,4 @@
BEGIN;
	ALTER TABLE person ADD COLUMN description_html TEXT;
	ALTER TABLE community ADD COLUMN description_html TEXT;
COMMIT;

M src/apub_util/ingest.rs => src/apub_util/ingest.rs +8 -10
@@ 215,10 215,9 @@ pub async fn ingest_object(
                        .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
                })
                .unwrap_or("");
            let description = group
            let description_html = group
                .summary()
                .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
                .unwrap_or("");
                .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next());
            let inbox = group.inbox_unchecked().as_str();
            let shared_inbox = group
                .endpoints_unchecked()


@@ 236,8 235,8 @@ pub async fn ingest_object(
                .and_then(|key| key.signature_algorithm.as_deref());

            let id = CommunityLocalID(db.query_one(
                "INSERT INTO community (name, local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description) VALUES ($1, FALSE, $2, $3, $4, $5, $6, $7) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description=$7 RETURNING id",
                &[&name, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description],
                "INSERT INTO community (name, local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description_html) VALUES ($1, FALSE, $2, $3, $4, $5, $6, $7) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description_html=$7 RETURNING id",
                &[&name, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description_html],
            ).await?.get(0));

            Ok(Some(IngestResult::Actor(


@@ 291,10 290,9 @@ pub async fn ingest_object(
                .public_key
                .as_ref()
                .and_then(|key| key.signature_algorithm.as_deref());
            let description = person
            let description_html = person
                .summary()
                .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
                .unwrap_or("");
                .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next());

            let avatar = person.icon().and_then(|icon| {
                icon.iter()


@@ 316,8 314,8 @@ pub async fn ingest_object(
                .map(|x| x.as_str());

            let id = UserLocalID(db.query_one(
                "INSERT INTO person (username, local, created_local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description, avatar) VALUES ($1, FALSE, localtimestamp, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description=$7, avatar=$8 RETURNING id",
                &[&username, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description, &avatar],
                "INSERT INTO person (username, local, created_local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description_html, avatar) VALUES ($1, FALSE, localtimestamp, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description_html=$7, avatar=$8 RETURNING id",
                &[&username, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description_html, &avatar],
            ).await?.get(0));

            Ok(Some(IngestResult::Actor(super::ActorLocalInfo::User {

M src/routes/api/communities.rs => src/routes/api/communities.rs +35 -9
@@ 13,6 13,8 @@ struct RespCommunityInfo<'a> {
    base: RespMinimalCommunityInfo<'a>,

    description: &'a str,
    description_html: Option<String>,
    description_text: Option<&'a str>,

    #[serde(skip_serializing_if = "Option::is_none")]
    you_are_moderator: Option<bool>,


@@ 25,6 27,20 @@ struct RespYourFollowInfo {
    accepted: bool,
}

fn get_community_description_fields<'a>(
    description_text: &'a str,
    description_html: Option<&'a str>,
) -> (&'a str, Option<&'a str>, Option<String>) {
    match description_html {
        Some(description_html) => (
            description_html,
            None,
            Some(ammonia::clean(description_html)),
        ),
        None => (description_text, Some(description_text), None),
    }
}

async fn route_unstable_communities_list(
    _: (),
    ctx: Arc<crate::RouteContext>,


@@ 43,7 59,7 @@ async fn route_unstable_communities_list(

    let query: CommunitiesListQuery = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;

    let mut sql = String::from("SELECT id, name, local, ap_id, description");
    let mut sql = String::from("SELECT id, name, local, ap_id, description, description_html");
    let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();

    let db = ctx.db_pool.get().await?;


@@ 77,7 93,9 @@ async fn route_unstable_communities_list(
            let name = row.get(1);
            let local = row.get(2);
            let ap_id = row.get(3);
            let description = row.get(4);

            let (description, description_text, description_html) =
                get_community_description_fields(row.get(4), row.get(5));

            let host = crate::get_actor_host_or_unknown(local, ap_id, &ctx.local_hostname);



@@ 91,13 109,16 @@ async fn route_unstable_communities_list(
                },

                description,
                description_html,
                description_text,

                you_are_moderator: if query.include_your {
                    Some(row.get(6))
                    Some(row.get(7))
                } else {
                    None
                },
                your_follow: if query.include_your {
                    Some(match row.get(5) {
                    Some(match row.get(6) {
                        Some(accepted) => Some(RespYourFollowInfo { accepted }),
                        None => None,
                    })


@@ 204,12 225,12 @@ async fn route_unstable_communities_get(
        (if query.include_your {
            let user = crate::require_login(&req, &db).await?;
            db.query_opt(
                "SELECT name, local, ap_id, description, (SELECT accepted FROM community_follow WHERE community=community.id AND follower=$2), EXISTS(SELECT 1 FROM community_moderator WHERE community=community.id AND person=$2) FROM community WHERE id=$1",
                "SELECT name, local, ap_id, description, description_html, (SELECT accepted FROM community_follow WHERE community=community.id AND follower=$2), EXISTS(SELECT 1 FROM community_moderator WHERE community=community.id AND person=$2) FROM community WHERE id=$1",
                &[&community_id.raw(), &user.raw()],
            ).await?
        } else {
            db.query_opt(
                "SELECT name, local, ap_id, description FROM community WHERE id=$1",
                "SELECT name, local, ap_id, description, description_html FROM community WHERE id=$1",
                &[&community_id.raw()],
            ).await?
        })


@@ 224,6 245,9 @@ async fn route_unstable_communities_get(
    let community_local = row.get(1);
    let community_ap_id: Option<&str> = row.get(2);

    let (description, description_text, description_html) =
        get_community_description_fields(row.get(3), row.get(4));

    let info = RespCommunityInfo {
        base: RespMinimalCommunityInfo {
            id: community_id,


@@ 239,14 263,16 @@ async fn route_unstable_communities_get(
            },
            remote_url: community_ap_id,
        },
        description: row.get(3),
        description,
        description_html,
        description_text,
        you_are_moderator: if query.include_your {
            Some(row.get(5))
            Some(row.get(6))
        } else {
            None
        },
        your_follow: if query.include_your {
            Some(match row.get(4) {
            Some(match row.get(5) {
                Some(accepted) => Some(RespYourFollowInfo { accepted }),
                None => None,
            })

M src/routes/api/users.rs => src/routes/api/users.rs +23 -6
@@ 143,6 143,8 @@ struct RespUserInfo<'a> {
    base: RespMinimalAuthorInfo<'a>,

    description: &'a str,
    description_html: Option<&'a str>,
    description_text: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    suspended: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]


@@ 252,7 254,8 @@ async fn route_unstable_users_patch(

    #[derive(Deserialize)]
    struct UsersEditBody<'a> {
        description: Option<Cow<'a, str>>,
        #[serde(alias = "description")]
        description_text: Option<Cow<'a, str>>,
        email_address: Option<Cow<'a, str>>,
        password: Option<String>,
        avatar: Option<Cow<'a, str>>,


@@ 266,7 269,7 @@ async fn route_unstable_users_patch(

    let mut changes = Vec::<(&str, &(dyn tokio_postgres::types::ToSql + Sync))>::new();

    if let Some(description) = body.description.as_ref() {
    if let Some(description) = body.description_text.as_ref() {
        changes.push(("description", description));
    }
    if let Some(email_address) = body.email_address.as_ref() {


@@ 534,7 537,7 @@ async fn route_unstable_users_get(

    let row = db
        .query_opt(
            "SELECT username, local, ap_id, description, avatar, suspended FROM person WHERE id=$1",
            "SELECT username, local, ap_id, description, description_html, avatar, suspended FROM person WHERE id=$1",
            &[&user_id],
        )
        .await?;


@@ 548,7 551,7 @@ async fn route_unstable_users_get(

    let local = row.get(1);
    let ap_id = row.get(2);
    let avatar: Option<&str> = row.get(4);
    let avatar: Option<&str> = row.get(5);

    let info = RespMinimalAuthorInfo {
        id: user_id,


@@ 561,10 564,24 @@ async fn route_unstable_users_get(
        }),
    };

    let (description, description_text, description_html) = match row.get(4) {
        Some(description_html) => (
            description_html,
            None,
            Some(ammonia::clean(description_html)),
        ),
        None => {
            let description_text: &str = row.get(3);
            (description_text, Some(description_text), None)
        }
    };

    let info = RespUserInfo {
        base: info,
        description: row.get(3),
        suspended: if local { Some(row.get(5)) } else { None },
        description,
        description_html: description_html.as_deref(),
        description_text,
        suspended: if local { Some(row.get(6)) } else { None },
        your_note,
    };


M src/routes/apub/communities.rs => src/routes/apub/communities.rs +5 -2
@@ 88,7 88,7 @@ async fn handler_communities_get(

    match db
        .query_opt(
            "SELECT name, local, public_key, description FROM community WHERE id=$1",
            "SELECT name, local, public_key, description, description_html FROM community WHERE id=$1",
            &[&community_id],
        )
        .await?


@@ 116,7 116,10 @@ async fn handler_communities_get(
                            None
                        }
                    });
            let description: &str = row.get(3);
            let description = match row.get(4) {
                Some(description_html) => description_html,
                None => v_htmlescape::escape(row.get(3)).to_string(),
            };

            let community_ap_id =
                crate::apub_util::get_local_community_apub_id(community_id, &ctx.host_url_apub);

M src/routes/apub/mod.rs => src/routes/apub/mod.rs +6 -3
@@ 118,7 118,7 @@ async fn handler_users_get(

    match db
        .query_opt(
            "SELECT username, local, public_key, description, avatar FROM person WHERE id=$1",
            "SELECT username, local, public_key, description, description_html, avatar FROM person WHERE id=$1",
            &[&user_id.raw()],
        )
        .await?


@@ 148,9 148,12 @@ async fn handler_users_get(
                        }
                    });

            let description: &str = row.get(3);
            let description = match row.get(4) {
                Some(description_html) => description_html,
                None => v_htmlescape::escape(row.get(3)).to_string(),
            };

            let avatar: Option<&str> = row.get(4);
            let avatar: Option<&str> = row.get(5);

            let user_ap_id =
                crate::apub_util::get_local_person_apub_id(user_id, &ctx.host_url_apub);