diff --git a/backend/launch_api.sh b/backend/launch_api.sh index 28c5516..e0cdac5 100755 --- a/backend/launch_api.sh +++ b/backend/launch_api.sh @@ -18,6 +18,10 @@ DJANGO_SUPERUSER_PASSWORD="${DJANGO_ADMIN_PASSWORD}" \ echo "* Collecting static files" ./manage.py collectstatic --noinput +# generate OpenAPI schema +echo "* Generating OpenAPI schema" +./manage.py spectacular --file /data/schema.yaml + # launch the server if [ "$DJANGO_DEBUG" = "1" ] ; then echo "* Serving via django runserver (debug mode)" diff --git a/backend/src/api/apps.py b/backend/src/api/apps.py index 878e7d5..ba57c28 100644 --- a/backend/src/api/apps.py +++ b/backend/src/api/apps.py @@ -4,3 +4,6 @@ class ApiConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "api" + + def ready(self): + import api.schema # noqa diff --git a/backend/src/api/models.py b/backend/src/api/models.py index d8096b7..21e7e72 100644 --- a/backend/src/api/models.py +++ b/backend/src/api/models.py @@ -480,6 +480,8 @@ class SearchTerm(models.Model): ) confidence = models.FloatField() related_words = models.TextField(null=True, blank=True) + log2_prob_prior = models.FloatField(null=True, blank=True) + prob = models.FloatField(null=True, blank=True) class Meta: indexes = [ diff --git a/backend/src/api/schema.py b/backend/src/api/schema.py new file mode 100644 index 0000000..2700eac --- /dev/null +++ b/backend/src/api/schema.py @@ -0,0 +1,19 @@ +""" +Contains OpenAPI schema definitions in cases where they can't be +simply inferred from the models/serializers/views. +""" + +from drf_spectacular.extensions import OpenApiAuthenticationExtension +from api.utils.auth import CsrfExemptSessionAuthentication + + +class CsrfExemptSessionAuthenticationScheme(OpenApiAuthenticationExtension): + target_class = CsrfExemptSessionAuthentication + name = "csrfExemptCookieAuth" + + def get_security_definition(self, auto_schema): + return { + "type": "apiKey", + "in": "cookie", + "name": "sessionid", + } diff --git a/backend/src/api/serializers.py b/backend/src/api/serializers.py index cd71ff3..a666cbc 100644 --- a/backend/src/api/serializers.py +++ b/backend/src/api/serializers.py @@ -39,7 +39,9 @@ class SearchTermSerializer(serializers.ModelSerializer): class Meta: model = SearchTerm - fields = ["id", "term", "series_id", "prob", "log2_prob_prior", "related_words"] + fields = [ + "id", "term", "series_id", "prob", "log2_prob_prior", "related_words" + ] # =========================================================================== @@ -133,13 +135,13 @@ class GEOSeriesSerializer(serializers.ModelSerializer): sample_count = serializers.SerializerMethodField(read_only=True) - def get_sample_count(self, obj): + def get_sample_count(self, obj) -> int: """Get the number of samples associated with this series.""" return obj.samples_ct if obj.samples_ct is not None else 0 confidence = serializers.SerializerMethodField() - def get_confidence(self, obj): + def get_confidence(self, obj) -> dict: """Compute confidence level based on prob.""" if obj.prob is None: label = "unknown" @@ -164,14 +166,14 @@ def get_confidence(self, obj): platform = serializers.SerializerMethodField() - def get_platform(self, obj): - """Get the platform name associated with this series.""" + def get_platform(self, obj) -> list[str]: + """Get the platform names associated with this series.""" gse_obj = GEOSeriesToGEOPlatforms.objects.filter(gse=obj.gse).first() - return str(gse_obj.platforms) if gse_obj else "" + return gse_obj.platforms if gse_obj else [] keywords = serializers.SerializerMethodField() - def get_keywords(self, obj): + def get_keywords(self, obj) -> list[str]: """Extract keywords from the series summary.""" if obj.keywords: return [kw.strip() for kw in obj.keywords.split(",")] @@ -179,7 +181,7 @@ def get_keywords(self, obj): classification = serializers.SerializerMethodField() - def get_classification(self, obj): + def get_classification(self, obj) -> str: """Returns values Positive or Negative; supposed to represent 'Classification of study in model training'?""" # FIXME: figure out how to actually determine this return "Positive" diff --git a/backend/src/api/views.py b/backend/src/api/views.py index afa7130..524a9bd 100644 --- a/backend/src/api/views.py +++ b/backend/src/api/views.py @@ -26,6 +26,10 @@ from rest_framework.permissions import AllowAny from rest_framework.response import Response +from drf_spectacular.utils import extend_schema, OpenApiParameter +from drf_spectacular.types import OpenApiTypes + + from .models import ( Cart, CartItem, @@ -468,7 +472,26 @@ class SearchTermViewSet(viewsets.ReadOnlyModelViewSet): # === Ontology search terms from meta-hq # =========================================================================== - +@extend_schema( + parameters=[ + OpenApiParameter( + name="query", + type=OpenApiTypes.STR, + location=OpenApiParameter.QUERY, + required=True, + description="Search query string", + ), + OpenApiParameter( + name="max_results", + type=OpenApiTypes.INT, + location=OpenApiParameter.QUERY, + required=False, + description="Maximum number of results to return", + default=50, + ), + ], + responses={200: OntologySearchResultsSerializer(many=True)}, +) @api_view(["GET"]) @permission_classes([AllowAny]) def ontology_search(request): diff --git a/backend/src/meta2onto/settings.py b/backend/src/meta2onto/settings.py index 57891d0..13c28b4 100644 --- a/backend/src/meta2onto/settings.py +++ b/backend/src/meta2onto/settings.py @@ -198,4 +198,5 @@ def is_truthy(value): "rest_framework.filters.SearchFilter", "rest_framework.filters.OrderingFilter", ], + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } diff --git a/data/schema.yaml b/data/schema.yaml new file mode 100644 index 0000000..c314f34 --- /dev/null +++ b/data/schema.yaml @@ -0,0 +1,1306 @@ +openapi: 3.0.3 +info: + title: '' + version: 0.0.0 +paths: + /api/cart/: + get: + operationId: cart_list + description: |- + API endpoint for viewing and managing Carts. + Accessible at /api/cart/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - cart + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCartList' + description: '' + post: + operationId: cart_create + description: |- + Create a new cart with the given name. + + Expects a JSON body with the following structure: + { + "studies": [ + { + "id": "GSE35357", + "added": "2025-12-12T11:21:35.895Z" + }, + { + "id": "GSE149008", + "added": "2025-12-12T11:21:36.627Z" + }, + { + "id": "GSE45968", + "added": "2025-12-12T11:21:37.293Z" + } + ], + "name": "yowza" + } + tags: + - cart + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Cart' + multipart/form-data: + schema: + $ref: '#/components/schemas/Cart' + required: true + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + description: '' + /api/cart/{id}/: + get: + operationId: cart_retrieve + description: |- + API endpoint for viewing and managing Carts. + Accessible at /api/cart/ + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this cart. + required: true + tags: + - cart + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + description: '' + put: + operationId: cart_update + description: |- + API endpoint for viewing and managing Carts. + Accessible at /api/cart/ + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this cart. + required: true + tags: + - cart + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Cart' + multipart/form-data: + schema: + $ref: '#/components/schemas/Cart' + required: true + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + description: '' + patch: + operationId: cart_partial_update + description: |- + API endpoint for viewing and managing Carts. + Accessible at /api/cart/ + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this cart. + required: true + tags: + - cart + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCart' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCart' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCart' + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + description: '' + delete: + operationId: cart_destroy + description: |- + API endpoint for viewing and managing Carts. + Accessible at /api/cart/ + parameters: + - in: path + name: id + schema: + type: string + format: uuid + description: A UUID string identifying this cart. + required: true + tags: + - cart + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '204': + description: No response body + /api/cart/download/: + post: + operationId: cart_download_create + description: |- + API endpoint for downloading cart contents. + + Expects a JSON body with the following structure: + { + "ids": [ + "GSE35357", + "GSE149008", + "GSE45968" + ] + } + + Query parameters: + - type (optional): 'json' or 'csv' (default: 'json') + - filename (optional): desired filename (default: 'cart_download') + tags: + - cart + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Cart' + multipart/form-data: + schema: + $ref: '#/components/schemas/Cart' + required: true + security: + - csrfExemptCookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Cart' + description: '' + /api/ontology/search/: + get: + operationId: ontology_search_list + description: |- + API endpoint for searching ontology terms. + Accessible at /api/ontology-search/ + + Query parameters: + - query (required): The search query string + - max_results (optional): Maximum number of results to return (default: 50) + parameters: + - in: query + name: max_results + schema: + type: integer + default: 50 + description: Maximum number of results to return + - in: query + name: query + schema: + type: string + description: Search query string + required: true + tags: + - ontology + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/OntologySearchResults' + description: '' + /api/organisms/: + get: + operationId: organisms_list + description: |- + ReadOnly API endpoint for viewing Organisms. + Accessible at /api/organisms/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - organisms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrganismList' + description: '' + /api/organisms/{id}/: + get: + operationId: organisms_retrieve + description: |- + ReadOnly API endpoint for viewing Organisms. + Accessible at /api/organisms/ + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this organism. + required: true + tags: + - organisms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Organism' + description: '' + /api/platforms/: + get: + operationId: platforms_list + description: |- + ReadOnly API endpoint for viewing GEOPlatforms. + Accessible at /api/platforms/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - platforms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedGEOPlatformList' + description: '' + /api/platforms/{gpl}/: + get: + operationId: platforms_retrieve + description: |- + ReadOnly API endpoint for viewing GEOPlatforms. + Accessible at /api/platforms/ + parameters: + - in: path + name: gpl + schema: + type: string + description: A unique value identifying this geo platform. + required: true + tags: + - platforms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOPlatform' + description: '' + /api/samples/: + get: + operationId: samples_list + description: |- + ReadOnly API endpoint for viewing GEOSamples. + Accessible at /api/samples/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - samples + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedGEOSampleList' + description: '' + /api/samples/{gsm}/: + get: + operationId: samples_retrieve + description: |- + ReadOnly API endpoint for viewing GEOSamples. + Accessible at /api/samples/ + parameters: + - in: path + name: gsm + schema: + type: string + description: GEOSample ID + required: true + tags: + - samples + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSample' + description: '' + /api/search-terms/: + get: + operationId: search_terms_list + description: |- + ReadOnly API endpoint for viewing SearchTerms. + Accessible at /api/search-terms/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - search-terms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedSearchTermList' + description: '' + /api/search-terms/{id}/: + get: + operationId: search_terms_retrieve + description: |- + ReadOnly API endpoint for viewing SearchTerms. + Accessible at /api/search-terms/ + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this search term. + required: true + tags: + - search-terms + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SearchTerm' + description: '' + /api/study/: + get: + operationId: study_list + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + parameters: + - name: limit + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: offset + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: ordering + required: false + in: query + description: Which field to use when ordering the results. + schema: + type: string + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - study + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedGEOSeriesList' + description: '' + /api/study/{gse}/: + get: + operationId: study_retrieve + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + parameters: + - in: path + name: gse + schema: + type: string + description: A unique value identifying this geo series. + required: true + tags: + - study + security: + - cookieAuth: [] + - basicAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + description: '' + /api/study/{gse}/samples/: + get: + operationId: study_samples_retrieve + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + parameters: + - in: path + name: gse + schema: + type: string + description: A unique value identifying this geo series. + required: true + tags: + - study + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + description: '' + /api/study/feedback/: + post: + operationId: study_feedback_create + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + tags: + - study + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/GEOSeries' + multipart/form-data: + schema: + $ref: '#/components/schemas/GEOSeries' + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + description: '' + /api/study/lookup/: + post: + operationId: study_lookup_create + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + tags: + - study + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/GEOSeries' + multipart/form-data: + schema: + $ref: '#/components/schemas/GEOSeries' + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + description: '' + /api/study/search/: + get: + operationId: study_search_retrieve + description: |- + ReadOnly API endpoint for viewing GEOSeries. + Accessible at /api/series/ + tags: + - study + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/GEOSeries' + description: '' +components: + schemas: + Cart: + type: object + description: Serializer for Cart model. + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + studies: + type: array + items: + $ref: '#/components/schemas/CartItem' + readOnly: true + required: + - id + - name + - studies + CartItem: + type: object + description: Serializer for CartItem model. + properties: + id: + type: string + readOnly: true + added: + type: string + format: date-time + readOnly: true + required: + - added + - id + GEOPlatform: + type: object + description: Serializer for GEOPlatform model. + properties: + gpl: + type: string + title: + type: string + nullable: true + status: + type: string + nullable: true + submission_date: + type: string + nullable: true + last_update_date: + type: string + nullable: true + technology: + type: string + nullable: true + distribution: + type: string + nullable: true + organism: + type: string + nullable: true + manufacturer: + type: string + nullable: true + manufacture_protocol: + type: string + nullable: true + coating: + type: string + nullable: true + catalog_number: + type: string + nullable: true + support: + type: string + nullable: true + description: + type: string + nullable: true + web_link: + type: string + nullable: true + contact: + type: string + nullable: true + data_row_count: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + supplementary_file: + type: string + nullable: true + bioc_package: + type: string + nullable: true + required: + - gpl + GEOSample: + type: object + description: Serializer for GEOSample model. + properties: + gsm: + type: string + description: GEOSample ID + id: + type: string + readOnly: true + title: + type: string + nullable: true + gpl_raw: + type: string + nullable: true + description: GEOPlatform ID + status: + type: string + nullable: true + submission_date: + type: string + nullable: true + last_update_date: + type: string + nullable: true + type: + type: string + nullable: true + source_name_ch1: + type: string + nullable: true + organism_ch1: + type: string + nullable: true + characteristics_ch1: + type: string + nullable: true + molecule_ch1: + type: string + nullable: true + label_ch1: + type: string + nullable: true + treatment_protocol_ch1: + type: string + nullable: true + extract_protocol_ch1: + type: string + nullable: true + label_protocol_ch1: + type: string + nullable: true + source_name_ch2: + type: string + nullable: true + organism_ch2: + type: string + nullable: true + characteristics_ch2: + type: string + nullable: true + molecule_ch2: + type: string + nullable: true + label_ch2: + type: string + nullable: true + treatment_protocol_ch2: + type: string + nullable: true + extract_protocol_ch2: + type: string + nullable: true + label_protocol_ch2: + type: string + nullable: true + hyb_protocol: + type: string + nullable: true + description: + type: string + nullable: true + data_processing: + type: string + nullable: true + contact: + type: string + nullable: true + supplementary_file: + type: string + nullable: true + data_row_count: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + channel_count: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + doc: + type: string + nullable: true + series: + type: string + nullable: true + required: + - gsm + - id + GEOSeries: + type: object + description: Serializer for GEOSeries model. + properties: + name: + type: string + readOnly: true + id: + type: string + readOnly: true + status: + type: string + nullable: true + submitted_at: + type: string + format: date-time + readOnly: true + last_update_date: + type: string + nullable: true + pubmed_id: + type: integer + maximum: 9223372036854775807 + minimum: -9223372036854775808 + format: int64 + nullable: true + summary: + type: string + nullable: true + description: + type: string + readOnly: true + type: + type: string + nullable: true + contributor: + type: string + nullable: true + web_link: + type: string + nullable: true + overall_design: + type: string + nullable: true + repeats: + type: string + nullable: true + repeats_sample_list: + type: string + nullable: true + variable: + type: string + nullable: true + variable_description: + type: string + nullable: true + contact: + type: string + nullable: true + supplementary_file: + type: string + nullable: true + confidence: + type: object + additionalProperties: {} + description: Compute confidence level based on prob. + readOnly: true + sample_count: + type: integer + description: Get the number of samples associated with this series. + readOnly: true + database: + type: array + items: + type: string + readOnly: true + platform: + type: array + items: + type: string + description: Get the platform name associated with this series. + readOnly: true + keywords: + type: array + items: + type: string + description: Extract keywords from the series summary. + readOnly: true + classification: + type: string + description: Returns values Positive or Negative; supposed to represent + 'Classification of study in model training'? + readOnly: true + required: + - classification + - confidence + - database + - description + - id + - keywords + - name + - platform + - sample_count + - submitted_at + OntologySearchResults: + type: object + description: Serializer for OntologySearchResults model. + properties: + id: + type: string + name: + type: string + description: + type: string + readOnly: true + series: + type: string + readOnly: true + ontology: + type: string + type: + type: string + synonym: + type: string + scope: + type: string + sim: + type: number + format: double + scope_weight: + type: number + format: double + overall_rank: + type: number + format: double + is_exact: + type: boolean + required: + - description + - id + - is_exact + - name + - ontology + - overall_rank + - scope + - scope_weight + - series + - sim + - synonym + - type + Organism: + type: object + description: Serializer for Organism model. + properties: + id: + type: integer + readOnly: true + name: + type: string + required: + - id + - name + PaginatedCartList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/Cart' + PaginatedGEOPlatformList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/GEOPlatform' + PaginatedGEOSampleList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/GEOSample' + PaginatedGEOSeriesList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/GEOSeries' + PaginatedOrganismList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/Organism' + PaginatedSearchTermList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=400&limit=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?offset=200&limit=100 + results: + type: array + items: + $ref: '#/components/schemas/SearchTerm' + PatchedCart: + type: object + description: Serializer for Cart model. + properties: + id: + type: string + format: uuid + readOnly: true + name: + type: string + studies: + type: array + items: + $ref: '#/components/schemas/CartItem' + readOnly: true + SearchTerm: + type: object + description: Serializer for SearchTerm model. + properties: + id: + type: integer + readOnly: true + term: + type: string + maxLength: 256 + series_id: + type: string + nullable: true + readOnly: true + prob: + type: number + format: double + nullable: true + log2_prob_prior: + type: number + format: double + nullable: true + related_words: + type: string + nullable: true + required: + - id + - series_id + - term + securitySchemes: + basicAuth: + type: http + scheme: basic + cookieAuth: + type: apiKey + in: cookie + name: sessionid + csrfExemptCookieAuth: + type: apiKey + in: cookie + name: sessionid diff --git a/docker-compose.yml b/docker-compose.yml index 3df1cc8..08bf525 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,10 @@ services: frontend: build: ./frontend + environment: + - VITE_SCHEMA_LOCATION=/data/schema.yaml + volumes: + - ./data/:/data/ depends_on: - backend diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 4eabf1f..7a2f12a 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -8,7 +8,7 @@ import globals from "globals"; import typescriptEslint from "typescript-eslint"; export default defineConfig([ - globalIgnores(["dist", "public"]), + globalIgnores(["dist", "public", "src/api/types.ts"]), eslintJs.configs.recommended, typescriptEslint.configs.recommended, eslintPluginPrettierRecommended, diff --git a/frontend/package.json b/frontend/package.json index db436e2..e5abbbd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "preview": "vite preview", "lint": "eslint . --fix", "format": "prettier --write .", + "schema": "bunx orval --config ./src/api/orval.config.cjs", "test:types": "tsc -b", "test:lint": "eslint .", "test:format": "prettier --check .", @@ -54,7 +55,6 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react-hooks": "^7.0.1", "globals": "^17.0.0", - "msw": "^2.12.7", "prettier": "^3.8.0", "prettier-plugin-jsdoc": "^1.8.0", "prettier-plugin-tailwindcss": "^0.7.2", @@ -63,10 +63,5 @@ "typescript-eslint": "^8.53.1", "vite": "^7.3.1", "vite-plugin-svgr": "^4.5.0" - }, - "msw": { - "workerDirectory": [ - "public" - ] } } diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js deleted file mode 100644 index 80f1930..0000000 --- a/frontend/public/mockServiceWorker.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.13.6' -const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - const requestInterceptedAt = Date.now() - - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been terminated (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - * @param {number} requestInterceptedAt - */ -async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse( - event, - client, - requestId, - requestInterceptedAt, - ) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @param {number} requestInterceptedAt - * @returns {Promise} - */ -async function getResponse(event, client, requestId, requestInterceptedAt) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - interceptedAt: requestInterceptedAt, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/frontend/src/api/mock.ts b/frontend/src/api/mock.ts deleted file mode 100644 index ba916b5..0000000 --- a/frontend/src/api/mock.ts +++ /dev/null @@ -1,236 +0,0 @@ -import type { - DefaultBodyType, - HttpResponseResolver, - JsonBodyType, - PathParams, -} from "msw"; -import type { - Cart, - Ontologies, - Sample, - Samples, - Studies, - Study, -} from "@/api/types"; -import type { ShareCart } from "@/state/cart"; -import { random, range, sample, uniq } from "lodash"; -import { http, HttpResponse, passthrough } from "msw"; -import { api } from "@/api"; -import { sleep } from "@/util/misc"; - -const handler = ( - method: Method, - url: string, - func: (props: Props) => JsonBodyType, -) => - http[method](url, async ({ request, params }) => { - const url = new URL(request.url); - const body = request.body ? await (await request.clone()).json() : {}; - await sleep(random(200, 1000)); - if (Math.random() < 0.1) - return HttpResponse.json(null, { status: 500, statusText: "fake error" }); - return HttpResponse.json(func({ url, body, params })); - }); - -/** non-mocked/handled request */ -const nonMocked: HttpResponseResolver = ({ request }) => { - console.debug("Non-mocked request", new URL(request.url).pathname); - return passthrough(); -}; - -const fakeWords = - "lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est laborum".split( - " ", - ); - -const fakeText = (min: number, max: number) => - range(random(min, max)) - .map(() => sample(fakeWords)) - .join(" "); - -const fakeHighlight = (string: string, highlight: string) => - string - .split(" ") - .map((word) => (word.includes(highlight) ? `${word}` : word)) - .join(" "); - -const fakeId = () => String(random(10000, 99999)); - -const fakeType = () => - sample([ - "type-a", - "type-a", - "type-a", - "type-a", - "type-a", - "type-b", - "type-c", - ]); - -const fakeDate = () => - new Date( - random( - new Date().getTime() - 1000 * 60 * 60 * 24 * 365 * 2, - new Date().getTime(), - ), - ).toISOString(); - -const fakeConfidence = () => { - const value = 1 - random(0, 1, true) * random(0, 1, true); - let name = "Low"; - if (value > 0.7) name = "Medium"; - if (value > 0.9) name = "High"; - return { name, value }; -}; - -const fakePlatform = () => sample(["RNA-seq", "scRNA-seq", "Microarray"]); - -const fakeDatabase = () => - ["GEO", "SRA", "Refine.bio", "ARCHS4"].filter(() => Math.random() > 0.5); - -const fakeClassification = () => sample(["Positive", "Negative", "Neutral"]); - -const fakeFacets = () => ({ - "Study Size": { - label: "samples", - min: 0, - max: 666, - }, - Confidence: { - label: "%", - min: 0, - max: 100, - }, - Classification: { - Positive: random(0, 20), - Negative: random(0, 20), - Neutral: random(0, 20), - }, - Platform: { - "RNA-seq": random(0, 200), - Microarray: random(0, 200), - }, -}); - -const fakeSearch = (data: T[], search: string) => - data.filter((item) => - JSON.stringify(item).toLowerCase().includes(search.toLowerCase()), - ); - -type Props = { - url: URL; - body: DefaultBodyType; - params: PathParams; -}; - -const fakeOntologySearchResults: Ontologies = range(100).map(() => { - const id = fakeText(1, 4); - return { - id, - name: id, - description: fakeText(4, 6), - type: fakeType(), - series: "", - }; -}); - -const fakeStudies: Study[] = range(100).map(() => ({ - id: fakeId(), - name: fakeText(4, 20), - description: fakeText(10, 200), - confidence: fakeConfidence(), - submitted_at: fakeDate(), - platform: fakePlatform(), - database: fakeDatabase(), - classification: fakeClassification(), - sample_count: random(1, 200), - keywords: [], -})); - -const fakeSamples: Sample[] = range(100).map(() => ({ - id: fakeId(), - type: fakeType(), - description: fakeText(5, 20), - created_at: fakeDate(), - updated_at: fakeDate(), -})); - -const fakeCarts: Cart[] = []; - -export const handlers = [ - handler("get", `${api}/ontology/search`, ({ url }): Ontologies => { - const search = url.searchParams.get("query") || ""; - const data = fakeOntologySearchResults.map((ontologySearchResult) => ({ - ...ontologySearchResult, - name: fakeHighlight(ontologySearchResult.name, search), - description: fakeHighlight(ontologySearchResult.description, search), - })); - return fakeSearch(data, search); - }), - - handler("get", `${api}/study/search`, ({ url }): Studies => { - let search = url.searchParams.get("query") || ""; - search = search.slice(0, search.indexOf(" ")); - const offset = Number(url.searchParams.get("offset")); - const limit = Number(url.searchParams.get("limit")); - const filteredData = fakeSearch(fakeStudies, search); - const paginatedData = filteredData - .slice(offset, offset + limit) - .map((study) => ({ - ...study, - description: fakeHighlight(study.description, search), - keywords: uniq(study.description.split(" ")).slice(0, 10), - })); - return { - count: filteredData.length, - results: paginatedData, - facets: fakeFacets(), - }; - }), - - handler("post", `${api}/study/lookup`, ({ url }): Studies => { - const offset = Number(url.searchParams.get("offset")); - const limit = Number(url.searchParams.get("limit")); - const filteredData = fakeStudies; - const paginatedData = fakeStudies.slice(offset, offset + limit); - return { - count: filteredData.length, - results: paginatedData, - facets: fakeFacets(), - }; - }), - - handler("get", `${api}/study/:id/samples`, ({ url }): Samples => { - const offset = Number(url.searchParams.get("offset")); - const limit = Number(url.searchParams.get("limit")); - const paginatedData = fakeSamples.slice(offset, offset + limit); - return { - count: fakeSamples.length, - results: paginatedData, - }; - }), - - handler("post", `${api}/study/feedback`, () => ({ - message: "Feedback received", - })), - - handler( - "get", - `${api}/cart/:id`, - ({ params }): Cart | object => - fakeCarts.find((cart) => cart.id === params.id) ?? {}, - ), - - handler("post", `${api}/cart`, ({ body }): Cart => { - const cart = { - ...(body as ShareCart), - id: fakeId(), - created_at: fakeDate(), - }; - fakeCarts.push(cart); - return cart; - }), - - http.get(/.*/, nonMocked), - http.post(/.*/, nonMocked), -]; diff --git a/frontend/src/api/orval.config.cjs b/frontend/src/api/orval.config.cjs new file mode 100644 index 0000000..72f86d8 --- /dev/null +++ b/frontend/src/api/orval.config.cjs @@ -0,0 +1,17 @@ +// fetch the location of the schema.yaml file from the environment variable +// if unspecified, defaults to a path that will work when the frontend is run on the host rather than in a container +// (the docker-compose.yml specifies the container-specific path for the file) +const schemaLocation = process.env.VITE_SCHEMA_LOCATION || "../../../data/schema.yaml"; + +module.exports = { + meta2onto: { + output: { + client: "zod", + mode: "split", + target: "./types.ts", + }, + input: { + target: schemaLocation, + }, + }, +}; diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 487b195..de80582 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -1,88 +1,773 @@ -import z from "zod"; - -export const ontology = z.object({ - id: z.string(), - type: z.string(), - name: z.string(), - description: z.string(), - series: z.string(), -}); - -export type Ontology = z.infer; - -export const ontologies = z.array(ontology); - -export type Ontologies = z.infer; - -export const study = z.object({ - id: z.string(), - name: z.string(), - description: z.string(), - confidence: z.object({ - name: z.string(), - value: z.number(), - }), - submitted_at: z.iso.date(), - platform: z.string(), - database: z.array(z.string()), - classification: z.string(), - sample_count: z.number(), - keywords: z.array(z.string()), -}); - -export type Study = z.infer; - -export const studies = z.object({ - count: z.number(), - results: z.array(study), - facets: z.record( - z.string(), - z.record(z.string(), z.union([z.number(), z.string()])), - ), -}); - -export type Studies = z.infer; - -export const sample = z.object({ - id: z.string(), - type: z.string(), - description: z.string(), - created_at: z.iso.datetime(), - updated_at: z.iso.datetime(), -}); - -export type Sample = z.infer; - -export const samples = z.object({ - count: z.number(), - results: z.array(sample), -}); - -export type Samples = z.infer; - -export const cart = z.object({ - id: z.string(), - name: z.string(), - created_at: z.string(), - studies: z.array( - z.object({ - id: z.string(), - added: z.string(), - }), - ), -}); - -export type Cart = z.infer; - -export const feedback = z.object({ - rating: z.number().min(-1).max(1), - qualities: z.array(z.string()), - keywords: z.record(z.string(), z.string()), - elaborate: z.string(), -}); - -export type Feedback = z.infer; - -export const feedbacks = z.record(z.string(), feedback); - -export type Feedbacks = z.infer; +/** + * Generated by orval v8.17.0 🍺 + * Do not edit manually. + * OpenAPI spec version: 0.0.0 + */ +import * as zod from 'zod'; + + +/** + * API endpoint for viewing and managing Carts. + * Accessible at /api/cart/ + */ +export const CartListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const CartListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.')) +}) + + +/** + * Create a new cart with the given name. + * + * Expects a JSON body with the following structure: + * { + * "studies": [ + * { + * "id": "GSE35357", + * "added": "2025-12-12T11:21:35.895Z" + * }, + * { + * "id": "GSE149008", + * "added": "2025-12-12T11:21:36.627Z" + * }, + * { + * "id": "GSE45968", + * "added": "2025-12-12T11:21:37.293Z" + * } + * ], + * "name": "yowza" + * } + */ +export const CartCreateBody = zod.object({ + "name": zod.string() +}).describe('Serializer for Cart model.') + +export const CartCreateResponse = zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.') + + +/** + * API endpoint for viewing and managing Carts. + * Accessible at /api/cart/ + */ +export const CartRetrieveParams = zod.object({ + "id": zod.uuid().describe('A UUID string identifying this cart.') +}) + +export const CartRetrieveResponse = zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.') + + +/** + * API endpoint for viewing and managing Carts. + * Accessible at /api/cart/ + */ +export const CartUpdateParams = zod.object({ + "id": zod.uuid().describe('A UUID string identifying this cart.') +}) + +export const CartUpdateBody = zod.object({ + "name": zod.string() +}).describe('Serializer for Cart model.') + +export const CartUpdateResponse = zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.') + + +/** + * API endpoint for viewing and managing Carts. + * Accessible at /api/cart/ + */ +export const CartPartialUpdateParams = zod.object({ + "id": zod.uuid().describe('A UUID string identifying this cart.') +}) + +export const CartPartialUpdateBody = zod.object({ + "name": zod.string().optional() +}).describe('Serializer for Cart model.') + +export const CartPartialUpdateResponse = zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.') + + +/** + * API endpoint for viewing and managing Carts. + * Accessible at /api/cart/ + */ +export const CartDestroyParams = zod.object({ + "id": zod.uuid().describe('A UUID string identifying this cart.') +}) + +export const CartDestroyResponse = zod.void() + + +/** + * API endpoint for downloading cart contents. + * + * Expects a JSON body with the following structure: + * { + * "ids": [ + * "GSE35357", + * "GSE149008", + * "GSE45968" + * ] + * } + * + * Query parameters: + * - type (optional): 'json' or 'csv' (default: 'json') + * - filename (optional): desired filename (default: 'cart_download') + */ +export const CartDownloadCreateBody = zod.object({ + "name": zod.string() +}).describe('Serializer for Cart model.') + +export const CartDownloadCreateResponse = zod.object({ + "id": zod.uuid(), + "name": zod.string(), + "studies": zod.array(zod.object({ + "id": zod.string(), + "added": zod.iso.datetime({"offset":true}) +}).describe('Serializer for CartItem model.')) +}).describe('Serializer for Cart model.') + + +/** + * API endpoint for searching ontology terms. + * Accessible at /api/ontology-search/ + * + * Query parameters: + * - query (required): The search query string + * - max_results (optional): Maximum number of results to return (default: 50) + */ +export const ontologySearchListQueryMaxResultsDefault = 50; + +export const OntologySearchListQueryParams = zod.object({ + "max_results": zod.number().default(ontologySearchListQueryMaxResultsDefault).describe('Maximum number of results to return'), + "query": zod.string().describe('Search query string') +}) + +export const OntologySearchListResponseItem = zod.object({ + "id": zod.string(), + "name": zod.string(), + "description": zod.string(), + "series": zod.string(), + "ontology": zod.string(), + "type": zod.string(), + "synonym": zod.string(), + "scope": zod.string(), + "sim": zod.number(), + "scope_weight": zod.number(), + "overall_rank": zod.number(), + "is_exact": zod.boolean() +}).describe('Serializer for OntologySearchResults model.') +export const OntologySearchListResponse = zod.array(OntologySearchListResponseItem) + + +/** + * ReadOnly API endpoint for viewing Organisms. + * Accessible at /api/organisms/ + */ +export const OrganismsListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const OrganismsListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "id": zod.number(), + "name": zod.string() +}).describe('Serializer for Organism model.')) +}) + + +/** + * ReadOnly API endpoint for viewing Organisms. + * Accessible at /api/organisms/ + */ +export const OrganismsRetrieveParams = zod.object({ + "id": zod.number().describe('A unique integer value identifying this organism.') +}) + +export const OrganismsRetrieveResponse = zod.object({ + "id": zod.number(), + "name": zod.string() +}).describe('Serializer for Organism model.') + + +/** + * ReadOnly API endpoint for viewing GEOPlatforms. + * Accessible at /api/platforms/ + */ +export const PlatformsListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const platformsListResponseResultsItemDataRowCountMin = -2147483648; +export const platformsListResponseResultsItemDataRowCountMax = 2147483647; + + + +export const PlatformsListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "gpl": zod.string(), + "title": zod.string().nullish(), + "status": zod.string().nullish(), + "submission_date": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "technology": zod.string().nullish(), + "distribution": zod.string().nullish(), + "organism": zod.string().nullish(), + "manufacturer": zod.string().nullish(), + "manufacture_protocol": zod.string().nullish(), + "coating": zod.string().nullish(), + "catalog_number": zod.string().nullish(), + "support": zod.string().nullish(), + "description": zod.string().nullish(), + "web_link": zod.string().nullish(), + "contact": zod.string().nullish(), + "data_row_count": zod.number().min(platformsListResponseResultsItemDataRowCountMin).max(platformsListResponseResultsItemDataRowCountMax).nullish(), + "supplementary_file": zod.string().nullish(), + "bioc_package": zod.string().nullish() +}).describe('Serializer for GEOPlatform model.')) +}) + + +/** + * ReadOnly API endpoint for viewing GEOPlatforms. + * Accessible at /api/platforms/ + */ +export const PlatformsRetrieveParams = zod.object({ + "gpl": zod.string().describe('A unique value identifying this geo platform.') +}) + +export const platformsRetrieveResponseDataRowCountMin = -2147483648; +export const platformsRetrieveResponseDataRowCountMax = 2147483647; + + + +export const PlatformsRetrieveResponse = zod.object({ + "gpl": zod.string(), + "title": zod.string().nullish(), + "status": zod.string().nullish(), + "submission_date": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "technology": zod.string().nullish(), + "distribution": zod.string().nullish(), + "organism": zod.string().nullish(), + "manufacturer": zod.string().nullish(), + "manufacture_protocol": zod.string().nullish(), + "coating": zod.string().nullish(), + "catalog_number": zod.string().nullish(), + "support": zod.string().nullish(), + "description": zod.string().nullish(), + "web_link": zod.string().nullish(), + "contact": zod.string().nullish(), + "data_row_count": zod.number().min(platformsRetrieveResponseDataRowCountMin).max(platformsRetrieveResponseDataRowCountMax).nullish(), + "supplementary_file": zod.string().nullish(), + "bioc_package": zod.string().nullish() +}).describe('Serializer for GEOPlatform model.') + + +/** + * ReadOnly API endpoint for viewing GEOSamples. + * Accessible at /api/samples/ + */ +export const SamplesListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const samplesListResponseResultsItemDataRowCountMin = -2147483648; +export const samplesListResponseResultsItemDataRowCountMax = 2147483647; + +export const samplesListResponseResultsItemChannelCountMin = -2147483648; +export const samplesListResponseResultsItemChannelCountMax = 2147483647; + + + +export const SamplesListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "gsm": zod.string().describe('GEOSample ID'), + "id": zod.string(), + "title": zod.string().nullish(), + "gpl_raw": zod.string().nullish().describe('GEOPlatform ID'), + "status": zod.string().nullish(), + "submission_date": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "type": zod.string().nullish(), + "source_name_ch1": zod.string().nullish(), + "organism_ch1": zod.string().nullish(), + "characteristics_ch1": zod.string().nullish(), + "molecule_ch1": zod.string().nullish(), + "label_ch1": zod.string().nullish(), + "treatment_protocol_ch1": zod.string().nullish(), + "extract_protocol_ch1": zod.string().nullish(), + "label_protocol_ch1": zod.string().nullish(), + "source_name_ch2": zod.string().nullish(), + "organism_ch2": zod.string().nullish(), + "characteristics_ch2": zod.string().nullish(), + "molecule_ch2": zod.string().nullish(), + "label_ch2": zod.string().nullish(), + "treatment_protocol_ch2": zod.string().nullish(), + "extract_protocol_ch2": zod.string().nullish(), + "label_protocol_ch2": zod.string().nullish(), + "hyb_protocol": zod.string().nullish(), + "description": zod.string().nullish(), + "data_processing": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "data_row_count": zod.number().min(samplesListResponseResultsItemDataRowCountMin).max(samplesListResponseResultsItemDataRowCountMax).nullish(), + "channel_count": zod.number().min(samplesListResponseResultsItemChannelCountMin).max(samplesListResponseResultsItemChannelCountMax).nullish(), + "doc": zod.string().nullish(), + "series": zod.string().nullish() +}).describe('Serializer for GEOSample model.')) +}) + + +/** + * ReadOnly API endpoint for viewing GEOSamples. + * Accessible at /api/samples/ + */ +export const SamplesRetrieveParams = zod.object({ + "gsm": zod.string() +}) + +export const samplesRetrieveResponseDataRowCountMin = -2147483648; +export const samplesRetrieveResponseDataRowCountMax = 2147483647; + +export const samplesRetrieveResponseChannelCountMin = -2147483648; +export const samplesRetrieveResponseChannelCountMax = 2147483647; + + + +export const SamplesRetrieveResponse = zod.object({ + "gsm": zod.string().describe('GEOSample ID'), + "id": zod.string(), + "title": zod.string().nullish(), + "gpl_raw": zod.string().nullish().describe('GEOPlatform ID'), + "status": zod.string().nullish(), + "submission_date": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "type": zod.string().nullish(), + "source_name_ch1": zod.string().nullish(), + "organism_ch1": zod.string().nullish(), + "characteristics_ch1": zod.string().nullish(), + "molecule_ch1": zod.string().nullish(), + "label_ch1": zod.string().nullish(), + "treatment_protocol_ch1": zod.string().nullish(), + "extract_protocol_ch1": zod.string().nullish(), + "label_protocol_ch1": zod.string().nullish(), + "source_name_ch2": zod.string().nullish(), + "organism_ch2": zod.string().nullish(), + "characteristics_ch2": zod.string().nullish(), + "molecule_ch2": zod.string().nullish(), + "label_ch2": zod.string().nullish(), + "treatment_protocol_ch2": zod.string().nullish(), + "extract_protocol_ch2": zod.string().nullish(), + "label_protocol_ch2": zod.string().nullish(), + "hyb_protocol": zod.string().nullish(), + "description": zod.string().nullish(), + "data_processing": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "data_row_count": zod.number().min(samplesRetrieveResponseDataRowCountMin).max(samplesRetrieveResponseDataRowCountMax).nullish(), + "channel_count": zod.number().min(samplesRetrieveResponseChannelCountMin).max(samplesRetrieveResponseChannelCountMax).nullish(), + "doc": zod.string().nullish(), + "series": zod.string().nullish() +}).describe('Serializer for GEOSample model.') + + +/** + * ReadOnly API endpoint for viewing SearchTerms. + * Accessible at /api/search-terms/ + */ +export const SearchTermsListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const searchTermsListResponseResultsItemTermMax = 256; + + + +export const SearchTermsListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "id": zod.number(), + "term": zod.string().max(searchTermsListResponseResultsItemTermMax), + "series_id": zod.string().nullable(), + "prob": zod.number().nullish(), + "log2_prob_prior": zod.number().nullish(), + "related_words": zod.string().nullish() +}).describe('Serializer for SearchTerm model.')) +}) + + +/** + * ReadOnly API endpoint for viewing SearchTerms. + * Accessible at /api/search-terms/ + */ +export const SearchTermsRetrieveParams = zod.object({ + "id": zod.number().describe('A unique integer value identifying this search term.') +}) + +export const searchTermsRetrieveResponseTermMax = 256; + + + +export const SearchTermsRetrieveResponse = zod.object({ + "id": zod.number(), + "term": zod.string().max(searchTermsRetrieveResponseTermMax), + "series_id": zod.string().nullable(), + "prob": zod.number().nullish(), + "log2_prob_prior": zod.number().nullish(), + "related_words": zod.string().nullish() +}).describe('Serializer for SearchTerm model.') + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const StudyListQueryParams = zod.object({ + "limit": zod.number().optional().describe('Number of results to return per page.'), + "offset": zod.number().optional().describe('The initial index from which to return the results.'), + "ordering": zod.string().optional().describe('Which field to use when ordering the results.'), + "search": zod.string().optional().describe('A search term.') +}) + +export const studyListResponseResultsItemPubmedIdMin = -9223372036854776000; +export const studyListResponseResultsItemPubmedIdMax = 9223372036854776000; + + + +export const StudyListResponse = zod.object({ + "count": zod.number(), + "next": zod.url().nullish(), + "previous": zod.url().nullish(), + "results": zod.array(zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyListResponseResultsItemPubmedIdMin).max(studyListResponseResultsItemPubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.')) +}) + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const StudyRetrieveParams = zod.object({ + "gse": zod.string().describe('A unique value identifying this geo series.') +}) + +export const studyRetrieveResponsePubmedIdMin = -9223372036854776000; +export const studyRetrieveResponsePubmedIdMax = 9223372036854776000; + + + +export const StudyRetrieveResponse = zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyRetrieveResponsePubmedIdMin).max(studyRetrieveResponsePubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.') + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const StudySamplesRetrieveParams = zod.object({ + "gse": zod.string().describe('A unique value identifying this geo series.') +}) + +export const studySamplesRetrieveResponsePubmedIdMin = -9223372036854776000; +export const studySamplesRetrieveResponsePubmedIdMax = 9223372036854776000; + + + +export const StudySamplesRetrieveResponse = zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studySamplesRetrieveResponsePubmedIdMin).max(studySamplesRetrieveResponsePubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.') + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const studyFeedbackCreateBodyPubmedIdMin = -9223372036854776000; +export const studyFeedbackCreateBodyPubmedIdMax = 9223372036854776000; + + + +export const StudyFeedbackCreateBody = zod.object({ + "status": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyFeedbackCreateBodyPubmedIdMin).max(studyFeedbackCreateBodyPubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish() +}).describe('Serializer for GEOSeries model.') + +export const studyFeedbackCreateResponsePubmedIdMin = -9223372036854776000; +export const studyFeedbackCreateResponsePubmedIdMax = 9223372036854776000; + + + +export const StudyFeedbackCreateResponse = zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyFeedbackCreateResponsePubmedIdMin).max(studyFeedbackCreateResponsePubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.') + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const studyLookupCreateBodyPubmedIdMin = -9223372036854776000; +export const studyLookupCreateBodyPubmedIdMax = 9223372036854776000; + + + +export const StudyLookupCreateBody = zod.object({ + "status": zod.string().nullish(), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyLookupCreateBodyPubmedIdMin).max(studyLookupCreateBodyPubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish() +}).describe('Serializer for GEOSeries model.') + +export const studyLookupCreateResponsePubmedIdMin = -9223372036854776000; +export const studyLookupCreateResponsePubmedIdMax = 9223372036854776000; + + + +export const StudyLookupCreateResponse = zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studyLookupCreateResponsePubmedIdMin).max(studyLookupCreateResponsePubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.') + + +/** + * ReadOnly API endpoint for viewing GEOSeries. + * Accessible at /api/series/ + */ +export const studySearchRetrieveResponsePubmedIdMin = -9223372036854776000; +export const studySearchRetrieveResponsePubmedIdMax = 9223372036854776000; + + + +export const StudySearchRetrieveResponse = zod.object({ + "name": zod.string(), + "id": zod.string(), + "status": zod.string().nullish(), + "submitted_at": zod.iso.datetime({"offset":true}), + "last_update_date": zod.string().nullish(), + "pubmed_id": zod.number().min(studySearchRetrieveResponsePubmedIdMin).max(studySearchRetrieveResponsePubmedIdMax).nullish(), + "summary": zod.string().nullish(), + "description": zod.string(), + "type": zod.string().nullish(), + "contributor": zod.string().nullish(), + "web_link": zod.string().nullish(), + "overall_design": zod.string().nullish(), + "repeats": zod.string().nullish(), + "repeats_sample_list": zod.string().nullish(), + "variable": zod.string().nullish(), + "variable_description": zod.string().nullish(), + "contact": zod.string().nullish(), + "supplementary_file": zod.string().nullish(), + "confidence": zod.record(zod.string(), zod.unknown()).describe('Compute confidence level based on prob.'), + "sample_count": zod.number().describe('Get the number of samples associated with this series.'), + "database": zod.array(zod.string()), + "platform": zod.array(zod.string()).describe('Get the platform name associated with this series.'), + "keywords": zod.array(zod.string()).describe('Extract keywords from the series summary.'), + "classification": zod.string().describe('Returns values Positive or Negative; supposed to represent \'Classification of study in model training\'?') +}).describe('Serializer for GEOSeries model.') + + diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b11f45e..1b43355 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,6 @@ import "@/util/seed"; import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import analytics from "react-ga4"; -import { setupWorker } from "msw/browser"; -import { handlers } from "@/api/mock"; -import { url } from "@/util/url"; import App from "./App"; console.debug({ env: import.meta.env }); @@ -12,11 +9,6 @@ console.debug({ env: import.meta.env }); /** init google analytics */ if (import.meta.env.PROD) analytics.initialize("G-CS5J55JQF5"); -/** whether to mock network requests with fake responses */ -export const mock = url.searchParams.get("mock") === "true"; - -if (mock) await setupWorker(...handlers).start(); - createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index a86d4de..58e463b 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -384,7 +384,7 @@ const Result = ({ }, { icon: Dna, - text: platform, + text: platform.join(", "), tooltip: "Platform used in study", }, {