mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 12:41:10 +00:00
initial user impl
This commit is contained in:
parent
98c6726b84
commit
dc50483e5c
15 changed files with 212 additions and 24 deletions
|
@ -9,10 +9,20 @@ create table instance (
|
||||||
local bool not null
|
local bool not null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create table user_ (
|
||||||
|
id serial primary key,
|
||||||
|
ap_id varchar(255) not null unique,
|
||||||
|
inbox_url text not null,
|
||||||
|
public_key text not null,
|
||||||
|
private_key text,
|
||||||
|
last_refreshed_at timestamptz not null default now(),
|
||||||
|
local bool not null
|
||||||
|
);
|
||||||
|
|
||||||
create table instance_follow (
|
create table instance_follow (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
follower_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
follower_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
pending boolean not null,
|
pending boolean not null,
|
||||||
unique(instance_id, follower_id)
|
unique(instance_id, follower_id)
|
||||||
);
|
);
|
|
@ -57,7 +57,7 @@ impl DbEditForm {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn generate_ap_id(
|
pub fn generate_ap_id(
|
||||||
article: &DbArticle,
|
article: &DbArticle,
|
||||||
version: &EditVersion,
|
version: &EditVersion,
|
||||||
) -> MyResult<ObjectId<DbEdit>> {
|
) -> MyResult<ObjectId<DbEdit>> {
|
||||||
|
|
|
@ -24,11 +24,11 @@ pub struct DbInstance {
|
||||||
pub articles_url: CollectionId<DbArticleCollection>,
|
pub articles_url: CollectionId<DbArticleCollection>,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) public_key: String,
|
pub public_key: String,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) last_refreshed_at: DateTime<Utc>,
|
pub last_refreshed_at: DateTime<Utc>,
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ pub struct DbInstanceForm {
|
||||||
pub ap_id: ObjectId<DbInstance>,
|
pub ap_id: ObjectId<DbInstance>,
|
||||||
pub articles_url: CollectionId<DbArticleCollection>,
|
pub articles_url: CollectionId<DbArticleCollection>,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub(crate) public_key: String,
|
pub public_key: String,
|
||||||
pub(crate) private_key: Option<String>,
|
pub private_key: Option<String>,
|
||||||
pub(crate) last_refreshed_at: DateTime<Utc>,
|
pub last_refreshed_at: DateTime<Utc>,
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub mod conflict;
|
||||||
pub mod edit;
|
pub mod edit;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
pub mod user;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -57,8 +57,30 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
user_ (id) {
|
||||||
|
id -> Int4,
|
||||||
|
#[max_length = 255]
|
||||||
|
ap_id -> Varchar,
|
||||||
|
inbox_url -> Text,
|
||||||
|
public_key -> Text,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
last_refreshed_at -> Timestamptz,
|
||||||
|
local -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::joinable!(article -> instance (instance_id));
|
diesel::joinable!(article -> instance (instance_id));
|
||||||
diesel::joinable!(conflict -> article (article_id));
|
diesel::joinable!(conflict -> article (article_id));
|
||||||
diesel::joinable!(edit -> article (article_id));
|
diesel::joinable!(edit -> article (article_id));
|
||||||
|
diesel::joinable!(instance_follow -> instance (instance_id));
|
||||||
|
diesel::joinable!(instance_follow -> user_ (follower_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(article, conflict, edit, instance, instance_follow,);
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
article,
|
||||||
|
conflict,
|
||||||
|
edit,
|
||||||
|
instance,
|
||||||
|
instance_follow,
|
||||||
|
user_,
|
||||||
|
);
|
||||||
|
|
63
src/database/user.rs
Normal file
63
src/database/user.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::database::schema::user_;
|
||||||
|
use crate::database::MyDataHandle;
|
||||||
|
use crate::error::MyResult;
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use diesel::ExpressionMethods;
|
||||||
|
use diesel::QueryDsl;
|
||||||
|
use diesel::{
|
||||||
|
insert_into, AsChangeset, Identifiable, Insertable, PgConnection, Queryable, RunQueryDsl,
|
||||||
|
Selectable,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)]
|
||||||
|
#[diesel(table_name = user_, check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct DbUser {
|
||||||
|
pub id: i32,
|
||||||
|
pub ap_id: ObjectId<DbUser>,
|
||||||
|
pub inbox_url: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub public_key: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub last_refreshed_at: DateTime<Utc>,
|
||||||
|
pub local: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable, AsChangeset)]
|
||||||
|
#[diesel(table_name = user_, check_for_backend(diesel::pg::Pg))]
|
||||||
|
pub struct DbUserForm {
|
||||||
|
pub ap_id: ObjectId<DbUser>,
|
||||||
|
pub inbox_url: String,
|
||||||
|
pub public_key: String,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub last_refreshed_at: DateTime<Utc>,
|
||||||
|
pub local: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbUser {
|
||||||
|
pub fn create(form: &DbUserForm, conn: &Mutex<PgConnection>) -> MyResult<Self> {
|
||||||
|
let mut conn = conn.lock().unwrap();
|
||||||
|
Ok(insert_into(user_::table)
|
||||||
|
.values(form)
|
||||||
|
.on_conflict(user_::dsl::ap_id)
|
||||||
|
.do_update()
|
||||||
|
.set(form)
|
||||||
|
.get_result(conn.deref_mut())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_from_ap_id(
|
||||||
|
ap_id: &ObjectId<DbUser>,
|
||||||
|
data: &Data<MyDataHandle>,
|
||||||
|
) -> MyResult<DbUser> {
|
||||||
|
let mut conn = data.db_connection.lock().unwrap();
|
||||||
|
Ok(user_::table
|
||||||
|
.filter(user_::dsl::ap_id.eq(ap_id))
|
||||||
|
.get_result(conn.deref_mut())?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use std::fmt::{Display, Formatter};
|
||||||
pub type MyResult<T> = Result<T, Error>;
|
pub type MyResult<T> = Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error(pub(crate) anyhow::Error);
|
pub struct Error(pub anyhow::Error);
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|
|
@ -13,8 +13,8 @@ use url::Url;
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Follow {
|
pub struct Follow {
|
||||||
pub(crate) actor: ObjectId<DbInstance>,
|
pub actor: ObjectId<DbInstance>,
|
||||||
pub(crate) object: ObjectId<DbInstance>,
|
pub object: ObjectId<DbInstance>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: FollowType,
|
kind: FollowType,
|
||||||
id: Url,
|
id: Url,
|
||||||
|
|
|
@ -19,11 +19,11 @@ use url::Url;
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ApubArticle {
|
pub struct ApubArticle {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) kind: ArticleType,
|
pub kind: ArticleType,
|
||||||
pub(crate) id: ObjectId<DbArticle>,
|
pub id: ObjectId<DbArticle>,
|
||||||
pub(crate) attributed_to: ObjectId<DbInstance>,
|
pub attributed_to: ObjectId<DbInstance>,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub to: Vec<Url>,
|
||||||
pub edits: CollectionId<DbEditCollection>,
|
pub edits: CollectionId<DbEditCollection>,
|
||||||
latest_version: EditVersion,
|
latest_version: EditVersion,
|
||||||
content: String,
|
content: String,
|
||||||
|
|
|
@ -16,10 +16,10 @@ use url::Url;
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ArticleCollection {
|
pub struct ArticleCollection {
|
||||||
pub(crate) r#type: CollectionType,
|
pub r#type: CollectionType,
|
||||||
pub(crate) id: Url,
|
pub id: Url,
|
||||||
pub(crate) total_items: i32,
|
pub total_items: i32,
|
||||||
pub(crate) items: Vec<ApubArticle>,
|
pub items: Vec<ApubArticle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -18,10 +18,10 @@ use url::Url;
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ApubEditCollection {
|
pub struct ApubEditCollection {
|
||||||
pub(crate) r#type: OrderedCollectionType,
|
pub r#type: OrderedCollectionType,
|
||||||
pub(crate) id: Url,
|
pub id: Url,
|
||||||
pub(crate) total_items: i32,
|
pub total_items: i32,
|
||||||
pub(crate) items: Vec<ApubEdit>,
|
pub items: Vec<ApubEdit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -66,6 +66,7 @@ impl DbInstance {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to user?
|
||||||
pub async fn send<Activity>(
|
pub async fn send<Activity>(
|
||||||
&self,
|
&self,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
|
|
|
@ -3,3 +3,4 @@ pub mod articles_collection;
|
||||||
pub mod edit;
|
pub mod edit;
|
||||||
pub mod edits_collection;
|
pub mod edits_collection;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
pub mod user;
|
||||||
|
|
90
src/federation/objects/user.rs
Normal file
90
src/federation/objects/user.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use crate::database::user::{DbUser, DbUserForm};
|
||||||
|
use crate::database::MyDataHandle;
|
||||||
|
use crate::error::Error;
|
||||||
|
use activitypub_federation::kinds::actor::PersonType;
|
||||||
|
use activitypub_federation::{
|
||||||
|
config::Data,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||||
|
traits::{Actor, Object},
|
||||||
|
};
|
||||||
|
use chrono::{DateTime, Local, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ApubUser {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
kind: PersonType,
|
||||||
|
id: ObjectId<DbUser>,
|
||||||
|
inbox: Url,
|
||||||
|
public_key: PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Object for DbUser {
|
||||||
|
type DataType = MyDataHandle;
|
||||||
|
type Kind = ApubUser;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
|
Some(self.last_refreshed_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_from_id(
|
||||||
|
object_id: Url,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Option<Self>, Self::Error> {
|
||||||
|
Ok(DbUser::read_from_ap_id(&object_id.into(), data).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
|
||||||
|
Ok(ApubUser {
|
||||||
|
kind: Default::default(),
|
||||||
|
id: self.ap_id.clone(),
|
||||||
|
inbox: Url::parse(&self.inbox_url)?,
|
||||||
|
public_key: self.public_key(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify(
|
||||||
|
json: &Self::Kind,
|
||||||
|
expected_domain: &Url,
|
||||||
|
_data: &Data<Self::DataType>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
verify_domains_match(json.id.inner(), expected_domain)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
||||||
|
let form = DbUserForm {
|
||||||
|
ap_id: json.id,
|
||||||
|
inbox_url: json.inbox.to_string(),
|
||||||
|
public_key: json.public_key.public_key_pem,
|
||||||
|
private_key: None,
|
||||||
|
last_refreshed_at: Local::now().into(),
|
||||||
|
local: false,
|
||||||
|
};
|
||||||
|
DbUser::create(&form, &data.db_connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for DbUser {
|
||||||
|
fn id(&self) -> Url {
|
||||||
|
self.ap_id.inner().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key_pem(&self) -> &str {
|
||||||
|
&self.public_key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
self.private_key.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inbox(&self) -> Url {
|
||||||
|
Url::parse(&self.inbox_url).unwrap()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue