diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..a1c6a60
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+*
+!src/
+!.editorconfig
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d9db946
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,24 @@
+###############################
+# Core EditorConfig Options #
+###############################
+
+root = true
+
+# All files
+[*]
+indent_style = space[*]
+insert_final_newline = true
+charset = utf-8
+
+[packages.lock.json]
+insert_final_newline = false
+
+[*.sh]
+end_of_line = lf
+
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+
+# YAML files
+[*.{yml, yaml}]
+indent_size = 2
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5d543fe
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+*.sh text eol=lf
+*.shellcheckrc text eol=lf
+*Dockerfile text eol=lf
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..8b50d4b
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,54 @@
+name: ci
+
+on:
+ push:
+ branches: [master]
+ release:
+ types: [created]
+ pull_request:
+ branches: [master]
+
+permissions: read-all
+
+jobs:
+ build:
+ uses: miracum/.github/.github/workflows/standard-build.yaml@d09a237ae62959d3cf89d526a035fbd9d9d816ee # v1.5.8
+ permissions:
+ contents: read
+ id-token: write
+ packages: write
+ pull-requests: write
+ actions: read
+ security-events: write
+ with:
+ enable-build-test-layer: false
+ enable-upload-test-image: false
+ secrets:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ lint:
+ uses: miracum/.github/.github/workflows/standard-lint.yaml@d09a237ae62959d3cf89d526a035fbd9d9d816ee # v1.5.8
+ permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+ security-events: write
+ actions: read
+ with:
+ enable-validate-gradle-wrapper: false
+ codeql-languages: '["csharp"]'
+ enable-codeql: true
+ enable-verify-base-image-signature: false
+ secrets:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ release:
+ uses: miracum/.github/.github/workflows/standard-release.yaml@d09a237ae62959d3cf89d526a035fbd9d9d816ee # v1.5.8
+ needs:
+ - build
+ permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+ secrets:
+ semantic-release-token: ${{ secrets.MIRACUM_BOT_SEMANTIC_RELEASE_TOKEN }}
diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml
new file mode 100644
index 0000000..a7ef31d
--- /dev/null
+++ b/.github/workflows/schedule.yaml
@@ -0,0 +1,19 @@
+name: scheduled
+
+on:
+ repository_dispatch: {}
+ workflow_dispatch: {}
+ schedule:
+ - cron: "00 18 * * *"
+
+permissions: read-all
+
+jobs:
+ schedule:
+ uses: miracum/.github/.github/workflows/standard-schedule.yaml@d09a237ae62959d3cf89d526a035fbd9d9d816ee # v1.5.8
+ permissions:
+ contents: read
+ issues: write
+ security-events: write
+ secrets:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scorecards.yaml b/.github/workflows/scorecards.yaml
new file mode 100644
index 0000000..936dd1f
--- /dev/null
+++ b/.github/workflows/scorecards.yaml
@@ -0,0 +1,73 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecards supply-chain security
+on:
+ workflow_dispatch:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: "37 19 * * 1"
+ push:
+ branches: ["master"]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecards analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: "Checkout code"
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecards on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+ # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard.
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@379614612a29c9e28f31f39a59013eb8012a51f0 # v3.24.3
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..104b544
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,484 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/.mega-linter.yml b/.mega-linter.yml
new file mode 100644
index 0000000..9e27172
--- /dev/null
+++ b/.mega-linter.yml
@@ -0,0 +1,30 @@
+# Configuration file for MegaLinter
+# See all available variables at https://oxsecurity.github.io/megalinter/configuration/ and in linters documentation
+
+APPLY_FIXES: none # all, none, or list of linter keys
+# ENABLE: # If you use ENABLE variable, all other languages/formats/tooling-formats will be disabled by default
+# ENABLE_LINTERS: # If you use ENABLE_LINTERS variable, all other linters will be disabled by default
+DISABLE:
+ - COPYPASTE # Comment to enable checks of excessive copy-pastes
+ - SPELL # Comment to enable checks of spelling mistakes
+
+DISABLE_LINTERS:
+ - REPOSITORY_DEVSKIM
+ - MARKDOWN_MARKDOWN_TABLE_FORMATTER
+ - CSHARP_DOTNET_FORMAT
+ - MARKDOWN_MARKDOWN_LINK_CHECK
+
+SHOW_ELAPSED_TIME: true
+FILEIO_REPORTER: false
+# DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass
+
+BASH_SHFMT_ARGUMENTS:
+ - "--indent=2"
+
+REPOSITORY_TRIVY_ARGUMENTS:
+ - "--severity=HIGH,CRITICAL"
+
+IGNORE_GITIGNORED_FILES: true
+
+REPOSITORY_KICS_ARGUMENTS:
+ - --fail-on=HIGH
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..c7e9712
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,34 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md
+ "name": ".NET Core Launch (console)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/src/PathlingS3Import/bin/Debug/net8.0/PathlingS3Import.dll",
+ "args": [
+ "--s3-endpoint=http://localhost:9000",
+ "--pathling-server-base-url=http://localhost:8082/fhir",
+ "--s3-access-key=admin",
+ "--s3-secret-key=miniopass",
+ "--s3-bucket-name=fhir",
+ "--s3-object-name-prefix=staging/"
+ ],
+ "env": {},
+ "cwd": "${workspaceFolder}/src/PathlingS3Import",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..da75242
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/PathlingS3Import.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/PathlingS3Import.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/PathlingS3Import.sln"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..43c6a0a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+FROM mcr.microsoft.com/dotnet/sdk:8.0.201-jammy@sha256:dc273e23006f85ef4bf154844a3147325c50adc4b5dac9191238bed4931743ac AS build
+WORKDIR /build
+ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+COPY src/PathlingS3Import/PathlingS3Import.csproj .
+COPY src/PathlingS3Import/packages.lock.json .
+
+RUN dotnet restore --locked-mode
+COPY . .
+
+RUN dotnet publish \
+ -c Release \
+ -o /build/publish \
+ src/PathlingS3Import/PathlingS3Import.csproj
+
+FROM mcr.microsoft.com/dotnet/runtime:8.0.2-jammy@sha256:8a8b6038864efd998525050d8dc16cec2825648d587747c30d1d6b4280a39880 AS runtime
+WORKDIR /opt/pathling-s3-import
+USER 65534:65534
+ENV DOTNET_ENVIRONMENT="Production" \
+ DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+COPY --from=build /build/publish .
+ENTRYPOINT ["/bin/bash", "-c"]
+CMD [ "/opt/pathling-s3-import/PathlingS3Import --help"]
diff --git a/PathlingS3Import.sln b/PathlingS3Import.sln
new file mode 100644
index 0000000..32a88c4
--- /dev/null
+++ b/PathlingS3Import.sln
@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7C72D930-9EE6-4CA3-8F01-40429262EE29}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PathlingS3Import", "src\PathlingS3Import\PathlingS3Import.csproj", "{1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1B97F255-F56D-4AE5-A25A-2C9C2AFEBFAB} = {7C72D930-9EE6-4CA3-8F01-40429262EE29}
+ EndGlobalSection
+EndGlobal
diff --git a/compose.yaml b/compose.yaml
new file mode 100644
index 0000000..021c727
--- /dev/null
+++ b/compose.yaml
@@ -0,0 +1,11 @@
+services:
+ minio:
+ image: docker.io/bitnami/minio:2024.2.17-debian-12-r2@sha256:4d04a41f9d385d51ecd9be8dafca13fe9d56be2cc1c5ea8f98e6cfb235d87ae5
+ environment:
+ MINIO_ROOT_USER: "admin"
+ # kics-scan ignore-line
+ MINIO_ROOT_PASSWORD: "miniopass" # gitleaks:allow
+ MINIO_DEFAULT_BUCKETS: "fhir"
+ ports:
+ - "127.0.0.1:9000:9000"
+ - "127.0.0.1:9001:9001"
diff --git a/src/PathlingS3Import/PathlingS3Import.csproj b/src/PathlingS3Import/PathlingS3Import.csproj
new file mode 100644
index 0000000..171d3d9
--- /dev/null
+++ b/src/PathlingS3Import/PathlingS3Import.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/PathlingS3Import/Program.cs b/src/PathlingS3Import/Program.cs
new file mode 100644
index 0000000..6b5a9ec
--- /dev/null
+++ b/src/PathlingS3Import/Program.cs
@@ -0,0 +1,140 @@
+using System.Reactive.Linq;
+using DotMake.CommandLine;
+using Hl7.Fhir.Model;
+using Hl7.Fhir.Rest;
+using Hl7.Fhir.Serialization;
+using Minio;
+using Minio.DataModel.Args;
+
+return Cli.Run(args);
+
+// Create a simple class like this to define your root command:
+[CliCommand(Description = "The import command")]
+public class ImportCliCommand
+{
+ [CliOption(Description = "The S3 endpoint URI", Name = "--s3-endpoint")]
+ public Uri? S3Endpoint { get; set; }
+
+ [CliOption(Description = "The S3 access key", Name = "--s3-access-key")]
+ public string? S3AccessKey { get; set; }
+
+ [CliOption(Description = "The S3 secret key", Name = "--s3-secret-key")]
+ public string? S3SecretKey { get; set; }
+
+ [CliOption(
+ Description = "The name of the bucket containing the resources to import",
+ Name = "--s3-bucket-name"
+ )]
+ public string? S3BucketName { get; set; } = "fhir";
+
+ [CliOption(
+ Description = "The S3 object name prefix. Corresponds to kafka-fhir-to-server's `S3_OBJECT_NAME_PREFIX`",
+ Name = "--s3-object-name-prefix"
+ )]
+ public string? S3ObjectNamePrefix { get; set; } = "";
+
+ [CliOption(Description = "The FHIR base URL of the Pathling server")]
+ public Uri? PathlingServerBaseUrl { get; set; }
+
+ [CliOption(Description = "The type of FHIR resource to import")]
+ public ResourceType ImportResourceType { get; set; } = ResourceType.Patient;
+
+ public void Run()
+ {
+ Console.WriteLine($@"Handler for '{GetType().FullName}' is run:");
+ Console.WriteLine($@"Value for {nameof(S3Endpoint)} property is '{S3Endpoint}'");
+ Console.WriteLine(
+ $@"Value for {nameof(PathlingServerBaseUrl)} property is '{PathlingServerBaseUrl}'"
+ );
+ Console.WriteLine();
+
+ DoAsync().Wait();
+ }
+
+ private async System.Threading.Tasks.Task DoAsync()
+ {
+ var fhirClient = new FhirClient(
+ PathlingServerBaseUrl,
+ settings: new FhirClientSettings
+ {
+ PreferredFormat = ResourceFormat.Json,
+ // TODO: change once we implement it client-side
+ UseAsync = false,
+ Timeout = (int)TimeSpan.FromMinutes(30).TotalMilliseconds,
+ }
+ );
+
+ var minio = new MinioClient()
+ .WithEndpoint(S3Endpoint)
+ .WithCredentials(S3AccessKey, S3SecretKey)
+ .Build();
+
+ var bucketExistsArgs = new BucketExistsArgs().WithBucket(S3BucketName);
+ bool found = await minio.BucketExistsAsync(bucketExistsArgs);
+ if (!found)
+ {
+ throw new ArgumentException($"Bucket {S3BucketName} doesn't exist.");
+ }
+
+ var prefix = $"{S3ObjectNamePrefix}{ImportResourceType}/";
+
+ Console.WriteLine($"Listing objects in {S3BucketName}/{prefix}.");
+
+ var listArgs = new ListObjectsArgs()
+ .WithBucket(S3BucketName)
+ .WithPrefix(prefix)
+ .WithRecursive(false);
+
+ var observable = minio.ListObjectsAsync(listArgs);
+
+ var allObjects = new List();
+
+ using var subscription = observable.Subscribe(
+ item =>
+ {
+ Console.WriteLine("OnNext: {0}", item.Key);
+ allObjects.Add(item);
+ },
+ ex => Console.WriteLine("OnError: {0}", ex.Message),
+ () => Console.WriteLine("Finished listing.")
+ );
+
+ observable.Wait();
+
+ foreach (var item in allObjects)
+ {
+ var parameter = new Parameters.ParameterComponent()
+ {
+ Name = "source",
+ Part =
+ [
+ new Parameters.ParameterComponent()
+ {
+ Name = "resourceType",
+ Value = new Code(ImportResourceType.ToString())
+ },
+ new Parameters.ParameterComponent()
+ {
+ Name = "mode",
+ Value = new Code("merge")
+ },
+ new Parameters.ParameterComponent()
+ {
+ Name = "url",
+ Value = new FhirUrl($"s3://{S3BucketName}/{item.Key}")
+ }
+ ]
+ };
+
+ var importParameters = new Parameters();
+ // for now, create one Import request per file. In the future,
+ // we might want to add multiple ndjson files at once in batches.
+ importParameters.Parameter.Add(parameter);
+
+ Console.WriteLine(importParameters.ToJson());
+
+ var response = await fhirClient.WholeSystemOperationAsync("import", importParameters);
+ Console.WriteLine(response.ToJson());
+ }
+ }
+}
diff --git a/src/PathlingS3Import/packages.lock.json b/src/PathlingS3Import/packages.lock.json
new file mode 100644
index 0000000..d201428
--- /dev/null
+++ b/src/PathlingS3Import/packages.lock.json
@@ -0,0 +1,134 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net8.0": {
+ "DotMake.CommandLine": {
+ "type": "Direct",
+ "requested": "[1.8.1, )",
+ "resolved": "1.8.1",
+ "contentHash": "FRbfKWmp90SGPpN4DSJmCxF29RYDpkxeUWK2Sz9kdQTvI3L4sBAvwatjAuG/65aC8K6xqaQ7EVOpKpCVWSE0jw=="
+ },
+ "Hl7.Fhir.R4": {
+ "type": "Direct",
+ "requested": "[5.6.1, )",
+ "resolved": "5.6.1",
+ "contentHash": "t7CclUNoIJowULFwArxZzc7SVLkQzqMjL79b5bxQI57Ar3Dkh8iz7dTtQNs/KZlFHXhE44DXfuzNayExH269xA==",
+ "dependencies": {
+ "Hl7.Fhir.Conformance": "5.6.1"
+ }
+ },
+ "Minio": {
+ "type": "Direct",
+ "requested": "[6.0.2, )",
+ "resolved": "6.0.2",
+ "contentHash": "4Od4uGANX5X0AL90WV0viBNzpE2+jDHro6CGvR4//MVj5SiTTwR5SUikXgd/2G2PtYyXw4b/IBpo7Kt5cCCndA==",
+ "dependencies": {
+ "CommunityToolkit.HighPerformance": "8.2.2",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Logging": "8.0.0",
+ "System.IO.Hashing": "8.0.0",
+ "System.Reactive": "6.0.0"
+ }
+ },
+ "CommunityToolkit.HighPerformance": {
+ "type": "Transitive",
+ "resolved": "8.2.2",
+ "contentHash": "+zIp8d3sbtYaRbM6hqDs4Ui/z34j7DcUmleruZlYLE4CVxXq+MO8XJyIs42vzeTYFX+k0Iq1dEbBUnQ4z/Gnrw=="
+ },
+ "Fhir.Metrics": {
+ "type": "Transitive",
+ "resolved": "1.2.2",
+ "contentHash": "600yqTbqREfogkwWH+MrXwq7fFI/gO5xn1+f7EYHlN69kXZhEpyG3V574PJiErfMQS1IpDNs4QT0aAu3KGlGFQ=="
+ },
+ "Hl7.Fhir.Base": {
+ "type": "Transitive",
+ "resolved": "5.6.1",
+ "contentHash": "vhtdt2hx/Jd6tarNF82Yi+ieSmm13YFXUx8kv02bq83dWOJ3uLCMDBeVTtcLI0pe7a9DqTP1PQgAIBfY2rQiFA==",
+ "dependencies": {
+ "Fhir.Metrics": "1.2.2",
+ "Newtonsoft.Json": "13.0.3",
+ "System.ComponentModel.Annotations": "5.0.0",
+ "System.Reflection.Emit.Lightweight": "4.7.0"
+ }
+ },
+ "Hl7.Fhir.Conformance": {
+ "type": "Transitive",
+ "resolved": "5.6.1",
+ "contentHash": "mQ//YjQrD+255TsYnVuC3qKFYagD8wHfNQLc5bd2tuSmpWbd8nQJJO/MZBMK98OsEO9uVg2b5rkYRMW3BD23Ug==",
+ "dependencies": {
+ "Hl7.Fhir.Base": "5.6.1"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg=="
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "8.0.0",
+ "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Options": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
+ "Microsoft.Extensions.Primitives": "8.0.0"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
+ },
+ "System.IO.Hashing": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA=="
+ },
+ "System.Reactive": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "31kfaW4ZupZzPsI5PVe77VhnvFF55qgma7KZr/E0iFTs6fmdhhG8j0mgEx620iLTey1EynOkEfnyTjtNEpJzGw=="
+ },
+ "System.Reflection.Emit.Lightweight": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA=="
+ }
+ }
+ }
+}
\ No newline at end of file