mirror of
https://git.joinplu.me/Plume/Plume.git
synced 2024-11-29 06:51:01 +00:00
Update user information if needed
When a remote is displayed, if it has not been updated since at least 24 hours, newer informations are fetched. Fixes #135
This commit is contained in:
parent
3373bb66cd
commit
32a4949f25
6 changed files with 52 additions and 7 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
ALTER TABLE users DROP COLUMN last_fetched_date;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
ALTER TABLE users ADD COLUMN last_fetched_date TIMESTAMP NOT NULL DEFAULT '2000-01-01 00:00:01';
|
|
@ -156,6 +156,7 @@ table! {
|
||||||
shared_inbox_url -> Nullable<Varchar>,
|
shared_inbox_url -> Nullable<Varchar>,
|
||||||
followers_endpoint -> Varchar,
|
followers_endpoint -> Varchar,
|
||||||
avatar_id -> Nullable<Int4>,
|
avatar_id -> Nullable<Int4>,
|
||||||
|
last_fetched_date -> Timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use activitypub::{
|
||||||
object::Image,
|
object::Image,
|
||||||
};
|
};
|
||||||
use bcrypt;
|
use bcrypt;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection, dsl::any};
|
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection, dsl::any};
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
|
@ -68,6 +68,7 @@ pub struct User {
|
||||||
pub shared_inbox_url: Option<String>,
|
pub shared_inbox_url: Option<String>,
|
||||||
pub followers_endpoint: String,
|
pub followers_endpoint: String,
|
||||||
pub avatar_id: Option<i32>,
|
pub avatar_id: Option<i32>,
|
||||||
|
pub last_fetched_date: NaiveDateTime
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
|
@ -158,7 +159,7 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_from_url(conn: &PgConnection, url: String) -> Option<User> {
|
fn fetch(url: String) -> Option<CustomPerson> {
|
||||||
let req = Client::new()
|
let req = Client::new()
|
||||||
.get(&url[..])
|
.get(&url[..])
|
||||||
.header(Accept(ap_accept_header().into_iter().map(|h| qitem(h.parse::<Mime>().expect("Invalid Content-Type"))).collect()))
|
.header(Accept(ap_accept_header().into_iter().map(|h| qitem(h.parse::<Mime>().expect("Invalid Content-Type"))).collect()))
|
||||||
|
@ -169,7 +170,7 @@ impl User {
|
||||||
if let Ok(ap_sign) = serde_json::from_str::<ApSignature>(text) {
|
if let Ok(ap_sign) = serde_json::from_str::<ApSignature>(text) {
|
||||||
if let Ok(mut json) = serde_json::from_str::<CustomPerson>(text) {
|
if let Ok(mut json) = serde_json::from_str::<CustomPerson>(text) {
|
||||||
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
|
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
|
||||||
Some(User::from_activity(conn, json, Url::parse(url.as_ref()).unwrap().host_str().unwrap().to_string()))
|
Some(json)
|
||||||
} else { None }
|
} else { None }
|
||||||
} else { None }
|
} else { None }
|
||||||
} else { None }
|
} else { None }
|
||||||
|
@ -181,6 +182,10 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fetch_from_url(conn: &PgConnection, url: String) -> Option<User> {
|
||||||
|
User::fetch(url.clone()).map(|json| (User::from_activity(conn, json, Url::parse(url.as_ref()).unwrap().host_str().unwrap().to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &PgConnection, acct: CustomPerson, inst: String) -> User {
|
fn from_activity(conn: &PgConnection, acct: CustomPerson, inst: String) -> User {
|
||||||
let instance = match Instance::find_by_domain(conn, inst.clone()) {
|
let instance = match Instance::find_by_domain(conn, inst.clone()) {
|
||||||
Some(instance) => instance,
|
Some(instance) => instance,
|
||||||
|
@ -227,6 +232,26 @@ impl User {
|
||||||
user
|
user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refetch(&self, conn: &PgConnection) {
|
||||||
|
User::fetch(self.ap_url.clone()).map(|json| {
|
||||||
|
let avatar = Media::save_remote(conn, json.object.object_props.icon_image().expect("User::refetch: icon error")
|
||||||
|
.object_props.url_string().expect("User::refetch: icon.url error"));
|
||||||
|
|
||||||
|
diesel::update(self)
|
||||||
|
.set((
|
||||||
|
users::username.eq(json.object.ap_actor_props.preferred_username_string().expect("User::refetch: preferredUsername error")),
|
||||||
|
users::display_name.eq(json.object.object_props.name_string().expect("User::refetch: name error")),
|
||||||
|
users::outbox_url.eq(json.object.ap_actor_props.outbox_string().expect("User::refetch: outbox error")),
|
||||||
|
users::inbox_url.eq(json.object.ap_actor_props.inbox_string().expect("User::refetch: inbox error")),
|
||||||
|
users::summary.eq(SafeString::new(&json.object.object_props.summary_string().unwrap_or(String::new()))),
|
||||||
|
users::followers_endpoint.eq(json.object.ap_actor_props.followers_string().expect("User::refetch: followers error")),
|
||||||
|
users::avatar_id.eq(Some(avatar.id)),
|
||||||
|
users::last_fetched_date.eq(Utc::now().naive_utc())
|
||||||
|
)).execute(conn)
|
||||||
|
.expect("Couldn't update user")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hash_pass(pass: String) -> String {
|
pub fn hash_pass(pass: String) -> String {
|
||||||
bcrypt::hash(pass.as_str(), 10).unwrap()
|
bcrypt::hash(pass.as_str(), 10).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -504,6 +529,10 @@ impl User {
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.expect("Couldn't update user avatar");
|
.expect("Couldn't update user avatar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn needs_update(&self) -> bool {
|
||||||
|
(Utc::now().naive_utc() - self.last_fetched_date).num_days() > 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
impl<'a, 'r> FromRequest<'a, 'r> for User {
|
||||||
|
|
|
@ -29,6 +29,7 @@ extern crate validator_derive;
|
||||||
extern crate webfinger;
|
extern crate webfinger;
|
||||||
extern crate workerpool;
|
extern crate workerpool;
|
||||||
|
|
||||||
|
use rocket::State;
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
use rocket_csrf::CsrfFairingBuilder;
|
use rocket_csrf::CsrfFairingBuilder;
|
||||||
use workerpool::{Pool, thunk::ThunkWorker};
|
use workerpool::{Pool, thunk::ThunkWorker};
|
||||||
|
@ -37,6 +38,8 @@ mod inbox;
|
||||||
mod setup;
|
mod setup;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
|
type Worker<'a> = State<'a, Pool<ThunkWorker<()>>>;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let pool = setup::check();
|
let pool = setup::check();
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
|
|
|
@ -5,7 +5,6 @@ use activitypub::{
|
||||||
};
|
};
|
||||||
use atom_syndication::{Entry, FeedBuilder};
|
use atom_syndication::{Entry, FeedBuilder};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
State,
|
|
||||||
request::LenientForm,
|
request::LenientForm,
|
||||||
response::{Redirect, Flash, Content},
|
response::{Redirect, Flash, Content},
|
||||||
http::ContentType
|
http::ContentType
|
||||||
|
@ -13,7 +12,7 @@ use rocket::{
|
||||||
use rocket_contrib::Template;
|
use rocket_contrib::Template;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use validator::{Validate, ValidationError};
|
use validator::{Validate, ValidationError};
|
||||||
use workerpool::{Pool, thunk::*};
|
use workerpool::thunk::*;
|
||||||
|
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
ActivityStream, broadcast, Id, IntoId, ApRequest,
|
ActivityStream, broadcast, Id, IntoId, ApRequest,
|
||||||
|
@ -31,6 +30,7 @@ use plume_models::{
|
||||||
};
|
};
|
||||||
use inbox::Inbox;
|
use inbox::Inbox;
|
||||||
use routes::Page;
|
use routes::Page;
|
||||||
|
use Worker;
|
||||||
|
|
||||||
#[get("/me")]
|
#[get("/me")]
|
||||||
fn me(user: Option<User>) -> Result<Redirect, Flash<Redirect>> {
|
fn me(user: Option<User>) -> Result<Redirect, Flash<Redirect>> {
|
||||||
|
@ -41,7 +41,7 @@ fn me(user: Option<User>) -> Result<Redirect, Flash<Redirect>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>", rank = 2)]
|
#[get("/@/<name>", rank = 2)]
|
||||||
fn details<'r>(name: String, conn: DbConn, account: Option<User>, worker: State<Pool<ThunkWorker<()>>>, fecth_articles_conn: DbConn, fecth_followers_conn: DbConn) -> Template {
|
fn details(name: String, conn: DbConn, account: Option<User>, worker: Worker, fecth_articles_conn: DbConn, fecth_followers_conn: DbConn, update_conn: DbConn) -> Template {
|
||||||
may_fail!(account.map(|a| a.to_json(&*conn)), User::find_by_fqn(&*conn, name), "Couldn't find requested user", |user| {
|
may_fail!(account.map(|a| a.to_json(&*conn)), User::find_by_fqn(&*conn, name), "Couldn't find requested user", |user| {
|
||||||
let recents = Post::get_recents_for_author(&*conn, &user, 6);
|
let recents = Post::get_recents_for_author(&*conn, &user, 6);
|
||||||
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6);
|
let reshares = Reshare::get_recents_for_author(&*conn, &user, 6);
|
||||||
|
@ -75,6 +75,14 @@ fn details<'r>(name: String, conn: DbConn, account: Option<User>, worker: State<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Update profile information if needed
|
||||||
|
let user_clone = user.clone();
|
||||||
|
if user.needs_update() {
|
||||||
|
worker.execute(Thunk::of(move || {
|
||||||
|
user_clone.refetch(&*update_conn);
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Template::render("users/details", json!({
|
Template::render("users/details", json!({
|
||||||
|
@ -106,7 +114,7 @@ fn dashboard_auth() -> Flash<Redirect> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/@/<name>/follow")]
|
#[get("/@/<name>/follow")]
|
||||||
fn follow(name: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
|
fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect {
|
||||||
let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
|
let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
|
||||||
let f = follows::Follow::insert(&*conn, follows::NewFollow {
|
let f = follows::Follow::insert(&*conn, follows::NewFollow {
|
||||||
follower_id: user.id,
|
follower_id: user.id,
|
||||||
|
|
Loading…
Reference in a new issue