Repository: safe-global/safe-config-service

Issue:
Use a generated unique id to represent a safe-app instead of a URL
Using the URL as a unique identifier for the safe-app is error prone as the same app can be represented in multiple forms (eg.: `https://example.com`, `https://example.com/`, `https://example.com/?`, `https://example.com/#` etc... 

We should then generate a unique identifier to represent each app instead of relying on the uniqueness of the url.

```javascript
GET /api/v1/safe-apps/

[
  {
    "id": <int>,
    ...
  }
]
```


Discussion:
Sounds good, normalizing an url is not an easy task as there are a lot of edge cases

Resolving patch:
diff --git a/src/safe_apps/migrations/0004_add_auto_pk.py b/src/safe_apps/migrations/0004_add_auto_pk.py
new file mode 100644
--- /dev/null
+++ b/src/safe_apps/migrations/0004_add_auto_pk.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.4 on 2021-06-28 14:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("safe_apps", "0003_alter_safeapp_provider"),
+    ]
+
+    operations = [
+        # Remove Primary Key constraint from safeapp.url
+        migrations.AlterField(
+            model_name="safeapp",
+            name="url",
+            field=models.URLField(),
+        ),
+        # Add safeapp.app_id as primary key
+        migrations.AddField(
+            model_name="safeapp",
+            name="app_id",
+            field=models.BigAutoField(primary_key=True, serialize=False),
+            preserve_default=False,
+        ),
+    ]
diff --git a/src/safe_apps/models.py b/src/safe_apps/models.py
--- a/src/safe_apps/models.py
+++ b/src/safe_apps/models.py
@@ -11,7 +11,8 @@ def __str__(self):
 
 
 class SafeApp(models.Model):
-    url = models.URLField(primary_key=True)
+    app_id = models.BigAutoField(primary_key=True)
+    url = models.URLField()
     name = models.CharField(max_length=200)
     icon_url = models.URLField()
     description = models.CharField(max_length=200)
diff --git a/src/safe_apps/serializers.py b/src/safe_apps/serializers.py
--- a/src/safe_apps/serializers.py
+++ b/src/safe_apps/serializers.py
@@ -10,8 +10,17 @@ class Meta:
 
 
 class SafeAppsResponseSerializer(serializers.ModelSerializer):
+    id = serializers.IntegerField(source="app_id")
     provider = ProviderSerializer()
 
     class Meta:
         model = SafeApp
-        fields = ["url", "name", "icon_url", "description", "chain_ids", "provider"]
+        fields = [
+            "id",
+            "url",
+            "name",
+            "icon_url",
+            "description",
+            "chain_ids",
+            "provider",
+        ]


Test patch:
diff --git a/src/safe_apps/tests/factories.py b/src/safe_apps/tests/factories.py
--- a/src/safe_apps/tests/factories.py
+++ b/src/safe_apps/tests/factories.py
@@ -16,6 +16,7 @@ class SafeAppFactory(DjangoModelFactory):
     class Meta:
         model = SafeApp
 
+    app_id = factory.Faker("pyint")
     url = factory.Faker("url")
     name = factory.Faker("company")
     icon_url = factory.Faker("image_url")
diff --git a/src/safe_apps/tests/test_views.py b/src/safe_apps/tests/test_views.py
--- a/src/safe_apps/tests/test_views.py
+++ b/src/safe_apps/tests/test_views.py
@@ -20,6 +20,7 @@ def test_json_payload_format(self):
 
         json_response = [
             {
+                "id": safe_app.app_id,
                 "url": safe_app.url,
                 "name": safe_app.name,
                 "iconUrl": safe_app.icon_url,
@@ -41,6 +42,7 @@ def test_all_safes_returned(self):
         (safe_app_1, safe_app_2, safe_app_3) = SafeAppFactory.create_batch(3)
         json_response = [
             {
+                "id": safe_app_1.app_id,
                 "url": safe_app_1.url,
                 "name": safe_app_1.name,
                 "icon_url": safe_app_1.icon_url,
@@ -49,6 +51,7 @@ def test_all_safes_returned(self):
                 "provider": None,
             },
             {
+                "id": safe_app_2.app_id,
                 "url": safe_app_2.url,
                 "name": safe_app_2.name,
                 "icon_url": safe_app_2.icon_url,
@@ -57,6 +60,7 @@ def test_all_safes_returned(self):
                 "provider": None,
             },
             {
+                "id": safe_app_3.app_id,
                 "url": safe_app_3.url,
                 "name": safe_app_3.name,
                 "icon_url": safe_app_3.icon_url,
@@ -76,6 +80,7 @@ def test_all_apps_returned_on_empty_chain_id_value(self):
         (safe_app_1, safe_app_2, safe_app_3) = SafeAppFactory.create_batch(3)
         json_response = [
             {
+                "id": safe_app_1.app_id,
                 "url": safe_app_1.url,
                 "name": safe_app_1.name,
                 "icon_url": safe_app_1.icon_url,
@@ -84,6 +89,7 @@ def test_all_apps_returned_on_empty_chain_id_value(self):
                 "provider": None,
             },
             {
+                "id": safe_app_2.app_id,
                 "url": safe_app_2.url,
                 "name": safe_app_2.name,
                 "icon_url": safe_app_2.icon_url,
@@ -92,6 +98,7 @@ def test_all_apps_returned_on_empty_chain_id_value(self):
                 "provider": None,
             },
             {
+                "id": safe_app_3.app_id,
                 "url": safe_app_3.url,
                 "name": safe_app_3.name,
                 "icon_url": safe_app_3.icon_url,
@@ -113,6 +120,7 @@ def test_apps_returned_on_filtered_chain_id(self):
 
         json_response = [
             {
+                "id": safe_app_4.app_id,
                 "url": safe_app_4.url,
                 "name": safe_app_4.name,
                 "icon_url": safe_app_4.icon_url,
@@ -121,6 +129,7 @@ def test_apps_returned_on_filtered_chain_id(self):
                 "provider": None,
             },
             {
+                "id": safe_app_5.app_id,
                 "url": safe_app_5.url,
                 "name": safe_app_5.name,
                 "icon_url": safe_app_5.icon_url,
@@ -151,6 +160,7 @@ def test_apps_returned_on_same_key_pair(self):
         SafeAppFactory.create(chain_ids=[2])
         json_response = [
             {
+                "id": safe_app_1.app_id,
                 "url": safe_app_1.url,
                 "name": safe_app_1.name,
                 "icon_url": safe_app_1.icon_url,
@@ -174,6 +184,7 @@ def test_provider_returned_in_response(self):
 
         json_response = [
             {
+                "id": safe_app.app_id,
                 "url": safe_app.url,
                 "name": safe_app.name,
                 "icon_url": safe_app.icon_url,
@@ -194,6 +205,7 @@ def test_provider_not_returned_in_response(self):
 
         json_response = [
             {
+                "id": safe_app.app_id,
                 "url": safe_app.url,
                 "name": safe_app.name,
                 "icon_url": safe_app.icon_url,
@@ -216,6 +228,7 @@ def test_should_cache_response(self):
 
         json_response = [
             {
+                "id": safe_app_1.app_id,
                 "url": safe_app_1.url,
                 "name": safe_app_1.name,
                 "icon_url": safe_app_1.icon_url,