From 4dba7f364e7fd0174db909e90ba53b3f8c0c3276 Mon Sep 17 00:00:00 2001 From: Alexander Piskun Date: Thu, 17 Oct 2024 13:46:37 +0300 Subject: [PATCH] implemented AppApi ExApps EnvironmentVariable feature Signed-off-by: Alexander Piskun --- nextcloudappstore/api/v1/release/importer.py | 10 ++++++ nextcloudappstore/api/v1/release/info.xsd | 16 ++++++++++ nextcloudappstore/api/v1/release/info.xslt | 20 ++++++++++++ .../api/v1/release/pre-info.xslt | 16 ++++++++++ nextcloudappstore/api/v1/serializers.py | 9 ++++++ .../api/v1/tests/data/infoxmls/app_api.xml | 13 ++++++++ nextcloudappstore/api/v1/tests/test_parser.py | 18 +++++++++++ .../api/v1/tests/test_release_importer.py | 6 ++++ nextcloudappstore/api/v1/views.py | 1 + nextcloudappstore/core/admin.py | 6 ++++ .../0034_add_appapi_deploy_env_vars.py | 31 +++++++++++++++++++ nextcloudappstore/core/models.py | 20 ++++++++++++ 12 files changed, 166 insertions(+) create mode 100644 nextcloudappstore/core/migrations/0034_add_appapi_deploy_env_vars.py diff --git a/nextcloudappstore/api/v1/release/importer.py b/nextcloudappstore/api/v1/release/importer.py index 65751d68cb6..c08524c1d94 100644 --- a/nextcloudappstore/api/v1/release/importer.py +++ b/nextcloudappstore/api/v1/release/importer.py @@ -8,6 +8,7 @@ from nextcloudappstore.core.facades import any_match from nextcloudappstore.core.models import ( App, + AppApiEnvironmentVariable, AppApiReleaseApiScope, AppApiReleaseDeployMethod, AppAuthor, @@ -194,6 +195,15 @@ def import_data(self, key: str, value: Any, obj: Any) -> None: ) for scope in value.get("scopes", []): AppApiReleaseApiScope.objects.get_or_create(app_release=obj, scope_name=scope["value"]) + for env_var_struct in value.get("environment_variables", []): + env_var = env_var_struct["variable"] + AppApiEnvironmentVariable.objects.get_or_create( + app_release=obj, + env_name=env_var["name"], + display_name=env_var["display_name"], + description=env_var.get("description", ""), + default=env_var.get("default", ""), + ) class AppReleaseImporter(Importer): diff --git a/nextcloudappstore/api/v1/release/info.xsd b/nextcloudappstore/api/v1/release/info.xsd index 7998fc28d0d..cd716e0052b 100644 --- a/nextcloudappstore/api/v1/release/info.xsd +++ b/nextcloudappstore/api/v1/release/info.xsd @@ -682,11 +682,27 @@ + + + + + + + + + + + + + + + + diff --git a/nextcloudappstore/api/v1/release/info.xslt b/nextcloudappstore/api/v1/release/info.xslt index c7347fde24d..93d81b5f44e 100644 --- a/nextcloudappstore/api/v1/release/info.xslt +++ b/nextcloudappstore/api/v1/release/info.xslt @@ -208,6 +208,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/nextcloudappstore/api/v1/release/pre-info.xslt b/nextcloudappstore/api/v1/release/pre-info.xslt index a5b5f3e9b9a..915aafb4575 100644 --- a/nextcloudappstore/api/v1/release/pre-info.xslt +++ b/nextcloudappstore/api/v1/release/pre-info.xslt @@ -156,6 +156,7 @@ + @@ -167,4 +168,19 @@ + + + + + + + + + + + + + + + diff --git a/nextcloudappstore/api/v1/serializers.py b/nextcloudappstore/api/v1/serializers.py index c703ea4dd4a..8bb9b08430d 100644 --- a/nextcloudappstore/api/v1/serializers.py +++ b/nextcloudappstore/api/v1/serializers.py @@ -6,6 +6,7 @@ from nextcloudappstore.core.models import ( App, + AppApiEnvironmentVariable, AppApiReleaseApiScope, AppApiReleaseDeployMethod, AppAuthor, @@ -128,9 +129,16 @@ class Meta: fields = ("scope_name",) +class AppApiEnvironmentVariableSerializer(serializers.ModelSerializer): + class Meta: + model = AppApiEnvironmentVariable + fields = ("env_name", "display_name", "description", "default") + + class AppApiAppReleaseSerializer(AppReleaseSerializer): deploy_methods = DeployMethodSerializer(many=True, read_only=True) api_scopes = ApiScopeSerializer(many=True, read_only=True) + environment_variables = AppApiEnvironmentVariableSerializer(many=True, read_only=True) class Meta: model = AppRelease @@ -155,6 +163,7 @@ class Meta: "aa_is_system", "deploy_methods", "api_scopes", + "environment_variables", ) diff --git a/nextcloudappstore/api/v1/tests/data/infoxmls/app_api.xml b/nextcloudappstore/api/v1/tests/data/infoxmls/app_api.xml index 8e4435cb830..afb9bc28d83 100644 --- a/nextcloudappstore/api/v1/tests/data/infoxmls/app_api.xml +++ b/nextcloudappstore/api/v1/tests/data/infoxmls/app_api.xml @@ -28,5 +28,18 @@ TALK true + + + SOME_VALUE1 + Display name 1 + Some useful description of it + + + SOME_VALUE2 + Display name 2 + Example of second variable with the 'default' value + 0 + + diff --git a/nextcloudappstore/api/v1/tests/test_parser.py b/nextcloudappstore/api/v1/tests/test_parser.py index 3197bf60cfa..dbe568faeca 100644 --- a/nextcloudappstore/api/v1/tests/test_parser.py +++ b/nextcloudappstore/api/v1/tests/test_parser.py @@ -571,6 +571,23 @@ def test_appapi(self): } assert r_ex_app["scopes"] == [{"value": "FILES"}, {"value": "NOTIFICATIONS"}, {"value": "TALK"}] assert r_ex_app["system"] == "true" + assert r_ex_app["environment_variables"] == [ + { + "variable": { + "name": "SOME_VALUE1", + "display_name": "Display name 1", + "description": "Some useful description of it", + } + }, + { + "variable": { + "name": "SOME_VALUE2", + "display_name": "Display name 2", + "description": "Example of second variable with the 'default' value", + "default": "0", + } + }, + ] def test_appapi_minimal(self): xml = self._get_contents("data/infoxmls/app_api_minimal.xml") @@ -583,6 +600,7 @@ def test_appapi_minimal(self): "registry": "ghcr.io", } assert r_ex_app["scopes"] == [] + assert r_ex_app["environment_variables"] == [] def _get_contents(self, target): path = self.get_path(target) diff --git a/nextcloudappstore/api/v1/tests/test_release_importer.py b/nextcloudappstore/api/v1/tests/test_release_importer.py index 6bba2e69905..764174a3f78 100644 --- a/nextcloudappstore/api/v1/tests/test_release_importer.py +++ b/nextcloudappstore/api/v1/tests/test_release_importer.py @@ -235,6 +235,8 @@ def test_import_minimal(self): self.assertEqual(1, release.deploy_methods.count()) all_scopes = release.api_scopes.all() self.assertEqual(0, len(all_scopes)) + all_deploy_env_vars = release.environment_variables.all() + self.assertEqual(0, len(all_deploy_env_vars)) def test_full(self): self._check_removed_translations() @@ -281,6 +283,10 @@ def test_full(self): self.assertEqual("FILES", all_scopes[0].scope_name) self.assertEqual("NOTIFICATIONS", all_scopes[1].scope_name) self.assertEqual("TALK", all_scopes[2].scope_name) + all_deploy_env_vars = release.environment_variables.all() + self.assertEqual(2, len(all_deploy_env_vars)) + self.assertEqual("SOME_VALUE1", all_deploy_env_vars[0].env_name) + self.assertEqual("SOME_VALUE2", all_deploy_env_vars[1].env_name) def test_release_no_update(self): result = parse_app_metadata(self.min, self.config.info_schema, self.config.pre_info_xslt, self.config.info_xslt) diff --git a/nextcloudappstore/api/v1/views.py b/nextcloudappstore/api/v1/views.py index 8d897721aa2..c93cb9059d3 100644 --- a/nextcloudappstore/api/v1/views.py +++ b/nextcloudappstore/api/v1/views.py @@ -73,6 +73,7 @@ *RELEASES_PREFETCH_LIST, "releases__deploy_methods", "releases__api_scopes", + "releases__environment_variables", ] diff --git a/nextcloudappstore/core/admin.py b/nextcloudappstore/core/admin.py index 3522f64e7bf..1291b8441ec 100644 --- a/nextcloudappstore/core/admin.py +++ b/nextcloudappstore/core/admin.py @@ -3,6 +3,7 @@ from nextcloudappstore.core.models import ( App, + AppApiEnvironmentVariable, AppApiReleaseApiScope, AppApiReleaseDeployMethod, AppAuthor, @@ -163,3 +164,8 @@ class AppApiReleaseApiScopeAdmin(admin.ModelAdmin): @admin.register(AppApiReleaseDeployMethod) class AppApiReleaseDeployMethodAdmin(admin.ModelAdmin): pass + + +@admin.register(AppApiEnvironmentVariable) +class AppApiEnvironmentVariable(admin.ModelAdmin): + pass diff --git a/nextcloudappstore/core/migrations/0034_add_appapi_deploy_env_vars.py b/nextcloudappstore/core/migrations/0034_add_appapi_deploy_env_vars.py new file mode 100644 index 00000000000..4346eed353f --- /dev/null +++ b/nextcloudappstore/core/migrations/0034_add_appapi_deploy_env_vars.py @@ -0,0 +1,31 @@ +# Generated by Django 4.2.16 on 2024-10-17 08:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0033_donation'), + ] + + operations = [ + migrations.CreateModel( + name='AppApiEnvironmentVariable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('env_name', models.CharField(max_length=64, verbose_name='Environment Variable Name')), + ('display_name', models.CharField(max_length=128, verbose_name='Display Name')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('default', models.CharField(blank=True, max_length=256, verbose_name='Default Value')), + ('app_release', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='environment_variables', to='core.apprelease', verbose_name='App Release')), + ], + options={ + 'verbose_name': 'AppAPI Release Environment Variable', + 'verbose_name_plural': 'AppAPI Release Environment Variables', + 'db_table': 'core_appapi_release_env_vars', + 'unique_together': {('app_release', 'env_name')}, + }, + ), + ] diff --git a/nextcloudappstore/core/models.py b/nextcloudappstore/core/models.py index b1c11e8f501..201585365f3 100644 --- a/nextcloudappstore/core/models.py +++ b/nextcloudappstore/core/models.py @@ -555,6 +555,26 @@ class Meta: verbose_name_plural = _("AppAPI release API Scopes") +class AppApiEnvironmentVariable(Model): + app_release = ForeignKey( + "AppRelease", + on_delete=CASCADE, + verbose_name=_("App Release"), + related_name="environment_variables", + db_index=True, + ) + env_name = CharField(max_length=64, verbose_name=_("Environment Variable Name")) + display_name = CharField(max_length=128, verbose_name=_("Display Name")) + description = TextField(verbose_name=_("Description"), blank=True) + default = CharField(max_length=256, verbose_name=_("Default Value"), blank=True) + + class Meta: + db_table = "core_appapi_release_env_vars" + verbose_name = _("AppAPI Release Environment Variable") + verbose_name_plural = _("AppAPI Release Environment Variables") + unique_together = (("app_release", "env_name"),) + + class Screenshot(Model): url = URLField(max_length=256, verbose_name=_("Image URL")) small_thumbnail = URLField(max_length=256, verbose_name=_("Small thumbnail"), default="")