From 47566f9a21141f0e8c9a1aad6ac707359b0608d9 Mon Sep 17 00:00:00 2001 From: Celine Pelletier Date: Fri, 28 Feb 2025 23:28:06 -0500 Subject: [PATCH] feat: SJRA-51 user set model and get user set by id endpoint --- backend/cmd/api/integration_test.go | 21 +++ backend/cmd/api/main.go | 7 +- backend/docs/docs.go | 4 +- backend/docs/swagger.json | 4 +- backend/docs/swagger.yaml | 51 ++++++ .../repository/postgres_repository.go | 5 +- backend/internal/repository/user_sets.go | 101 +++++++++++ backend/internal/server/handlers.go | 40 ++++- .../server/handlers_user_sets_test.go | 57 +++++++ backend/internal/types/user_set.go | 52 +++++- .../000002_create_user_set_tables.down.sql | 5 + .../000002_create_user_set_tables.up.sql | 50 ++++++ frontend/api/api.ts | 161 ++++++++++++++++++ 13 files changed, 542 insertions(+), 16 deletions(-) create mode 100644 backend/internal/repository/user_sets.go create mode 100644 backend/internal/server/handlers_user_sets_test.go create mode 100644 backend/scripts/init-sql/migrations/000002_create_user_set_tables.down.sql create mode 100644 backend/scripts/init-sql/migrations/000002_create_user_set_tables.up.sql diff --git a/backend/cmd/api/integration_test.go b/backend/cmd/api/integration_test.go index 0e15178..2cc67e6 100644 --- a/backend/cmd/api/integration_test.go +++ b/backend/cmd/api/integration_test.go @@ -286,6 +286,27 @@ func assertPostInterpretationSomatic(t *testing.T, repo repository.Interpretatio return nil } +func assertGetUserSet(t *testing.T, repo repository.UserSetsDAO, userSetId string, status int, expected string) { + router := gin.Default() + router.GET("/users/sets/:user_set_id", server.GetUserSet(repo)) + + req, _ := http.NewRequest("GET", fmt.Sprintf("/users/sets/%s", userSetId), bytes.NewBuffer([]byte("{}"))) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, status, w.Code) + assert.JSONEq(t, expected, w.Body.String()) +} + +func Test_GetUserSet(t *testing.T) { + testutils.ParallelPostgresTestWithDb(t, func(t *testing.T, db *gorm.DB) { + pubmedService := &MockExternalClient{} + repo := repository.NewPostgresRepository(db, pubmedService) + // not found + assertGetUserSet(t, repo.UserSets, "bce3b031-c691-4680-878f-f43d661f9a9f", http.StatusNotFound, `{"error":"not found"}`) + }) +} + func TestMain(m *testing.M) { testutils.StartAllContainers() code := m.Run() diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 4851d8a..555b86f 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -46,7 +46,7 @@ func init() { func main() { flag.Parse() defer glog.Flush() - + database.MigrateWithEnvDefault() // Initialize database connection @@ -77,7 +77,7 @@ func main() { AllowHeaders: []string{"Accept", "Authorization", "Content-Type"}, AllowCredentials: true, // Enable cookies/auth })) - + occurrencesGroup := r.Group("/occurrences") role := os.Getenv("KEYCLOAK_CLIENT_ROLE") @@ -104,6 +104,9 @@ func main() { interpretationsSomaticGroup.GET("", server.GetInterpretationSomatic(repoPostgres.Interpretations)) interpretationsSomaticGroup.POST("", server.PostInterpretationSomatic(repoPostgres.Interpretations)) + usersGroup := r.Group("/users") + usersGroup.GET("/sets/:user_set_id", server.GetUserSet(repoPostgres.UserSets)) + r.Use(gin.Recovery()) r.Run(":8090") } diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 2d64b87..991da06 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -6,10 +6,10 @@ import "github.com/swaggo/swag/v2" const docTemplate = `{ "schemes": {{ marshal .Schemes }}, - "components": {"schemas":{"types.Aggregation":{"description":"Aggregation represents an aggregation result","properties":{"count":{"description":"Count in the bucket","type":"integer"},"key":{"description":"Bucket key","type":"string"}},"type":"object"},"types.AggregationBody":{"properties":{"field":{"type":"string"},"size":{"type":"integer"},"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.Count":{"description":"Count represents count result","properties":{"count":{"description":"Number of results","type":"integer"}},"type":"object"},"types.CountBody":{"properties":{"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.InterpretationGermline":{"properties":{"classification":{"type":"string"},"classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"condition":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"pubmed":{"items":{"$ref":"#/components/schemas/types.InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"transmission_modes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"types.InterpretationPubmed":{"properties":{"citation":{"type":"string"},"citation_id":{"type":"string"}},"type":"object"},"types.InterpretationSomatic":{"properties":{"clinical_utility":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"oncogenicity":{"type":"string"},"oncogenicity_classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"pubmed":{"items":{"$ref":"#/components/schemas/types.InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"tumoral_type":{"type":"string"}},"type":"object"},"types.LeafContent":{"properties":{"field":{"type":"string"},"value":{}},"type":"object"},"types.ListBody":{"description":"Body of a list request","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"selected_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"sort":{"items":{"$ref":"#/components/schemas/types.SortBody"},"type":"array","uniqueItems":false},"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.Occurrence":{"description":"Occurrence represents an occurrence","properties":{"ad_ratio":{"type":"number"},"af":{"type":"number"},"canonical":{"type":"boolean"},"chromosome":{"type":"string"},"clinvar":{"items":{"type":"string"},"type":"array","uniqueItems":false},"filter":{"type":"string"},"genotype_quality":{"type":"integer"},"gnomad_v3_af":{"type":"number"},"hgvsg":{"type":"string"},"locus_id":{"type":"integer"},"mane_select":{"type":"boolean"},"omim_inheritance_code":{"type":"string"},"pf":{"type":"number"},"seq_id":{"type":"integer"},"symbol":{"type":"string"},"variant_class":{"type":"string"},"vep_impact":{"type":"string"},"zygosity":{"type":"string"}},"type":"object"},"types.PubmedCitation":{"properties":{"id":{"type":"string"},"nlm":{"$ref":"#/components/schemas/types.PubmedCitationDetails"}},"type":"object"},"types.PubmedCitationDetails":{"properties":{"format":{"type":"string"}},"type":"object"},"types.SortBody":{"properties":{"field":{"type":"string"},"order":{"enum":["asc","desc"],"type":"string"}},"type":"object"},"types.Sqon":{"properties":{"content":{"oneOf":[{"$ref":"#/components/schemas/types.LeafContent"},{"items":{"$ref":"#/components/schemas/types.Sqon"},"type":"array"}]},"op":{"enum":["in","and","or","not","between","\u003e","\u003c","\u003e=","\u003c=","not-in","all"],"type":"string"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, + "components": {"schemas":{"types.Aggregation":{"description":"Aggregation represents an aggregation result","properties":{"count":{"description":"Count in the bucket","type":"integer"},"key":{"description":"Bucket key","type":"string"}},"type":"object"},"types.AggregationBody":{"properties":{"field":{"type":"string"},"size":{"type":"integer"},"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.Count":{"description":"Count represents count result","properties":{"count":{"description":"Number of results","type":"integer"}},"type":"object"},"types.CountBody":{"properties":{"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.InterpretationGermline":{"properties":{"classification":{"type":"string"},"classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"condition":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"pubmed":{"items":{"$ref":"#/components/schemas/types.InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"transmission_modes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"types.InterpretationPubmed":{"properties":{"citation":{"type":"string"},"citation_id":{"type":"string"}},"type":"object"},"types.InterpretationSomatic":{"properties":{"clinical_utility":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"oncogenicity":{"type":"string"},"oncogenicity_classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"pubmed":{"items":{"$ref":"#/components/schemas/types.InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"tumoral_type":{"type":"string"}},"type":"object"},"types.LeafContent":{"properties":{"field":{"type":"string"},"value":{}},"type":"object"},"types.ListBody":{"description":"Body of a list request","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"selected_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"sort":{"items":{"$ref":"#/components/schemas/types.SortBody"},"type":"array","uniqueItems":false},"sqon":{"$ref":"#/components/schemas/types.Sqon"}},"type":"object"},"types.Occurrence":{"description":"Occurrence represents an occurrence","properties":{"ad_ratio":{"type":"number"},"af":{"type":"number"},"canonical":{"type":"boolean"},"chromosome":{"type":"string"},"clinvar":{"items":{"type":"string"},"type":"array","uniqueItems":false},"filter":{"type":"string"},"genotype_quality":{"type":"integer"},"gnomad_v3_af":{"type":"number"},"hgvsg":{"type":"string"},"locus_id":{"type":"integer"},"mane_select":{"type":"boolean"},"omim_inheritance_code":{"type":"string"},"pf":{"type":"number"},"seq_id":{"type":"integer"},"symbol":{"type":"string"},"variant_class":{"type":"string"},"vep_impact":{"type":"string"},"zygosity":{"type":"string"}},"type":"object"},"types.PubmedCitation":{"properties":{"id":{"type":"string"},"nlm":{"$ref":"#/components/schemas/types.PubmedCitationDetails"}},"type":"object"},"types.PubmedCitationDetails":{"properties":{"format":{"type":"string"}},"type":"object"},"types.SortBody":{"properties":{"field":{"type":"string"},"order":{"enum":["asc","desc"],"type":"string"}},"type":"object"},"types.Sqon":{"properties":{"content":{"oneOf":[{"$ref":"#/components/schemas/types.LeafContent"},{"items":{"$ref":"#/components/schemas/types.Sqon"},"type":"array"}]},"op":{"enum":["in","and","or","not","between","\u003e","\u003c","\u003e=","\u003c=","not-in","all"],"type":"string"}},"type":"object"},"types.UserSet":{"properties":{"active":{"type":"boolean"},"id":{"type":"string"},"ids":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"type":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, "info": {"description":"{{escape .Description}}","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"{{.Title}}","version":"{{.Version}}"}, "externalDocs": {"description":"","url":""}, - "paths": {"/interpretations/germline/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation germline","operationId":"GetInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation germline","tags":["interpretations"]},"post":{"description":"Create or Update interpretation germline","operationId":"PostInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation germline","tags":["interpretations"]}},"/interpretations/pubmed/{citation_id}":{"get":{"description":"Get pubmed citation by ID","operationId":"GetPubmedCitation","parameters":[{"description":"Citation ID","in":"path","name":"citation_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.PubmedCitation"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get pubmed citation by ID","tags":["interpretations"]}},"/interpretations/somatic/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation somatic","operationId":"GetInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation somatic","tags":["interpretations"]},"post":{"description":"Create or Update interpretation somatic","operationId":"PostInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation somatic","tags":["interpretations"]}},"/occurrences/{seq_id}/aggregate":{"post":{"description":"Aggregate occurrences for a given sequence ID","operationId":"aggregateOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.AggregationBody"}}},"description":"Aggregation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.Aggregation"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Aggregate occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/count":{"post":{"description":"Counts occurrences for a given sequence ID","operationId":"countOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.CountBody"}}},"description":"Count Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.Count"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Count occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/list":{"post":{"description":"List occurrences for a given sequence ID","operationId":"listOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.ListBody"}}},"description":"List Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/types.Occurrence"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"List occurrences","tags":["occurrences"]}},"/status":{"get":{"description":"Returns the current status of the API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Get API status","tags":["status"]}}}, + "paths": {"/interpretations/germline/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation germline","operationId":"GetInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation germline","tags":["interpretations"]},"post":{"description":"Create or Update interpretation germline","operationId":"PostInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationGermline"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation germline","tags":["interpretations"]}},"/interpretations/pubmed/{citation_id}":{"get":{"description":"Get pubmed citation by ID","operationId":"GetPubmedCitation","parameters":[{"description":"Citation ID","in":"path","name":"citation_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.PubmedCitation"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get pubmed citation by ID","tags":["interpretations"]}},"/interpretations/somatic/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation somatic","operationId":"GetInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation somatic","tags":["interpretations"]},"post":{"description":"Create or Update interpretation somatic","operationId":"PostInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.InterpretationSomatic"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation somatic","tags":["interpretations"]}},"/occurrences/{seq_id}/aggregate":{"post":{"description":"Aggregate occurrences for a given sequence ID","operationId":"aggregateOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.AggregationBody"}}},"description":"Aggregation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.Aggregation"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Aggregate occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/count":{"post":{"description":"Counts occurrences for a given sequence ID","operationId":"countOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.CountBody"}}},"description":"Count Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.Count"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Count occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/list":{"post":{"description":"List occurrences for a given sequence ID","operationId":"listOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.ListBody"}}},"description":"List Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/types.Occurrence"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"List occurrences","tags":["occurrences"]}},"/status":{"get":{"description":"Returns the current status of the API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Get API status","tags":["status"]}},"/users/sets/{user_set_id}":{"get":{"description":"Get user set","operationId":"GetUserSet","parameters":[{"description":"UserSet ID","in":"path","name":"user_set_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/types.UserSet"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get user set by id","tags":["user_sets"]}}}, "openapi": "3.1.0", "servers": [ {"url":"/"} diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 51e0663..a913eea 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -1,8 +1,8 @@ { - "components": {"schemas":{"Aggregation":{"description":"Aggregation represents an aggregation result","properties":{"count":{"description":"Count in the bucket","type":"integer"},"key":{"description":"Bucket key","type":"string"}},"type":"object"},"AggregationBody":{"properties":{"field":{"type":"string"},"size":{"type":"integer"},"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"Count":{"description":"Count represents count result","properties":{"count":{"description":"Number of results","type":"integer"}},"type":"object"},"CountBody":{"properties":{"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"InterpretationGermline":{"properties":{"classification":{"type":"string"},"classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"condition":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"pubmed":{"items":{"$ref":"#/components/schemas/InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"transmission_modes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"InterpretationPubmed":{"properties":{"citation":{"type":"string"},"citation_id":{"type":"string"}},"type":"object"},"InterpretationSomatic":{"properties":{"clinical_utility":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"oncogenicity":{"type":"string"},"oncogenicity_classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"pubmed":{"items":{"$ref":"#/components/schemas/InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"tumoral_type":{"type":"string"}},"type":"object"},"LeafContent":{"properties":{"field":{"type":"string"},"value":{}},"type":"object"},"ListBody":{"description":"Body of a list request","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"selected_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"sort":{"items":{"$ref":"#/components/schemas/SortBody"},"type":"array","uniqueItems":false},"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"Occurrence":{"description":"Occurrence represents an occurrence","properties":{"ad_ratio":{"type":"number"},"af":{"type":"number"},"canonical":{"type":"boolean"},"chromosome":{"type":"string"},"clinvar":{"items":{"type":"string"},"type":"array","uniqueItems":false},"filter":{"type":"string"},"genotype_quality":{"type":"integer"},"gnomad_v3_af":{"type":"number"},"hgvsg":{"type":"string"},"locus_id":{"type":"integer"},"mane_select":{"type":"boolean"},"omim_inheritance_code":{"type":"string"},"pf":{"type":"number"},"seq_id":{"type":"integer"},"symbol":{"type":"string"},"variant_class":{"type":"string"},"vep_impact":{"type":"string"},"zygosity":{"type":"string"}},"type":"object"},"PubmedCitation":{"properties":{"id":{"type":"string"},"nlm":{"$ref":"#/components/schemas/PubmedCitationDetails"}},"type":"object"},"PubmedCitationDetails":{"properties":{"format":{"type":"string"}},"type":"object"},"SortBody":{"properties":{"field":{"type":"string"},"order":{"enum":["asc","desc"],"type":"string"}},"type":"object"},"Sqon":{"properties":{"content":{"oneOf":[{"$ref":"#/components/schemas/LeafContent"},{"items":{"$ref":"#/components/schemas/Sqon"},"type":"array"}]},"op":{"enum":["in","and","or","not","between","\u003e","\u003c","\u003e=","\u003c=","not-in","all"],"type":"string"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, + "components": {"schemas":{"Aggregation":{"description":"Aggregation represents an aggregation result","properties":{"count":{"description":"Count in the bucket","type":"integer"},"key":{"description":"Bucket key","type":"string"}},"type":"object"},"AggregationBody":{"properties":{"field":{"type":"string"},"size":{"type":"integer"},"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"Count":{"description":"Count represents count result","properties":{"count":{"description":"Number of results","type":"integer"}},"type":"object"},"CountBody":{"properties":{"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"InterpretationGermline":{"properties":{"classification":{"type":"string"},"classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"condition":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"pubmed":{"items":{"$ref":"#/components/schemas/InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"transmission_modes":{"items":{"type":"string"},"type":"array","uniqueItems":false}},"type":"object"},"InterpretationPubmed":{"properties":{"citation":{"type":"string"},"citation_id":{"type":"string"}},"type":"object"},"InterpretationSomatic":{"properties":{"clinical_utility":{"type":"string"},"id":{"type":"string"},"interpretation":{"type":"string"},"locus_id":{"type":"string"},"oncogenicity":{"type":"string"},"oncogenicity_classification_criterias":{"items":{"type":"string"},"type":"array","uniqueItems":false},"pubmed":{"items":{"$ref":"#/components/schemas/InterpretationPubmed"},"type":"array","uniqueItems":false},"sequencing_id":{"type":"string"},"transcript_id":{"type":"string"},"tumoral_type":{"type":"string"}},"type":"object"},"LeafContent":{"properties":{"field":{"type":"string"},"value":{}},"type":"object"},"ListBody":{"description":"Body of a list request","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"selected_fields":{"items":{"type":"string"},"type":"array","uniqueItems":false},"sort":{"items":{"$ref":"#/components/schemas/SortBody"},"type":"array","uniqueItems":false},"sqon":{"$ref":"#/components/schemas/Sqon"}},"type":"object"},"Occurrence":{"description":"Occurrence represents an occurrence","properties":{"ad_ratio":{"type":"number"},"af":{"type":"number"},"canonical":{"type":"boolean"},"chromosome":{"type":"string"},"clinvar":{"items":{"type":"string"},"type":"array","uniqueItems":false},"filter":{"type":"string"},"genotype_quality":{"type":"integer"},"gnomad_v3_af":{"type":"number"},"hgvsg":{"type":"string"},"locus_id":{"type":"integer"},"mane_select":{"type":"boolean"},"omim_inheritance_code":{"type":"string"},"pf":{"type":"number"},"seq_id":{"type":"integer"},"symbol":{"type":"string"},"variant_class":{"type":"string"},"vep_impact":{"type":"string"},"zygosity":{"type":"string"}},"type":"object"},"PubmedCitation":{"properties":{"id":{"type":"string"},"nlm":{"$ref":"#/components/schemas/PubmedCitationDetails"}},"type":"object"},"PubmedCitationDetails":{"properties":{"format":{"type":"string"}},"type":"object"},"SortBody":{"properties":{"field":{"type":"string"},"order":{"enum":["asc","desc"],"type":"string"}},"type":"object"},"Sqon":{"properties":{"content":{"oneOf":[{"$ref":"#/components/schemas/LeafContent"},{"items":{"$ref":"#/components/schemas/Sqon"},"type":"array"}]},"op":{"enum":["in","and","or","not","between","\u003e","\u003c","\u003e=","\u003c=","not-in","all"],"type":"string"}},"type":"object"},"UserSet":{"properties":{"active":{"type":"boolean"},"id":{"type":"string"},"ids":{"items":{"type":"string"},"type":"array","uniqueItems":false},"name":{"type":"string"},"type":{"type":"string"},"updated_at":{"type":"string"},"user_id":{"type":"string"}},"type":"object"}},"securitySchemes":{"bearerauth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"}}}, "info": {"description":"This is the API for Radiant data platform.","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"Radiant API","version":"1.0"}, "externalDocs": {"description":"","url":""}, - "paths": {"/interpretations/germline/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation germline","operationId":"GetInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation germline","tags":["interpretations"]},"post":{"description":"Create or Update interpretation germline","operationId":"PostInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation germline","tags":["interpretations"]}},"/interpretations/pubmed/{citation_id}":{"get":{"description":"Get pubmed citation by ID","operationId":"GetPubmedCitation","parameters":[{"description":"Citation ID","in":"path","name":"citation_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PubmedCitation"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get pubmed citation by ID","tags":["interpretations"]}},"/interpretations/somatic/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation somatic","operationId":"GetInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation somatic","tags":["interpretations"]},"post":{"description":"Create or Update interpretation somatic","operationId":"PostInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation somatic","tags":["interpretations"]}},"/occurrences/{seq_id}/aggregate":{"post":{"description":"Aggregate occurrences for a given sequence ID","operationId":"aggregateOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregationBody"}}},"description":"Aggregation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Aggregation"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Aggregate occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/count":{"post":{"description":"Counts occurrences for a given sequence ID","operationId":"countOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountBody"}}},"description":"Count Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Count"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Count occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/list":{"post":{"description":"List occurrences for a given sequence ID","operationId":"listOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBody"}}},"description":"List Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Occurrence"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"List occurrences","tags":["occurrences"]}},"/status":{"get":{"description":"Returns the current status of the API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Get API status","tags":["status"]}}}, + "paths": {"/interpretations/germline/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation germline","operationId":"GetInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation germline","tags":["interpretations"]},"post":{"description":"Create or Update interpretation germline","operationId":"PostInterpretationGermline","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationGermline"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation germline","tags":["interpretations"]}},"/interpretations/pubmed/{citation_id}":{"get":{"description":"Get pubmed citation by ID","operationId":"GetPubmedCitation","parameters":[{"description":"Citation ID","in":"path","name":"citation_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PubmedCitation"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get pubmed citation by ID","tags":["interpretations"]}},"/interpretations/somatic/{sequencing_id}/{locus_id}/{transcript_id}":{"get":{"description":"Get interpretation somatic","operationId":"GetInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get interpretation somatic","tags":["interpretations"]},"post":{"description":"Create or Update interpretation somatic","operationId":"PostInterpretationSomatic","parameters":[{"description":"Sequencing ID","in":"path","name":"sequencing_id","required":true,"schema":{"type":"string"}},{"description":"Locus ID","in":"path","name":"locus_id","required":true,"schema":{"type":"string"}},{"description":"Transcript ID","in":"path","name":"transcript_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"Interpretation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretationSomatic"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Create or Update interpretation somatic","tags":["interpretations"]}},"/occurrences/{seq_id}/aggregate":{"post":{"description":"Aggregate occurrences for a given sequence ID","operationId":"aggregateOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregationBody"}}},"description":"Aggregation Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Aggregation"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Aggregate occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/count":{"post":{"description":"Counts occurrences for a given sequence ID","operationId":"countOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountBody"}}},"description":"Count Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Count"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"Count occurrences","tags":["occurrences"]}},"/occurrences/{seq_id}/list":{"post":{"description":"List occurrences for a given sequence ID","operationId":"listOccurrences","parameters":[{"description":"Sequence ID","in":"path","name":"seq_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBody"}}},"description":"List Body","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Occurrence"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad Request"}},"security":[{"bearerauth":[]}],"summary":"List occurrences","tags":["occurrences"]}},"/status":{"get":{"description":"Returns the current status of the API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Get API status","tags":["status"]}},"/users/sets/{user_set_id}":{"get":{"description":"Get user set","operationId":"GetUserSet","parameters":[{"description":"UserSet ID","in":"path","name":"user_set_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserSet"}}},"description":"OK"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not Found"}},"security":[{"bearerauth":[]}],"summary":"Get user set by id","tags":["user_sets"]}}}, "openapi": "3.1.0", "servers": [ {"url":"/"} diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 8988995..254f57c 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -213,6 +213,26 @@ components: - all type: string type: object + UserSet: + properties: + active: + type: boolean + id: + type: string + ids: + items: + type: string + type: array + uniqueItems: false + name: + type: string + type: + type: string + updated_at: + type: string + user_id: + type: string + type: object securitySchemes: bearerauth: bearerFormat: JWT @@ -576,5 +596,36 @@ paths: summary: Get API status tags: - status + /users/sets/{user_set_id}: + get: + description: Get user set + operationId: GetUserSet + parameters: + - description: UserSet ID + in: path + name: user_set_id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/UserSet' + description: OK + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Not Found + security: + - bearerauth: [] + summary: Get user set by id + tags: + - user_sets servers: - url: / diff --git a/backend/internal/repository/postgres_repository.go b/backend/internal/repository/postgres_repository.go index 21bff04..a0cf85b 100644 --- a/backend/internal/repository/postgres_repository.go +++ b/backend/internal/repository/postgres_repository.go @@ -8,8 +8,9 @@ import ( ) type PostgresRepository struct { - db *gorm.DB + db *gorm.DB Interpretations *InterpretationsRepository + UserSets *UserSetsRepository } type PostgresDAO interface { @@ -17,7 +18,7 @@ type PostgresDAO interface { } func NewPostgresRepository(db *gorm.DB, pubmedClient client.PubmedClientService) *PostgresRepository { - return &PostgresRepository{db: db, Interpretations: NewInterpretationsRepository(db, pubmedClient)} + return &PostgresRepository{db: db, Interpretations: NewInterpretationsRepository(db, pubmedClient), UserSets: NewUserSetsRepository(db)} } func (r *PostgresRepository) CheckDatabaseConnection() string { diff --git a/backend/internal/repository/user_sets.go b/backend/internal/repository/user_sets.go new file mode 100644 index 0000000..d0cbb7a --- /dev/null +++ b/backend/internal/repository/user_sets.go @@ -0,0 +1,101 @@ +package repository + +import ( + "errors" + "fmt" + "github.com/Ferlab-Ste-Justine/radiant-api/internal/types" + "gorm.io/gorm" +) + +type UserSetsRepository struct { + db *gorm.DB +} + +type UserSetsDAO interface { + GetUserSet(userSetId string) (*types.UserSet, error) +} + +func NewUserSetsRepository(db *gorm.DB) *UserSetsRepository { + return &UserSetsRepository{db: db} +} + +func (r *UserSetsRepository) GetUserSet(userSetId string) (*types.UserSet, error) { + var dao types.UserSetDAO + if result := r.db. + Table(types.UserSetTable.Name). + Preload("ParticipantIds"). + Preload("FileIds"). + Preload("BiospecimenIds"). + Preload("VariantIds"). + Where("id = ?", userSetId). + First(&dao); result.Error != nil { + err := result.Error + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("error while fetching user set: %w", err) + } else { + return nil, nil + } + } + mapped, err := r.mapToUserSet(&dao) + if err != nil { + return nil, err + } + return mapped, nil +} + +func (r *UserSetsRepository) mapToUserSet(dao *types.UserSetDAO) (*types.UserSet, error) { + ids := make([]string, 0) + if len(dao.ParticipantIds) > 0 { + ids = r.ParticipantIds(dao.ParticipantIds) + } else if len(dao.FileIds) > 0 { + ids = r.FileIds(dao.FileIds) + } else if len(dao.BiospecimenIds) > 0 { + ids = r.BiospecimenIds(dao.BiospecimenIds) + } else if len(dao.VariantIds) > 0 { + ids = r.VariantIds(dao.VariantIds) + } + + userSet := &types.UserSet{ + ID: dao.ID, + UserId: dao.UserId, + Name: dao.Name, + Type: dao.Type, + Active: dao.Active, + CreatedAt: dao.CreatedAt, + UpdatedAt: dao.UpdatedAt, + Ids: ids, + } + return userSet, nil +} + +func (r *UserSetsRepository) ParticipantIds(dao []types.UserSetParticipantDAO) []string { + var list []string + for _, elem := range dao { + list = append(list, elem.ParticipantId) + } + return list +} + +func (r *UserSetsRepository) FileIds(dao []types.UserSetFileDAO) []string { + var list []string + for _, elem := range dao { + list = append(list, elem.FileId) + } + return list +} + +func (r *UserSetsRepository) BiospecimenIds(dao []types.UserSetBiospecimenDAO) []string { + var list []string + for _, elem := range dao { + list = append(list, elem.BiospecimenId) + } + return list +} + +func (r *UserSetsRepository) VariantIds(dao []types.UserSetVariantDAO) []string { + var list []string + for _, elem := range dao { + list = append(list, elem.VariantId) + } + return list +} diff --git a/backend/internal/server/handlers.go b/backend/internal/server/handlers.go index 6f7500f..74301cb 100644 --- a/backend/internal/server/handlers.go +++ b/backend/internal/server/handlers.go @@ -182,6 +182,11 @@ func extractInterpretationParams(c *gin.Context) (string, string, string) { return sequencingId, locusId, transcriptId } +func extractUserSetParams(c *gin.Context) string { + userSetId := c.Param("user_set_id") + return userSetId +} + func fillInterpretationCommonWithContext(c *gin.Context, interpretation *types.InterpretationCommon) { sequencingId, locusId, transcriptId := extractInterpretationParams(c) @@ -222,7 +227,7 @@ func GetInterpretationGermline(repo repository.InterpretationsDAO) gin.HandlerFu c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } - if (interpretation == nil) { + if interpretation == nil { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } @@ -290,7 +295,7 @@ func GetInterpretationSomatic(repo repository.InterpretationsDAO) gin.HandlerFun c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } - if (interpretation == nil) { + if interpretation == nil { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } @@ -355,10 +360,37 @@ func GetPubmedCitation(pubmedClient client.PubmedClientService) gin.HandlerFunc c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) return } - if (citation == nil) { + if citation == nil { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } c.JSON(http.StatusOK, citation) } -} \ No newline at end of file +} + +// GetUserSet +// @Summary Get user set by id +// @Id GetUserSet +// @Description Get user set +// @Tags user_sets +// @Security bearerauth +// @Param user_set_id path string true "UserSet ID" +// @Produce json +// @Success 200 {object} types.UserSet +// @Failure 404 {object} map[string]string +// @Router /users/sets/{user_set_id} [get] +func GetUserSet(repo repository.UserSetsDAO) gin.HandlerFunc { + return func(c *gin.Context) { + userSetId := extractUserSetParams(c) + userSet, err := repo.GetUserSet(userSetId) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) + return + } + if userSet == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) + return + } + c.JSON(http.StatusOK, userSet) + } +} diff --git a/backend/internal/server/handlers_user_sets_test.go b/backend/internal/server/handlers_user_sets_test.go new file mode 100644 index 0000000..72d37f5 --- /dev/null +++ b/backend/internal/server/handlers_user_sets_test.go @@ -0,0 +1,57 @@ +package server + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Ferlab-Ste-Justine/radiant-api/internal/types" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func (m *MockRepository) GetUserSet(userSetId string) (*types.UserSet, error) { + if userSetId == "set1" { + return &types.UserSet{ + ID: userSetId, + UserId: "user1", + Name: "set_name", + Type: "set_type", + Active: true, + UpdatedAt: time.Date( + 2000, 1, 1, 0, 0, 0, 0, time.UTC), + }, nil + } else if userSetId == "set2" { + return nil, fmt.Errorf("error") + } + return nil, nil +} + +func assertGetUserSet(t *testing.T, userSetId string, status int, expected string) { + repo := &MockRepository{} + router := gin.Default() + router.GET("/users/sets/:user_set_id", GetUserSet(repo)) + + req, _ := http.NewRequest("GET", fmt.Sprintf("/users/sets/%s", userSetId), bytes.NewBuffer([]byte("{}"))) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, status, w.Code) + assert.JSONEq(t, expected, w.Body.String()) +} + +func Test_GetUserSet_ok(t *testing.T) { + assertGetUserSet(t, "set1", http.StatusOK, `{"id":"set1", "user_id":"user1", "name":"set_name", "type": "set_type", "active":true, "updated_at": "2000-01-01T00:00:00Z"}`) +} + +func Test_GetUserSet_error(t *testing.T) { + assertGetUserSet(t, "set2", http.StatusInternalServerError, `{"error":"internal server error"}`) +} + +func Test_GetUserSet_notFound(t *testing.T) { + assertGetUserSet(t, "set3", http.StatusNotFound, `{"error":"not found"}`) +} diff --git a/backend/internal/types/user_set.go b/backend/internal/types/user_set.go index c69280e..710bcab 100644 --- a/backend/internal/types/user_set.go +++ b/backend/internal/types/user_set.go @@ -4,6 +4,30 @@ import ( "time" ) +type Tabler interface { + TableName() string +} + +func (UserSetDAO) TableName() string { + return UserSetTable.Name +} + +func (UserSetParticipantDAO) TableName() string { + return UserSetParticipantTable.Name +} + +func (UserSetFileDAO) TableName() string { + return UserSetFileTable.Name +} + +func (UserSetBiospecimenDAO) TableName() string { + return UserSetBiospecimenTable.Name +} + +func (UserSetVariantDAO) TableName() string { + return UserSetVariantTable.Name +} + type UserSet struct { ID string `json:"id,omitempty"` UserId string `json:"user_id,omitempty"` @@ -35,6 +59,26 @@ var UserSetVariantTable = Table{ Name: "user_set_variant", } +type UserSetParticipantDAO struct { + UserSetId string + ParticipantId string +} + +type UserSetFileDAO struct { + UserSetId string + FileId string +} + +type UserSetBiospecimenDAO struct { + UserSetId string + BiospecimenId string +} + +type UserSetVariantDAO struct { + UserSetId string + VariantId string +} + type UserSetDAO struct { ID string `gorm:"primary_key; unique; type:uuid; default:gen_random_uuid()"` UserId string @@ -43,8 +87,8 @@ type UserSetDAO struct { Active bool CreatedAt time.Time UpdatedAt time.Time - ParticipantIds []string `gorm:"many2many:user_set_participant;"` - FileIds []string `gorm:"many2many:user_set_file;"` - BiospecimenIds []string `gorm:"many2many:user_set_biospecimen;"` - VariantIds []string `gorm:"many2many:user_set_variant;"` + ParticipantIds []UserSetParticipantDAO `gorm:"foreignKey:UserSetId"` + FileIds []UserSetFileDAO `gorm:"foreignKey:UserSetId"` + BiospecimenIds []UserSetBiospecimenDAO `gorm:"foreignKey:UserSetId"` + VariantIds []UserSetVariantDAO `gorm:"foreignKey:UserSetId"` } diff --git a/backend/scripts/init-sql/migrations/000002_create_user_set_tables.down.sql b/backend/scripts/init-sql/migrations/000002_create_user_set_tables.down.sql new file mode 100644 index 0000000..7e0f4fd --- /dev/null +++ b/backend/scripts/init-sql/migrations/000002_create_user_set_tables.down.sql @@ -0,0 +1,5 @@ +DROP TABLE user_set; +DROP TABLE user_set_participant; +DROP TABLE user_set_file; +DROP TABLE user_set_biospecimen; +DROP TABLE user_set_variant; \ No newline at end of file diff --git a/backend/scripts/init-sql/migrations/000002_create_user_set_tables.up.sql b/backend/scripts/init-sql/migrations/000002_create_user_set_tables.up.sql new file mode 100644 index 0000000..b38c176 --- /dev/null +++ b/backend/scripts/init-sql/migrations/000002_create_user_set_tables.up.sql @@ -0,0 +1,50 @@ +CREATE TABLE user_set ( + id uuid DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(255) NOT NULL, + name TEXT NOT NULL, + type TEXT NOT NULL, + active BOOLEAN NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); +ALTER TABLE user_set ADD PRIMARY KEY (id); + +CREATE TABLE user_set_participant ( + user_set_id uuid NOT NULL, + participant_id VARCHAR(255) NOT NULL +); +ALTER TABLE user_set_participant ADD PRIMARY KEY (user_set_id, participant_id); +ALTER TABLE user_set_participant + ADD CONSTRAINT fk_user_set_participant_user_set + FOREIGN KEY (user_set_id) + REFERENCES user_set (id); + +CREATE TABLE user_set_file ( + user_set_id uuid NOT NULL, + file_id VARCHAR(255) NOT NULL +); +ALTER TABLE user_set_file ADD PRIMARY KEY (user_set_id, file_id); +ALTER TABLE user_set_file + ADD CONSTRAINT fk_user_set_file_user_set + FOREIGN KEY (user_set_id) + REFERENCES user_set (id); + +CREATE TABLE user_set_biospecimen ( + user_set_id uuid NOT NULL, + biospecimen_id VARCHAR(255) NOT NULL +); +ALTER TABLE user_set_biospecimen ADD PRIMARY KEY (user_set_id, biospecimen_id); +ALTER TABLE user_set_biospecimen + ADD CONSTRAINT fk_user_set_biospecimen_user_set + FOREIGN KEY (user_set_id) + REFERENCES user_set (id); + +CREATE TABLE user_set_variant ( + user_set_id uuid NOT NULL, + variant_id VARCHAR(255) NOT NULL +); +ALTER TABLE user_set_variant ADD PRIMARY KEY (user_set_id, variant_id); +ALTER TABLE user_set_variant + ADD CONSTRAINT fk_user_set_variant_user_set + FOREIGN KEY (user_set_id) + REFERENCES user_set (id); \ No newline at end of file diff --git a/frontend/api/api.ts b/frontend/api/api.ts index c705e8a..33f7ed2 100644 --- a/frontend/api/api.ts +++ b/frontend/api/api.ts @@ -518,6 +518,55 @@ export type SqonOpEnum = typeof SqonOpEnum[keyof typeof SqonOpEnum]; */ export type SqonContent = Array | LeafContent; +/** + * + * @export + * @interface UserSet + */ +export interface UserSet { + /** + * + * @type {boolean} + * @memberof UserSet + */ + 'active'?: boolean; + /** + * + * @type {string} + * @memberof UserSet + */ + 'id'?: string; + /** + * + * @type {Array} + * @memberof UserSet + */ + 'ids'?: Array; + /** + * + * @type {string} + * @memberof UserSet + */ + 'name'?: string; + /** + * + * @type {string} + * @memberof UserSet + */ + 'type'?: string; + /** + * + * @type {string} + * @memberof UserSet + */ + 'updated_at'?: string; + /** + * + * @type {string} + * @memberof UserSet + */ + 'user_id'?: string; +} /** * InterpretationsApi - axios parameter creator @@ -1383,3 +1432,115 @@ export class StatusApi extends BaseAPI { +/** + * UserSetsApi - axios parameter creator + * @export + */ +export const UserSetsApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Get user set + * @summary Get user set by id + * @param {string} userSetId UserSet ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getUserSet: async (userSetId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'userSetId' is not null or undefined + assertParamExists('getUserSet', 'userSetId', userSetId) + const localVarPath = `/users/sets/{user_set_id}` + .replace(`{${"user_set_id"}}`, encodeURIComponent(String(userSetId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerauth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * UserSetsApi - functional programming interface + * @export + */ +export const UserSetsApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = UserSetsApiAxiosParamCreator(configuration) + return { + /** + * Get user set + * @summary Get user set by id + * @param {string} userSetId UserSet ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getUserSet(userSetId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getUserSet(userSetId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['UserSetsApi.getUserSet']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * UserSetsApi - factory interface + * @export + */ +export const UserSetsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = UserSetsApiFp(configuration) + return { + /** + * Get user set + * @summary Get user set by id + * @param {string} userSetId UserSet ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getUserSet(userSetId: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getUserSet(userSetId, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * UserSetsApi - object-oriented interface + * @export + * @class UserSetsApi + * @extends {BaseAPI} + */ +export class UserSetsApi extends BaseAPI { + /** + * Get user set + * @summary Get user set by id + * @param {string} userSetId UserSet ID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UserSetsApi + */ + public getUserSet(userSetId: string, options?: RawAxiosRequestConfig) { + return UserSetsApiFp(this.configuration).getUserSet(userSetId, options).then((request) => request(this.axios, this.basePath)); + } +} + + +