This commit is contained in:
Felix Ableitner 2023-12-08 22:21:31 +01:00
parent dc50483e5c
commit bda146cf05
7 changed files with 141 additions and 39 deletions

View File

@ -2,4 +2,6 @@ drop table conflict;
drop table edit; drop table edit;
drop table article; drop table article;
drop table instance_follow; drop table instance_follow;
drop table local_user;
drop table person;
drop table instance; drop table instance;

View File

@ -9,20 +9,27 @@ create table instance (
local bool not null local bool not null
); );
create table user_ ( create table person (
id serial primary key, id serial primary key,
ap_id varchar(255) not null unique, username text not null,
inbox_url text not null, ap_id varchar(255) not null unique,
public_key text not null, inbox_url text not null,
private_key text, public_key text not null,
last_refreshed_at timestamptz not null default now(), private_key text,
local bool not null last_refreshed_at timestamptz not null default now(),
); local bool not null
);
create table local_user (
id serial primary key,
password_encrypted text not null,
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE 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 user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, follower_id int REFERENCES person 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)
); );

View File

@ -19,6 +19,7 @@ use diffy::create_patch;
use futures::future::try_join_all; use futures::future::try_join_all;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use crate::database::user::{DbLocalUserForm, DbPerson, DbPersonForm};
pub fn api_routes() -> Router { pub fn api_routes() -> Router {
Router::new() Router::new()
@ -33,6 +34,8 @@ pub fn api_routes() -> Router {
.route("/instance", get(get_local_instance)) .route("/instance", get(get_local_instance))
.route("/instance/follow", post(follow_instance)) .route("/instance/follow", post(follow_instance))
.route("/search", get(search_article)) .route("/search", get(search_article))
.route("/user/register", post(register_user))
.route("/user/login", post(login_user))
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -291,3 +294,42 @@ async fn fork_article(
Ok(Json(DbArticle::read_view(article.id, &data.db_connection)?)) Ok(Json(DbArticle::read_view(article.id, &data.db_connection)?))
} }
#[derive(Deserialize, Serialize)]
pub struct RegisterUserData {
name: String,
password: String,
}
#[derive(Deserialize, Serialize)]
#[serde(transparent)]
pub struct Jwt(String);
#[debug_handler]
async fn register_user(
data: Data<MyDataHandle>,
Form(form): Form<RegisterUserData>,
) -> MyResult<Json<Jwt>> {
let local_user_form = DbLocalUserForm {
};
let person_form = DbPersonForm {
};
DbPerson::create(&person_form, Some(&local_user_form), &data.db_connection)?;
}
#[derive(Deserialize, Serialize)]
pub struct LoginUserData {
name: String,
password: String,
}
#[debug_handler]
async fn login_user(
data: Data<MyDataHandle>,
Form(form): Form<Jwt>,
) -> MyResult<Json<ArticleView>> {
todo!()
}

View File

@ -58,8 +58,17 @@ diesel::table! {
} }
diesel::table! { diesel::table! {
user_ (id) { local_user (id) {
id -> Int4, id -> Int4,
password_encrypted -> Text,
person_id -> Int4,
}
}
diesel::table! {
person (id) {
id -> Int4,
username -> Text,
#[max_length = 255] #[max_length = 255]
ap_id -> Varchar, ap_id -> Varchar,
inbox_url -> Text, inbox_url -> Text,
@ -74,7 +83,8 @@ 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 -> instance (instance_id));
diesel::joinable!(instance_follow -> user_ (follower_id)); diesel::joinable!(instance_follow -> person (follower_id));
diesel::joinable!(local_user -> person (person_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
article, article,
@ -82,5 +92,6 @@ diesel::allow_tables_to_appear_in_same_query!(
edit, edit,
instance, instance,
instance_follow, instance_follow,
user_, local_user,
person,
); );

View File

@ -1,4 +1,4 @@
use crate::database::schema::user_; use crate::database::schema::{local_user, person};
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::MyResult; use crate::error::MyResult;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
@ -14,11 +14,29 @@ use serde::{Deserialize, Serialize};
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex; use std::sync::Mutex;
/// A user with account registered on local instance.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)]
#[diesel(table_name = user_, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = local_user, check_for_backend(diesel::pg::Pg))]
pub struct DbUser { pub struct DbLocalUser {
pub id: i32, pub id: i32,
pub ap_id: ObjectId<DbUser>, pub password_encrypted: String,
pub person_id: i32,
}
#[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = local_user, check_for_backend(diesel::pg::Pg))]
pub struct DbLocalUserForm {
pub password_encrypted: String,
pub person_id: i32,
}
/// Federation related data from a local or remote user.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)]
#[diesel(table_name = person, check_for_backend(diesel::pg::Pg))]
pub struct DbPerson {
pub id: i32,
pub username: String,
pub ap_id: ObjectId<DbPerson>,
pub inbox_url: String, pub inbox_url: String,
#[serde(skip)] #[serde(skip)]
pub public_key: String, pub public_key: String,
@ -30,9 +48,10 @@ pub struct DbUser {
} }
#[derive(Debug, Clone, Insertable, AsChangeset)] #[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = user_, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = person, check_for_backend(diesel::pg::Pg))]
pub struct DbUserForm { pub struct DbPersonForm {
pub ap_id: ObjectId<DbUser>, pub username: String,
pub ap_id: ObjectId<DbPerson>,
pub inbox_url: String, pub inbox_url: String,
pub public_key: String, pub public_key: String,
pub private_key: Option<String>, pub private_key: Option<String>,
@ -40,24 +59,32 @@ pub struct DbUserForm {
pub local: bool, pub local: bool,
} }
impl DbUser { impl DbPerson {
pub fn create(form: &DbUserForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(person_form: &DbPersonForm, local_user_form: Option<&DbLocalUserForm>, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(insert_into(user_::table) let person = insert_into(person::table)
.values(form) .values(person_form)
.on_conflict(user_::dsl::ap_id) .on_conflict(person::dsl::ap_id)
.do_update() .do_update()
.set(form) .set(person_form)
.get_result(conn.deref_mut())?) .get_result::<DbPerson>(conn.deref_mut())?;
if let Some(local_user_form) = local_user_form {
insert_into(local_user::table)
.values(local_user_form)
.get_result::<DbLocalUser>(conn.deref_mut())?;
}
Ok(person)
} }
pub fn read_from_ap_id( pub fn read_from_ap_id(
ap_id: &ObjectId<DbUser>, ap_id: &ObjectId<DbPerson>,
data: &Data<MyDataHandle>, data: &Data<MyDataHandle>,
) -> MyResult<DbUser> { ) -> MyResult<DbPerson> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_connection.lock().unwrap();
Ok(user_::table Ok(person::table
.filter(user_::dsl::ap_id.eq(ap_id)) .filter(person::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
} }

View File

@ -1,4 +1,4 @@
use crate::database::user::{DbUser, DbUserForm}; use crate::database::user::{DbLocalUser, DbLocalUserForm, DbPerson, DbPersonForm};
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::Error; use crate::error::Error;
use activitypub_federation::kinds::actor::PersonType; use activitypub_federation::kinds::actor::PersonType;
@ -18,13 +18,14 @@ use url::Url;
pub struct ApubUser { pub struct ApubUser {
#[serde(rename = "type")] #[serde(rename = "type")]
kind: PersonType, kind: PersonType,
id: ObjectId<DbUser>, id: ObjectId<DbPerson>,
preferred_username: String,
inbox: Url, inbox: Url,
public_key: PublicKey, public_key: PublicKey,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl Object for DbUser { impl Object for DbPerson {
type DataType = MyDataHandle; type DataType = MyDataHandle;
type Kind = ApubUser; type Kind = ApubUser;
type Error = Error; type Error = Error;
@ -37,13 +38,14 @@ impl Object for DbUser {
object_id: Url, object_id: Url,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> { ) -> Result<Option<Self>, Self::Error> {
Ok(DbUser::read_from_ap_id(&object_id.into(), data).ok()) Ok(DbPerson::read_from_ap_id(&object_id.into(), data).ok())
} }
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
Ok(ApubUser { Ok(ApubUser {
kind: Default::default(), kind: Default::default(),
id: self.ap_id.clone(), id: self.ap_id.clone(),
preferred_username: self.username.clone(),
inbox: Url::parse(&self.inbox_url)?, inbox: Url::parse(&self.inbox_url)?,
public_key: self.public_key(), public_key: self.public_key(),
}) })
@ -59,7 +61,8 @@ impl Object for DbUser {
} }
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> { async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
let form = DbUserForm { let form = DbPersonForm {
username: json.preferred_username,
ap_id: json.id, ap_id: json.id,
inbox_url: json.inbox.to_string(), inbox_url: json.inbox.to_string(),
public_key: json.public_key.public_key_pem, public_key: json.public_key.public_key_pem,
@ -67,11 +70,11 @@ impl Object for DbUser {
last_refreshed_at: Local::now().into(), last_refreshed_at: Local::now().into(),
local: false, local: false,
}; };
DbUser::create(&form, &data.db_connection) DbPerson::create(&form, None, &data.db_connection)
} }
} }
impl Actor for DbUser { impl Actor for DbPerson {
fn id(&self) -> Url { fn id(&self) -> Url {
self.ap_id.inner().clone() self.ap_id.inner().clone()
} }

View File

@ -7,7 +7,7 @@ use crate::common::{
get_query, post, TestData, TEST_ARTICLE_DEFAULT_TEXT, get_query, post, TestData, TEST_ARTICLE_DEFAULT_TEXT,
}; };
use common::get; use common::get;
use fediwiki::api::{EditArticleData, ForkArticleData, ResolveObject, SearchArticleData}; use fediwiki::api::{EditArticleData, ForkArticleData, RegisterUserData, ResolveObject, SearchArticleData};
use fediwiki::database::article::{ArticleView, DbArticle}; use fediwiki::database::article::{ArticleView, DbArticle};
use fediwiki::error::MyResult; use fediwiki::error::MyResult;
@ -464,3 +464,13 @@ async fn test_fork_article() -> MyResult<()> {
data.stop() data.stop()
} }
#[tokio::test]
async fn test_user_registration_login() -> MyResult<()> {
let data = TestData::start();
let data = RegisterUserData {
}
post(data.alpha.hostname, "user/register")
data.stop()
}