feat: Autorefresh (#44)
* feat: use gotrue from source instead of docker hub image * test: fix test due to gotrue upgrade * fix: update prod docker-compose * chore: cargo fmt --all * chore: cargo fmt --all * feat: autorefresh * test: add test case and auto refresh scenario
This commit is contained in:
parent
939ea29c3b
commit
7345da7c46
|
|
@ -4,7 +4,7 @@ WORKDIR /app
|
|||
RUN apt update && apt install lld clang -y
|
||||
|
||||
FROM chef as planner
|
||||
COPY .. .
|
||||
COPY . .
|
||||
# Compute a lock-like file for our project
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ FROM chef as builder
|
|||
COPY --from=planner /app/recipe.json recipe.json
|
||||
# Build our project dependencies
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
COPY .. .
|
||||
COPY . .
|
||||
ENV SQLX_OFFLINE true
|
||||
# Build the project
|
||||
RUN cargo build --release --bin appflowy_cloud
|
||||
|
|
@ -55,7 +55,7 @@ services:
|
|||
- APP__GOTRUE__JWT_SECRET=${GOTRUE_JWT_SECRET}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
image: appflowy_cloud:${BACKEND_VERSION:-latest}
|
||||
depends_on:
|
||||
- redis
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use gotrue_entity::OAuthProvider;
|
||||
use gotrue_entity::OAuthURL;
|
||||
use reqwest::Method;
|
||||
|
|
@ -112,10 +114,11 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn profile(&self) -> Result<AFUserProfileView, AppError> {
|
||||
pub async fn profile(&mut self) -> Result<AFUserProfileView, AppError> {
|
||||
let url = format!("{}/api/user/profile", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)?
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<AFUserProfileView>::from_response(resp)
|
||||
|
|
@ -123,10 +126,11 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn workspaces(&self) -> Result<AFWorkspaces, AppError> {
|
||||
pub async fn workspaces(&mut self) -> Result<AFWorkspaces, AppError> {
|
||||
let url = format!("{}/api/user/workspaces", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)?
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<AFWorkspaces>::from_response(resp)
|
||||
|
|
@ -169,10 +173,11 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn sign_out(&self) -> Result<(), AppError> {
|
||||
pub async fn sign_out(&mut self) -> Result<(), AppError> {
|
||||
let url = format!("{}/api/user/sign_out", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)?
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()?;
|
||||
|
|
@ -186,7 +191,8 @@ impl Client {
|
|||
"password": password,
|
||||
});
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)?
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?;
|
||||
|
|
@ -199,30 +205,33 @@ impl Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_collab(&self, params: InsertCollabParams) -> Result<(), AppError> {
|
||||
pub async fn create_collab(&mut self, params: InsertCollabParams) -> Result<(), AppError> {
|
||||
let url = format!("{}/api/collab/", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::POST, &url)?
|
||||
.http_client_with_auth(Method::POST, &url)
|
||||
.await?
|
||||
.json(¶ms)
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn update_collab(&self, params: InsertCollabParams) -> Result<(), AppError> {
|
||||
pub async fn update_collab(&mut self, params: InsertCollabParams) -> Result<(), AppError> {
|
||||
let url = format!("{}/api/collab/", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::PUT, &url)?
|
||||
.http_client_with_auth(Method::PUT, &url)
|
||||
.await?
|
||||
.json(¶ms)
|
||||
.send()
|
||||
.await?;
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
pub async fn get_collab(&self, params: QueryCollabParams) -> Result<RawData, AppError> {
|
||||
pub async fn get_collab(&mut self, params: QueryCollabParams) -> Result<RawData, AppError> {
|
||||
let url = format!("{}/api/collab/", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)?
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.json(¶ms)
|
||||
.send()
|
||||
.await?;
|
||||
|
|
@ -231,10 +240,11 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
pub async fn delete_collab(&self, params: DeleteCollabParams) -> Result<(), AppError> {
|
||||
pub async fn delete_collab(&mut self, params: DeleteCollabParams) -> Result<(), AppError> {
|
||||
let url = format!("{}/api/collab/", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::DELETE, &url)?
|
||||
.http_client_with_auth(Method::DELETE, &url)
|
||||
.await?
|
||||
.json(¶ms)
|
||||
.send()
|
||||
.await?;
|
||||
|
|
@ -254,17 +264,34 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
fn http_client_with_auth(&self, method: Method, url: &str) -> Result<RequestBuilder, AppError> {
|
||||
match &self.token {
|
||||
None => Err(ErrorCode::NotLoggedIn.into()),
|
||||
Some(t) => {
|
||||
let request_builder = self
|
||||
.http_client
|
||||
.request(method, url)
|
||||
.bearer_auth(&t.access_token);
|
||||
Ok(request_builder)
|
||||
},
|
||||
async fn http_client_with_auth(
|
||||
&mut self,
|
||||
method: Method,
|
||||
url: &str,
|
||||
) -> Result<RequestBuilder, AppError> {
|
||||
let token = self.token().ok_or(ErrorCode::NotLoggedIn)?;
|
||||
|
||||
// Refresh token if it's about to expire
|
||||
let time_now_sec = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64;
|
||||
if time_now_sec + 60 > token.expires_at {
|
||||
// Add 60 seconds buffer
|
||||
self.refresh().await?;
|
||||
}
|
||||
|
||||
let access_token = self
|
||||
.token()
|
||||
.ok_or(ErrorCode::NotLoggedIn)?
|
||||
.access_token
|
||||
.as_str();
|
||||
|
||||
let request_builder = self
|
||||
.http_client
|
||||
.request(method, url)
|
||||
.bearer_auth(access_token);
|
||||
Ok(request_builder)
|
||||
}
|
||||
|
||||
// pub async fn change_password(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::fmt::Display;
|
||||
use std::num::ParseIntError;
|
||||
use std::time::SystemTimeError;
|
||||
use std::{borrow::Cow, str};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -133,3 +134,9 @@ impl From<ParseIntError> for AppError {
|
|||
AppError::new(ErrorCode::InvalidUrl, value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTimeError> for AppError {
|
||||
fn from(value: SystemTimeError) -> Self {
|
||||
AppError::new(ErrorCode::Unhandled, value.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use gotrue_entity::AccessTokenResponse;
|
||||
|
||||
use crate::{
|
||||
client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX},
|
||||
client_api_client,
|
||||
|
|
@ -11,5 +15,39 @@ async fn refresh_success() {
|
|||
let password = ®ISTERED_PASSWORD;
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(email, password).await.unwrap();
|
||||
let old_token = c.token().unwrap().access_token.to_owned();
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
c.refresh().await.unwrap();
|
||||
let new_token = c.token().unwrap().access_token.to_owned();
|
||||
assert_ne!(old_token, new_token);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_trigger() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let email = ®ISTERED_EMAIL;
|
||||
let password = ®ISTERED_PASSWORD;
|
||||
|
||||
let mut c = client_api_client();
|
||||
|
||||
c.sign_in_password(email, password).await.unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
let token = c.token().unwrap();
|
||||
let old_access_token = token.access_token.to_owned();
|
||||
|
||||
// Set the token to be expired
|
||||
unsafe {
|
||||
let token_mut = token as *const AccessTokenResponse as *mut AccessTokenResponse;
|
||||
token_mut.as_mut().unwrap().expires_at = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i64
|
||||
};
|
||||
|
||||
// querying that requires auth should trigger a refresh
|
||||
let _workspaces = c.workspaces().await.unwrap();
|
||||
let new_token = c.token().unwrap().access_token.to_owned();
|
||||
|
||||
assert_ne!(old_access_token, new_token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::client_api_client;
|
|||
|
||||
#[tokio::test]
|
||||
async fn sign_out_but_not_sign_in() {
|
||||
let c = client_api_client();
|
||||
let mut c = client_api_client();
|
||||
let res = c.sign_out().await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use client_api::Client;
|
|||
|
||||
mod storage_test;
|
||||
|
||||
pub(crate) async fn workspace_id_from_client(c: &Client) -> String {
|
||||
pub(crate) async fn workspace_id_from_client(c: &mut Client) -> String {
|
||||
c.workspaces()
|
||||
.await
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ async fn success_insert_collab_test() {
|
|||
.unwrap();
|
||||
|
||||
let raw_data = "hello world".to_string().as_bytes().to_vec();
|
||||
let workspace_id = workspace_id_from_client(&c).await;
|
||||
let workspace_id = workspace_id_from_client(&mut c).await;
|
||||
let object_id = Uuid::new_v4().to_string();
|
||||
c.create_collab(InsertCollabParams::new(
|
||||
1,
|
||||
|
|
@ -50,7 +50,7 @@ async fn success_delete_collab_test() {
|
|||
.unwrap();
|
||||
|
||||
let raw_data = "hello world".to_string().as_bytes().to_vec();
|
||||
let workspace_id = workspace_id_from_client(&c).await;
|
||||
let workspace_id = workspace_id_from_client(&mut c).await;
|
||||
let object_id = Uuid::new_v4().to_string();
|
||||
c.create_collab(InsertCollabParams::new(
|
||||
1,
|
||||
|
|
@ -88,7 +88,7 @@ async fn fail_insert_collab_with_empty_payload_test() {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let workspace_id = workspace_id_from_client(&c).await;
|
||||
let workspace_id = workspace_id_from_client(&mut c).await;
|
||||
let error = c
|
||||
.create_collab(InsertCollabParams::new(
|
||||
1,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use storage::collab::FLUSH_PER_UPDATE;
|
|||
async fn realtime_write_collab_test() {
|
||||
let object_id = uuid::Uuid::new_v4().to_string();
|
||||
let collab_type = CollabType::Document;
|
||||
let test_client = TestClient::new(&object_id, collab_type.clone()).await;
|
||||
let mut test_client = TestClient::new(&object_id, collab_type.clone()).await;
|
||||
|
||||
// Edit the collab
|
||||
for i in 0..=5 {
|
||||
|
|
@ -28,7 +28,7 @@ async fn realtime_write_collab_test() {
|
|||
test_client.disconnect().await;
|
||||
|
||||
assert_collab_json(
|
||||
&test_client.api_client,
|
||||
&mut test_client.api_client,
|
||||
&object_id,
|
||||
&collab_type,
|
||||
3,
|
||||
|
|
@ -48,7 +48,7 @@ async fn realtime_write_collab_test() {
|
|||
async fn one_direction_peer_sync_test() {
|
||||
let object_id = uuid::Uuid::new_v4().to_string();
|
||||
let collab_type = CollabType::Document;
|
||||
let client_1 = TestClient::new(&object_id, collab_type.clone()).await;
|
||||
let mut client_1 = TestClient::new(&object_id, collab_type.clone()).await;
|
||||
let client_2 = TestClient::new(&object_id, collab_type.clone()).await;
|
||||
|
||||
// Edit the collab from client 1 and then the server will broadcast to client 2
|
||||
|
|
@ -58,7 +58,7 @@ async fn one_direction_peer_sync_test() {
|
|||
}
|
||||
|
||||
assert_collab_json(
|
||||
&client_1.api_client,
|
||||
&mut client_1.api_client,
|
||||
&object_id,
|
||||
&collab_type,
|
||||
5,
|
||||
|
|
@ -129,13 +129,13 @@ async fn multiple_collab_edit_test() {
|
|||
let collab_type = CollabType::Document;
|
||||
|
||||
let object_id_1 = uuid::Uuid::new_v4().to_string();
|
||||
let client_1 = TestClient::new(&object_id_1, collab_type.clone()).await;
|
||||
let mut client_1 = TestClient::new(&object_id_1, collab_type.clone()).await;
|
||||
|
||||
let object_id_2 = uuid::Uuid::new_v4().to_string();
|
||||
let client_2 = TestClient::new(&object_id_2, collab_type.clone()).await;
|
||||
let mut client_2 = TestClient::new(&object_id_2, collab_type.clone()).await;
|
||||
|
||||
let object_id_3 = uuid::Uuid::new_v4().to_string();
|
||||
let client_3 = TestClient::new(&object_id_3, collab_type.clone()).await;
|
||||
let mut client_3 = TestClient::new(&object_id_3, collab_type.clone()).await;
|
||||
|
||||
client_1.collab.lock().insert("title", "I am client 1");
|
||||
client_2.collab.lock().insert("title", "I am client 2");
|
||||
|
|
@ -143,7 +143,7 @@ async fn multiple_collab_edit_test() {
|
|||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
assert_collab_json(
|
||||
&client_1.api_client,
|
||||
&mut client_1.api_client,
|
||||
&object_id_1,
|
||||
&collab_type,
|
||||
3,
|
||||
|
|
@ -154,7 +154,7 @@ async fn multiple_collab_edit_test() {
|
|||
.await;
|
||||
|
||||
assert_collab_json(
|
||||
&client_2.api_client,
|
||||
&mut client_2.api_client,
|
||||
&object_id_2,
|
||||
&collab_type,
|
||||
3,
|
||||
|
|
@ -164,7 +164,7 @@ async fn multiple_collab_edit_test() {
|
|||
)
|
||||
.await;
|
||||
assert_collab_json(
|
||||
&client_3.api_client,
|
||||
&mut client_3.api_client,
|
||||
&object_id_3,
|
||||
&collab_type,
|
||||
3,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ impl TestClient {
|
|||
|
||||
#[allow(dead_code)]
|
||||
pub async fn assert_collab_json(
|
||||
client: &client_api::Client,
|
||||
client: &mut client_api::Client,
|
||||
object_id: &str,
|
||||
collab_type: &CollabType,
|
||||
secs: u64,
|
||||
|
|
@ -141,7 +141,7 @@ pub async fn assert_collab_json(
|
|||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_collab_json_from_server(
|
||||
client: &client_api::Client,
|
||||
client: &mut client_api::Client,
|
||||
object_id: &str,
|
||||
collab_type: CollabType,
|
||||
) -> serde_json::Value {
|
||||
|
|
|
|||
Loading…
Reference in New Issue