Compare commits

..

10 Commits

Author SHA1 Message Date
Nathan.fooo b7e08687b4
chore: use curl for gotrue health check when using docker-compose-ci.yml (#1189) 2025-01-22 21:40:47 +08:00
Khor Shu Heng 2c7f0131c4
Merge pull request #1188 from AppFlowy-IO/better-gotrue-healthcheck
chore: use curl for gotrue health check
2025-01-22 17:26:46 +08:00
khorshuheng f033e34ab1 chore: use curl for gotrue health check 2025-01-22 17:23:56 +08:00
Khor Shu Heng 626c8a5be4
Merge pull request #1186 from AppFlowy-IO/add-curl-for-gotrue-image
chore: add curl for gotrue image for better healthcheck
2025-01-22 16:57:44 +08:00
Khor Shu Heng a133a3311a
Merge pull request #1187 from AppFlowy-IO/remove-self-sign-for-appflowy-cloud
chore: remove unnecessary self sign related functionality in appflowy cloud
2025-01-22 16:57:30 +08:00
khorshuheng 9f546ce249 chore: remove unnecessary self sign related functionality in appflowy cloud 2025-01-22 15:49:03 +08:00
khorshuheng 682d5ea788 chore: add curl for gotrue image for better healthcheck 2025-01-22 15:30:42 +08:00
Nathan 4a26572a4e chore: ai image response error code 2025-01-22 02:06:43 +08:00
Nathan.fooo e990bad68f
chore: add metrics for ai (#1184) 2025-01-20 23:22:33 +08:00
Nathan.fooo 5d9efb4243
Return ai image count (#1182)
* chore: return ai image count
2025-01-20 19:36:31 +08:00
14 changed files with 62 additions and 148 deletions

View File

@ -61,10 +61,10 @@ services:
postgres:
condition: service_healthy
healthcheck:
test: [ "CMD", "nc", "-z", "localhost", "9999" ]
test: "curl --fail http://127.0.0.1:9999/health || exit 1"
interval: 5s
timeout: 5s
retries: 6
retries: 12
environment:
# There are a lot of options to configure GoTrue. You can reference the example config:
# https://github.com/supabase/gotrue/blob/master/example.env
@ -165,6 +165,9 @@ services:
- ADMIN_FRONTEND_REDIS_URL=${ADMIN_FRONTEND_REDIS_URL:-redis://redis:6379}
- ADMIN_FRONTEND_GOTRUE_URL=${ADMIN_FRONTEND_GOTRUE_URL:-http://gotrue:9999}
- ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=${ADMIN_FRONTEND_APPFLOWY_CLOUD_URL:-http://appflowy_cloud:8000}
depends_on:
appflowy_cloud:
condition: service_started
ai:
restart: on-failure

View File

@ -37,7 +37,7 @@ services:
test: [ "CMD", "pg_isready", "-U", "${POSTGRES_USER}" ]
interval: 5s
timeout: 5s
retries: 6
retries: 12
volumes:
- ./migrations/before:/docker-entrypoint-initdb.d
- postgres_data:/var/lib/postgresql/data
@ -55,10 +55,10 @@ services:
postgres:
condition: service_healthy
healthcheck:
test: [ "CMD", "nc", "-z", "localhost", "9999" ]
test: "curl --fail http://127.0.0.1:9999/health || exit 1"
interval: 5s
timeout: 5s
retries: 6
retries: 12
image: appflowyinc/gotrue:${GOTRUE_VERSION:-latest}
environment:
# There are a lot of options to configure GoTrue. You can reference the example config:
@ -144,6 +144,9 @@ services:
args:
FEATURES: ""
image: appflowyinc/appflowy_cloud:${APPFLOWY_CLOUD_VERSION:-latest}
depends_on:
gotrue:
condition: service_healthy
admin_frontend:
restart: on-failure
@ -156,6 +159,9 @@ services:
- ADMIN_FRONTEND_REDIS_URL=${ADMIN_FRONTEND_REDIS_URL:-redis://redis:6379}
- ADMIN_FRONTEND_GOTRUE_URL=${ADMIN_FRONTEND_GOTRUE_URL:-http://gotrue:9999}
- ADMIN_FRONTEND_APPFLOWY_CLOUD_URL=${ADMIN_FRONTEND_APPFLOWY_CLOUD_URL:-http://appflowy_cloud:8000}
depends_on:
appflowy_cloud:
condition: service_started
ai:
restart: on-failure
@ -165,6 +171,9 @@ services:
- APPFLOWY_AI_SERVER_PORT=${AI_SERVER_PORT}
- APPFLOWY_AI_DATABASE_URL=${AI_DATABASE_URL}
- APPFLOWY_AI_REDIS_URL=${AI_REDIS_URL}
depends_on:
postgres:
condition: service_healthy
appflowy_worker:
restart: on-failure
@ -190,6 +199,9 @@ services:
- APPFLOWY_MAILER_SMTP_USERNAME=${APPFLOWY_MAILER_SMTP_USERNAME}
- APPFLOWY_MAILER_SMTP_EMAIL=${APPFLOWY_MAILER_SMTP_EMAIL}
- APPFLOWY_MAILER_SMTP_PASSWORD=${APPFLOWY_MAILER_SMTP_PASSWORD}
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
minio_data:

View File

@ -10,7 +10,7 @@ RUN rm /go/src/supabase/auth/migrations/20240612123726_enable_rls_update_grants.
FROM alpine:3.20
RUN adduser -D -u 1000 supabase
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache ca-certificates curl
USER supabase
COPY --from=base /auth .

View File

@ -430,6 +430,7 @@ pub enum ErrorCode {
DecodeUpdateError = 1055,
ApplyUpdateError = 1056,
ActionTimeout = 1057,
AIImageResponseLimitExceeded = 1058,
}
impl ErrorCode {

View File

@ -117,6 +117,10 @@ pub struct WorkspaceUsageAndLimit {
pub single_upload_unlimited: bool,
pub ai_responses_count: i64,
pub ai_responses_count_limit: i64,
#[serde(default)]
pub ai_image_responses_count: i64,
#[serde(default)]
pub ai_image_responses_count_limit: i64,
pub local_ai: bool,
pub ai_responses_unlimited: bool,

View File

@ -16,6 +16,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let listener = TcpListener::bind("0.0.0.0:4001").await.unwrap();
let config = Config::from_env().expect("failed to load config");
let config = Config::from_env()?;
run_server(listener, config).await
}

View File

@ -37,6 +37,7 @@ async fn stream_complete_text_handler(
) -> actix_web::Result<HttpResponse> {
let ai_model = ai_model_from_header(&req);
let params = payload.into_inner();
state.metrics.ai_metrics.record_total_completion_count(1);
match state
.ai_client
@ -80,6 +81,7 @@ async fn summarize_row_handler(
);
}
state.metrics.ai_metrics.record_total_summary_row_count(1);
let ai_model = ai_model_from_header(&req);
let result = state.ai_client.summarize_row(&content, ai_model).await;
let resp = match result {
@ -105,6 +107,7 @@ async fn translate_row_handler(
) -> actix_web::Result<Json<AppResponse<TranslateRowResponse>>> {
let params = payload.into_inner();
let ai_model = ai_model_from_header(&req);
state.metrics.ai_metrics.record_total_translate_row_count(1);
match state.ai_client.translate_row(params.data, ai_model).await {
Ok(resp) => Ok(AppResponse::Ok().with_data(resp).into()),
Err(err) => {

View File

@ -27,8 +27,6 @@ use aws_sdk_s3::types::{
BucketInfo, BucketLocationConstraint, BucketType, CreateBucketConfiguration,
};
use mailer::config::MailerSetting;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
use openssl::x509::X509;
use secrecy::{ExposeSecret, Secret};
use sqlx::{postgres::PgPoolOptions, PgPool};
use tokio::sync::RwLock;
@ -72,7 +70,6 @@ use crate::config::config::{
use crate::mailer::AFCloudMailer;
use crate::middleware::metrics_mw::MetricsMiddleware;
use crate::middleware::request_id::RequestIdMiddleware;
use crate::self_signed::create_self_signed_certificate;
use crate::state::{AppMetrics, AppState, GoTrueAdmin, UserCache};
pub struct Application {
@ -119,11 +116,6 @@ pub async fn run_actix_server(
e
)
})?;
let pair = get_certificate_and_server_key(&config);
let key = pair
.as_ref()
.map(|(_, server_key)| Key::from(server_key.expose_secret().as_bytes()))
.unwrap_or_else(Key::generate);
let storage = state.collab_access_control_storage.clone();
@ -150,7 +142,7 @@ pub async fn run_actix_server(
.wrap(MetricsMiddleware)
.wrap(IdentityMiddleware::default())
.wrap(
SessionMiddleware::builder(redis_store.clone(), key.clone())
SessionMiddleware::builder(redis_store.clone(), Key::generate())
.build(),
)
.wrap(RequestIdMiddleware)
@ -178,24 +170,11 @@ pub async fn run_actix_server(
.app_data(Data::new(state.published_collab_store.clone()))
});
server = match pair {
None => server.listen(listener)?,
Some((certificate, _)) => {
server.listen_openssl(listener, make_ssl_acceptor_builder(certificate))?
},
};
server = server.listen(listener)?;
Ok(server.run())
}
fn get_certificate_and_server_key(config: &Config) -> Option<(Secret<String>, Secret<String>)> {
if config.application.use_tls {
Some(create_self_signed_certificate().unwrap())
} else {
None
}
}
pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<AppState, Error> {
// Print the feature flags
@ -523,22 +502,3 @@ async fn get_gotrue_client(setting: &GoTrueSetting) -> Result<gotrue::api::Clien
.map_err(|e| anyhow::anyhow!("Failed to connect to GoTrue: {}", e));
Ok(gotrue_client)
}
fn make_ssl_acceptor_builder(certificate: Secret<String>) -> SslAcceptorBuilder {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
let x509_cert = X509::from_pem(certificate.expose_secret().as_bytes()).unwrap();
builder.set_certificate(&x509_cert).unwrap();
builder
.set_private_key_file("./cert/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("./cert/cert.pem")
.unwrap();
builder
.set_min_proto_version(Some(openssl::ssl::SslVersion::TLS1_2))
.unwrap();
builder
.set_max_proto_version(Some(openssl::ssl::SslVersion::TLS1_3))
.unwrap();
builder
}

View File

@ -5,6 +5,9 @@ pub struct AIMetrics {
total_stream_count: Counter,
failed_stream_count: Counter,
stream_image_count: Counter,
total_completion_count: Counter,
total_summary_row_count: Counter,
total_translate_row_count: Counter,
}
impl AIMetrics {
@ -28,6 +31,21 @@ impl AIMetrics {
"Total count of image streams processed",
metrics.stream_image_count.clone(),
);
realtime_registry.register(
"total_completion_count",
"Total count of completions processed",
metrics.total_completion_count.clone(),
);
realtime_registry.register(
"total_summary_row_count",
"Total count of summary rows processed",
metrics.total_summary_row_count.clone(),
);
realtime_registry.register(
"total_translate_row_count",
"Total count of translation rows processed",
metrics.total_translate_row_count.clone(),
);
metrics
}
@ -43,4 +61,16 @@ impl AIMetrics {
pub fn record_stream_image_count(&self, count: u64) {
self.stream_image_count.inc_by(count);
}
pub fn record_total_completion_count(&self, count: u64) {
self.total_completion_count.inc_by(count);
}
pub fn record_total_summary_row_count(&self, count: u64) {
self.total_summary_row_count.inc_by(count);
}
pub fn record_total_translate_row_count(&self, count: u64) {
self.total_translate_row_count.inc_by(count);
}
}

View File

@ -97,8 +97,6 @@ impl AppFlowyAISetting {
pub struct ApplicationSetting {
pub port: u16,
pub host: String,
pub server_key: Secret<String>,
pub use_tls: bool,
}
#[derive(Clone, Debug)]
@ -209,10 +207,6 @@ pub fn get_configuration() -> Result<Config, anyhow::Error> {
application: ApplicationSetting {
port: get_env_var("APPFLOWY_APPLICATION_PORT", "8000").parse()?,
host: get_env_var("APPFLOWY_APPLICATION_HOST", "0.0.0.0"),
use_tls: get_env_var("APPFLOWY_APPLICATION_USE_TLS", "false")
.parse()
.context("fail to get APPFLOWY_APPLICATION_USE_TLS")?,
server_key: get_env_var("APPFLOWY_APPLICATION_SERVER_KEY", "server_key").into(),
},
websocket: WebsocketSetting {
heartbeat_interval: get_env_var("APPFLOWY_WEBSOCKET_HEARTBEAT_INTERVAL", "6").parse()?,

View File

@ -5,6 +5,5 @@ pub mod config;
pub mod domain;
pub mod mailer;
pub mod middleware;
mod self_signed;
pub mod state;
pub mod telemetry;

View File

@ -1,61 +0,0 @@
use actix_http::Payload;
use actix_service::{forward_ready, Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use bytes::Bytes;
use bytes::BytesMut;
use futures::future::Ready;
use futures_util::future::{ready, LocalBoxFuture};
use futures_util::{stream, StreamExt};
pub struct DecryptPayloadMiddleware;
impl<S, B> Transform<S, ServiceRequest> for DecryptPayloadMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = DecryptPayloadMiddlewareService<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(DecryptPayloadMiddlewareService { service }))
}
}
pub struct DecryptPayloadMiddlewareService<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for DecryptPayloadMiddlewareService<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let (http_req, mut payload) = req.into_parts();
let payload_stream = stream::once(async move {
let mut body = BytesMut::new();
while let Some(chunk) = payload.next().await {
body.extend_from_slice(&chunk?);
}
Ok(Bytes::from(body))
});
let payload = Box::pin(payload_stream);
let new_req = ServiceRequest::from_parts(http_req, Payload::Stream { payload });
let fut = self.service.call(new_req);
Box::pin(fut)
}
}

View File

@ -1,3 +1,2 @@
pub mod encrypt_mw;
pub mod metrics_mw;
pub mod request_id;

View File

@ -1,30 +0,0 @@
use rcgen::{Certificate, CertificateParams, KeyPair, RcgenError, SanType};
use secrecy::Secret;
pub const CA_CRT: &str = include_str!("../cert/cert.pem");
pub const CA_KEY: &str = include_str!("../cert/key.pem");
pub fn create_self_signed_certificate() -> Result<(Secret<String>, Secret<String>), RcgenError> {
let key = KeyPair::from_pem(CA_KEY)?;
let params = CertificateParams::from_ca_cert_pem(CA_CRT, key)?;
let ca_cert = Certificate::from_params(params)?;
let mut params = CertificateParams::default();
params
.subject_alt_names
.push(SanType::IpAddress("127.0.0.1".parse().unwrap()));
params
.subject_alt_names
.push(SanType::IpAddress("0.0.0.0".parse().unwrap()));
params
.subject_alt_names
.push(SanType::DnsName("localhost".to_string()));
// Generate a certificate that's valid for:
// 1. localhost
// 2. 127.0.0.1
let gen_cert = Certificate::from_params(params)?;
let server_crt = Secret::new(gen_cert.serialize_pem_with_signer(&ca_cert)?);
let server_key = Secret::new(gen_cert.serialize_private_key_pem());
Ok((server_crt, server_key))
}