connected-to-google
This commit is contained in:
19
Dockerfile
19
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"]
|
||||
CMD ["./ohif-proxy"]
|
||||
@@ -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)
|
||||
- "*" # For development; restrict this in production
|
||||
12
docker-compose.yaml
Normal file
12
docker-compose.yaml
Normal file
@@ -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
|
||||
20
go.mod
20
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
|
||||
|
||||
52
go.sum
52
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=
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
// 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"})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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()
|
||||
path := c.Request.URL.Path
|
||||
query := c.Request.URL.RawQuery
|
||||
|
||||
// Create a wrapped response writer to capture the status code
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
|
||||
// Process request
|
||||
c.Next()
|
||||
next.ServeHTTP(ww, r)
|
||||
|
||||
// Calculate request time
|
||||
latency := time.Since(start)
|
||||
|
||||
// Get status
|
||||
status := c.Writer.Status()
|
||||
|
||||
// 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.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", c.ClientIP()),
|
||||
zap.String("user-agent", c.Request.UserAgent()),
|
||||
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)
|
||||
}
|
||||
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
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"
|
||||
|
||||
// Process request
|
||||
c.Next()
|
||||
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
|
||||
next.ServeHTTP(ww, r)
|
||||
|
||||
// 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()),
|
||||
zap.String("action", r.Method),
|
||||
zap.String("resource", r.URL.Path),
|
||||
zap.Int("status", ww.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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
BIN
ohif-proxy
Executable file
BIN
ohif-proxy
Executable file
Binary file not shown.
69
test/http/test.http
Normal file
69
test/http/test.http
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user