pib-service 0.33.0

Politik im Blick service for serving public governmental information in OParl format, with some extra addon information
Documentation
// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

mod settings;

use settings::{ApiOidcSettings, ApiSettings, Settings};

use std::sync::Arc;

use dirs::data_local_dir;
use oparl_service_facade::OParlServiceHandle;
use oparl_types::DateTime;
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
use pib_oparl_service::{PibOParlService, ServiceUrlBuilder, UrlBuilder};
use pib_service_api_auth::ApiAuth;
use pib_service_api_auth_oidc::OidcAuth;
use pib_service_api_permissions::Permissions;
use pib_service_api_permissions_inventory::InventoryPermissions;
use pib_service_api_types::config::{oidc::OidcConfig, oparl::OParlConfig};
use pib_service_backend::Backend;
use pib_service_inventory::InventoryProvider;
use pib_service_inventory_db::DatabaseInventoryProvider;
use tokio::{select, sync::RwLock};

#[derive(Default)]
pub struct RunParameters {
    pub program_name: String,
    pub suffix: Option<String>,
}

pub async fn run(
    RunParameters {
        program_name,
        suffix,
    }: RunParameters,
) -> anyhow::Result<()> {
    env_logger::init();

    let settings = Settings::load(&program_name, suffix.as_deref())?;

    let data_dir = data_local_dir().unwrap().join(&program_name);
    std::fs::create_dir_all(&data_dir)?;
    let suffix = suffix.unwrap_or_default();
    let db_path = data_dir.join(format!("inventory{suffix}.sqlite?mode=rwc"));
    log::info!("using database {db_path:?}");
    let db_url = format!("turso:{}", db_path.to_str().unwrap());
    let db = toasty::Db::builder()
        .models(pib_service_db_models::models())
        .connect(&db_url)
        .await?;
    pib_service_db_migration::migrate(&db).await?;

    let name = Some(program_name.into());
    let vendor = Some("https://codeberg.org/pib".parse()?);
    let product = Some("https://codeberg.org/pib/pib-service".parse()?);
    let now = DateTime::now();
    let created = now.clone();
    let modified = now.clone();
    let pib_about = Some(
        "An experimental OParl service developed by the \"Politik im Blick\" developers".into(),
    );

    let database_inventory_provider: Arc<dyn InventoryProvider> = Arc::new(
        DatabaseInventoryProvider::new(db, name, vendor, product, created, modified, pib_about),
    );

    let permissions: Arc<dyn Permissions> = Arc::new(InventoryPermissions::new(
        database_inventory_provider.clone(),
    ));

    let url_builder: Arc<dyn UrlBuilder> =
        Arc::new(ServiceUrlBuilder::new(settings.oparl.base_url.clone()));

    let service = OParlServiceHandle::new(Arc::new(RwLock::new(PibOParlService::new(
        database_inventory_provider.clone(),
        url_builder,
    ))));
    let service_future = oparl_web_service_axum::run(service, settings.http.bind.as_slice());

    if let Some(ApiSettings { oidc, http }) = settings.api.as_ref() {
        let ApiOidcSettings {
            issuer,
            client_id,
            client_secret,
        } = oidc.clone();
        let auth: Arc<dyn ApiAuth> = Arc::new(OidcAuth::new(
            IssuerUrl::new(issuer.clone())?,
            ClientId::new(client_id.clone()),
            ClientSecret::new(client_secret.clone()),
            database_inventory_provider.clone(),
        ));
        let oidc = OidcConfig {
            issuer,
            client_id,
            client_secret,
        };
        let oparl = Some(OParlConfig {
            url: settings.oparl.base_url.clone(),
        });

        let service = Arc::new(Backend::new(
            database_inventory_provider,
            permissions,
            oidc,
            oparl,
        ));

        let service_api_future = pib_service_api::run(http.bind.as_slice(), service, auth);

        select! {
            result = service_future => result?,
            result = service_api_future => result?
        }
    } else {
        service_future.await?;
    }

    Ok(())
}