Repository: learningequality/le-utils

Issue:
Update `embed_content_request.json` schema
## Overview
This task involves updating the `embed_content_request.json` schema to accept a list of files.

## Description and outcomes
- Modify the [embed_content_request.json](https://github.com/learningequality/le-utils/blob/main/spec/schema-embed_content_request.json) schema to accept a list of files.  
    - Name the property `files` of type `array`
    - Each `file` in `files` is of type `object`
    - `file` contains the following fields;
        - `url` of type `string`. The `url` should be a valid URL/URI and open to specific domains.
            - The GCP buckets domain
            - Localhost domains
        - `preset` of type `enum`. See [format_presets.py](https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py) for accepted list of options
        - `language` of type `string`. Use the [`language`](https://github.com/learningequality/le-utils/blob/main/spec/schema-embed_content_request.json#L8-L12) for this.
    - `files` should be added to the `resource` property
    - It is an optional property
- Add additional tests to verify correctness of implementation 

## Acceptance Criteria
- A new property `files` is added to the `embed_content_request.json` schema
- Appropriate tests are added to verify correctness

## Assumptions and Dependencies
- Blocks
    - https://github.com/learningequality/studio/issues/4455 

## Scope
The scope of this task is limited to;
- Adding the `files` property to the `embed_content_request.json` schema.

## Accessibility Requirements
NA

## Resources

Discussion:

Patch:
diff --git a/js/EmbedContentRequest.js b/js/EmbedContentRequest.js
index 88176c6..c6ae021 100644
--- a/js/EmbedContentRequest.js
+++ b/js/EmbedContentRequest.js
@@ -15,6 +15,65 @@ export const SCHEMA = {
       "description": "Language code from https://github.com/learningequality/le-utils/blob/main/le_utils/resources/languagelookup.json",
       "pattern": "^[a-z]{2,3}(?:-[a-zA-Z]+)?$"
     },
+    "url": {
+      "type": "string",
+      "pattern": "^(https?:\\/\\/(?:storage\\.cloud\\.google\\.com|localhost(?::[0-9]+)?)\\/[a-z0-9-._~!$&'()*+,;=:@%\\/\\?]+)$"
+    },
+    "preset": {
+      "type": "string",
+      "description": "Presets from https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py",
+      "enum": [
+        "high_res_video",
+        "low_res_video",
+        "video_thumbnail",
+        "video_subtitle",
+        "video_dependency",
+        "audio",
+        "audio_thumbnail",
+        "audio_dependency",
+        "document",
+        "epub",
+        "document_thumbnail",
+        "exercise",
+        "exercise_thumbnail",
+        "exercise_image",
+        "exercise_graphie",
+        "channel_thumbnail",
+        "topic_thumbnail",
+        "html5_zip",
+        "html5_dependency",
+        "html5_thumbnail",
+        "h5p",
+        "h5p_thumbnail",
+        "zim",
+        "zim_thumbnail",
+        "qti",
+        "qti_thumbnail",
+        "slideshow_image",
+        "slideshow_thumbnail",
+        "slideshow_manifest",
+        "imscp_zip"
+      ]
+    },
+    "file": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "url": {
+          "$ref": "#/definitions/url"
+        },
+        "preset": {
+          "$ref": "#/definitions/preset"
+        },
+        "language": {
+          "$ref": "#/definitions/language"
+        }
+      },
+      "required": [
+        "url",
+        "preset"
+      ]
+    },
     "resource": {
       "type": "object",
       "description": "The key textual metadata and data for a content resource",
@@ -38,6 +97,13 @@ export const SCHEMA = {
         },
         "language": {
           "$ref": "#/definitions/language"
+        },
+        "files": {
+          "type": "array",
+          "description": "A list of files associated with the content resource",
+          "items": {
+            "$ref": "#/definitions/file"
+          }
         }
       },
       "required": [
diff --git a/le_utils/constants/embed_content_request.py b/le_utils/constants/embed_content_request.py
index cce01be..f7344c4 100644
--- a/le_utils/constants/embed_content_request.py
+++ b/le_utils/constants/embed_content_request.py
@@ -21,6 +21,56 @@ SCHEMA = {
             "description": "Language code from https://github.com/learningequality/le-utils/blob/main/le_utils/resources/languagelookup.json",
             "pattern": "^[a-z]{2,3}(?:-[a-zA-Z]+)?$",
         },
+        "url": {
+            "type": "string",
+            "pattern": "^(https?:\\/\\/(?:storage\\.cloud\\.google\\.com|localhost(?::[0-9]+)?)\\/[a-z0-9-._~!$&'()*+,;=:@%\\/\\?]+)$",
+        },
+        "preset": {
+            "type": "string",
+            "description": "Presets from https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py",
+            "enum": [
+                "high_res_video",
+                "low_res_video",
+                "video_thumbnail",
+                "video_subtitle",
+                "video_dependency",
+                "audio",
+                "audio_thumbnail",
+                "audio_dependency",
+                "document",
+                "epub",
+                "document_thumbnail",
+                "exercise",
+                "exercise_thumbnail",
+                "exercise_image",
+                "exercise_graphie",
+                "channel_thumbnail",
+                "topic_thumbnail",
+                "html5_zip",
+                "html5_dependency",
+                "html5_thumbnail",
+                "h5p",
+                "h5p_thumbnail",
+                "zim",
+                "zim_thumbnail",
+                "qti",
+                "qti_thumbnail",
+                "slideshow_image",
+                "slideshow_thumbnail",
+                "slideshow_manifest",
+                "imscp_zip",
+            ],
+        },
+        "file": {
+            "type": "object",
+            "additionalProperties": False,
+            "properties": {
+                "url": {"$ref": "#/definitions/url"},
+                "preset": {"$ref": "#/definitions/preset"},
+                "language": {"$ref": "#/definitions/language"},
+            },
+            "required": ["url", "preset"],
+        },
         "resource": {
             "type": "object",
             "description": "The key textual metadata and data for a content resource",
@@ -43,6 +93,11 @@ SCHEMA = {
                     "description": "The cleaned up text extracted from the content resource (in markdown or plaintext format)",
                 },
                 "language": {"$ref": "#/definitions/language"},
+                "files": {
+                    "type": "array",
+                    "description": "A list of files associated with the content resource",
+                    "items": {"$ref": "#/definitions/file"},
+                },
             },
             "required": ["id", "title", "description", "text"],
         },
diff --git a/spec/schema-embed_content_request.json b/spec/schema-embed_content_request.json
index 8e805bb..761bce8 100644
--- a/spec/schema-embed_content_request.json
+++ b/spec/schema-embed_content_request.json
@@ -10,6 +10,65 @@
       "description": "Language code from https://github.com/learningequality/le-utils/blob/main/le_utils/resources/languagelookup.json",
       "pattern": "^[a-z]{2,3}(?:-[a-zA-Z]+)?$"
     },
+    "url": {
+      "type": "string",
+      "pattern": "^(https?:\\/\\/(?:storage\\.cloud\\.google\\.com|localhost(?::[0-9]+)?)\\/[a-z0-9-._~!$&'()*+,;=:@%\\/\\?]+)$"
+    },
+    "preset": {
+      "type": "string",
+      "description": "Presets from https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py",
+      "enum": [
+        "high_res_video",
+        "low_res_video",
+        "video_thumbnail",
+        "video_subtitle",
+        "video_dependency",
+        "audio",
+        "audio_thumbnail",
+        "audio_dependency",
+        "document",
+        "epub",
+        "document_thumbnail",
+        "exercise",
+        "exercise_thumbnail",
+        "exercise_image",
+        "exercise_graphie",
+        "channel_thumbnail",
+        "topic_thumbnail",
+        "html5_zip",
+        "html5_dependency",
+        "html5_thumbnail",
+        "h5p",
+        "h5p_thumbnail",
+        "zim",
+        "zim_thumbnail",
+        "qti",
+        "qti_thumbnail",
+        "slideshow_image",
+        "slideshow_thumbnail",
+        "slideshow_manifest",
+        "imscp_zip"
+      ]
+    },
+    "file": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "url": {
+          "$ref": "#/definitions/url"
+        },
+        "preset": {
+          "$ref": "#/definitions/preset"
+        },
+        "language": {
+          "$ref": "#/definitions/language"
+        }
+      },
+      "required": [
+        "url",
+        "preset"
+      ]
+    },
     "resource": {
       "type": "object",
       "description": "The key textual metadata and data for a content resource",
@@ -33,6 +92,13 @@
         },
         "language": {
           "$ref": "#/definitions/language"
+        },
+        "files": {
+          "type": "array",
+          "description": "A list of files associated with the content resource",
+          "items": {
+            "$ref": "#/definitions/file"
+          }
         }
       },
       "required": [


Test patch:
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
index 82c6301..29f2e13 100644
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -345,3 +345,97 @@ def test_embed__content__valid():
                 },
             }
         )
+
+
+def test_embed__content__valid_with_files():
+    with _assert_not_raises(jsonschema.ValidationError):
+        validate_embed_content_request(
+            {
+                "resources": [
+                    {
+                        "id": "123",
+                        "title": "Resource title",
+                        "description": "Resource description",
+                        "text": "Resource text",
+                        "language": "en",
+                        "files": [
+                            {
+                                "url": "http://localhost:8000/media/1234.jpg",
+                                "preset": "video_thumbnail",
+                            },
+                            {
+                                "url": "https://storage.cloud.google.com/test.appspot.com/test/test.mp4",
+                                "preset": "high_res_video",
+                                "language": "en",
+                            },
+                            {
+                                "url": "http://storage.cloud.google.com/test.appspot.com/test/test.mp4",
+                                "preset": "high_res_video",
+                                "language": "es",
+                            },
+                        ],
+                    },
+                ],
+                "metadata": {
+                    "channel_id": "000",
+                    "channel_title": "Channel title",
+                    "some_additional_field": "some_random_value",
+                },
+            }
+        )
+
+
+def test_embed__content__invalid_url_files():
+    with pytest.raises(jsonschema.ValidationError):
+        validate_embed_content_request(
+            {
+                "resources": [
+                    {
+                        "id": "123",
+                        "title": "Resource title",
+                        "description": "Resource description",
+                        "text": "Resource text",
+                        "language": "en",
+                        "files": [
+                            {
+                                "url": "https://example.com/media/1234.jpg",
+                                "preset": "video_thumbnail",
+                            },
+                        ],
+                    },
+                ],
+                "metadata": {
+                    "channel_id": "000",
+                    "channel_title": "Channel title",
+                    "some_additional_field": "some_random_value",
+                },
+            }
+        )
+
+
+def test_embed__content__invalid_preset_files():
+    with pytest.raises(jsonschema.ValidationError):
+        validate_embed_content_request(
+            {
+                "resources": [
+                    {
+                        "id": "123",
+                        "title": "Resource title",
+                        "description": "Resource description",
+                        "text": "Resource text",
+                        "language": "en",
+                        "files": [
+                            {
+                                "url": "http://localhost:8080/media/1234.jpg",
+                                "preset": "invalid_preset",
+                            },
+                        ],
+                    },
+                ],
+                "metadata": {
+                    "channel_id": "000",
+                    "channel_title": "Channel title",
+                    "some_additional_field": "some_random_value",
+                },
+            }
+        )