feat(proxy): implement log setting, proxy setting, rewrite rule, server block, and SSL certificate services

- Added LogSettingService with CRUD operations for log settings.
- Introduced ProxySettingService for managing proxy settings with CRUD functionality.
- Created RewriteRuleService to handle rewrite rules with full CRUD capabilities.
- Implemented ServerBlockService for managing server blocks, including relationships with access rules and location blocks.
- Added SslCertificateService for managing SSL certificates with create, read, update, and delete operations.
- Updated types.rs to include new service configurations and ensure proper merging of overrides.
- Enhanced existing tests to cover new functionalities and ensure correctness.
This commit is contained in:
GW_MC
2026-06-21 12:14:26 +00:00
parent 2e758c67fc
commit 7c233b5f77
15 changed files with 1833 additions and 35 deletions

View File

@@ -0,0 +1,155 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{AccessRuleConfig, ProxyServiceError, ProxyServiceResult};
pub struct CreateAccessRuleParams {
pub server_id: Option<Uuid>,
pub location_id: Option<Uuid>,
pub r#type: String,
pub ip_cidr: String,
pub description: Option<String>,
pub priority: i32,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateAccessRuleParams {
pub server_id: Option<Option<Uuid>>,
pub location_id: Option<Option<Uuid>>,
pub r#type: Option<String>,
pub ip_cidr: Option<String>,
pub description: Option<Option<String>>,
pub priority: Option<i32>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait AccessRuleService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<AccessRuleConfig>;
async fn list_by_server(&self, server_id: Uuid) -> ProxyServiceResult<Vec<AccessRuleConfig>>;
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<AccessRuleConfig>>;
async fn create(&self, params: CreateAccessRuleParams) -> ProxyServiceResult<AccessRuleConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateAccessRuleParams,
) -> ProxyServiceResult<AccessRuleConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct AccessRuleServiceImpl {
db: DatabaseConnection,
}
impl AccessRuleServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl AccessRuleService for AccessRuleServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<AccessRuleConfig> {
use crate::db::entities::access_rule;
let model = access_rule::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list_by_server(&self, server_id: Uuid) -> ProxyServiceResult<Vec<AccessRuleConfig>> {
use crate::db::entities::access_rule;
let models = access_rule::Entity::find()
.filter(access_rule::Column::ServerId.eq(server_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<AccessRuleConfig>> {
use crate::db::entities::access_rule;
let models = access_rule::Entity::find()
.filter(access_rule::Column::LocationId.eq(location_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(&self, params: CreateAccessRuleParams) -> ProxyServiceResult<AccessRuleConfig> {
use crate::db::entities::access_rule::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
server_id: Set(params.server_id),
location_id: Set(params.location_id),
r#type: Set(params.r#type),
ip_cidr: Set(params.ip_cidr),
description: Set(params.description),
priority: Set(params.priority),
is_deleted: Set(false),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateAccessRuleParams,
) -> ProxyServiceResult<AccessRuleConfig> {
use crate::db::entities::access_rule::{ActiveModel, Entity as AccessRuleEntity};
let existing = AccessRuleEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(server_id) = params.server_id {
model.server_id = Set(server_id);
}
if let Some(location_id) = params.location_id {
model.location_id = Set(location_id);
}
if let Some(r#type) = params.r#type {
model.r#type = Set(r#type);
}
if let Some(ip_cidr) = params.ip_cidr {
model.ip_cidr = Set(ip_cidr);
}
if let Some(description) = params.description {
model.description = Set(description);
}
if let Some(priority) = params.priority {
model.priority = Set(priority);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::access_rule::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,115 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{CacheZoneConfig, ProxyServiceError, ProxyServiceResult};
pub struct CreateCacheZoneParams {
pub name: String,
pub path: String,
pub size_limit: String,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateCacheZoneParams {
pub name: Option<String>,
pub path: Option<String>,
pub size_limit: Option<String>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait CacheZoneService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<CacheZoneConfig>;
async fn list(&self) -> ProxyServiceResult<Vec<CacheZoneConfig>>;
async fn create(&self, params: CreateCacheZoneParams) -> ProxyServiceResult<CacheZoneConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateCacheZoneParams,
) -> ProxyServiceResult<CacheZoneConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct CacheZoneServiceImpl {
db: DatabaseConnection,
}
impl CacheZoneServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl CacheZoneService for CacheZoneServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<CacheZoneConfig> {
use crate::db::entities::cache_zone;
let model = cache_zone::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list(&self) -> ProxyServiceResult<Vec<CacheZoneConfig>> {
use crate::db::entities::cache_zone;
let models = cache_zone::Entity::find().all(&self.db).await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(&self, params: CreateCacheZoneParams) -> ProxyServiceResult<CacheZoneConfig> {
use crate::db::entities::cache_zone::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
name: Set(params.name),
path: Set(params.path),
size_limit: Set(params.size_limit),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateCacheZoneParams,
) -> ProxyServiceResult<CacheZoneConfig> {
use crate::db::entities::cache_zone::{ActiveModel, Entity as CacheZoneEntity};
let existing = CacheZoneEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(name) = params.name {
model.name = Set(name);
}
if let Some(path) = params.path {
model.path = Set(path);
}
if let Some(size_limit) = params.size_limit {
model.size_limit = Set(size_limit);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::cache_zone::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,143 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::ProxyServiceResult;
pub struct AddInheritanceParams {
pub child_config_id: Uuid,
pub parent_config_id: Uuid,
pub priority: Option<i32>,
}
pub struct ConfigInheritanceRecord {
pub id: Uuid,
pub child_config_id: Uuid,
pub parent_config_id: Uuid,
pub priority: Option<i32>,
pub applied_at: chrono::NaiveDateTime,
}
#[async_trait::async_trait]
pub trait ConfigInheritanceService: Send + Sync + 'static {
async fn add(
&self,
params: AddInheritanceParams,
) -> ProxyServiceResult<ConfigInheritanceRecord>;
async fn remove(
&self,
child_config_id: Uuid,
parent_config_id: Uuid,
) -> ProxyServiceResult<bool>;
async fn list_parents(
&self,
child_config_id: Uuid,
) -> ProxyServiceResult<Vec<ConfigInheritanceRecord>>;
async fn list_children(
&self,
parent_config_id: Uuid,
) -> ProxyServiceResult<Vec<ConfigInheritanceRecord>>;
}
pub(crate) struct ConfigInheritanceServiceImpl {
db: DatabaseConnection,
}
impl ConfigInheritanceServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl ConfigInheritanceService for ConfigInheritanceServiceImpl {
async fn add(
&self,
params: AddInheritanceParams,
) -> ProxyServiceResult<ConfigInheritanceRecord> {
use crate::db::entities::config_inheritance::ActiveModel;
let now = chrono::Utc::now().naive_utc();
let model = ActiveModel {
id: Set(Uuid::new_v4()),
child_config_id: Set(params.child_config_id),
parent_config_id: Set(params.parent_config_id),
priority: Set(params.priority),
applied_at: Set(now),
};
let result = model.insert(&self.db).await?;
Ok(ConfigInheritanceRecord {
id: result.id,
child_config_id: result.child_config_id,
parent_config_id: result.parent_config_id,
priority: result.priority,
applied_at: result.applied_at,
})
}
async fn remove(
&self,
child_config_id: Uuid,
parent_config_id: Uuid,
) -> ProxyServiceResult<bool> {
use crate::db::entities::config_inheritance::{Column, Entity as ConfigInheritanceEntity};
use sea_orm::Condition;
let result = ConfigInheritanceEntity::delete_many()
.filter(
Condition::all()
.add(Column::ChildConfigId.eq(child_config_id))
.add(Column::ParentConfigId.eq(parent_config_id)),
)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
async fn list_parents(
&self,
child_config_id: Uuid,
) -> ProxyServiceResult<Vec<ConfigInheritanceRecord>> {
use crate::db::entities::config_inheritance;
let results = config_inheritance::Entity::find()
.filter(config_inheritance::Column::ChildConfigId.eq(child_config_id))
.all(&self.db)
.await?;
Ok(results
.into_iter()
.map(|m| ConfigInheritanceRecord {
id: m.id,
child_config_id: m.child_config_id,
parent_config_id: m.parent_config_id,
priority: m.priority,
applied_at: m.applied_at,
})
.collect())
}
async fn list_children(
&self,
parent_config_id: Uuid,
) -> ProxyServiceResult<Vec<ConfigInheritanceRecord>> {
use crate::db::entities::config_inheritance;
let results = config_inheritance::Entity::find()
.filter(config_inheritance::Column::ParentConfigId.eq(parent_config_id))
.all(&self.db)
.await?;
Ok(results
.into_iter()
.map(|m| ConfigInheritanceRecord {
id: m.id,
child_config_id: m.child_config_id,
parent_config_id: m.parent_config_id,
priority: m.priority,
applied_at: m.applied_at,
})
.collect())
}
}

View File

@@ -0,0 +1,129 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{LimitRuleConfig, ProxyServiceError, ProxyServiceResult};
pub struct CreateLimitRuleParams {
pub location_id: Uuid,
pub zone_id: Uuid,
pub burst: Option<i32>,
pub nodelay: Option<bool>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateLimitRuleParams {
pub location_id: Option<Uuid>,
pub zone_id: Option<Uuid>,
pub burst: Option<Option<i32>>,
pub nodelay: Option<Option<bool>>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait LimitRuleService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LimitRuleConfig>;
async fn list_by_location(&self, location_id: Uuid)
-> ProxyServiceResult<Vec<LimitRuleConfig>>;
async fn create(&self, params: CreateLimitRuleParams) -> ProxyServiceResult<LimitRuleConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateLimitRuleParams,
) -> ProxyServiceResult<LimitRuleConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct LimitRuleServiceImpl {
db: DatabaseConnection,
}
impl LimitRuleServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl LimitRuleService for LimitRuleServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LimitRuleConfig> {
use crate::db::entities::limit_rule;
let model = limit_rule::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<LimitRuleConfig>> {
use crate::db::entities::limit_rule;
let models = limit_rule::Entity::find()
.filter(limit_rule::Column::LocationId.eq(location_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(&self, params: CreateLimitRuleParams) -> ProxyServiceResult<LimitRuleConfig> {
use crate::db::entities::limit_rule::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
location_id: Set(params.location_id),
zone_id: Set(params.zone_id),
burst: Set(params.burst),
nodelay: Set(params.nodelay),
is_deleted: Set(false),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateLimitRuleParams,
) -> ProxyServiceResult<LimitRuleConfig> {
use crate::db::entities::limit_rule::{ActiveModel, Entity as LimitRuleEntity};
let existing = LimitRuleEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(location_id) = params.location_id {
model.location_id = Set(location_id);
}
if let Some(zone_id) = params.zone_id {
model.zone_id = Set(zone_id);
}
if let Some(burst) = params.burst {
model.burst = Set(burst);
}
if let Some(nodelay) = params.nodelay {
model.nodelay = Set(nodelay);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::limit_rule::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,115 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{LimitZoneConfig, ProxyServiceError, ProxyServiceResult};
pub struct CreateLimitZoneParams {
pub name: String,
pub key: String,
pub rate: String,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateLimitZoneParams {
pub name: Option<String>,
pub key: Option<String>,
pub rate: Option<String>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait LimitZoneService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LimitZoneConfig>;
async fn list(&self) -> ProxyServiceResult<Vec<LimitZoneConfig>>;
async fn create(&self, params: CreateLimitZoneParams) -> ProxyServiceResult<LimitZoneConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateLimitZoneParams,
) -> ProxyServiceResult<LimitZoneConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct LimitZoneServiceImpl {
db: DatabaseConnection,
}
impl LimitZoneServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl LimitZoneService for LimitZoneServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LimitZoneConfig> {
use crate::db::entities::limit_zone;
let model = limit_zone::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list(&self) -> ProxyServiceResult<Vec<LimitZoneConfig>> {
use crate::db::entities::limit_zone;
let models = limit_zone::Entity::find().all(&self.db).await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(&self, params: CreateLimitZoneParams) -> ProxyServiceResult<LimitZoneConfig> {
use crate::db::entities::limit_zone::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
name: Set(params.name),
key: Set(params.key),
rate: Set(params.rate),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateLimitZoneParams,
) -> ProxyServiceResult<LimitZoneConfig> {
use crate::db::entities::limit_zone::{ActiveModel, Entity as LimitZoneEntity};
let existing = LimitZoneEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(name) = params.name {
model.name = Set(name);
}
if let Some(key) = params.key {
model.key = Set(key);
}
if let Some(rate) = params.rate {
model.rate = Set(rate);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::limit_zone::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,205 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{
LocationBlockConfig, OverrideRef, ProxyServiceError, ProxyServiceResult,
};
pub struct CreateLocationBlockParams {
pub server_id: Uuid,
pub path_pattern: String,
pub proxy_pass_upstream_id: Option<Uuid>,
pub metadata: Option<serde_json::Value>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateLocationBlockParams {
pub server_id: Option<Uuid>,
pub path_pattern: Option<String>,
pub proxy_pass_upstream_id: Option<Option<Uuid>>,
pub metadata: Option<Option<serde_json::Value>>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait LocationBlockService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LocationBlockConfig>;
async fn list_by_server(&self, server_id: Uuid)
-> ProxyServiceResult<Vec<LocationBlockConfig>>;
async fn create(
&self,
params: CreateLocationBlockParams,
) -> ProxyServiceResult<LocationBlockConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateLocationBlockParams,
) -> ProxyServiceResult<LocationBlockConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct LocationBlockServiceImpl {
db: DatabaseConnection,
}
impl LocationBlockServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
async fn build_with_children(
&self,
model: crate::db::entities::location_block::Model,
) -> ProxyServiceResult<LocationBlockConfig> {
use crate::db::entities::{access_rule, limit_rule, proxy_setting, rewrite_rule};
let access_rules = access_rule::Entity::find()
.filter(access_rule::Column::LocationId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|a| OverrideRef {
id: a.id,
override_of_id: a.override_of_id,
})
.collect();
let limit_rules = limit_rule::Entity::find()
.filter(limit_rule::Column::LocationId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|l| OverrideRef {
id: l.id,
override_of_id: l.override_of_id,
})
.collect();
let proxy_settings = proxy_setting::Entity::find()
.filter(proxy_setting::Column::LocationId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|p| OverrideRef {
id: p.id,
override_of_id: p.override_of_id,
})
.collect();
let rewrite_rules = rewrite_rule::Entity::find()
.filter(rewrite_rule::Column::LocationId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|r| OverrideRef {
id: r.id,
override_of_id: r.override_of_id,
})
.collect();
Ok(LocationBlockConfig {
id: model.id,
server_id: model.server_id,
path_pattern: model.path_pattern,
proxy_pass_upstream_id: model.proxy_pass_upstream_id,
metadata: model.metadata,
override_of_id: model.override_of_id,
access_rules,
limit_rules,
proxy_settings,
rewrite_rules,
})
}
}
#[async_trait::async_trait]
impl LocationBlockService for LocationBlockServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LocationBlockConfig> {
use crate::db::entities::location_block;
let model = location_block::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
self.build_with_children(model).await
}
async fn list_by_server(
&self,
server_id: Uuid,
) -> ProxyServiceResult<Vec<LocationBlockConfig>> {
use crate::db::entities::location_block;
let models = location_block::Entity::find()
.filter(location_block::Column::ServerId.eq(server_id))
.all(&self.db)
.await?;
let mut results = Vec::with_capacity(models.len());
for m in models {
results.push(self.build_with_children(m).await?);
}
Ok(results)
}
async fn create(
&self,
params: CreateLocationBlockParams,
) -> ProxyServiceResult<LocationBlockConfig> {
use crate::db::entities::location_block::ActiveModel;
let id = Uuid::new_v4();
let model = ActiveModel {
id: Set(id),
server_id: Set(params.server_id),
path_pattern: Set(params.path_pattern),
proxy_pass_upstream_id: Set(params.proxy_pass_upstream_id),
metadata: Set(params.metadata),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
self.build_with_children(result).await
}
async fn update(
&self,
id: Uuid,
params: UpdateLocationBlockParams,
) -> ProxyServiceResult<LocationBlockConfig> {
use crate::db::entities::location_block::{ActiveModel, Entity as LocationBlockEntity};
let existing = LocationBlockEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(server_id) = params.server_id {
model.server_id = Set(server_id);
}
if let Some(path_pattern) = params.path_pattern {
model.path_pattern = Set(path_pattern);
}
if let Some(proxy_pass_upstream_id) = params.proxy_pass_upstream_id {
model.proxy_pass_upstream_id = Set(proxy_pass_upstream_id);
}
if let Some(metadata) = params.metadata {
model.metadata = Set(metadata);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
self.build_with_children(result).await
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::location_block::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,124 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{LogSettingConfig, ProxyServiceError, ProxyServiceResult};
pub struct CreateLogSettingParams {
pub server_id: Uuid,
pub access_log_path: Option<String>,
pub error_log_path: Option<String>,
pub log_level: Option<String>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateLogSettingParams {
pub server_id: Option<Uuid>,
pub access_log_path: Option<Option<String>>,
pub error_log_path: Option<Option<String>>,
pub log_level: Option<Option<String>>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait LogSettingService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LogSettingConfig>;
async fn list_by_server(&self, server_id: Uuid) -> ProxyServiceResult<Vec<LogSettingConfig>>;
async fn create(&self, params: CreateLogSettingParams) -> ProxyServiceResult<LogSettingConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateLogSettingParams,
) -> ProxyServiceResult<LogSettingConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct LogSettingServiceImpl {
db: DatabaseConnection,
}
impl LogSettingServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl LogSettingService for LogSettingServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<LogSettingConfig> {
use crate::db::entities::log_setting;
let model = log_setting::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list_by_server(&self, server_id: Uuid) -> ProxyServiceResult<Vec<LogSettingConfig>> {
use crate::db::entities::log_setting;
let models = log_setting::Entity::find()
.filter(log_setting::Column::ServerId.eq(server_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(&self, params: CreateLogSettingParams) -> ProxyServiceResult<LogSettingConfig> {
use crate::db::entities::log_setting::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
server_id: Set(params.server_id),
access_log_path: Set(params.access_log_path),
error_log_path: Set(params.error_log_path),
log_level: Set(params.log_level),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateLogSettingParams,
) -> ProxyServiceResult<LogSettingConfig> {
use crate::db::entities::log_setting::{ActiveModel, Entity as LogSettingEntity};
let existing = LogSettingEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(server_id) = params.server_id {
model.server_id = Set(server_id);
}
if let Some(access_log_path) = params.access_log_path {
model.access_log_path = Set(access_log_path);
}
if let Some(error_log_path) = params.error_log_path {
model.error_log_path = Set(error_log_path);
}
if let Some(log_level) = params.log_level {
model.log_level = Set(log_level);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::log_setting::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -1,10 +1,22 @@
use crate::service::proxy::types::{ use crate::service::proxy::types::{
AgentConfigBinding, CreateProxyConfigParams, ProxyConfig, ProxyConfigSummary, AgentConfigBinding, CreateProxyConfigParams, ProxyConfig, ProxyConfigSummary,
ProxyServiceResult, ProxyType, UpdateProxyConfigParams, ProxyServiceResult, UpdateProxyConfigParams,
}; };
pub(crate) mod access_rule;
pub(crate) mod cache_zone;
pub(crate) mod config_inheritance;
pub(crate) mod limit_rule;
pub(crate) mod limit_zone;
pub(crate) mod location_block;
pub(crate) mod log_setting;
pub(crate) mod nginx; pub(crate) mod nginx;
pub(crate) mod proxy_setting;
pub(crate) mod repo; pub(crate) mod repo;
pub(crate) mod rewrite_rule;
pub(crate) mod server_block;
pub(crate) mod ssl_certificate;
pub(crate) mod upstream;
pub mod service; pub mod service;
pub mod types; pub mod types;

View File

@@ -187,6 +187,7 @@ impl ProxyConfigRenderer for NginxConfigRenderer {
} }
#[cfg(test)] #[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests { mod tests {
use super::*; use super::*;
use crate::service::proxy::types::*; use crate::service::proxy::types::*;
@@ -271,13 +272,31 @@ mod tests {
let renderer = NginxConfigRenderer; let renderer = NginxConfigRenderer;
let output = renderer.render(&config); let output = renderer.render(&config);
assert!(output.contains("upstream backend {"), "should contain upstream block"); assert!(
assert!(output.contains("server 127.0.0.1:3000;"), "should contain upstream server"); output.contains("upstream backend {"),
"should contain upstream block"
);
assert!(
output.contains("server 127.0.0.1:3000;"),
"should contain upstream server"
);
assert!(output.contains("server {"), "should contain server block"); assert!(output.contains("server {"), "should contain server block");
assert!(output.contains("listen 80;"), "should contain listen directive"); assert!(
assert!(output.contains("server_name example.com;"), "should contain server name"); output.contains("listen 80;"),
assert!(output.contains("location /api {"), "should contain location block"); "should contain listen directive"
assert!(output.contains("proxy_pass http://backend;"), "should contain proxy pass"); );
assert!(
output.contains("server_name example.com;"),
"should contain server name"
);
assert!(
output.contains("location /api {"),
"should contain location block"
);
assert!(
output.contains("proxy_pass http://backend;"),
"should contain proxy pass"
);
} }
#[test] #[test]
@@ -296,7 +315,9 @@ mod tests {
); );
let renderer = NginxConfigRenderer; let renderer = NginxConfigRenderer;
let output = renderer.render(&config); let output = renderer.render(&config);
assert!(output.contains("proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m;")); assert!(
output.contains("proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m;")
);
} }
#[test] #[test]

View File

@@ -0,0 +1,148 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{ProxyServiceError, ProxyServiceResult, ProxySettingConfig};
pub struct CreateProxySettingParams {
pub location_id: Uuid,
pub read_timeout: Option<i32>,
pub connect_timeout: Option<i32>,
pub buffer_size: Option<i32>,
pub cache_enabled: Option<bool>,
pub cache_zone: Option<Uuid>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateProxySettingParams {
pub location_id: Option<Uuid>,
pub read_timeout: Option<Option<i32>>,
pub connect_timeout: Option<Option<i32>>,
pub buffer_size: Option<Option<i32>>,
pub cache_enabled: Option<Option<bool>>,
pub cache_zone: Option<Option<Uuid>>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait ProxySettingService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<ProxySettingConfig>;
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<ProxySettingConfig>>;
async fn create(
&self,
params: CreateProxySettingParams,
) -> ProxyServiceResult<ProxySettingConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateProxySettingParams,
) -> ProxyServiceResult<ProxySettingConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct ProxySettingServiceImpl {
db: DatabaseConnection,
}
impl ProxySettingServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl ProxySettingService for ProxySettingServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<ProxySettingConfig> {
use crate::db::entities::proxy_setting;
let model = proxy_setting::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<ProxySettingConfig>> {
use crate::db::entities::proxy_setting;
let models = proxy_setting::Entity::find()
.filter(proxy_setting::Column::LocationId.eq(location_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(
&self,
params: CreateProxySettingParams,
) -> ProxyServiceResult<ProxySettingConfig> {
use crate::db::entities::proxy_setting::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
location_id: Set(params.location_id),
read_timeout: Set(params.read_timeout),
connect_timeout: Set(params.connect_timeout),
buffer_size: Set(params.buffer_size),
cache_enabled: Set(params.cache_enabled),
cache_zone: Set(params.cache_zone),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateProxySettingParams,
) -> ProxyServiceResult<ProxySettingConfig> {
use crate::db::entities::proxy_setting::{ActiveModel, Entity as ProxySettingEntity};
let existing = ProxySettingEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(location_id) = params.location_id {
model.location_id = Set(location_id);
}
if let Some(read_timeout) = params.read_timeout {
model.read_timeout = Set(read_timeout);
}
if let Some(connect_timeout) = params.connect_timeout {
model.connect_timeout = Set(connect_timeout);
}
if let Some(buffer_size) = params.buffer_size {
model.buffer_size = Set(buffer_size);
}
if let Some(cache_enabled) = params.cache_enabled {
model.cache_enabled = Set(cache_enabled);
}
if let Some(cache_zone) = params.cache_zone {
model.cache_zone = Set(cache_zone);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::proxy_setting::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,143 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{ProxyServiceError, ProxyServiceResult, RewriteRuleConfig};
pub struct CreateRewriteRuleParams {
pub location_id: Uuid,
pub pattern: String,
pub replacement: String,
pub flag: Option<String>,
pub priority: i32,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateRewriteRuleParams {
pub location_id: Option<Uuid>,
pub pattern: Option<String>,
pub replacement: Option<String>,
pub flag: Option<Option<String>>,
pub priority: Option<i32>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait RewriteRuleService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<RewriteRuleConfig>;
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<RewriteRuleConfig>>;
async fn create(
&self,
params: CreateRewriteRuleParams,
) -> ProxyServiceResult<RewriteRuleConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateRewriteRuleParams,
) -> ProxyServiceResult<RewriteRuleConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct RewriteRuleServiceImpl {
db: DatabaseConnection,
}
impl RewriteRuleServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl RewriteRuleService for RewriteRuleServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<RewriteRuleConfig> {
use crate::db::entities::rewrite_rule;
let model = rewrite_rule::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list_by_location(
&self,
location_id: Uuid,
) -> ProxyServiceResult<Vec<RewriteRuleConfig>> {
use crate::db::entities::rewrite_rule;
let models = rewrite_rule::Entity::find()
.filter(rewrite_rule::Column::LocationId.eq(location_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(
&self,
params: CreateRewriteRuleParams,
) -> ProxyServiceResult<RewriteRuleConfig> {
use crate::db::entities::rewrite_rule::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
location_id: Set(params.location_id),
pattern: Set(params.pattern),
replacement: Set(params.replacement),
flag: Set(params.flag),
priority: Set(params.priority),
is_deleted: Set(false),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateRewriteRuleParams,
) -> ProxyServiceResult<RewriteRuleConfig> {
use crate::db::entities::rewrite_rule::{ActiveModel, Entity as RewriteRuleEntity};
let existing = RewriteRuleEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(location_id) = params.location_id {
model.location_id = Set(location_id);
}
if let Some(pattern) = params.pattern {
model.pattern = Set(pattern);
}
if let Some(replacement) = params.replacement {
model.replacement = Set(replacement);
}
if let Some(flag) = params.flag {
model.flag = Set(flag);
}
if let Some(priority) = params.priority {
model.priority = Set(priority);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::rewrite_rule::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,201 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{
OverrideRef, ProxyServiceError, ProxyServiceResult, ServerBlockConfig,
};
pub struct CreateServerBlockParams {
pub config_id: Uuid,
pub server_name: Option<Vec<String>>,
pub listen_port: i32,
pub ssl_enabled: Option<bool>,
pub ssl_cert_id: Option<Uuid>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateServerBlockParams {
pub server_name: Option<Option<Vec<String>>>,
pub listen_port: Option<i32>,
pub ssl_enabled: Option<Option<bool>>,
pub ssl_cert_id: Option<Option<Uuid>>,
pub override_of_id: Option<Option<Uuid>>,
}
#[async_trait::async_trait]
pub trait ServerBlockService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<ServerBlockConfig>;
async fn list_by_config(&self, config_id: Uuid) -> ProxyServiceResult<Vec<ServerBlockConfig>>;
async fn create(
&self,
params: CreateServerBlockParams,
) -> ProxyServiceResult<ServerBlockConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateServerBlockParams,
) -> ProxyServiceResult<ServerBlockConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct ServerBlockServiceImpl {
db: DatabaseConnection,
}
impl ServerBlockServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
async fn build_with_children(
&self,
model: crate::db::entities::server_block::Model,
) -> ProxyServiceResult<ServerBlockConfig> {
use crate::db::entities::{access_rule, location_block, log_setting};
let access_rules = access_rule::Entity::find()
.filter(access_rule::Column::ServerId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|a| OverrideRef {
id: a.id,
override_of_id: a.override_of_id,
})
.collect();
let location_blocks = location_block::Entity::find()
.filter(location_block::Column::ServerId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|l| OverrideRef {
id: l.id,
override_of_id: l.override_of_id,
})
.collect();
let log_settings = log_setting::Entity::find()
.filter(log_setting::Column::ServerId.eq(model.id))
.all(&self.db)
.await?
.into_iter()
.map(|l| OverrideRef {
id: l.id,
override_of_id: l.override_of_id,
})
.collect();
let ssl_certificates = model
.ssl_cert_id
.map(|id| {
vec![OverrideRef {
id,
override_of_id: None,
}]
})
.unwrap_or_default();
Ok(ServerBlockConfig {
id: model.id,
server_name: model.server_name,
listen_port: model.listen_port,
ssl_enabled: model.ssl_enabled,
override_of_id: model.override_of_id,
access_rules,
location_blocks,
log_settings,
ssl_certificates,
})
}
}
#[async_trait::async_trait]
impl ServerBlockService for ServerBlockServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<ServerBlockConfig> {
use crate::db::entities::server_block;
let model = server_block::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
self.build_with_children(model).await
}
async fn list_by_config(&self, config_id: Uuid) -> ProxyServiceResult<Vec<ServerBlockConfig>> {
use crate::db::entities::server_block;
let models = server_block::Entity::find()
.filter(server_block::Column::ConfigId.eq(config_id))
.all(&self.db)
.await?;
let mut results = Vec::with_capacity(models.len());
for m in models {
results.push(self.build_with_children(m).await?);
}
Ok(results)
}
async fn create(
&self,
params: CreateServerBlockParams,
) -> ProxyServiceResult<ServerBlockConfig> {
use crate::db::entities::server_block::ActiveModel;
let id = Uuid::new_v4();
let model = ActiveModel {
id: Set(id),
config_id: Set(params.config_id),
server_name: Set(params.server_name),
listen_port: Set(params.listen_port),
ssl_enabled: Set(params.ssl_enabled),
ssl_cert_id: Set(params.ssl_cert_id),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
self.build_with_children(result).await
}
async fn update(
&self,
id: Uuid,
params: UpdateServerBlockParams,
) -> ProxyServiceResult<ServerBlockConfig> {
use crate::db::entities::server_block::{ActiveModel, Entity as ServerBlockEntity};
let existing = ServerBlockEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(server_name) = params.server_name {
model.server_name = Set(server_name);
}
if let Some(listen_port) = params.listen_port {
model.listen_port = Set(listen_port);
}
if let Some(ssl_enabled) = params.ssl_enabled {
model.ssl_enabled = Set(ssl_enabled);
}
if let Some(ssl_cert_id) = params.ssl_cert_id {
model.ssl_cert_id = Set(ssl_cert_id);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
self.build_with_children(result).await
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::server_block::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -0,0 +1,122 @@
use chrono::Utc;
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{ProxyServiceError, ProxyServiceResult, SslCertificateConfig};
pub struct CreateSslCertificateParams {
pub name: String,
pub cert_path: String,
pub key_path: String,
pub expiry_date: chrono::DateTime<Utc>,
}
pub struct UpdateSslCertificateParams {
pub name: Option<String>,
pub cert_path: Option<String>,
pub key_path: Option<String>,
pub expiry_date: Option<chrono::DateTime<Utc>>,
}
#[async_trait::async_trait]
pub trait SslCertificateService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<SslCertificateConfig>;
async fn list(&self) -> ProxyServiceResult<Vec<SslCertificateConfig>>;
async fn create(
&self,
params: CreateSslCertificateParams,
) -> ProxyServiceResult<SslCertificateConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateSslCertificateParams,
) -> ProxyServiceResult<SslCertificateConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct SslCertificateServiceImpl {
db: DatabaseConnection,
}
impl SslCertificateServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl SslCertificateService for SslCertificateServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<SslCertificateConfig> {
use crate::db::entities::ssl_certificate;
let model = ssl_certificate::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model.into())
}
async fn list(&self) -> ProxyServiceResult<Vec<SslCertificateConfig>> {
use crate::db::entities::ssl_certificate;
let models = ssl_certificate::Entity::find().all(&self.db).await?;
Ok(models.into_iter().map(Into::into).collect())
}
async fn create(
&self,
params: CreateSslCertificateParams,
) -> ProxyServiceResult<SslCertificateConfig> {
use crate::db::entities::ssl_certificate::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
name: Set(params.name),
cert_path: Set(params.cert_path),
key_path: Set(params.key_path),
expiry_date: Set(params.expiry_date.naive_utc()),
};
let result = model.insert(&self.db).await?;
Ok(result.into())
}
async fn update(
&self,
id: Uuid,
params: UpdateSslCertificateParams,
) -> ProxyServiceResult<SslCertificateConfig> {
use crate::db::entities::ssl_certificate::{ActiveModel, Entity as SslCertificateEntity};
let existing = SslCertificateEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(name) = params.name {
model.name = Set(name);
}
if let Some(cert_path) = params.cert_path {
model.cert_path = Set(cert_path);
}
if let Some(key_path) = params.key_path {
model.key_path = Set(key_path);
}
if let Some(expiry_date) = params.expiry_date {
model.expiry_date = Set(expiry_date.naive_utc());
}
let result = model.update(&self.db).await?;
Ok(result.into())
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::ssl_certificate::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}

View File

@@ -34,10 +34,8 @@ pub fn merge_override_vecs(
mut child: Vec<OverrideRef>, mut child: Vec<OverrideRef>,
parent: Vec<OverrideRef>, parent: Vec<OverrideRef>,
) -> Vec<OverrideRef> { ) -> Vec<OverrideRef> {
let overridden_ids: std::collections::HashSet<uuid::Uuid> = child let overridden_ids: std::collections::HashSet<uuid::Uuid> =
.iter() child.iter().filter_map(|r| r.override_of_id).collect();
.filter_map(|r| r.override_of_id)
.collect();
for item in parent { for item in parent {
if !overridden_ids.contains(&item.id) { if !overridden_ids.contains(&item.id) {
child.push(item); child.push(item);
@@ -76,7 +74,8 @@ impl Mergeable<ProxyConfig> for ProxyConfig {
macro_rules! merge_overridable_field { macro_rules! merge_overridable_field {
($field:ident) => { ($field:ident) => {
let overridden: HashSet<uuid::Uuid> = self.$field let overridden: HashSet<uuid::Uuid> = self
.$field
.values() .values()
.filter_map(|v| v.override_of_id()) .filter_map(|v| v.override_of_id())
.collect(); .collect();
@@ -90,7 +89,8 @@ impl Mergeable<ProxyConfig> for ProxyConfig {
// server_blocks: merge matching entries (field-level), handle overrides // server_blocks: merge matching entries (field-level), handle overrides
{ {
let overridden: HashSet<uuid::Uuid> = self.server_blocks let overridden: HashSet<uuid::Uuid> = self
.server_blocks
.values() .values()
.filter_map(|sb| sb.override_of_id) .filter_map(|sb| sb.override_of_id)
.collect(); .collect();
@@ -179,18 +179,14 @@ impl Mergeable<ServerBlockConfig> for ServerBlockConfig {
self.override_of_id = other.override_of_id; self.override_of_id = other.override_of_id;
} }
self.access_rules = merge_override_vecs( self.access_rules =
std::mem::take(&mut self.access_rules), merge_override_vecs(std::mem::take(&mut self.access_rules), other.access_rules);
other.access_rules,
);
self.location_blocks = merge_override_vecs( self.location_blocks = merge_override_vecs(
std::mem::take(&mut self.location_blocks), std::mem::take(&mut self.location_blocks),
other.location_blocks, other.location_blocks,
); );
self.log_settings = merge_override_vecs( self.log_settings =
std::mem::take(&mut self.log_settings), merge_override_vecs(std::mem::take(&mut self.log_settings), other.log_settings);
other.log_settings,
);
self.ssl_certificates = merge_override_vecs( self.ssl_certificates = merge_override_vecs(
std::mem::take(&mut self.ssl_certificates), std::mem::take(&mut self.ssl_certificates),
other.ssl_certificates, other.ssl_certificates,
@@ -452,34 +448,54 @@ impl From<crate::db::entities::rewrite_rule::Model> for RewriteRuleConfig {
// ── Overridable implementations ── // ── Overridable implementations ──
impl Overridable for ServerBlockConfig { impl Overridable for ServerBlockConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for UpstreamConfig { impl Overridable for UpstreamConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for AccessRuleConfig { impl Overridable for AccessRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for CacheZoneConfig { impl Overridable for CacheZoneConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for LimitRuleConfig { impl Overridable for LimitRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for LimitZoneConfig { impl Overridable for LimitZoneConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for LocationBlockConfig { impl Overridable for LocationBlockConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for LogSettingConfig { impl Overridable for LogSettingConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for ProxySettingConfig { impl Overridable for ProxySettingConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
impl Overridable for RewriteRuleConfig { impl Overridable for RewriteRuleConfig {
fn override_of_id(&self) -> Option<uuid::Uuid> { self.override_of_id } fn override_of_id(&self) -> Option<uuid::Uuid> {
self.override_of_id
}
} }
// ── CRUD types ── // ── CRUD types ──
@@ -623,7 +639,10 @@ mod tests {
}; };
child.merge(parent); child.merge(parent);
// child keeps its own values (self overrides other) // child keeps its own values (self overrides other)
assert_eq!(child.server_name, Some(vec!["child.example.com".to_string()])); assert_eq!(
child.server_name,
Some(vec!["child.example.com".to_string()])
);
assert_eq!(child.listen_port, 443); assert_eq!(child.listen_port, 443);
assert_eq!(child.ssl_enabled, Some(true)); assert_eq!(child.ssl_enabled, Some(true));
} }
@@ -655,7 +674,10 @@ mod tests {
}; };
child.merge(parent); child.merge(parent);
// child fills missing optional fields from parent // child fills missing optional fields from parent
assert_eq!(child.server_name, Some(vec!["parent.example.com".to_string()])); assert_eq!(
child.server_name,
Some(vec!["parent.example.com".to_string()])
);
assert_eq!(child.listen_port, 443); // non-optional: child keeps its own assert_eq!(child.listen_port, 443); // non-optional: child keeps its own
assert_eq!(child.ssl_enabled, Some(false)); assert_eq!(child.ssl_enabled, Some(false));
} }
@@ -725,7 +747,10 @@ mod tests {
child_proxy.merge(parent_proxy); child_proxy.merge(parent_proxy);
assert_eq!(child_proxy.server_blocks.len(), 1); assert_eq!(child_proxy.server_blocks.len(), 1);
let merged_sb = &child_proxy.server_blocks[&sb_id]; let merged_sb = &child_proxy.server_blocks[&sb_id];
assert_eq!(merged_sb.server_name, Some(vec!["child.example.com".to_string()])); assert_eq!(
merged_sb.server_name,
Some(vec!["child.example.com".to_string()])
);
assert_eq!(merged_sb.listen_port, 443); assert_eq!(merged_sb.listen_port, 443);
assert_eq!(merged_sb.ssl_enabled, Some(true)); assert_eq!(merged_sb.ssl_enabled, Some(true));
} }

View File

@@ -0,0 +1,140 @@
use sea_orm::{ActiveModelTrait, ActiveValue::Set, DatabaseConnection, EntityTrait, prelude::*};
use uuid::Uuid;
use crate::service::proxy::types::{
OverrideRef, ProxyServiceError, ProxyServiceResult, UpstreamConfig,
};
pub struct CreateUpstreamParams {
pub config_id: Uuid,
pub name: String,
pub target_host: String,
pub target_port: i32,
pub metadata: Option<serde_json::Value>,
pub override_of_id: Option<Uuid>,
}
pub struct UpdateUpstreamParams {
pub name: Option<String>,
pub target_host: Option<String>,
pub target_port: Option<i32>,
pub metadata: Option<Option<serde_json::Value>>,
pub override_of_id: Option<Option<Uuid>>,
}
fn model_to_config(model: crate::db::entities::upstream::Model) -> UpstreamConfig {
UpstreamConfig {
id: model.id,
name: model.name,
target_host: model.target_host,
target_port: model.target_port,
metadata: model.metadata,
override_of_id: model.override_of_id,
location_blocks: vec![],
}
}
#[async_trait::async_trait]
pub trait UpstreamService: Send + Sync + 'static {
async fn get(&self, id: Uuid) -> ProxyServiceResult<UpstreamConfig>;
async fn list_by_config(&self, config_id: Uuid) -> ProxyServiceResult<Vec<UpstreamConfig>>;
async fn create(&self, params: CreateUpstreamParams) -> ProxyServiceResult<UpstreamConfig>;
async fn update(
&self,
id: Uuid,
params: UpdateUpstreamParams,
) -> ProxyServiceResult<UpstreamConfig>;
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool>;
}
pub(crate) struct UpstreamServiceImpl {
db: DatabaseConnection,
}
impl UpstreamServiceImpl {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
}
#[async_trait::async_trait]
impl UpstreamService for UpstreamServiceImpl {
async fn get(&self, id: Uuid) -> ProxyServiceResult<UpstreamConfig> {
use crate::db::entities::upstream;
let model = upstream::Entity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
Ok(model_to_config(model))
}
async fn list_by_config(&self, config_id: Uuid) -> ProxyServiceResult<Vec<UpstreamConfig>> {
use crate::db::entities::upstream;
let models = upstream::Entity::find()
.filter(upstream::Column::ConfigId.eq(config_id))
.all(&self.db)
.await?;
Ok(models.into_iter().map(model_to_config).collect())
}
async fn create(&self, params: CreateUpstreamParams) -> ProxyServiceResult<UpstreamConfig> {
use crate::db::entities::upstream::ActiveModel;
let model = ActiveModel {
id: Set(Uuid::new_v4()),
config_id: Set(params.config_id),
name: Set(params.name),
target_host: Set(params.target_host),
target_port: Set(params.target_port),
metadata: Set(params.metadata),
override_of_id: Set(params.override_of_id),
};
let result = model.insert(&self.db).await?;
Ok(model_to_config(result))
}
async fn update(
&self,
id: Uuid,
params: UpdateUpstreamParams,
) -> ProxyServiceResult<UpstreamConfig> {
use crate::db::entities::upstream::{ActiveModel, Entity as UpstreamEntity};
let existing = UpstreamEntity::find_by_id(id)
.one(&self.db)
.await?
.ok_or(ProxyServiceError::ConfigNotFound)?;
let mut model: ActiveModel = existing.into();
if let Some(name) = params.name {
model.name = Set(name);
}
if let Some(target_host) = params.target_host {
model.target_host = Set(target_host);
}
if let Some(target_port) = params.target_port {
model.target_port = Set(target_port);
}
if let Some(metadata) = params.metadata {
model.metadata = Set(metadata);
}
if let Some(override_of_id) = params.override_of_id {
model.override_of_id = Set(override_of_id);
}
let result = model.update(&self.db).await?;
Ok(model_to_config(result))
}
async fn delete(&self, id: Uuid) -> ProxyServiceResult<bool> {
let result = crate::db::entities::upstream::Entity::delete_by_id(id)
.exec(&self.db)
.await?;
Ok(result.rows_affected > 0)
}
}