Skip to main content

stackless_integrations/providers/base44_projects/
app.rs

1//! `base44_projects/app` integration.
2
3use std::collections::BTreeMap;
4
5use serde::Serialize;
6use stackless_stripe_projects::catalog::verify::CatalogService;
7use stackless_stripe_projects::provision::ProvisionContext;
8
9use super::FamilyResource;
10use crate::error::IntegrationError;
11use crate::hostable::{ConfigScope, Hostable, IntegrationHosting};
12
13pub const RESOURCE_KIND: &str = "integration-base44";
14
15#[derive(Debug, Serialize)]
16pub struct Base44ProjectsAppConfig {
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub app_name: Option<String>,
19}
20
21impl CatalogService for Base44ProjectsAppConfig {
22    const REFERENCE: &'static str = "base44_projects/app";
23}
24
25#[derive(Debug)]
26pub struct Base44ProjectsApp;
27
28impl Hostable for Base44ProjectsApp {
29    const PROVIDER: &'static str = "base44";
30    const HOSTING: IntegrationHosting = IntegrationHosting::Managed;
31    const CONFIG_SCOPE: ConfigScope = ConfigScope::GlobalOnly;
32    const RESOURCE_KIND: &'static str = RESOURCE_KIND;
33    const OUTPUTS: &'static [&'static str] = &["app_id"];
34}
35
36impl FamilyResource for Base44ProjectsApp {
37    type Config = Base44ProjectsAppConfig;
38    const PROVIDER_PREFIX: &'static str = "BASE44";
39    // Provisional until pinned by `mise run discover base44_projects/app`.
40    const OUTPUT_FIELDS: &'static [(&'static str, &'static str, bool)] =
41        &[("APP_ID", "app_id", true)];
42
43    fn build_config(
44        ctx: &ProvisionContext<'_>,
45    ) -> Result<Base44ProjectsAppConfig, IntegrationError> {
46        let config = super::integration_config(ctx)?;
47        Ok(Base44ProjectsAppConfig {
48            app_name: super::interp_optional(ctx, &config, "app_name")?,
49        })
50    }
51}
52
53pub fn validate_config(
54    name: &str,
55    config: &BTreeMap<String, toml::Value>,
56) -> Result<(), IntegrationError> {
57    let _ = (name, config);
58    Ok(())
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::ProviderOps;
65    use crate::resource::ResourcePayload;
66    use stackless_core::def::StackDef;
67    use stackless_stripe_projects::stripe::StripeProjects;
68    use stackless_stripe_projects::test_support;
69
70    #[test]
71    fn config_matches_catalog() {
72        const FIXTURE: &str = include_str!(concat!(
73            env!("CARGO_MANIFEST_DIR"),
74            "/../stackless-stripe-projects/tests/fixtures/catalog.json"
75        ));
76        let catalog = stackless_stripe_projects::Catalog::from_json_envelope(FIXTURE).unwrap();
77        let failures = stackless_stripe_projects::verify_service(
78            &catalog,
79            &Base44ProjectsAppConfig { app_name: None },
80        );
81        assert!(
82            failures.is_empty(),
83            "base44_projects/app catalog gaps:\n{}",
84            failures.join("\n")
85        );
86    }
87
88    const CATALOG_ENVELOPE: &str = r##"{"ok":true,"command":"projects catalog","data":{"last_updated":"2026-07-11T00:00:00Z","services":[{"id":"prvsvc_app","object":"v2.provisioning.provider_service_detail","provider_id":"prvdr_base44_projects","provider_name":"Base44_Projects","service_id":"app","categories":["database"],"kind":"deployable","scope":"project","availability":"available","development":false,"livemode":true,"pricing":{"type":"free"},"configuration_schema":{"additionalProperties":false,"properties":{"app_name":{"description":"Human-readable name for the Base44 app. Shown in the Base44 dashboard.","maxLength":50,"minLength":1,"title":"App name","type":"string"}},"required":[],"type":"object"}}]}}"##;
89
90    fn test_def() -> StackDef {
91        StackDef::parse(
92            r#"
93[stack]
94name = "atto"
95[stack.projects.stripe]
96project = "project_1"
97[integrations.res]
98provider = "base44"
99[services.api]
100source = { repo = "r", ref = "main" }
101env = { OUT = "${integrations.res.app_id}" }
102health = { path = "/health" }
103[services.api.local]
104run = "true"
105"#,
106        )
107        .unwrap()
108    }
109
110    #[tokio::test]
111    async fn provision_records_outputs() {
112        let runner = test_support::provision_script(
113            CATALOG_ENVELOPE,
114            serde_json::json!({"BASE44_APP_ID": "val_app_id"}),
115            0,
116        );
117        let dir = tempfile::tempdir().unwrap();
118        std::fs::write(
119            dir.path().join("stackless.toml"),
120            "[stack]\nname=\"atto\"\n",
121        )
122        .unwrap();
123        let stripe = StripeProjects::new(&runner, dir.path());
124
125        let resource = Base44ProjectsApp
126            .provision(
127                &stripe.as_dyn(),
128                &test_def(),
129                dir.path(),
130                "demo",
131                "res",
132                "local",
133                false,
134            )
135            .await
136            .unwrap();
137        assert_eq!(resource.resource_kind, "integration-base44");
138        let payload: ResourcePayload = serde_json::from_str(&resource.payload).unwrap();
139        assert_eq!(payload.outputs["app_id"], "val_app_id");
140    }
141}