diff --git a/Dockerfile b/Dockerfile index 10ef711..7f2d92f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM golang:1.18-alpine AS builder +FROM golang:1.22 AS builder # Set working directory WORKDIR /app @@ -14,10 +14,10 @@ RUN go mod download COPY . . # Build the application -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o dicom-proxy cmd/server/main.go +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o ohif-proxy cmd/server/main.go # Final stage -FROM alpine:3.15 +FROM alpine:3.18 # Install CA certificates for HTTPS connections RUN apk --no-cache add ca-certificates @@ -26,19 +26,14 @@ RUN apk --no-cache add ca-certificates WORKDIR /app # Copy binary from build stage -COPY --from=builder /app/dicom-proxy . +COPY --from=builder /app/ohif-proxy . -# Copy configuration +# Copy configuration and create dirs COPY config/config.yaml ./config/ - -# Create directory for credentials RUN mkdir -p /app/credentials # Expose port -EXPOSE 8080 - -# Set environment variable for config file location -ENV CONFIG_FILE=/app/config/config.yaml +EXPOSE 5555 # Run the application -ENTRYPOINT ["./dicom-proxy"] \ No newline at end of file +CMD ["./ohif-proxy"] \ No newline at end of file diff --git a/cmd/main.go b/cmd/server/main.go similarity index 100% rename from cmd/main.go rename to cmd/server/main.go diff --git a/config/config.yaml b/config/config.yaml index fb4a960..1b6db1a 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,5 +1,5 @@ server: - port: 8080 + port: 5555 read_timeout_seconds: 30 write_timeout_seconds: 30 idle_timeout_seconds: 60 @@ -7,12 +7,11 @@ server: log_level: "info" # debug, info, warn, error google: - project_id: "your-project-id" - location: "asia-southeast2" - dataset: "dicom-storage" - dicom_store: "ohif" + project_id: "ohifproxy" # Replace with your GCP project ID + location: "asia-southeast2" # Match your dataset region + dataset: "sas-storage" # Your dataset name + dicom_store: "store-1" # Your DICOM store name credentials_path: "./credentials/service-account.json" -# CORS settings - origins that are allowed to access the API allowed_origins: - - "*" # Allow all origins (you may want to restrict this in production) \ No newline at end of file + - "*" # For development; restrict this in production \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..ebea860 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,12 @@ +version: '3.8' + +services: + ohif-proxy: + build: . + ports: + - "5555:5555" + volumes: + - ./config:/app/config + - ./credentials:/app/credentials + environment: + - LOG_LEVEL=debug \ No newline at end of file diff --git a/go.mod b/go.mod index 864a1ab..73cab5f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module devone.aplikasi.web.id/gitea/mario/go-ohif-proxy go 1.19 require ( - github.com/gin-gonic/gin v1.9.1 + github.com/go-chi/chi/v5 v5.2.1 + github.com/go-chi/cors v1.2.1 github.com/spf13/viper v1.17.0 go.uber.org/zap v1.27.0 golang.org/x/oauth2 v0.13.0 @@ -13,15 +14,7 @@ require ( require ( cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect @@ -29,14 +22,8 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -45,11 +32,8 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/go.sum b/go.sum index c1744b9..c0a5bd1 100644 --- a/go.sum +++ b/go.sum @@ -42,13 +42,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -68,24 +62,13 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -130,7 +113,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -163,33 +145,19 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -216,22 +184,16 @@ github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -249,9 +211,6 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -388,9 +347,7 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -577,6 +534,5 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/api/handlers/auth.go b/internal/api/handlers/auth.go index 981cc3b..9071f15 100644 --- a/internal/api/handlers/auth.go +++ b/internal/api/handlers/auth.go @@ -1,32 +1,42 @@ package handlers import ( - "github.com/gin-gonic/gin" + "encoding/json" + "net/http" + "go.uber.org/zap" ) -// AuthHandler handles authentication-related requests +// AuthHandler handles authentication requests type AuthHandler struct { logger *zap.Logger } -// NewAuthHandler creates a new AuthHandler +// NewAuthHandler creates a new auth handler func NewAuthHandler(logger *zap.Logger) *AuthHandler { return &AuthHandler{ logger: logger, } } -// Login handles user login -func (h *AuthHandler) Login(c *gin.Context) { - // TODO: Implement login logic - h.logger.Info("Login endpoint hit") - c.JSON(200, gin.H{"message": "Login not implemented"}) +// Login handles user login - placeholder for future implementation +func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { + response := map[string]string{ + "message": "Login functionality will be implemented in a future version", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } -// Logout handles user logout -func (h *AuthHandler) Logout(c *gin.Context) { - // TODO: Implement logout logic - h.logger.Info("Logout endpoint hit") - c.JSON(200, gin.H{"message": "Logout not implemented"}) +// Logout handles user logout - placeholder for future implementation +func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { + response := map[string]string{ + "message": "Logout functionality will be implemented in a future version", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } diff --git a/internal/api/handlers/dicom.go b/internal/api/handlers/dicom.go index cf91139..c378d74 100644 --- a/internal/api/handlers/dicom.go +++ b/internal/api/handlers/dicom.go @@ -4,9 +4,10 @@ import ( "fmt" "io" "net/http" + "strings" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/proxy" - "github.com/gin-gonic/gin" + "github.com/go-chi/chi/v5" "go.uber.org/zap" ) @@ -25,31 +26,49 @@ func NewDicomHandler(client *proxy.Client, logger *zap.Logger) *DicomHandler { } // ForwardRequest forwards the request to Google Healthcare API -func (h *DicomHandler) ForwardRequest(c *gin.Context) { - path := c.Param("path") - requestType := getRequestType(c.Request.URL.Path) +func (h *DicomHandler) ForwardRequest(w http.ResponseWriter, r *http.Request) { + // Get the path after /dicomWeb + urlPath := chi.URLParam(r, "*") + + // If the URL parameter is empty, try to extract it from the URL path + if urlPath == "" { + // Remove /dicomWeb prefix from the URL + prefix := "/dicomWeb" + if strings.HasPrefix(r.URL.Path, prefix) { + urlPath = r.URL.Path[len(prefix):] + } + } h.logger.Debug("Forwarding request", - zap.String("path", path), - zap.String("method", c.Request.Method), - zap.String("type", requestType), + zap.String("path", urlPath), + zap.String("method", r.Method), + zap.String("url", r.URL.String()), ) // Read request body if present var bodyBytes []byte - if c.Request.Body != nil && c.Request.ContentLength > 0 { + if r.Body != nil && r.ContentLength > 0 { var err error - bodyBytes, err = io.ReadAll(c.Request.Body) + bodyBytes, err = io.ReadAll(r.Body) if err != nil { h.logger.Error("Failed to read request body", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read request body"}) + http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } } + // Copy query parameters + queryParams := r.URL.Query().Encode() + if queryParams != "" { + if !strings.HasPrefix(urlPath, "/") { + urlPath = "/" + urlPath + } + urlPath = urlPath + "?" + queryParams + } + // Get request headers headers := make(map[string]string) - for k, v := range c.Request.Header { + for k, v := range r.Header { if len(v) > 0 { headers[k] = v[0] } @@ -57,45 +76,25 @@ func (h *DicomHandler) ForwardRequest(c *gin.Context) { // Forward the request to Healthcare API response, err := h.client.ForwardRequest( - c.Request.Context(), - c.Request.Method, - requestType, - path, + r.Context(), + r.Method, + urlPath, headers, bodyBytes, ) if err != nil { h.logger.Error("Request forwarding failed", zap.Error(err)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Request failed: %v", err)}) + http.Error(w, fmt.Sprintf("Request failed: %v", err), http.StatusInternalServerError) return } // Set response headers for k, v := range response.Headers { - c.Header(k, v) + w.Header().Set(k, v) } // Set status and write response body - c.Status(response.StatusCode) - c.Writer.Write(response.Body) -} - -// getRequestType determines if the request is WADO, QIDO or STOW -func getRequestType(path string) string { - if len(path) < 9 { - return "unknown" - } - - pathComponent := path[9:] // Remove "/dicomWeb/" - - if len(pathComponent) >= 4 && pathComponent[:4] == "wado" { - return "wado" - } else if len(pathComponent) >= 4 && pathComponent[:4] == "qido" { - return "qido" - } else if len(pathComponent) >= 4 && pathComponent[:4] == "stow" { - return "stow" - } - - return "unknown" + w.WriteHeader(response.StatusCode) + w.Write(response.Body) } diff --git a/internal/api/handlers/healthcheck.go b/internal/api/handlers/healthcheck.go index 258959c..64d1cc3 100644 --- a/internal/api/handlers/healthcheck.go +++ b/internal/api/handlers/healthcheck.go @@ -1,12 +1,19 @@ package handlers import ( + "encoding/json" "net/http" - - "github.com/gin-gonic/gin" + "time" ) -// HealthCheck is a simple health check handler -func HealthCheck(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "ok"}) +// HealthCheck provides a simple health check endpoint +func HealthCheck(w http.ResponseWriter, r *http.Request) { + response := map[string]interface{}{ + "status": "ok", + "timestamp": time.Now().Format(time.RFC3339), + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } diff --git a/internal/api/middleware/logging.go b/internal/api/middleware/logging.go index 66ec9b3..bd593de 100644 --- a/internal/api/middleware/logging.go +++ b/internal/api/middleware/logging.go @@ -1,94 +1,61 @@ package middleware import ( + "net/http" "time" - "github.com/gin-gonic/gin" + "github.com/go-chi/chi/v5/middleware" "go.uber.org/zap" ) // Logger middleware adds request logging -func Logger(logger *zap.Logger) gin.HandlerFunc { - return func(c *gin.Context) { - start := time.Now() - path := c.Request.URL.Path - query := c.Request.URL.RawQuery +func Logger(logger *zap.Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() - // Process request - c.Next() + // Create a wrapped response writer to capture the status code + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) - // Calculate request time - latency := time.Since(start) + // Process request + next.ServeHTTP(ww, r) - // Get status - status := c.Writer.Status() + // Calculate request time + latency := time.Since(start) - // Log request details - logger.Info("API Request", - zap.String("method", c.Request.Method), - zap.String("path", path), - zap.String("query", query), - zap.Int("status", status), - zap.Duration("latency", latency), - zap.String("ip", c.ClientIP()), - zap.String("user-agent", c.Request.UserAgent()), - ) + // Log request details + logger.Info("API Request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("query", r.URL.RawQuery), + zap.Int("status", ww.Status()), + zap.Duration("latency", latency), + zap.String("ip", r.RemoteAddr), + zap.String("user-agent", r.UserAgent()), + zap.Int("bytes", ww.BytesWritten()), + ) + }) } } // AuditLog middleware records detailed information about DICOM requests -func AuditLog(logger *zap.Logger) gin.HandlerFunc { - return func(c *gin.Context) { - // We'll extract user info here when auth is implemented - userID := "anonymous" - if id, exists := c.Get("userID"); exists { - userID = id.(string) - } +func AuditLog(logger *zap.Logger) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Extract user info (placeholder for now) + userID := "TODO: userID" - path := c.Request.URL.Path - method := c.Request.Method + // Process request + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + next.ServeHTTP(ww, r) - // Process request - c.Next() - - // Audit log after request completes - logger.Info("DICOM Access", - zap.String("userID", userID), - zap.String("action", method), - zap.String("resource", path), - zap.Int("status", c.Writer.Status()), - ) - } -} - -// CORS middleware to handle cross-origin requests -func CORS(allowedOrigins []string) gin.HandlerFunc { - return func(c *gin.Context) { - origin := c.Request.Header.Get("Origin") - - // Check if origin is allowed - allowed := false - for _, o := range allowedOrigins { - if o == "*" || o == origin { - allowed = true - break - } - } - - // Set CORS headers if allowed - if allowed { - c.Header("Access-Control-Allow-Origin", origin) - c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, Authorization") - c.Header("Access-Control-Allow-Credentials", "true") - } - - // Handle preflight requests - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(204) - return - } - - c.Next() + // Audit log after request completes + logger.Info("DICOM Access", + zap.String("userID", userID), + zap.String("action", r.Method), + zap.String("resource", r.URL.Path), + zap.Int("status", ww.Status()), + ) + }) } } diff --git a/internal/api/routes.go b/internal/api/routes.go index d179a66..b3c9a2b 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -1,36 +1,44 @@ package api import ( + "net/http" + "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/config" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/handlers" - "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/middleware" + apiMiddleware "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/middleware" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth" "devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/proxy" - "github.com/gin-gonic/gin" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" "go.uber.org/zap" ) -// TODO: Refactor dari gin dengan chi - // SetupRouter configures and returns the API router -func SetupRouter(cfg *config.Config, logger *zap.Logger) *gin.Engine { - // Set Gin mode - if cfg.LogLevel == "debug" { - gin.SetMode(gin.DebugMode) - } else { - gin.SetMode(gin.ReleaseMode) - } +func SetupRouter(cfg *config.Config, logger *zap.Logger) http.Handler { + r := chi.NewRouter() - // Initialize the router - r := gin.New() + // Built-in Chi middleware + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Recoverer) + r.Use(middleware.StripSlashes) - // Add middleware - r.Use(gin.Recovery()) - r.Use(middleware.Logger(logger)) - r.Use(middleware.CORS(cfg.AllowedOrigins)) + // Custom middleware + r.Use(apiMiddleware.Logger(logger)) + + // CORS middleware + r.Use(cors.Handler(cors.Options{ + AllowedOrigins: cfg.AllowedOrigins, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: true, + MaxAge: 300, + })) // Setup health check - r.GET("/health", handlers.HealthCheck) + r.Get("/health", handlers.HealthCheck) // Initialize Google auth client googleAuth, err := auth.NewGoogleClient(cfg.Google.CredentialsPath) @@ -41,32 +49,24 @@ func SetupRouter(cfg *config.Config, logger *zap.Logger) *gin.Engine { // Initialize Healthcare API client healthcareClient := proxy.NewClient(googleAuth, cfg.Google) - // DICOM Web routes - dicomGroup := r.Group("/dicomWeb") - { + // DICOM Web routes - simplified approach + r.Route("/dicomWeb", func(r chi.Router) { + // Add audit logging middleware to DICOM routes + r.Use(apiMiddleware.AuditLog(logger)) + + // Create single handler for all DICOM requests dicomHandler := handlers.NewDicomHandler(healthcareClient, logger) - // Add audit logging middleware to DICOM routes - dicomGroup.Use(middleware.AuditLog(logger)) - - // WADO routes - dicomGroup.Any("/wado/*path", dicomHandler.ForwardRequest) - - // QIDO routes - dicomGroup.Any("/qido/*path", dicomHandler.ForwardRequest) - - // STOW routes - dicomGroup.Any("/stow/*path", dicomHandler.ForwardRequest) - } + // Catch all routes under /dicomWeb and forward them + r.HandleFunc("/*", dicomHandler.ForwardRequest) + }) // Future auth routes for doctors - authGroup := r.Group("/auth") - { - // Auth handlers will be implemented later + r.Route("/auth", func(r chi.Router) { authHandler := handlers.NewAuthHandler(logger) - authGroup.POST("/login", authHandler.Login) - authGroup.POST("/logout", authHandler.Logout) - } + r.Post("/login", authHandler.Login) + r.Post("/logout", authHandler.Logout) + }) return r } diff --git a/internal/auth/google.go b/internal/auth/google.go index a540e69..e9f0b4d 100644 --- a/internal/auth/google.go +++ b/internal/auth/google.go @@ -3,6 +3,7 @@ package auth import ( "context" "fmt" + "os" "time" "golang.org/x/oauth2" @@ -11,12 +12,10 @@ import ( "google.golang.org/api/option" ) -// TODO: Ganti Auth dengan lib Goth - // GoogleClient handles authentication with Google APIs type GoogleClient struct { credentialsPath string - tokenSource *google.Credentials + tokenSource oauth2.TokenSource } // NewGoogleClient creates a new Google authentication client @@ -26,24 +25,33 @@ func NewGoogleClient(credentialsPath string) (*GoogleClient, error) { } // Initialize on creation to validate credentials - if _, err := client.getToken(); err != nil { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Try reading the credentials file + credBytes, err := os.ReadFile(credentialsPath) + if err != nil { + return nil, fmt.Errorf("failed to read credentials file: %w", err) + } + + // Create credentials from the JSON key file + creds, err := google.CredentialsFromJSON(ctx, credBytes, healthcare.CloudPlatformScope) + if err != nil { return nil, fmt.Errorf("failed to initialize Google client: %w", err) } + client.tokenSource = creds.TokenSource return client, nil } // GetAccessToken returns a valid access token for Google APIs func (c *GoogleClient) GetAccessToken() (string, error) { - tokenSource, err := c.getToken() - if err != nil { - return "", err - } + _, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() - // Retrieve the token using the Token() method - token, err := tokenSource.Token() + token, err := c.tokenSource.Token() if err != nil { - return "", fmt.Errorf("failed to retrieve token: %w", err) + return "", fmt.Errorf("failed to get access token: %w", err) } return token.AccessToken, nil @@ -58,21 +66,3 @@ func (c *GoogleClient) GetHealthcareClient(ctx context.Context) (*healthcare.Ser return healthcare.NewService(ctx, opts...) } - -// getToken retrieves a token from the credentials file -func (c *GoogleClient) getToken() (oauth2.TokenSource, error) { // Change return type to oauth2.TokenSource - if c.tokenSource != nil { - return c.tokenSource.TokenSource, nil // Access the TokenSource field - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - credentials, err := google.FindDefaultCredentials(ctx, healthcare.CloudPlatformScope) - if err != nil { - return nil, fmt.Errorf("failed to get default credentials: %w", err) - } - - c.tokenSource = credentials - return credentials.TokenSource, nil // Return the TokenSource field -} diff --git a/internal/proxy/client.go b/internal/proxy/client.go index aa59e77..fb8f6f6 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -45,17 +45,25 @@ type Response struct { } // ForwardRequest forwards a request to Google Healthcare API -func (c *Client) ForwardRequest(ctx context.Context, method, requestType, path string, headers map[string]string, body []byte) (*Response, error) { - // Get access token +func (c *Client) ForwardRequest(ctx context.Context, method, path string, headers map[string]string, body []byte) (*Response, error) { token, err := c.googleAuth.GetAccessToken() if err != nil { return nil, fmt.Errorf("failed to get access token: %w", err) } - // Build URL + // Build URL - Simplified to exactly match OHIF's expected structure baseURL := fmt.Sprintf("https://healthcare.googleapis.com/v1/projects/%s/locations/%s/datasets/%s/dicomStores/%s/dicomWeb", c.projectID, c.location, c.dataset, c.dicomStore) - fullURL := fmt.Sprintf("%s/%s%s", baseURL, requestType, path) + + // Ensure path starts with / + if len(path) > 0 && path[0] != '/' { + path = "/" + path + } + + fullURL := baseURL + path + + // Log the full URL for debugging + fmt.Printf("Requesting URL: %s\n", fullURL) // Create request req, err := http.NewRequestWithContext(ctx, method, fullURL, bytes.NewReader(body)) diff --git a/ohif-proxy b/ohif-proxy new file mode 100755 index 0000000..1866000 Binary files /dev/null and b/ohif-proxy differ diff --git a/test/http/test.http b/test/http/test.http new file mode 100644 index 0000000..f82275f --- /dev/null +++ b/test/http/test.http @@ -0,0 +1,69 @@ +### Local OHIF Proxy Test File +@baseUrl = http://localhost:5555 + +### 1. Health Check +# Verifies that the proxy server is running +GET {{baseUrl}}/health +Accept: application/json + +### 2. QIDO-RS: Search for Studies +# Returns all studies (should return a list of DICOM studies if any exist) +GET {{baseUrl}}/dicomWeb/studies +Accept: application/dicom+json + +### 3. QIDO-RS: Search for Studies with Patient Name +# Returns studies matching patient name (replace SMITH with a name in your dataset) +GET {{baseUrl}}/dicomWeb/studies?limit=10&offset=0&fuzzymatching=true&includefield=00081030%2C00080060%2C00080090&00080090=DR.%20HERWINDO%20RIDWAN%2C%20SP.OT +Accept: application/dicom+json + +### 4. QIDO-RS: Search for Studies with Date Range +# Returns studies within a date range +GET {{baseUrl}}/dicomWeb/studies?StudyDate=20200101-20230101 +Accept: application/dicom+json + +### 5. QIDO-RS: Search for Series in a Study +# Replace STUDY_INSTANCE_UID with an actual Study UID from your data +# (Run test #2 first and copy a StudyInstanceUID from the response) +GET {{baseUrl}}/dicomWeb/studies/STUDY_INSTANCE_UID/series +Accept: application/dicom+json + +### 6. QIDO-RS: Search for Instances in a Series +# Replace STUDY_INSTANCE_UID and SERIES_INSTANCE_UID with actual values +GET {{baseUrl}}/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances +Accept: application/dicom+json + +### 7. WADO-RS: Retrieve Study Metadata +# Replace STUDY_INSTANCE_UID with an actual Study UID +GET {{baseUrl}}/dicomWeb/studies/STUDY_INSTANCE_UID/metadata +Accept: application/dicom+json + +### 8. WADO-RS: Retrieve Series Metadata +# Replace STUDY_INSTANCE_UID and SERIES_INSTANCE_UID with actual values +GET {{baseUrl}}/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/metadata +Accept: application/dicom+json + +### 9. WADO-RS: Retrieve Instance Metadata +# Replace STUDY_INSTANCE_UID, SERIES_INSTANCE_UID and SOP_INSTANCE_UID with actual values +GET {{baseUrl}}/dicomWeb/studies/1.2.826.0.1.3680043.9.7307.1.202503196393.01/series/1.2.826.0.1.3680043.2.1545.1.2.1.7.20250319.100353.734.4/metadata +Accept: */* + +### 10. WADO-RS: Retrieve Instance +# Replace STUDY_INSTANCE_UID, SERIES_INSTANCE_UID and SOP_INSTANCE_UID with actual values +GET {{baseUrl}}/dicomWeb/studies/STUDY_INSTANCE_UID/series/SERIES_INSTANCE_UID/instances/SOP_INSTANCE_UID +Accept: application/dicom + +### 11. WADO-RS: Retrieve Frame +# Replace STUDY_INSTANCE_UID, SERIES_INSTANCE_UID, SOP_INSTANCE_UID with actual values +# This retrieves frame #1 from a multiframe image +GET {{baseUrl}}/dicomWeb/studies/1.2.826.0.1.3680043.9.7307.1.202503196393.01/series/1.2.826.0.1.3680043.2.1545.1.2.1.7.20250319.100353.734.4/instances/1.2.826.0.1.3680043.2.1545.1.2.1.7.20250319.100353.1.5/frames/1 +Accept: */* + +### 12. WADO-RS: Retrieve Frame as JPEG +# Replace STUDY_INSTANCE_UID, SERIES_INSTANCE_UID, SOP_INSTANCE_UID with actual values +# This retrieves frame #1 as a rendered JPEG image +GET {{baseUrl}}/dicomWeb/studies/1.2.826.0.1.3680043.9.7307.1.202503196393.01/series/1.2.826.0.1.3680043.2.1545.1.2.1.7.20250319.100353.734.4/instances/1.2.826.0.1.3680043.2.1545.1.2.1.7.20250319.100353.1.5/frames/1/rendered +Accept: image/jpeg + + +#### +GET {{baseUrl}}/dicomWeb/studies?limit=101&offset=0&fuzzymatching=true&includefield=00081030,00080060&StudyInstanceUID=1.2.826.0.1.3680043.9.7307.1.202503196393.01