mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 03:31:08 +00:00
registration and login working
This commit is contained in:
parent
89a71c7fcd
commit
8ca01dee07
25 changed files with 424 additions and 181 deletions
118
Cargo.lock
generated
118
Cargo.lock
generated
|
@ -233,6 +233,28 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-extra"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a93e433be9382c737320af3924f7d5fc6f89c155cf2bf88949d8f5126fab283f"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"cookie 0.17.0",
|
||||
"futures-util",
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.5",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.3.8"
|
||||
|
@ -539,6 +561,45 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie_store"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa"
|
||||
dependencies = [
|
||||
"cookie 0.16.2",
|
||||
"idna 0.2.3",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
|
@ -1325,6 +1386,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"axum-macros",
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
|
@ -1351,6 +1413,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
|
@ -1365,6 +1428,27 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
|
@ -1802,6 +1886,12 @@ dependencies = [
|
|||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
|
@ -2245,6 +2335,22 @@ dependencies = [
|
|||
"yansi 1.0.0-rc.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
|
||||
dependencies = [
|
||||
"idna 0.3.0",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
|
@ -2389,6 +2495,8 @@ checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
|||
dependencies = [
|
||||
"base64 0.21.5",
|
||||
"bytes",
|
||||
"cookie 0.16.2",
|
||||
"cookie_store",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
@ -2949,9 +3057,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
|
@ -2969,9 +3077,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
@ -3268,7 +3376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"idna 0.5.0",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@ default = ["ssr"]
|
|||
ssr = [
|
||||
"axum",
|
||||
"axum-macros",
|
||||
"axum-extra",
|
||||
"tower-http",
|
||||
"diesel",
|
||||
"diesel-derive-newtype",
|
||||
|
@ -28,6 +29,7 @@ anyhow = "1.0.75"
|
|||
async-trait = "0.1.74"
|
||||
axum = { version = "0.6.20", optional = true }
|
||||
axum-macros = { version = "0.3.8", optional = true }
|
||||
axum-extra = { version = "0.7.7", features = ["cookie"], optional = true }
|
||||
leptos = "0.5.4"
|
||||
leptos_meta = "0.5.4"
|
||||
leptos_router = "0.5.4"
|
||||
|
@ -62,10 +64,11 @@ once_cell = "1.18.0"
|
|||
wasm-bindgen = "0.2.89"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0.0"
|
||||
time = "0.3.31"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
reqwest = "0.11.22"
|
||||
reqwest = { version = "0.11.22", features = ["cookies"] }
|
||||
|
||||
[package.metadata.leptos]
|
||||
output-name = "ibis"
|
||||
|
|
|
@ -11,11 +11,11 @@ The Ibis is a [bird which is related to the Egyptian god of knowledge and scienc
|
|||
|
||||
You need to install [cargo](https://rustup.rs/) and [trunk](https://trunkrs.dev). Then run the following commands in separate terminals:
|
||||
```
|
||||
# start backend
|
||||
cargo run
|
||||
# start backend, with separate target folder to avoid rebuilds from arch change
|
||||
cargo run --target-dir target/backend
|
||||
|
||||
# start frontend
|
||||
trunk serve
|
||||
# start frontend, automatic rebuild on changes
|
||||
trunk serve -w src/frontend/
|
||||
```
|
||||
## License
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ use crate::backend::database::article::DbArticleForm;
|
|||
use crate::backend::database::conflict::{ApiConflict, DbConflict, DbConflictForm};
|
||||
use crate::backend::database::edit::DbEditForm;
|
||||
use crate::backend::database::instance::DbInstance;
|
||||
use crate::backend::database::user::LocalUserView;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::backend::federation::activities::create_article::CreateArticle;
|
||||
|
@ -10,6 +9,7 @@ use crate::backend::federation::activities::submit_article_update;
|
|||
use crate::backend::utils::generate_article_version;
|
||||
use crate::common::EditVersion;
|
||||
use crate::common::GetArticleData;
|
||||
use crate::common::LocalUserView;
|
||||
use crate::common::{ArticleView, DbArticle, DbEdit};
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::backend::database::instance::{DbInstance, InstanceView};
|
||||
use crate::backend::database::user::LocalUserView;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::backend::federation::activities::follow::Follow;
|
||||
use crate::common::LocalUserView;
|
||||
use activitypub_federation::config::Data;
|
||||
use axum::Extension;
|
||||
use axum::{Form, Json};
|
||||
|
|
|
@ -2,15 +2,16 @@ use crate::backend::api::article::create_article;
|
|||
use crate::backend::api::article::{edit_article, fork_article, get_article};
|
||||
use crate::backend::api::instance::follow_instance;
|
||||
use crate::backend::api::instance::get_local_instance;
|
||||
use crate::backend::api::user::login_user;
|
||||
use crate::backend::api::user::my_profile;
|
||||
use crate::backend::api::user::register_user;
|
||||
use crate::backend::api::user::validate;
|
||||
use crate::backend::api::user::{login_user, logout_user};
|
||||
use crate::backend::database::conflict::{ApiConflict, DbConflict};
|
||||
use crate::backend::database::instance::DbInstance;
|
||||
use crate::backend::database::user::LocalUserView;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::common::DbEdit;
|
||||
use crate::common::LocalUserView;
|
||||
use crate::common::{ArticleView, DbArticle};
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
|
@ -49,8 +50,10 @@ pub fn api_routes() -> Router {
|
|||
.route("/instance", get(get_local_instance))
|
||||
.route("/instance/follow", post(follow_instance))
|
||||
.route("/search", get(search_article))
|
||||
.route("/user/register", post(register_user))
|
||||
.route("/user/login", post(login_user))
|
||||
.route("/account/register", post(register_user))
|
||||
.route("/account/login", post(login_user))
|
||||
.route("/account/my_profile", get(my_profile))
|
||||
.route("/account/logout", get(logout_user))
|
||||
.route_layer(middleware::from_fn(auth))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::backend::database::user::{DbLocalUser, DbPerson, LocalUserView};
|
||||
use crate::backend::database::{read_jwt_secret, MyDataHandle};
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::common::{LoginResponse, LoginUserData, RegisterUserData};
|
||||
use crate::common::{DbLocalUser, DbPerson, LocalUserView, LoginUserData, RegisterUserData};
|
||||
use activitypub_federation::config::Data;
|
||||
use anyhow::anyhow;
|
||||
use axum::{Form, Json};
|
||||
use axum_extra::extract::cookie::{Cookie, CookieJar, Expiration, SameSite};
|
||||
use axum_macros::debug_handler;
|
||||
use bcrypt::verify;
|
||||
use chrono::Utc;
|
||||
|
@ -13,6 +13,9 @@ use jsonwebtoken::Validation;
|
|||
use jsonwebtoken::{decode, get_current_timestamp};
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
pub static AUTH_COOKIE: &str = "auth";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
|
@ -26,22 +29,19 @@ struct Claims {
|
|||
pub exp: u64,
|
||||
}
|
||||
|
||||
fn generate_login_token(
|
||||
local_user: DbLocalUser,
|
||||
data: &Data<MyDataHandle>,
|
||||
) -> MyResult<LoginResponse> {
|
||||
fn generate_login_token(local_user: &DbLocalUser, data: &Data<MyDataHandle>) -> MyResult<String> {
|
||||
let hostname = data.domain().to_string();
|
||||
let claims = Claims {
|
||||
sub: local_user.id.to_string(),
|
||||
iss: hostname,
|
||||
iat: Utc::now().timestamp(),
|
||||
exp: get_current_timestamp(),
|
||||
exp: get_current_timestamp() + 60 * 60 * 24 * 365,
|
||||
};
|
||||
|
||||
let secret = read_jwt_secret(data)?;
|
||||
let key = EncodingKey::from_secret(secret.as_bytes());
|
||||
let jwt = encode(&Header::default(), &claims, &key)?;
|
||||
Ok(LoginResponse { jwt })
|
||||
Ok(jwt)
|
||||
}
|
||||
|
||||
pub async fn validate(jwt: &str, data: &Data<MyDataHandle>) -> MyResult<LocalUserView> {
|
||||
|
@ -55,21 +55,58 @@ pub async fn validate(jwt: &str, data: &Data<MyDataHandle>) -> MyResult<LocalUse
|
|||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn register_user(
|
||||
data: Data<MyDataHandle>,
|
||||
jar: CookieJar,
|
||||
Form(form): Form<RegisterUserData>,
|
||||
) -> MyResult<Json<LoginResponse>> {
|
||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||
let user = DbPerson::create_local(form.username, form.password, &data)?;
|
||||
Ok(Json(generate_login_token(user.local_user, &data)?))
|
||||
let token = generate_login_token(&user.local_user, &data)?;
|
||||
let jar = jar.add(create_cookie(token));
|
||||
Ok((jar, Json(user)))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn login_user(
|
||||
data: Data<MyDataHandle>,
|
||||
jar: CookieJar,
|
||||
Form(form): Form<LoginUserData>,
|
||||
) -> MyResult<Json<LoginResponse>> {
|
||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||
let user = DbPerson::read_local_from_name(&form.username, &data)?;
|
||||
let valid = verify(&form.password, &user.local_user.password_encrypted)?;
|
||||
if !valid {
|
||||
return Err(anyhow!("Invalid login").into());
|
||||
}
|
||||
Ok(Json(generate_login_token(user.local_user, &data)?))
|
||||
let token = generate_login_token(&user.local_user, &data)?;
|
||||
let jar = jar.add(create_cookie(token));
|
||||
Ok((jar, Json(user)))
|
||||
}
|
||||
|
||||
fn create_cookie(jwt: String) -> Cookie<'static> {
|
||||
Cookie::build(AUTH_COOKIE, jwt)
|
||||
.domain("localhost")
|
||||
.same_site(SameSite::Strict)
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.expires(Expiration::DateTime(
|
||||
OffsetDateTime::now_utc() + Duration::weeks(52),
|
||||
))
|
||||
.finish()
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn my_profile(
|
||||
data: Data<MyDataHandle>,
|
||||
jar: CookieJar,
|
||||
) -> MyResult<Json<LocalUserView>> {
|
||||
let jwt = jar.get(AUTH_COOKIE).map(|c| c.value());
|
||||
if let Some(jwt) = jwt {
|
||||
Ok(Json(validate(jwt, &data).await?))
|
||||
} else {
|
||||
Err(anyhow!("invalid/missing auth").into())
|
||||
}
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn logout_user(jar: CookieJar) -> MyResult<CookieJar> {
|
||||
let jar = jar.remove(Cookie::named(AUTH_COOKIE));
|
||||
Ok(jar)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::backend::database::schema::conflict;
|
||||
use crate::backend::database::user::DbLocalUser;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::backend::federation::activities::submit_article_update;
|
||||
use crate::backend::utils::generate_article_version;
|
||||
use crate::common::DbArticle;
|
||||
use crate::common::DbEdit;
|
||||
use crate::common::DbLocalUser;
|
||||
use crate::common::EditVersion;
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use diesel::ExpressionMethods;
|
||||
use diesel::{
|
||||
delete, insert_into, Identifiable, Insertable, PgConnection, QueryDsl, Queryable, RunQueryDsl,
|
||||
|
@ -76,9 +75,7 @@ impl DbConflict {
|
|||
) -> MyResult<Option<ApiConflict>> {
|
||||
let article = DbArticle::read(self.article_id, &data.db_connection)?;
|
||||
// Make sure to get latest version from origin so that all conflicts can be resolved
|
||||
let original_article = ObjectId::parse(&article.ap_id)?
|
||||
.dereference_forced(data)
|
||||
.await?;
|
||||
let original_article = article.ap_id.dereference_forced(data).await?;
|
||||
|
||||
// create common ancestor version
|
||||
let edits = DbEdit::read_for_article(&original_article, &data.db_connection)?;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::backend::database::schema::{instance, instance_follow};
|
||||
use crate::backend::database::user::DbPerson;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::backend::federation::objects::articles_collection::DbArticleCollection;
|
||||
use crate::common::DbPerson;
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::backend::database::schema::{local_user, person};
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::common::{DbLocalUser, DbPerson, LocalUserView};
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use activitypub_federation::http_signatures::generate_actor_keypair;
|
||||
|
@ -9,30 +10,10 @@ use bcrypt::DEFAULT_COST;
|
|||
use chrono::{DateTime, Local, Utc};
|
||||
use diesel::ExpressionMethods;
|
||||
use diesel::QueryDsl;
|
||||
use diesel::{
|
||||
insert_into, AsChangeset, Identifiable, Insertable, PgConnection, Queryable, RunQueryDsl,
|
||||
Selectable,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use diesel::{insert_into, AsChangeset, Insertable, PgConnection, RunQueryDsl};
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct LocalUserView {
|
||||
pub person: DbPerson,
|
||||
pub local_user: DbLocalUser,
|
||||
}
|
||||
|
||||
/// A user with account registered on local instance.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)]
|
||||
#[diesel(table_name = local_user, check_for_backend(diesel::pg::Pg))]
|
||||
pub struct DbLocalUser {
|
||||
pub id: i32,
|
||||
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 {
|
||||
|
@ -40,23 +21,6 @@ pub struct DbLocalUserForm {
|
|||
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,
|
||||
#[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 = person, check_for_backend(diesel::pg::Pg))]
|
||||
pub struct DbPersonForm {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::backend::database::instance::DbInstance;
|
||||
use crate::backend::database::user::DbPerson;
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::backend::federation::send_activity;
|
||||
use crate::backend::{
|
||||
database::MyDataHandle, federation::activities::accept::Accept, generate_activity_id,
|
||||
};
|
||||
use crate::common::DbPerson;
|
||||
use activitypub_federation::{
|
||||
config::Data,
|
||||
fetch::object_id::ObjectId,
|
||||
|
|
|
@ -35,7 +35,7 @@ pub async fn submit_article_update(
|
|||
id: -1,
|
||||
creator_id,
|
||||
hash: form.hash,
|
||||
ap_id: form.ap_id.to_string(),
|
||||
ap_id: form.ap_id,
|
||||
diff: form.diff,
|
||||
article_id: form.article_id,
|
||||
previous_version_id: form.previous_version_id,
|
||||
|
|
|
@ -49,7 +49,7 @@ impl Object for DbArticle {
|
|||
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
||||
Ok(ApubArticle {
|
||||
kind: Default::default(),
|
||||
id: ObjectId::parse(&self.ap_id)?,
|
||||
id: self.ap_id.clone(),
|
||||
attributed_to: local_instance.ap_id.clone(),
|
||||
to: vec![public(), local_instance.followers_url()?],
|
||||
edits: self.edits_id()?,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::backend::database::edit::DbEditForm;
|
||||
use crate::backend::database::user::DbPerson;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::Error;
|
||||
use crate::common::DbPerson;
|
||||
use crate::common::EditVersion;
|
||||
use crate::common::{DbArticle, DbEdit};
|
||||
use activitypub_federation::config::Data;
|
||||
|
@ -48,11 +48,11 @@ impl Object for DbEdit {
|
|||
let creator = DbPerson::read(self.creator_id, data)?;
|
||||
Ok(ApubEdit {
|
||||
kind: PatchType::Patch,
|
||||
id: ObjectId::parse(&self.ap_id)?,
|
||||
id: self.ap_id,
|
||||
content: self.diff,
|
||||
version: self.hash,
|
||||
previous_version: self.previous_version_id,
|
||||
object: ObjectId::parse(&article.ap_id)?,
|
||||
object: article.ap_id,
|
||||
attributed_to: creator.ap_id,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::backend::database::user::{DbPerson, DbPersonForm};
|
||||
use crate::backend::database::user::DbPersonForm;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::Error;
|
||||
use crate::common::DbPerson;
|
||||
use activitypub_federation::kinds::actor::PersonType;
|
||||
use activitypub_federation::{
|
||||
config::Data,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::backend::database::instance::DbInstance;
|
||||
use crate::backend::database::user::DbPerson;
|
||||
use crate::backend::database::MyDataHandle;
|
||||
use crate::backend::error::Error;
|
||||
use crate::backend::error::MyResult;
|
||||
|
@ -17,6 +16,7 @@ use crate::backend::federation::objects::edits_collection::{ApubEditCollection,
|
|||
use crate::backend::federation::objects::instance::ApubInstance;
|
||||
use crate::backend::federation::objects::user::ApubUser;
|
||||
use crate::common::DbArticle;
|
||||
use crate::common::DbPerson;
|
||||
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
||||
use activitypub_federation::axum::json::FederationJson;
|
||||
use activitypub_federation::config::Data;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
use crate::backend::database::schema::{article, edit};
|
||||
#[cfg(feature = "ssr")]
|
||||
use diesel::{Identifiable, Queryable, Selectable};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
#[cfg(feature = "ssr")]
|
||||
use {
|
||||
crate::backend::database::schema::{article, edit, local_user, person},
|
||||
activitypub_federation::fetch::object_id::ObjectId,
|
||||
diesel::{Identifiable, Queryable, Selectable},
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct GetArticleData {
|
||||
|
@ -26,6 +29,9 @@ pub struct DbArticle {
|
|||
pub id: i32,
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
#[cfg(feature = "ssr")]
|
||||
pub ap_id: ObjectId<DbArticle>,
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub ap_id: String,
|
||||
pub instance_id: i32,
|
||||
pub local: bool,
|
||||
|
@ -42,6 +48,9 @@ pub struct DbEdit {
|
|||
pub creator_id: i32,
|
||||
/// UUID built from sha224 hash of diff
|
||||
pub hash: EditVersion,
|
||||
#[cfg(feature = "ssr")]
|
||||
pub ap_id: ObjectId<DbEdit>,
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub ap_id: String,
|
||||
pub diff: String,
|
||||
pub article_id: i32,
|
||||
|
@ -55,19 +64,53 @@ pub struct DbEdit {
|
|||
#[cfg_attr(feature = "ssr", derive(diesel_derive_newtype::DieselNewType))]
|
||||
pub struct EditVersion(pub(crate) Uuid);
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct RegisterUserData {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct LoginResponse {
|
||||
pub jwt: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LoginUserData {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "ssr", derive(Queryable))]
|
||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct LocalUserView {
|
||||
pub person: DbPerson,
|
||||
pub local_user: DbLocalUser,
|
||||
}
|
||||
|
||||
/// A user with account registered on local instance.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
|
||||
#[cfg_attr(feature = "ssr", diesel(table_name = local_user, check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct DbLocalUser {
|
||||
pub id: i32,
|
||||
pub password_encrypted: String,
|
||||
pub person_id: i32,
|
||||
}
|
||||
|
||||
/// Federation related data from a local or remote user.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
|
||||
#[cfg_attr(feature = "ssr", diesel(table_name = person, check_for_backend(diesel::pg::Pg)))]
|
||||
pub struct DbPerson {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
#[cfg(feature = "ssr")]
|
||||
pub ap_id: ObjectId<DbPerson>,
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub ap_id: String,
|
||||
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,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::common::GetArticleData;
|
||||
use crate::common::{ArticleView, LoginResponse, LoginUserData, RegisterUserData};
|
||||
use crate::common::LocalUserView;
|
||||
use crate::common::{ArticleView, LoginUserData, RegisterUserData};
|
||||
use crate::frontend::error::MyResult;
|
||||
use anyhow::anyhow;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -39,20 +40,29 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn register(hostname: &str, register_form: RegisterUserData) -> MyResult<LoginResponse> {
|
||||
pub async fn register(hostname: &str, register_form: RegisterUserData) -> MyResult<LocalUserView> {
|
||||
let req = CLIENT
|
||||
.post(format!("http://{}/api/v1/user/register", hostname))
|
||||
.post(format!("http://{}/api/v1/account/register", hostname))
|
||||
.form(®ister_form);
|
||||
handle_json_res(req).await
|
||||
handle_json_res::<LocalUserView>(req).await
|
||||
}
|
||||
|
||||
pub async fn login(hostname: &str, username: &str, password: &str) -> MyResult<LoginResponse> {
|
||||
let login_form = LoginUserData {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
};
|
||||
pub async fn login(hostname: &str, login_form: LoginUserData) -> MyResult<LocalUserView> {
|
||||
let req = CLIENT
|
||||
.post(format!("http://{}/api/v1/user/login", hostname))
|
||||
.post(format!("http://{}/api/v1/account/login", hostname))
|
||||
.form(&login_form);
|
||||
handle_json_res(req).await
|
||||
handle_json_res::<LocalUserView>(req).await
|
||||
}
|
||||
|
||||
pub async fn my_profile(hostname: &str) -> MyResult<LocalUserView> {
|
||||
let req = CLIENT.get(format!("http://{}/api/v1/account/my_profile", hostname));
|
||||
handle_json_res::<LocalUserView>(req).await
|
||||
}
|
||||
|
||||
pub async fn logout(hostname: &str) -> MyResult<()> {
|
||||
CLIENT
|
||||
.get(format!("http://{}/api/v1/account/logout", hostname))
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,33 +1,62 @@
|
|||
use crate::common::LocalUserView;
|
||||
use crate::frontend::api::my_profile;
|
||||
use crate::frontend::components::nav::Nav;
|
||||
use crate::frontend::pages::article::Article;
|
||||
use crate::frontend::pages::login::Login;
|
||||
use crate::frontend::pages::register::Register;
|
||||
use crate::frontend::pages::Page;
|
||||
use leptos::{component, provide_context, use_context, view, IntoView};
|
||||
use leptos::{
|
||||
component, create_local_resource, create_rw_signal, expect_context, provide_context,
|
||||
use_context, view, IntoView, RwSignal, SignalGetUntracked, SignalUpdate,
|
||||
};
|
||||
use leptos_meta::provide_meta_context;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::Route;
|
||||
use leptos_router::Router;
|
||||
use leptos_router::Routes;
|
||||
|
||||
// TODO: change to GlobalState and also store auth token here
|
||||
// https://book.leptos.dev/15_global_state.html
|
||||
// https://book.leptos.dev/15_global_state.html
|
||||
#[derive(Clone)]
|
||||
pub struct BackendHostname(String);
|
||||
pub struct GlobalState {
|
||||
backend_hostname: String,
|
||||
pub(crate) my_profile: Option<LocalUserView>,
|
||||
}
|
||||
|
||||
impl BackendHostname {
|
||||
pub fn read() -> String {
|
||||
use_context::<BackendHostname>()
|
||||
impl GlobalState {
|
||||
pub fn read_hostname() -> String {
|
||||
use_context::<RwSignal<GlobalState>>()
|
||||
.expect("backend hostname is provided")
|
||||
.0
|
||||
.get_untracked()
|
||||
.backend_hostname
|
||||
}
|
||||
|
||||
pub fn update_my_profile(&self) {
|
||||
let backend_hostname_ = self.backend_hostname.clone();
|
||||
create_local_resource(
|
||||
move || backend_hostname_.clone(),
|
||||
|backend_hostname| async move {
|
||||
if let Ok(my_profile) = my_profile(&backend_hostname).await {
|
||||
expect_context::<RwSignal<GlobalState>>()
|
||||
.update(|state| state.my_profile = Some(my_profile.clone()))
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
let backend_hostname = "localhost:8080".to_string();
|
||||
|
||||
provide_meta_context();
|
||||
let backend_hostname = BackendHostname("localhost:8080".to_string());
|
||||
provide_context(backend_hostname);
|
||||
let backend_hostname = GlobalState {
|
||||
backend_hostname,
|
||||
my_profile: None,
|
||||
};
|
||||
// Load user profile in case we are already logged in
|
||||
backend_hostname.update_my_profile();
|
||||
provide_context(create_rw_signal(backend_hostname));
|
||||
|
||||
view! {
|
||||
<>
|
||||
<Stylesheet id="simple" href="/assets/simple.css"/>
|
||||
|
@ -42,7 +71,6 @@ pub fn App() -> impl IntoView {
|
|||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,55 @@
|
|||
use leptos::{component, view, IntoView};
|
||||
use crate::frontend::api::logout;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::*;
|
||||
use leptos::{component, use_context, view, IntoView, RwSignal, SignalWith};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Nav() -> impl IntoView {
|
||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||
// TODO: use `<Show when` based on auth token for login/register/logout
|
||||
view! {
|
||||
<nav class="inner">
|
||||
<li>
|
||||
<A href="/">"Main Page"</A>
|
||||
</li>
|
||||
<Show
|
||||
when=move || global_state.with(|state| state.my_profile.is_none())
|
||||
fallback=move || {
|
||||
view! {
|
||||
<p>"Logged in as: "
|
||||
{
|
||||
move || global_state.with(|state| state.my_profile.clone().unwrap().person.username)
|
||||
}
|
||||
<button on:click=move |_| {
|
||||
// TODO: not executed
|
||||
dbg!(1);
|
||||
do_logout()
|
||||
}>
|
||||
Logout
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
>
|
||||
<li>
|
||||
<A href="/login">"Login"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/register">"Register"</A>
|
||||
</li>
|
||||
</Show>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
fn do_logout() {
|
||||
dbg!("do logout");
|
||||
create_action(move |()| async move {
|
||||
dbg!("run logout action");
|
||||
logout(&GlobalState::read_hostname()).await.unwrap();
|
||||
expect_context::<RwSignal<GlobalState>>()
|
||||
.get()
|
||||
.update_my_profile();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,51 +1,58 @@
|
|||
use crate::common::LoginUserData;
|
||||
use crate::frontend::api::login;
|
||||
use leptos::ev::SubmitEvent;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::components::credentials::*;
|
||||
use leptos::*;
|
||||
use log::info;
|
||||
|
||||
// TODO: this seems to be working, but need to implement registration also
|
||||
// TODO: use leptos_form if possible
|
||||
// https://github.com/leptos-form/leptos_form/issues/18
|
||||
fn do_login(ev: SubmitEvent, username: String, password: String) {
|
||||
ev.prevent_default();
|
||||
spawn_local(async move {
|
||||
let res = login("localhost:8080", &username, &password).await;
|
||||
info!("{}", res.unwrap().jwt);
|
||||
});
|
||||
}
|
||||
use leptos_router::Redirect;
|
||||
|
||||
#[component]
|
||||
pub fn Login() -> impl IntoView {
|
||||
let name = RwSignal::new(String::new());
|
||||
let password = RwSignal::new(String::new());
|
||||
let (login_response, set_login_response) = create_signal(None::<()>);
|
||||
let (login_error, set_login_error) = create_signal(None::<String>);
|
||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
||||
|
||||
let login_action = create_action(move |(email, password): &(String, String)| {
|
||||
let username = email.to_string();
|
||||
let password = password.to_string();
|
||||
let credentials = LoginUserData { username, password };
|
||||
async move {
|
||||
set_wait_for_response.update(|w| *w = true);
|
||||
let result = login(&GlobalState::read_hostname(), credentials).await;
|
||||
set_wait_for_response.update(|w| *w = false);
|
||||
match result {
|
||||
Ok(res) => {
|
||||
expect_context::<RwSignal<GlobalState>>()
|
||||
.update(|state| state.my_profile = Some(res));
|
||||
set_login_response.update(|v| *v = Some(()));
|
||||
set_login_error.update(|e| *e = None);
|
||||
}
|
||||
Err(err) => {
|
||||
let msg = err.0.to_string();
|
||||
log::warn!("Unable to login: {msg}");
|
||||
set_login_error.update(|e| *e = Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let disabled = Signal::derive(move || wait_for_response.get());
|
||||
|
||||
view! {
|
||||
<form on:submit=move |ev| do_login(ev, name.get(), password.get())>
|
||||
<div>
|
||||
<label for="username">Username: </label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
on:input=move |ev| name.set(event_target_value(&ev))
|
||||
label="Username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password">Password: </label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
on:input=move |ev| password.set(event_target_value(&ev))
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit">
|
||||
"Login"
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<Show
|
||||
when=move || login_response.get().is_some()
|
||||
fallback=move || {
|
||||
view! {
|
||||
<CredentialsForm
|
||||
title="Please enter the desired credentials"
|
||||
action_label="Login"
|
||||
action=login_action
|
||||
error=login_error.into()
|
||||
disabled
|
||||
/>
|
||||
}
|
||||
}
|
||||
>
|
||||
<Redirect path="/"/>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
use crate::common::{LoginResponse, RegisterUserData};
|
||||
use crate::common::RegisterUserData;
|
||||
use crate::frontend::api::register;
|
||||
use crate::frontend::app::BackendHostname;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::components::credentials::*;
|
||||
use crate::frontend::pages::Page;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Register() -> impl IntoView {
|
||||
let (register_response, set_register_response) = create_signal(None::<LoginResponse>);
|
||||
let (register_response, set_register_response) = create_signal(None::<()>);
|
||||
let (register_error, set_register_error) = create_signal(None::<String>);
|
||||
let (wait_for_response, set_wait_for_response) = create_signal(false);
|
||||
|
||||
|
@ -19,11 +17,13 @@ pub fn Register() -> impl IntoView {
|
|||
log!("Try to register new account for {}", credentials.username);
|
||||
async move {
|
||||
set_wait_for_response.update(|w| *w = true);
|
||||
let result = register(&BackendHostname::read(), credentials).await;
|
||||
let result = register(&GlobalState::read_hostname(), credentials).await;
|
||||
set_wait_for_response.update(|w| *w = false);
|
||||
match result {
|
||||
Ok(res) => {
|
||||
set_register_response.update(|v| *v = Some(res));
|
||||
expect_context::<RwSignal<GlobalState>>()
|
||||
.update(|state| state.my_profile = Some(res));
|
||||
set_register_response.update(|v| *v = Some(()));
|
||||
set_register_error.update(|e| *e = None);
|
||||
}
|
||||
Err(err) => {
|
||||
|
@ -53,7 +53,6 @@ pub fn Register() -> impl IntoView {
|
|||
}
|
||||
>
|
||||
<p>"You have successfully registered."</p>
|
||||
<p>"You can now " <A href=Page::Login.path()>"login"</A> " with your new account."</p>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,5 +23,4 @@ fn main() {
|
|||
mount_to_body(|| {
|
||||
view! { <App/> }
|
||||
});
|
||||
log::info!("test 2");
|
||||
}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
use anyhow::anyhow;
|
||||
use ibis::backend::api::article::{CreateArticleData, EditArticleData, ForkArticleData};
|
||||
use ibis::backend::api::instance::FollowInstance;
|
||||
use ibis::backend::api::user::RegisterUserData;
|
||||
use ibis::backend::api::user::{LoginResponse, LoginUserData};
|
||||
use ibis::backend::api::user::AUTH_COOKIE;
|
||||
use ibis::backend::api::ResolveObject;
|
||||
use ibis::backend::database::conflict::ApiConflict;
|
||||
use ibis::backend::database::instance::DbInstance;
|
||||
use ibis::backend::error::MyResult;
|
||||
use ibis::backend::start;
|
||||
use ibis::common::ArticleView;
|
||||
use ibis::frontend::api;
|
||||
use ibis::frontend::api::get_query;
|
||||
use ibis_lib::frontend::api;
|
||||
use ibis::common::LoginUserData;
|
||||
use ibis::common::RegisterUserData;
|
||||
use ibis::frontend::api::{get_article, get_query, handle_json_res, register};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{Client, StatusCode};
|
||||
use reqwest::{Client, ClientBuilder, StatusCode};
|
||||
use serde::de::Deserialize;
|
||||
use std::env::current_dir;
|
||||
use std::fs::create_dir_all;
|
||||
|
@ -97,6 +96,7 @@ fn generate_db_path(name: &'static str, port: i32) -> String {
|
|||
|
||||
pub struct IbisInstance {
|
||||
pub hostname: String,
|
||||
pub client: Client,
|
||||
pub jwt: String,
|
||||
db_path: String,
|
||||
db_handle: JoinHandle<()>,
|
||||
|
@ -123,11 +123,18 @@ impl IbisInstance {
|
|||
});
|
||||
// wait a moment for the backend to start
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
let register_res = api::register(&hostname, username, "hunter2").await.unwrap();
|
||||
assert!(!register_res.jwt.is_empty());
|
||||
let form = RegisterUserData {
|
||||
username: username.to_string(),
|
||||
password: "hunter2".to_string(),
|
||||
};
|
||||
// TODO: use a separate http client for each backend instance, with cookie store for auth
|
||||
// TODO: how to pass the client/hostname to api client methods?
|
||||
// probably create a struct ApiClient(hostname, client) with all api methods in impl
|
||||
let client = ClientBuilder::new().cookie_store(true).build();
|
||||
let register_res = register(&hostname, form).await.unwrap();
|
||||
Self {
|
||||
jwt: register_res.jwt,
|
||||
hostname,
|
||||
client,
|
||||
db_path,
|
||||
db_handle: handle,
|
||||
}
|
||||
|
@ -156,7 +163,7 @@ pub async fn create_article(instance: &IbisInstance, title: String) -> MyResult<
|
|||
.post(format!("http://{}/api/v1/article", &instance.hostname))
|
||||
.form(&create_form)
|
||||
.bearer_auth(&instance.jwt);
|
||||
let article: ArticleView = api::handle_json_res(req).await?;
|
||||
let article: ArticleView = handle_json_res(req).await?;
|
||||
|
||||
// create initial edit to ensure that conflicts are generated (there are no conflicts on empty file)
|
||||
let edit_form = EditArticleData {
|
||||
|
@ -176,7 +183,7 @@ pub async fn edit_article_with_conflict(
|
|||
.patch(format!("http://{}/api/v1/article", instance.hostname))
|
||||
.form(edit_form)
|
||||
.bearer_auth(&instance.jwt);
|
||||
api::handle_json_res(req).await
|
||||
handle_json_res(req).await?
|
||||
}
|
||||
|
||||
pub async fn get_conflicts(instance: &IbisInstance) -> MyResult<Vec<ApiConflict>> {
|
||||
|
@ -186,7 +193,7 @@ pub async fn get_conflicts(instance: &IbisInstance) -> MyResult<Vec<ApiConflict>
|
|||
&instance.hostname
|
||||
))
|
||||
.bearer_auth(&instance.jwt);
|
||||
api::handle_json_res(req).await
|
||||
handle_json_res(req).await?
|
||||
}
|
||||
|
||||
pub async fn edit_article(
|
||||
|
@ -195,7 +202,8 @@ pub async fn edit_article(
|
|||
) -> MyResult<ArticleView> {
|
||||
let edit_res = edit_article_with_conflict(instance, edit_form).await?;
|
||||
assert!(edit_res.is_none());
|
||||
api::get_article(&instance.hostname, edit_form.article_id).await
|
||||
|
||||
get_article(&instance.hostname, todo!("{}", edit_form.article_id)).await
|
||||
}
|
||||
|
||||
pub async fn get<T>(hostname: &str, endpoint: &str) -> MyResult<T>
|
||||
|
@ -213,7 +221,7 @@ pub async fn fork_article(
|
|||
.post(format!("http://{}/api/v1/article/fork", instance.hostname))
|
||||
.form(form)
|
||||
.bearer_auth(&instance.jwt);
|
||||
api::handle_json_res(req).await
|
||||
handle_json_res(req).await?
|
||||
}
|
||||
|
||||
pub async fn follow_instance(instance: &IbisInstance, follow_instance: &str) -> MyResult<()> {
|
||||
|
@ -222,7 +230,7 @@ pub async fn follow_instance(instance: &IbisInstance, follow_instance: &str) ->
|
|||
id: Url::parse(&format!("http://{}", follow_instance))?,
|
||||
};
|
||||
let instance_resolved: DbInstance =
|
||||
api::get_query(&instance.hostname, "resolve_instance", Some(resolve_form)).await?;
|
||||
get_query(&instance.hostname, "resolve_instance", Some(resolve_form)).await?;
|
||||
|
||||
// send follow
|
||||
let follow_form = FollowInstance {
|
||||
|
|
|
@ -2,12 +2,12 @@ extern crate ibis;
|
|||
|
||||
mod common;
|
||||
|
||||
use crate::common::fork_article;
|
||||
use crate::common::get_conflicts;
|
||||
use crate::common::{
|
||||
create_article, edit_article, edit_article_with_conflict, follow_instance, get, TestData,
|
||||
CLIENT, TEST_ARTICLE_DEFAULT_TEXT,
|
||||
};
|
||||
use crate::common::{fork_article, login};
|
||||
use crate::common::{get_conflicts, register};
|
||||
use ibis::backend::api::article::{CreateArticleData, EditArticleData, ForkArticleData};
|
||||
use ibis::backend::api::{ResolveObject, SearchArticleData};
|
||||
use ibis::backend::database::instance::{DbInstance, InstanceView};
|
||||
|
@ -17,6 +17,7 @@ use ibis::common::DbArticle;
|
|||
use ibis::frontend::api::get_article;
|
||||
use ibis::frontend::api::get_query;
|
||||
use ibis::frontend::api::handle_json_res;
|
||||
use ibis::frontend::api::login;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use url::Url;
|
||||
|
||||
|
|
Loading…
Reference in a new issue