diff --git a/.gitignore b/.gitignore index 1f6cf5ae..cfce715e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ logback.xml *.DS_Store web/src/main/javascript/public/ web/src/main/resources/static -web/src/main/go/docs \ No newline at end of file +web/src/main/go/docs +trees/oncotree_development.json.etag +web/src/main/javascript/public/assets \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 3df2df68..05e4c124 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,4 +31,5 @@ ENV STATIC_DIR=/root/backend/frontend/static EXPOSE 8080 ENV GIN_MODE=release +ENV APP_ENV=production CMD ["./main"] \ No newline at end of file diff --git a/web/src/main/go/cmd/server/main.go b/web/src/main/go/cmd/server/main.go index 70b5a7d2..bfc7cb76 100644 --- a/web/src/main/go/cmd/server/main.go +++ b/web/src/main/go/cmd/server/main.go @@ -298,12 +298,13 @@ func tumorTypesTreeHandler(c *gin.Context) { treeFile := fmt.Sprintf("%s.json", resolvedVersion) - raw, err := os.ReadFile(filepath.Join(TreeDir, treeFile)) + raw, err := internal.ReadTreeRaw(treeFile) if err != nil { c.Error(fmt.Errorf("Failed to read raw tree file '%s': %w", treeFile, err)) c.String(http.StatusServiceUnavailable, "Required data source unavailable") return } + c.Data(http.StatusOK, "application/json", raw) } diff --git a/web/src/main/go/internal/constants.go b/web/src/main/go/internal/constants.go index 5e3b05af..9d9a676a 100644 --- a/web/src/main/go/internal/constants.go +++ b/web/src/main/go/internal/constants.go @@ -19,6 +19,10 @@ func init() { TSV_FILES_PATH = filepath.Join(TREE_FILES_PATH, "tsv") } +const ( + DEV_TREE_GITHUB_RAW_URL = "https://raw.githubusercontent.com/cBioPortal/oncotree/master/trees/oncotree_development.json" +) + const ( LEGACY_TREE_IDENTIFIER = "oncotree_legacy_1.1" CANDIDATE_TREE_IDENTIFIER = "oncotree_candidate_release" diff --git a/web/src/main/go/internal/utils.go b/web/src/main/go/internal/utils.go index 79284074..12c3192c 100644 --- a/web/src/main/go/internal/utils.go +++ b/web/src/main/go/internal/utils.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "path/filepath" "regexp" @@ -17,7 +18,7 @@ import ( ) func ReadTreeFromFile(name string) (Tree, error) { - treeBytes, err := os.ReadFile(GetTreeFilepath(name)) + treeBytes, err := ReadTreeRaw(name) if err != nil { return nil, fmt.Errorf("error reading file '%v': %v", name, err) } @@ -202,3 +203,72 @@ func (file *DatedFile) GetDatedFilenameWithoutExtension() string { name := strings.Replace(file.Name, ".txt", "", 1) return strings.Replace(name, ".json", "", 1) } + +func fetchDevTreeIfChanged(devTreePath string) error { + cacheDir := filepath.Join(TREE_FILES_PATH, "..", "cache") + if err := os.MkdirAll(cacheDir, 0755); err != nil { + return fmt.Errorf("failed to create cache dir: %w", err) + } + + base := filepath.Base(devTreePath) + tmpPath := filepath.Join(cacheDir, base+".tmp") + etagPath := filepath.Join(cacheDir, base+".etag") + + req, err := http.NewRequest(http.MethodGet, DEV_TREE_GITHUB_RAW_URL, nil) + if err != nil { + return err + } + + if etag, err := os.ReadFile(etagPath); err == nil { + req.Header.Set("If-None-Match", string(etag)) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusNotModified: + return nil + + case http.StatusOK: + out, err := os.Create(tmpPath) + if err != nil { + return err + } + defer out.Close() + + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + + if err := os.Rename(tmpPath, devTreePath); err != nil { + return err + } + + if etag := resp.Header.Get("ETag"); etag != "" { + if err := os.WriteFile(etagPath, []byte(etag), 0644); err != nil { + return fmt.Errorf("failed to write etag: %w", err) + } + } + + return nil + + default: + return fmt.Errorf("unexpected status from GitHub: %s", resp.Status) + } +} + +func ReadTreeRaw(name string) ([]byte, error) { + appEnv := os.Getenv("APP_ENV") + if name == DEV_TREE_IDENTIFIER+".json" && appEnv == "production" { + devTreePath := filepath.Join(TREE_FILES_PATH, name) + if err := fetchDevTreeIfChanged(devTreePath); err != nil { + return nil, err + } + } + + return os.ReadFile(GetTreeFilepath(name)) +} diff --git a/web/src/main/javascript/public/assets/news.md.Bh7u_kab.js b/web/src/main/javascript/public/assets/news.md.Bh7u_kab.js deleted file mode 100644 index 7d9a5551..00000000 --- a/web/src/main/javascript/public/assets/news.md.Bh7u_kab.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as l,c as a,o as r,a3 as i,j as e,a as o}from"./chunks/framework.CyEiTwkJ.js";const y=JSON.parse('{"title":"News","description":"","frontmatter":{},"headers":[],"relativePath":"news.md","filePath":"news.md"}'),t={name:"news.md"},n=i('

News

October 9, 2025

April 8, 2025

November 2, 2021

November 4, 2020

October 1, 2020

April 1, 2020

February 6, 2020

February 1, 2020

December 1, 2019

August 1, 2019

May 2, 2019

May 1, 2019

March 26, 2019

March 14, 2019

March 1, 2019

February 1, 2019

November 1, 2018

September 1, 2018

August 1, 2018

July 1, 2018

June 15, 2018

June 1, 2018

May 1, 2018

April 23, 2018

',48),s=e("ul",null,[e("li",null,[e("strong",null,"New Web API Version Available"),e("ul",null,[e("li",null,[o("A new version (v1.0.0) of the OncoTree Web API is available. It can be explored here: "),e("a",{href:"http://oncotree.mskcc.org/swagger-ui.html",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/swagger-ui.html"),o(),e("br"),o(" The previous version is still available, but is scheduled to be discontinued May 31, 2018 You can continue to access the previous version (v0.0.1) in its original location summarized here: ~~"),e("a",{href:"http://oncotree.mskcc.org/oncotree/swagger-ui.html~~",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/oncotree/swagger-ui.html~~")])])]),e("li",null,[e("strong",null,"Details and Migration Guidance"),e("ul",null,[e("li",null,[o("The base URL for accessing all API functionality is being simplified from ~~"),e("a",{href:"http://oncotree.mskcc.org/oncotree/~~",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/oncotree/~~"),o(" to "),e("a",{href:"http://oncotree.mskcc.org/",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/")]),e("li",null,[e("span",{class:"oi oi-warning text-danger","aria-hidden":"true"}),o(" The /api/tumor_types.txt endpoint is now deprecated. It is scheduled for deletion as part of the next API version release.")]),e("li",null,[o("Most endpoint paths in the API remain the same and provide the same services. Exceptions are: "),e("ul",null,[e("li",null,'/api/tumorTypes used to accept a query parameter ("flat") which controlled the output format for receiving a tree representation or a flat representation of the full set of TumorTypes. Now this endpoint always returns a flat list of all TumorTypes and a new endpoint path (/api/tumorTypes/tree) is used to retrieve a tree representation of the OncoTree. Previous requests which included "flat=false" should be adjusted to use the /api/tumorTypes/tree endpoint. Otherwise "flat=true" should be dropped from the request.'),e("li",null,'/api/tumorTypes used to accept a query parameter ("deprecated") which is no longer recognized. This parameter should be dropped from requests. Deprecated OncoTree codes can instead be found in the history attribute of the response.'),e("li",null,"the POST request endpoint (/api/tumorTypes/search) which accepted a list of TumorType queries has been deprecated and is no longer available through the swagger-ui interface. The GET request endpoint /api/tumorTypes/search/{type}/{query} remains available as before. If you previously submitted an array of query requests, you should iterate through the array and call the GET request endpoint to make one query per request.")])]),e("li",null,[o("The output format (schema) of many endpoints has been simplified. You will need to adjust your result handling accordingly. Changes include: "),e("ul",null,[e("li",null,'responses no longer include a "meta" element with associated code and error messages. Instead HTTP status codes are set appropriately and error messages are supplied in message bodies. Responses also no longer contain a "data" element. Objects representing the API output are directly returned instead.'),e("li",null,"MainType values are no longer modeled as objects. Each MainType value is now represented as a simple string. The /api/mainTypes endpoint now returns an array of strings rather than an object mapping MainType names to MainType objects."),e("li",{"UMLS:":"","[CL497188,C1510796],NCI:":"","[C123384,C40361]":""},'TumorType values no longer contain elements "id", "deprecated", "links", "NCI", "UMLS". A new element ("externalReferences") has been added which contains a JSON object mapping external authority names to arrays of associated identifiers. Such as "externalReferences":')])]),e("li",null,'Argument validation has been strengthened for several parameters, such as "type" and "levels" in the /api/tumorTypes/search/{type}/{query} endpoint. Now improper arguments cause an a HTTP status response indicating error, with a description of the problem in the body.'),e("li",null,[o("Some requests which fail to find matching entities now return NOT_FOUND HTTP status code 404 rather than an empty result. Examples: "),e("a",{href:"http://oncotree.mskcc.org/api/tumorTypes/search/code/TEST_UNDEFINED_CODE",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/api/tumorTypes/search/code/TEST_UNDEFINED_CODE"),o(" or "),e("a",{href:"http://oncotree.mskcc.org/api/crosswalk?vocabularyId=ICDO&conceptId=C15",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/api/crosswalk?vocabularyId=ICDO&conceptId=C15")])])])],-1),c=i('

April 1, 2018

March 1, 2018

February 7, 2018

February 1, 2018

',8),u=[n,s,c];function d(m,h,p,T,L,b){return r(),a("div",null,u)}const M=l(t,[["render",d]]);export{y as __pageData,M as default}; diff --git a/web/src/main/resources/static/assets/news.md.Bh7u_kab.js b/web/src/main/resources/static/assets/news.md.Bh7u_kab.js deleted file mode 100644 index 7d9a5551..00000000 --- a/web/src/main/resources/static/assets/news.md.Bh7u_kab.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as l,c as a,o as r,a3 as i,j as e,a as o}from"./chunks/framework.CyEiTwkJ.js";const y=JSON.parse('{"title":"News","description":"","frontmatter":{},"headers":[],"relativePath":"news.md","filePath":"news.md"}'),t={name:"news.md"},n=i('

News

October 9, 2025

April 8, 2025

November 2, 2021

November 4, 2020

October 1, 2020

April 1, 2020

February 6, 2020

February 1, 2020

December 1, 2019

August 1, 2019

May 2, 2019

May 1, 2019

March 26, 2019

March 14, 2019

March 1, 2019

February 1, 2019

November 1, 2018

September 1, 2018

August 1, 2018

July 1, 2018

June 15, 2018

June 1, 2018

May 1, 2018

April 23, 2018

',48),s=e("ul",null,[e("li",null,[e("strong",null,"New Web API Version Available"),e("ul",null,[e("li",null,[o("A new version (v1.0.0) of the OncoTree Web API is available. It can be explored here: "),e("a",{href:"http://oncotree.mskcc.org/swagger-ui.html",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/swagger-ui.html"),o(),e("br"),o(" The previous version is still available, but is scheduled to be discontinued May 31, 2018 You can continue to access the previous version (v0.0.1) in its original location summarized here: ~~"),e("a",{href:"http://oncotree.mskcc.org/oncotree/swagger-ui.html~~",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/oncotree/swagger-ui.html~~")])])]),e("li",null,[e("strong",null,"Details and Migration Guidance"),e("ul",null,[e("li",null,[o("The base URL for accessing all API functionality is being simplified from ~~"),e("a",{href:"http://oncotree.mskcc.org/oncotree/~~",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/oncotree/~~"),o(" to "),e("a",{href:"http://oncotree.mskcc.org/",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/")]),e("li",null,[e("span",{class:"oi oi-warning text-danger","aria-hidden":"true"}),o(" The /api/tumor_types.txt endpoint is now deprecated. It is scheduled for deletion as part of the next API version release.")]),e("li",null,[o("Most endpoint paths in the API remain the same and provide the same services. Exceptions are: "),e("ul",null,[e("li",null,'/api/tumorTypes used to accept a query parameter ("flat") which controlled the output format for receiving a tree representation or a flat representation of the full set of TumorTypes. Now this endpoint always returns a flat list of all TumorTypes and a new endpoint path (/api/tumorTypes/tree) is used to retrieve a tree representation of the OncoTree. Previous requests which included "flat=false" should be adjusted to use the /api/tumorTypes/tree endpoint. Otherwise "flat=true" should be dropped from the request.'),e("li",null,'/api/tumorTypes used to accept a query parameter ("deprecated") which is no longer recognized. This parameter should be dropped from requests. Deprecated OncoTree codes can instead be found in the history attribute of the response.'),e("li",null,"the POST request endpoint (/api/tumorTypes/search) which accepted a list of TumorType queries has been deprecated and is no longer available through the swagger-ui interface. The GET request endpoint /api/tumorTypes/search/{type}/{query} remains available as before. If you previously submitted an array of query requests, you should iterate through the array and call the GET request endpoint to make one query per request.")])]),e("li",null,[o("The output format (schema) of many endpoints has been simplified. You will need to adjust your result handling accordingly. Changes include: "),e("ul",null,[e("li",null,'responses no longer include a "meta" element with associated code and error messages. Instead HTTP status codes are set appropriately and error messages are supplied in message bodies. Responses also no longer contain a "data" element. Objects representing the API output are directly returned instead.'),e("li",null,"MainType values are no longer modeled as objects. Each MainType value is now represented as a simple string. The /api/mainTypes endpoint now returns an array of strings rather than an object mapping MainType names to MainType objects."),e("li",{"UMLS:":"","[CL497188,C1510796],NCI:":"","[C123384,C40361]":""},'TumorType values no longer contain elements "id", "deprecated", "links", "NCI", "UMLS". A new element ("externalReferences") has been added which contains a JSON object mapping external authority names to arrays of associated identifiers. Such as "externalReferences":')])]),e("li",null,'Argument validation has been strengthened for several parameters, such as "type" and "levels" in the /api/tumorTypes/search/{type}/{query} endpoint. Now improper arguments cause an a HTTP status response indicating error, with a description of the problem in the body.'),e("li",null,[o("Some requests which fail to find matching entities now return NOT_FOUND HTTP status code 404 rather than an empty result. Examples: "),e("a",{href:"http://oncotree.mskcc.org/api/tumorTypes/search/code/TEST_UNDEFINED_CODE",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/api/tumorTypes/search/code/TEST_UNDEFINED_CODE"),o(" or "),e("a",{href:"http://oncotree.mskcc.org/api/crosswalk?vocabularyId=ICDO&conceptId=C15",target:"_blank",rel:"noreferrer"},"http://oncotree.mskcc.org/api/crosswalk?vocabularyId=ICDO&conceptId=C15")])])])],-1),c=i('

April 1, 2018

March 1, 2018

February 7, 2018

February 1, 2018

',8),u=[n,s,c];function d(m,h,p,T,L,b){return r(),a("div",null,u)}const M=l(t,[["render",d]]);export{y as __pageData,M as default};