use std::borrow::Cow;
use std::collections::BTreeMap;
use testcontainers_modules::testcontainers::core::{
ContainerPort, ContainerState, ExecCommand, WaitFor,
};
use testcontainers_modules::testcontainers::{Image, TestcontainersError};
const NAME: &str = "supabase/logflare";
const TAG: &str = "1.26.13";
pub const ANALYTICS_PORT: u16 = 4000;
#[derive(Debug, Clone)]
pub struct Analytics {
env_vars: BTreeMap<String, String>,
tag: String,
}
impl Analytics {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_env(envs: BTreeMap<&str, &str>) -> Self {
let mut instance = Self::default();
for (key, val) in envs {
instance.env_vars.insert(key.to_string(), val.to_string());
}
instance
}
pub fn with_postgres_backend_url(mut self, url: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_BACKEND_URL".to_string(), url.into());
self
}
pub fn with_postgres_backend_schema(mut self, schema: impl Into<String>) -> Self {
self.env_vars
.insert("POSTGRES_BACKEND_SCHEMA".to_string(), schema.into());
self
}
pub fn with_db_hostname(mut self, hostname: impl Into<String>) -> Self {
self.env_vars
.insert("DB_HOSTNAME".to_string(), hostname.into());
self
}
pub fn with_db_port(mut self, port: u16) -> Self {
self.env_vars
.insert("DB_PORT".to_string(), port.to_string());
self
}
pub fn with_db_username(mut self, username: impl Into<String>) -> Self {
self.env_vars
.insert("DB_USERNAME".to_string(), username.into());
self
}
pub fn with_db_password(mut self, password: impl Into<String>) -> Self {
self.env_vars
.insert("DB_PASSWORD".to_string(), password.into());
self
}
pub fn with_db_database(mut self, database: impl Into<String>) -> Self {
self.env_vars
.insert("DB_DATABASE".to_string(), database.into());
self
}
pub fn with_db_schema(mut self, schema: impl Into<String>) -> Self {
self.env_vars.insert("DB_SCHEMA".to_string(), schema.into());
self
}
pub fn with_public_access_token(mut self, token: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_PUBLIC_ACCESS_TOKEN".to_string(), token.into());
self
}
pub fn with_private_access_token(mut self, token: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_PRIVATE_ACCESS_TOKEN".to_string(), token.into());
self
}
pub fn with_encryption_key(mut self, key: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_DB_ENCRYPTION_KEY".to_string(), key.into());
self
}
pub fn with_node_host(mut self, host: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_NODE_HOST".to_string(), host.into());
self
}
pub fn with_single_tenant(mut self, enabled: bool) -> Self {
self.env_vars
.insert("LOGFLARE_SINGLE_TENANT".to_string(), enabled.to_string());
self
}
pub fn with_supabase_mode(mut self, enabled: bool) -> Self {
self.env_vars
.insert("LOGFLARE_SUPABASE_MODE".to_string(), enabled.to_string());
self
}
pub fn with_feature_flag_override(mut self, flags: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_FEATURE_FLAG_OVERRIDE".to_string(), flags.into());
self
}
pub fn with_log_level(mut self, level: impl Into<String>) -> Self {
self.env_vars
.insert("LOGFLARE_LOG_LEVEL".to_string(), level.into());
self
}
pub fn with_http_port(mut self, port: u16) -> Self {
self.env_vars
.insert("PHX_HTTP_PORT".to_string(), port.to_string());
self
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tag = tag.into();
self
}
pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env_vars.insert(key.into(), value.into());
self
}
}
impl Default for Analytics {
fn default() -> Self {
let mut env_vars = BTreeMap::new();
env_vars.insert("PHX_HTTP_PORT".to_string(), ANALYTICS_PORT.to_string());
env_vars.insert("LOGFLARE_NODE_HOST".to_string(), "127.0.0.1".to_string());
env_vars.insert("LOGFLARE_SINGLE_TENANT".to_string(), "true".to_string());
env_vars.insert("LOGFLARE_SUPABASE_MODE".to_string(), "true".to_string());
env_vars.insert("DB_SCHEMA".to_string(), "_analytics".to_string());
env_vars.insert(
"POSTGRES_BACKEND_SCHEMA".to_string(),
"_analytics".to_string(),
);
env_vars.insert(
"LOGFLARE_FEATURE_FLAG_OVERRIDE".to_string(),
"multibackend=true".to_string(),
);
Self {
env_vars,
tag: TAG.to_string(),
}
}
}
impl Image for Analytics {
fn name(&self) -> &str {
NAME
}
fn tag(&self) -> &str {
&self.tag
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stdout("Starting migration")]
}
fn expose_ports(&self) -> &[ContainerPort] {
&[ContainerPort::Tcp(ANALYTICS_PORT)]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
#[allow(unused_variables)]
fn exec_after_start(
&self,
cs: ContainerState,
) -> Result<Vec<ExecCommand>, TestcontainersError> {
Ok(vec![])
}
}
#[cfg(test)]
#[cfg(feature = "analytics")]
mod tests {
use super::*;
use testcontainers_modules::testcontainers::Image;
#[test]
fn test_default_configuration() {
let analytics = Analytics::default();
assert_eq!(
analytics.env_vars.get("PHX_HTTP_PORT"),
Some(&"4000".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_NODE_HOST"),
Some(&"127.0.0.1".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_SINGLE_TENANT"),
Some(&"true".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_SUPABASE_MODE"),
Some(&"true".to_string())
);
assert_eq!(
analytics.env_vars.get("DB_SCHEMA"),
Some(&"_analytics".to_string())
);
assert_eq!(
analytics.env_vars.get("POSTGRES_BACKEND_SCHEMA"),
Some(&"_analytics".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_FEATURE_FLAG_OVERRIDE"),
Some(&"multibackend=true".to_string())
);
}
#[test]
fn test_name_returns_correct_image() {
let analytics = Analytics::default();
assert_eq!(analytics.name(), "supabase/logflare");
}
#[test]
fn test_tag_returns_correct_version() {
let analytics = Analytics::default();
assert_eq!(analytics.tag(), "1.26.13");
}
#[test]
fn test_analytics_port_constant() {
assert_eq!(ANALYTICS_PORT, 4000);
}
#[test]
fn test_with_postgres_backend_url() {
let analytics = Analytics::default()
.with_postgres_backend_url("postgresql://user:pass@localhost:5432/db");
assert_eq!(
analytics.env_vars.get("POSTGRES_BACKEND_URL"),
Some(&"postgresql://user:pass@localhost:5432/db".to_string())
);
}
#[test]
fn test_with_postgres_backend_schema() {
let analytics = Analytics::default().with_postgres_backend_schema("custom_schema");
assert_eq!(
analytics.env_vars.get("POSTGRES_BACKEND_SCHEMA"),
Some(&"custom_schema".to_string())
);
}
#[test]
fn test_with_db_hostname() {
let analytics = Analytics::default().with_db_hostname("db.example.com");
assert_eq!(
analytics.env_vars.get("DB_HOSTNAME"),
Some(&"db.example.com".to_string())
);
}
#[test]
fn test_with_db_port() {
let analytics = Analytics::default().with_db_port(5433);
assert_eq!(analytics.env_vars.get("DB_PORT"), Some(&"5433".to_string()));
}
#[test]
fn test_with_db_username() {
let analytics = Analytics::default().with_db_username("supabase_admin");
assert_eq!(
analytics.env_vars.get("DB_USERNAME"),
Some(&"supabase_admin".to_string())
);
}
#[test]
fn test_with_db_password() {
let analytics = Analytics::default().with_db_password("secret123");
assert_eq!(
analytics.env_vars.get("DB_PASSWORD"),
Some(&"secret123".to_string())
);
}
#[test]
fn test_with_db_database() {
let analytics = Analytics::default().with_db_database("_supabase");
assert_eq!(
analytics.env_vars.get("DB_DATABASE"),
Some(&"_supabase".to_string())
);
}
#[test]
fn test_with_db_schema() {
let analytics = Analytics::default().with_db_schema("my_schema");
assert_eq!(
analytics.env_vars.get("DB_SCHEMA"),
Some(&"my_schema".to_string())
);
}
#[test]
fn test_with_public_access_token() {
let analytics = Analytics::default().with_public_access_token("public-token-123");
assert_eq!(
analytics.env_vars.get("LOGFLARE_PUBLIC_ACCESS_TOKEN"),
Some(&"public-token-123".to_string())
);
}
#[test]
fn test_with_private_access_token() {
let analytics = Analytics::default().with_private_access_token("private-token-456");
assert_eq!(
analytics.env_vars.get("LOGFLARE_PRIVATE_ACCESS_TOKEN"),
Some(&"private-token-456".to_string())
);
}
#[test]
fn test_with_encryption_key() {
let analytics = Analytics::default().with_encryption_key("base64-encoded-key==");
assert_eq!(
analytics.env_vars.get("LOGFLARE_DB_ENCRYPTION_KEY"),
Some(&"base64-encoded-key==".to_string())
);
}
#[test]
fn test_with_node_host() {
let analytics = Analytics::default().with_node_host("192.168.1.1");
assert_eq!(
analytics.env_vars.get("LOGFLARE_NODE_HOST"),
Some(&"192.168.1.1".to_string())
);
}
#[test]
fn test_with_single_tenant() {
let analytics = Analytics::default().with_single_tenant(false);
assert_eq!(
analytics.env_vars.get("LOGFLARE_SINGLE_TENANT"),
Some(&"false".to_string())
);
}
#[test]
fn test_with_supabase_mode() {
let analytics = Analytics::default().with_supabase_mode(false);
assert_eq!(
analytics.env_vars.get("LOGFLARE_SUPABASE_MODE"),
Some(&"false".to_string())
);
}
#[test]
fn test_with_feature_flag_override() {
let analytics =
Analytics::default().with_feature_flag_override("multibackend=true,feature2=false");
assert_eq!(
analytics.env_vars.get("LOGFLARE_FEATURE_FLAG_OVERRIDE"),
Some(&"multibackend=true,feature2=false".to_string())
);
}
#[test]
fn test_with_log_level() {
let analytics = Analytics::default().with_log_level("warning");
assert_eq!(
analytics.env_vars.get("LOGFLARE_LOG_LEVEL"),
Some(&"warning".to_string())
);
}
#[test]
fn test_with_http_port() {
let analytics = Analytics::default().with_http_port(8080);
assert_eq!(
analytics.env_vars.get("PHX_HTTP_PORT"),
Some(&"8080".to_string())
);
}
#[test]
fn test_with_tag_overrides_default() {
let analytics = Analytics::default().with_tag("1.25.0");
assert_eq!(analytics.tag(), "1.25.0");
}
#[test]
fn test_with_env_adds_custom_variable() {
let analytics = Analytics::default()
.with_env("CUSTOM_VAR", "custom_value")
.with_env("ANOTHER_VAR", "another_value");
assert_eq!(
analytics.env_vars.get("CUSTOM_VAR"),
Some(&"custom_value".to_string())
);
assert_eq!(
analytics.env_vars.get("ANOTHER_VAR"),
Some(&"another_value".to_string())
);
}
#[test]
fn test_builder_method_chaining() {
let analytics = Analytics::default()
.with_postgres_backend_url("postgresql://user:pass@localhost:5432/db")
.with_postgres_backend_schema("_analytics")
.with_public_access_token("public-token")
.with_private_access_token("private-token")
.with_encryption_key("base64key==")
.with_log_level("info")
.with_tag("1.25.0");
assert_eq!(
analytics.env_vars.get("POSTGRES_BACKEND_URL"),
Some(&"postgresql://user:pass@localhost:5432/db".to_string())
);
assert_eq!(
analytics.env_vars.get("POSTGRES_BACKEND_SCHEMA"),
Some(&"_analytics".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_PUBLIC_ACCESS_TOKEN"),
Some(&"public-token".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_PRIVATE_ACCESS_TOKEN"),
Some(&"private-token".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_DB_ENCRYPTION_KEY"),
Some(&"base64key==".to_string())
);
assert_eq!(
analytics.env_vars.get("LOGFLARE_LOG_LEVEL"),
Some(&"info".to_string())
);
assert_eq!(analytics.tag(), "1.25.0");
}
#[test]
fn test_new_creates_default_instance() {
let analytics = Analytics::new();
assert_eq!(analytics.name(), NAME);
assert_eq!(analytics.tag(), TAG);
}
#[test]
fn test_expose_ports() {
let analytics = Analytics::default();
let ports = analytics.expose_ports();
assert_eq!(ports.len(), 1);
assert_eq!(ports[0], ContainerPort::Tcp(4000));
}
#[test]
fn test_ready_conditions() {
let analytics = Analytics::default();
let conditions = analytics.ready_conditions();
assert_eq!(conditions.len(), 1);
}
}