diff --git a/.cruft.json b/.cruft.json
new file mode 100644
index 0000000..7b7997a
--- /dev/null
+++ b/.cruft.json
@@ -0,0 +1,21 @@
+{
+  "template": "D:\\open-source\\gsoc\\final\\cookiecutter-python",
+  "commit": "88967f43af9799dfa0ad04b2908d7f40259c02d2",
+  "checkout": null,
+  "context": {
+    "cookiecutter": {
+      "author_name": "Elixir Cloud AAI",
+      "author_email": "cloud-service@elixir-europe.org",
+      "development_status": "Beta",
+      "short_description": "File Handler Utilizing TUS and Minio (S3 Storage) with DRS Filer Integration",
+      "project_name": "tus-storagehandler",
+      "project_slug": "tus_storagehandler",
+      "github_username": "elixir-cloud-aai",
+      "python_version": "3.11",
+      "add_script": "y",
+      "year": "2024",
+      "_template": "D:\\open-source\\gsoc\\final\\cookiecutter-python"
+    }
+  },
+  "directory": null
+}
diff --git a/.github/ISSUE_TEMPLATE/general-purpose.md b/.github/ISSUE_TEMPLATE/general-purpose.md
new file mode 100644
index 0000000..8a11bde
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/general-purpose.md
@@ -0,0 +1,47 @@
+Your issue may already be reported! Please search on the
+[issue tracker](https://github.com/elixir-cloud-aai/tus-storagehandler/issues) before creating
+one.
+
+## Expected Behavior
+
+<!--- If you're describing a bug, tell us what should happen -->
+
+<!--- If you're suggesting a change/improvement, tell us how it should work -->
+
+## Current Behavior
+
+<!--- If describing a bug, tell us what happens instead of the expected behavior -->
+
+<!--- If suggesting a change/improvement, explain the difference from current behavior -->
+
+## Possible Solution
+
+<!--- Not obligatory, but suggest a fix/reason for the bug, -->
+
+<!--- or ideas how to implement the addition or change -->
+
+## Steps to Reproduce (for bugs)
+
+<!--- Provide a link to a live example, or an unambiguous set of steps to -->
+
+<!--- reproduce this bug. Include code to reproduce, if relevant -->
+
+1.
+1.
+1.
+1.
+
+## Context
+
+<!--- How has this issue affected you? What are you trying to accomplish? -->
+
+<!--- Providing context helps us come up with a solution that is most useful in the real world -->
+
+## Your Environment
+
+<!--- Include as many relevant details about the environment you experienced the bug in -->
+
+- Version used:
+- Browser Name and version:
+- Operating System and version (desktop or mobile):
+- Link to your project:
diff --git a/.github/actions/setup/poetry/action.yaml b/.github/actions/setup/poetry/action.yaml
new file mode 100644
index 0000000..92154b2
--- /dev/null
+++ b/.github/actions/setup/poetry/action.yaml
@@ -0,0 +1,92 @@
+---
+name: Setup Python and Poetry Action
+description: Configure system, Python, Poetry and deps and cache management.
+
+inputs:
+  os:
+    default: ubuntu-latest
+    description: The operating system to use
+  python-version:
+    default: '3.11'
+    description: The version of Python to use
+  poetry-version:
+    default: '1.8.2'
+    description: The version of Poetry to install
+  poetry-install-options:
+    default: ''
+    description: Additional options to pass to poetry install
+  poetry-export-options:
+    default: ''
+    description: Options to pass to poetry export for hash generation for cache
+                 invalidation
+
+runs:
+  using: composite
+  steps:
+    - uses: 'actions/setup-python@v5'
+      id: setup-python
+      with:
+        python-version: '${{ inputs.python-version }}'
+
+    - name: Setup pipx environment Variables
+      id: pipx-env-setup
+      # pipx default home and bin dir are not writable by the cache action
+      # so override them here and add the bin dir to PATH for later steps.
+      # This also ensures the pipx cache only contains poetry
+      run: |
+        SEP="${{ !startsWith(runner.os, 'windows') && '/' || '\\' }}"
+        PIPX_CACHE="${{ github.workspace }}${SEP}pipx_cache"
+        echo "pipx-cache-path=${PIPX_CACHE}" >> $GITHUB_OUTPUT
+        echo "pipx-version=$(pipx --version)" >> $GITHUB_OUTPUT
+        echo "PIPX_HOME=${PIPX_CACHE}${SEP}home" >> $GITHUB_ENV
+        echo "PIPX_BIN_DIR=${PIPX_CACHE}${SEP}bin" >> $GITHUB_ENV
+        echo "PIPX_MAN_DIR=${PIPX_CACHE}${SEP}man" >> $GITHUB_ENV
+        echo "${PIPX_CACHE}${SEP}bin" >> $GITHUB_PATH
+      shell: bash
+
+    - name: Pipx cache
+      id: pipx-cache
+      uses: actions/cache@v4
+      with:
+        path: ${{ steps.pipx-env-setup.outputs.pipx-cache-path }}
+        key: ${{ runner.os }}-python-
+             ${{ steps.setup-python.outputs.python-version }}-
+             pipx-${{ steps.pipx-env-setup.outputs.pipx-version }}-
+             poetry-${{ inputs.poetry-version }}
+
+    - name: Install poetry
+      if: steps.pipx-cache.outputs.cache-hit != 'true'
+      id: install-poetry
+      shell: bash
+      run: |
+        pipx install poetry \
+        --python "${{ steps.setup-python.outputs.python-path }}"
+
+    - name: Read poetry cache location
+      id: poetry-cache-location
+      shell: bash
+      run: |
+        echo "poetry-venv-location=$(poetry config virtualenvs.path)" \
+        >> $GITHUB_OUTPUT
+
+    - name: Generate hash only for required deps
+      run: |
+        poetry export ${{ inputs.poetry-export-options }} \
+        --format=requirements.txt --output=requirements.txt
+        echo "DEP_HASH=$(sha256sum requirements.txt | cut -d ' ' -f 1)" \
+         >> $GITHUB_ENV
+      shell: bash
+
+    - uses: actions/cache@v4
+      name: Poetry cache
+      with:
+        path: ${{ steps.poetry-cache-location.outputs.poetry-venv-location }}
+        key: ${{ runner.os }}-[python-
+             ${{ steps.setup-python.outputs.python-version }}]-[
+             ${{ env.DEP_HASH }}]-[${{ inputs.poetry-install-options }}]
+
+    - name: 'Poetry install'
+      if: steps.poetry-cache.outputs.cache-hit != 'true'
+      shell: bash
+      run: poetry install ${{ inputs.poetry-install-options }} --no-interaction
+...
diff --git a/.github/workflows/code_quality.yaml b/.github/workflows/code_quality.yaml
new file mode 100644
index 0000000..0c252da
--- /dev/null
+++ b/.github/workflows/code_quality.yaml
@@ -0,0 +1,84 @@
+---
+name: Code Quality
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  format:
+    name: Format
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=lint --no-root"
+          poetry-export-options: "--only=lint"
+
+      - name: Check code style
+        run: poetry run ruff format --check
+
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=lint --no-root"
+          poetry-export-options: "--only=lint"
+
+      - name: Check code quality
+        run: poetry run ruff check .
+
+  spell-check:
+    name: Spell Check
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=lint --no-root"
+          poetry-export-options: "--only=lint"
+
+      - name: Check spellings
+        run: poetry run typos .
+
+  type-check:
+    name: Type Check
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=types --no-root"
+          poetry-export-options: "--only=types"
+
+      - name: Check types
+        run: poetry run mypy tus_storagehandler/
+...
diff --git a/.github/workflows/code_test.yaml b/.github/workflows/code_test.yaml
new file mode 100644
index 0000000..0266d92
--- /dev/null
+++ b/.github/workflows/code_test.yaml
@@ -0,0 +1,74 @@
+---
+name: Code Test
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  integration-test:
+    name: Unit Test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--with=test"
+          poetry-export-options: "--with=test"
+
+      - name: Run tests and generate coverage as test_integration.xml
+        run: |
+          poetry run pytest \
+            --cov-report term \
+            --cov-report xml:test_integration.xml \
+            --cov=tests/test_integration
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v4
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          flags: test_integration
+          files: ./test_integration.xml
+          fail_ci_if_error: true
+          verbose: true
+
+  unit-test:
+    name: Unit Test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--with=test"
+          poetry-export-options: "--with=test"
+
+      - name: Run tests and generate coverage as test_unit.xml
+        run: |
+          poetry run pytest \
+            --cov-report term \
+            --cov-report xml:test_unit.xml \
+            --cov=tests/test_unit
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v4
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          flags: test_unit
+          files: ./test_unit.xml
+          fail_ci_if_error: true
+          verbose: true
+...
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 0000000..1b417b5
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,44 @@
+---
+name: Documentation Check
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  check-api-docs:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--with=docs"
+          poetry-export-options: "--with=docs"
+
+      - name: Generate API docs
+        run: |
+          sphinx-apidoc -o /tmp/docs tus_storagehandler/
+
+      - name: Compare docs with main branch
+        id: check_docs_diff
+        run: |
+          shasum /tmp/docs/* > /tmp/docs.sha
+          shasum /docs/pages/* > /docs/project_doc.sha
+          diff=$(diff /tmp/docs.sha /docs/project_doc.sha) || true
+          if [[ -n "$diff" ]]; then
+            echo "::error::API documentation is out of date."
+            exit 1
+          else
+            echo "API documentation is up to date."
+          fi
+...
diff --git a/.github/workflows/pr_validation.yaml b/.github/workflows/pr_validation.yaml
new file mode 100644
index 0000000..4e43caa
--- /dev/null
+++ b/.github/workflows/pr_validation.yaml
@@ -0,0 +1,67 @@
+---
+name: PR Evaluation
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review, edited]
+    branches: ['main']
+
+jobs:
+  detect-unresolved-conflicts:
+    name: Detect unresolved merge conflicts
+    runs-on: ubuntu-latest
+    needs: semantic_pr
+    steps:
+      - uses: actions/checkout@v3
+      - name: List files with merge conflict markers
+        run: git --no-pager grep "<<<<<<<" ":(exclude).github/" || true
+      - name: Fail or succeed job if any files with merge conflict markers
+        run: exit $(git grep "<<<<<<<" ":(exclude).github/" | wc --lines)
+
+  pre-commit:
+    name: Pre-commit checks
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=misc --no-root"
+          poetry-export-options: "--only=misc"
+
+      - name: Check all the pre-commit hooks passed
+        run: pre-commit run --all-files
+
+  semantic-pr:
+    name: Semantic PR title
+    runs-on: ubuntu-latest
+    if: ${{ github.event.action != 'edited' ||
+            github.event.changes.title != null }}
+    steps:
+      - uses: amannn/action-semantic-pull-request@v5
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          types: |
+            fix
+            feat
+            docs
+            style
+            refactor
+            perf
+            test
+            build
+            ci
+            chore
+            revert
+          subjectPattern: ^(?![A-Z])(?=.{1,50}$).+$
+          subjectPatternError: |
+            The subject "{subject}" found in the pull request title "{title}"
+            didn't match the configured pattern. Please ensure that the subject
+            doesn't start with an uppercase character & not have more than 50
+            characters.
+...
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 0000000..1e214ed
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,26 @@
+---
+name: Release package
+
+on:
+  release:
+    types: [released]
+
+jobs:
+  pypi:
+    name: Publish to PyPI
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=main"
+          poetry-export-options: "--only=main"
+
+      - name: Publish package to PyPI
+        run: poetry publish --build -u __token__ -p ${{ secrets.PYPI_PASSWORD }}
+...
diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml
new file mode 100644
index 0000000..4edade9
--- /dev/null
+++ b/.github/workflows/update.yaml
@@ -0,0 +1,55 @@
+---
+name: Update project structure
+
+on:
+  schedule:
+    - cron: "0 2 * * 1" # Every Monday at 2am
+
+jobs:
+  update-template:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--with=misc --no-root"
+          poetry-export-options: "--with=misc"
+
+      - name: Update project structure
+        run: |
+          cruft update -y
+
+      - name: Check if there are changes
+        id: changes
+        uses: UnicornGlobal/has-changes-action@v1.0.11
+
+      - name: Apply additional changes and fixes
+        if: steps.changes.outputs.changed == 1
+        run: |
+          poetry lock --no-update
+          poetry install
+          pre-commit run --all-files
+
+      - name: Get new template version
+        if: steps.changes.outputs.changed == 1
+        run: |
+          COMMIT_HASH=$(cat .cruft.json | jello -r "_['commit'][:8]")
+          echo "TEMPLATE_COMMIT=${COMMIT_HASH}" >> $GITHUB_ENV
+
+      - name: Create Pull Request
+        if: steps.changes.outputs.changed == 1
+        uses: peter-evans/create-pull-request@v6
+        with:
+          token: ${{ secrets.AUTO_UPDATE_GITHUB_TOKEN }}
+          commit-message: >-
+            chore: update project structure to ${{ env.TEMPLATE_COMMIT }}
+          title: "chore: auto-sync cookiecutter template"
+          body: ""
+          branch: chore/cookiecutter-pypackage
+          delete-branch: true
+...
diff --git a/.github/workflows/vulnerability.yaml b/.github/workflows/vulnerability.yaml
new file mode 100644
index 0000000..5bb93b5
--- /dev/null
+++ b/.github/workflows/vulnerability.yaml
@@ -0,0 +1,48 @@
+---
+name: Vulnerability Test
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+jobs:
+  code-vulnerabilities:
+    name: Code
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=security --no-root"
+          poetry-export-options: "--only=security"
+
+      - name: Check code vulnerabilities with bandit
+        run: poetry run bandit -c pyproject.toml -r tus_storagehandler/
+
+  dependency-vulnerabilities:
+    name: Dependencies
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out repository
+        uses: actions/checkout@v4
+
+      - name: Set up environment
+        uses: ./.github/actions/setup/poetry
+        with:
+          os: ${{ job.os }}
+          python-version: '3.11'
+          poetry-install-options: "--only=security --no-root"
+          poetry-export-options: "--only=security"
+
+      - name: Check dependency vulnerabilities with safety
+        run: poetry run safety check --full-report
+...
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a558c9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,182 @@
+######## This section contains auto-generated .gitignore patterns for #########
+######## Python projects. Write project level gitignore below this    #########
+######## section.                                                     #########
+
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+Pipfile.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
+SquareX Safe File Viewer
+✕
+
+######### End of auto generated .gitignore file for Python projects. #########
+######### Project level .gitignore.                                  #########
+
+# Keep pre-commit hooks and .yamllint file
+!.pre-commit-config.yaml
+!.yamllint.yaml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..8a34822
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,40 @@
+---
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+# Common commands:
+# pre-commit install
+# pre-commit autoupdate
+# pre-commit run --all-files --hook-stage commit
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.6.0
+    hooks:
+      - id: check-added-large-files
+      - id: destroyed-symlinks
+      - id: detect-private-key
+      - id: end-of-file-fixer
+      - id: mixed-line-ending
+        args: [--fix=auto]
+      - id: trailing-whitespace
+  - repo: https://github.com/executablebooks/mdformat
+    rev: 0.7.17
+    hooks:
+      - id: mdformat
+        additional_dependencies:
+          - mdformat-config
+          - mdformat-black
+          - mdformat-frontmatter
+        args: [--wrap=80]
+        exclude: _.+\.md|CHANGELOG\.md
+  - repo: https://github.com/adrienverge/yamllint.git
+    rev: v1.35.1
+    hooks:
+      - id: yamllint
+        exclude: ^deployment/charts
+  - repo: https://github.com/pappasam/toml-sort
+    rev: v0.23.1
+    hooks:
+      - id: toml-sort-fix
+        args: [--in-place, --all, --trailing-comma-inline-array]
+        exclude: poetry\.lock
+...
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..ec7defb
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,16 @@
+---
+version: 2
+
+build:
+  os: ubuntu-22.04
+  tools:
+    python: '3.11'
+  jobs:
+    post_create_environment:
+      - python -m pip install poetry
+    post_install:
+      - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
+
+sphinx:
+  configuration: docs/source/conf.py
+...
diff --git a/.safety-policy.yml b/.safety-policy.yml
new file mode 100644
index 0000000..7089764
--- /dev/null
+++ b/.safety-policy.yml
@@ -0,0 +1,43 @@
+---
+version: '3.0'
+
+scanning-settings:
+  max-depth: 6
+  exclude: []
+  include-files: []
+  system:
+    targets: []
+
+security:
+  ignore-vulnerabilities:
+    70612:
+      reason: "Won't use untrusted templates without sandboxing."
+      expires: '2025-5-27'
+
+report:
+  dependency-vulnerabilities:
+    enabled: true
+    auto-ignore-in-report:
+      python:
+        environment-results: true
+        unpinned-requirements: true
+      cvss-severity: []
+
+fail-scan-with-exit-code:
+  dependency-vulnerabilities:
+    enabled: true
+    fail-on-any-of:
+      cvss-severity:
+        - critical
+        - high
+        - medium
+      exploitability:
+        - critical
+        - high
+        - medium
+
+security-updates:
+  dependency-vulnerabilities:
+    auto-security-updates-limit:
+      - patch
+...
diff --git a/.yamllint.yaml b/.yamllint.yaml
new file mode 100644
index 0000000..ad3171a
--- /dev/null
+++ b/.yamllint.yaml
@@ -0,0 +1,13 @@
+---
+# https://yamllint.readthedocs.io/en/stable/configuration.html#default-configuration
+extends: default
+
+rules:
+  truthy:
+    ignore:
+      .github/**/*.yaml
+  document-start:
+    level: error
+  document-end:
+    level: error
+...
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..bc09eb3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,75 @@
+# ELIXIR Cloud & AAI Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+  advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting [cloud-service@elixir-europe.org][elixir-cloud-aai-email]. All complaints
+will be reviewed and investigated and will result in a response that is deemed
+necessary and appropriate to the circumstances. The project team is obligated
+to maintain confidentiality with regard to the reporter of an incident. Further
+details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor
+Covenant][contributor-covenant], version 1.4, available at
+<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
+
+[contributor-covenant]: https://contributor-covenant.org/
+[elixir-cloud-aai-email]: mailto:cloud-service@elixir-europe.org
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..975b5b9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2024 Elixir Cloud AAI
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..27caedf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,125 @@
+# NOTE: This Makefile assumes that dependencies are installed and, if a virtual
+# environment is used, it is activated.
+
+## Variables ##################################################################
+# NOTE: Define any variables here if needed in the future
+
+## Documentation ##############################################################
+# NOTE: Keep all the targets in alphabetical order for better readability.
+
+default: help
+
+.PHONY: help
+help:
+	@echo "\nUsage: make [target] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@echo "Available targets:\n"
+
+	@echo "Environment Management --------------------------------------------------------"
+	@echo "  \033[1m\033[35mclean-venv\033[0m \033[37m(cv)\033[0m: \033[36mRemove virtual environment.\033[0m"
+	@echo "  \033[1m\033[35minstall\033[0m \033[37m(i)\033[0m: \033[36mInstall dependencies and tus_storagehandler.\033[0m"
+	@echo "  \033[1m\033[35mvenv\033[0m \033[37m(v)\033[0m: \033[36mCreate virtual environment.\033[0m\n"
+
+	@echo "Code Quality ------------------------------------------------------------------"
+	@echo "  \033[1m\033[35mformat-lint\033[0m \033[37m(fl)\033[0m: \033[36mRun linter, formatter, spellcheck.\033[0m"
+	@echo "  \033[1m\033[35mprecommit-check\033[0m \033[37m(pc)\033[0m: \033[36mRun all pre-commit checks.\033[0m"
+	@echo "  \033[1m\033[35msecurity\033[0m \033[37m(s)\033[0m: \033[36mRun security scans.\033[0m"
+	@echo "  \033[1m\033[35mtype-check\033[0m \033[37m(tc)\033[0m: \033[36mPerform type checking.\033[0m\n"
+
+	@echo "Testing -----------------------------------------------------------------------"
+	@echo "  \033[1m\033[35mtest\033[0m \033[37m(t)\033[0m: \033[36mRun all tests.\033[0m\n"
+
+	@echo "Documentation -----------------------------------------------------------------"
+	@echo "  \033[1m\033[35mdocs\033[0m \033[37m(d)\033[0m: \033[36mGenerate project documentation.\033[0m\n"
+
+## Autogenerated Targets ######################################################
+# NOTE: Keep all the targets in alphabetical order for better readability.
+# NOTE: Do not modify the autogenerated targets, unless necessary, write custom
+# targets in the custom section below..
+
+.PHONY: clean-venv
+clean-venv:
+	@echo "\nRemoving the virtual environment ++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@rm -rf .venv
+
+.PHONY: cv
+cv: clean-venv
+
+.PHONY: docs
+docs:
+	@echo "\nGenerating project documentation ++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@sphinx-apidoc -f -o docs/source/pages tus_storagehandler
+	@cd docs && make html
+	@echo "\nSummary ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@echo "Documentation generated successfully."
+	@echo "Open docs/build/html/index.html in your browser."
+	@echo "Or serve it locally using:"
+	@echo "python -m http.server -d docs/build/html/"
+
+.PHONY: d
+d: docs
+
+.PHONY: format-lint
+format-lint:
+	@echo "\nRunning linter and formatter using ruff and typos +++++++++++++++++++++++++++++\n"
+	@ruff format && ruff check --fix
+	@typos .
+
+.PHONY: fl
+fl: format-lint
+
+.PHONY: install
+install:
+	@echo "\nInstalling dependencies and with this package +++++++++++++++++++++++++++++++++\n"
+	@poetry install
+
+.PHONY: i
+i: install
+
+.PHONY: precommit-check
+precommit-check:
+	@echo "\nRunning pre-commit checks +++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@pre-commit run --all-files
+
+.PHONY: pc
+pc: precommit-check
+
+.PHONY: security
+security:
+	@echo "\nRunning security scans using bandit and safety ++++++++++++++++++++++++++++++++\n"
+	@safety check --full-report
+	@bandit -c pyproject.toml -r tus_storagehandler
+
+.PHONY: s
+s: security
+
+.PHONY: test
+test:
+	@echo "\nRunning tests using pytest ++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@pytest tests/
+
+.PHONY: t
+t: test
+
+.PHONY: type-check
+type-check:
+	@echo "\nPerforming type checking with mypy ++++++++++++++++++++++++++++++++++++++++++++\n"
+	@mypy tus_storagehandler
+
+.PHONY: tc
+tc: type-check
+
+.PHONY: venv
+venv:
+	@echo "\nCreating a virtual environment ++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@python -m venv .venv
+	@echo "\nSummary +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"
+	@echo "Virtual environment created successfully."
+	@echo "To activate the environment for this shell session, run:"
+	@echo "source .venv/bin/activate"
+
+.PHONY: v
+v: venv
+
+## Custom Targets #############################################################
+# NOTE: Keep all the targets in alphabetical order for better readability.
+# NOTE: Add any custom targets here if needed in the future.
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..b5dd732
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,64 @@
+#### Description
+
+<!-- Please include a summary of the change and the relevant issue(s) it
+resolves, if any (otherwise delete that line), e.g., `Fixes #123`. If the PR
+addresses more than one issue, please add multiple lines, each starting with
+'Fixes #'. Please stick to that syntax precisely, including whitespaces,
+otherwise the issue(s) may not be linked to the PR.
+
+In the summary, list any dependencies that are required for this change.
+Please use bullet points for the description. Please also briefly describe
+the relevant motivation and context briefly. For very trivial changes that are
+duly explained by the PR title, a description can be omitted. -->
+
+- Fixes #(issue number)
+
+#### Checklist
+
+<!-- Please go through the following checklist to ensure that your change is
+ready for review. Please do not forget to double check the list after you have
+modified your PR, e.g., if you have added commits to address reviewer
+comments or to fix failing automated checks. **Please check items also if they
+do not apply to your change**, e.g., if your change does not require an update
+of the user-facing documentation, still check the box.
+
+Generally, **PRs are only reviewed when all boxes are ticked off and all
+automated checks pass** (use the comment section below if you believe that
+your PR is ready to be merged even though not all boxes were ticked off). -->
+
+- [ ] My code follows the [contributing guidelines][contributing-guidelines] of this
+    project, including, in particular, with regard to any style guidelines
+- [ ] The title of my PR complies with the [Conventional Commits
+    specification][conv-commits]; in particular, it clearly indicates
+    that a change is a breaking change
+- [ ] I acknowledge that all my commits will be squashed into a single commit,
+    using the PR title as the commit message
+- [ ] I have performed a self-review of my own code
+- [ ] I have commented my code in hard-to-understand areas
+- [ ] I have updated the user-facing documentation to describe any new or
+    changed behavior
+- [ ] I have added type annotations for all function/class/method interfaces
+    or updated existing ones (only for Python, TypeScript, etc.)
+- [ ] I have provided appropriate documentation ([Google-style Python
+    docstrings][py-doc-google]) for all packages/modules/functions/classes/
+    methods or updated existing ones
+- [ ] My changes generate no new warnings
+- [ ] I have added tests that prove my fix is effective or that my feature
+    works
+- [ ] New and existing unit tests pass locally with my changes
+- [ ] I have not reduced the existing code coverage
+
+
+#### Comments
+
+<!-- If there are unchecked boxes in the list above, but you would still like
+your PR to be reviewed or considered for merging, please describe here why
+boxes were not checked. For example, if you are positive that your commits
+should _not_ be squashed when merging, please explain why you think the PR
+warrants or requires multiple commits to be added to the history (but note that
+in that case, it is a prerequisite that all commits follow the Conventional
+Commits specification). -->
+
+[contributing-guidelines]: https://elixir-cloud-aai.github.io/guides/guide-contributor/workflow/
+[conv-commits]: https://www.conventionalcommits.org/en
+[py-doc-google]: https://google.github.io/styleguide/pyguide.html
diff --git a/README.md b/README.md
deleted file mode 100644
index 9ce6277..0000000
--- a/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# TUS Storage Handler
-
-## Synopsis
-This Flask application provides endpoints for uploading, downloading, and listing files in a MinIO bucket, with TUS protocol support for uploads, and CORS enabled for cross-origin requests.
-
-## Installation
-
-### Prerequisites
-This flask application requires a running instance of [minio](https://min.io/download)
-
-Run the minio instance by executing the following command in the location where minio is installed
-
-`minio server /data --console-address ":9001"`
-
-Download the required dependencies
-
-1. Navigate to the folder `TusStorageHandler`
-2. Create a virtual environment and activate it (optional)
-3. `pip install -r requirements.txt`
-
-### Running the application
-
-`flask run`
-The application will be running on `http://127.0.0.1:5000` by default.
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = source
+BUILDDIR      = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..747ffb7
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..cfe671f
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,102 @@
+"""Configuration file for the Sphinx documentation builder."""
+
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+import datetime
+import os
+import sys
+from pathlib import Path
+
+import tomli
+
+sys.path.insert(0, os.path.abspath("../.."))
+
+
+# -- Project information -----------------------------------------------------
+def _get_project_meta():
+  _pyproject_path = Path(__file__).parents[2] / "pyproject.toml"
+  with open(_pyproject_path, mode="rb") as pyproject:
+    return tomli.load(pyproject)["tool"]["poetry"]
+
+
+pkg_meta = _get_project_meta()
+current_year = datetime.datetime.now().year
+project = str(pkg_meta["name"])
+project_copyright = f"{current_year}, {str(pkg_meta['authors'][0])}"
+author = str(pkg_meta["authors"][0])
+
+version = str(pkg_meta["version"])
+release = version
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+  "sphinx.ext.autodoc",
+  "sphinx.ext.doctest",
+  "sphinx.ext.todo",
+  "sphinx.ext.coverage",
+  "sphinx.ext.viewcode",
+  "sphinx.ext.autosummary",
+  # Used to write beautiful docstrings:
+  "sphinx.ext.napoleon",
+  # Used to include .md files:
+  "m2r2",
+  # Used to insert typehints into the final docs:
+  "sphinx_autodoc_typehints",
+  # Used to embed values from the source code into the docs:
+  "added_value",
+]
+# Set `typing.TYPE_CHECKING` to `True`:
+# https://pypi.org/project/sphinx-autodoc-typehints/
+set_type_checking_flag = False
+always_document_param_types = False
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+
+source_suffix = [".rst", ".md"]
+
+# The master toctree document.
+master_doc = "index"
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = "en"
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path .
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = "furo"
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# -- Extension configuration -------------------------------------------------
+napoleon_numpy_docstring = False
+
+# -- Options for todo extension ----------------------------------------------
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = True
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..f51987b
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,15 @@
+Welcome to tus_storagehandler's documentation!
+===========================================================
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   pages/modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/images/logo-elixir-cloud-aai.svg b/images/logo-elixir-cloud-aai.svg
new file mode 100644
index 0000000..18ecf21
--- /dev/null
+++ b/images/logo-elixir-cloud-aai.svg
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="121.508"
+   height="128"
+   viewBox="0 0 32.148991 33.866664"
+   version="1.1"
+   id="svg8"
+   inkscape:version="1.2 (56b05e47e7, 2022-06-09, custom)"
+   sodipodi:docname="logo-elixir-cloud-aai.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="-32.324881"
+     inkscape:cy="6.8185296"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     units="px"
+     inkscape:window-width="1848"
+     inkscape:window-height="1016"
+     inkscape:window-x="72"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:document-rotation="0"
+     inkscape:showpageshadow="2"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-75.684046,-39.803727)">
+    <g
+       id="g984"
+       transform="matrix(1.5094201,0,0,1.509249,-51.566629,-16.028093)"
+       style="stroke-width:0.49464">
+      <g
+         style="stroke-width:1.37493"
+         id="g1227"
+         transform="matrix(0.07369114,0,0,0.07390181,66.854225,-101.99645)">
+        <path
+           sodipodi:nodetypes="ccccc"
+           inkscape:connector-curvature="0"
+           id="rect828-9-5-0"
+           d="m 422.307,1979.569 28.684,17.791 h -48.234 l -28.684,-17.791 z"
+           style="fill:#d8e0e3;fill-opacity:1;stroke:none;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           sodipodi:nodetypes="ccccc"
+           inkscape:connector-curvature="0"
+           id="rect911-0-3"
+           d="m 381.11912,1953.2738 h 94.98398 l -7.05727,26.295 H 374.0734 Z"
+           style="fill:#f95b45;fill-opacity:1;stroke:#e2483e;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           inkscape:connector-curvature="0"
+           id="rect911-6-8-1-4"
+           d="m 302.71148,1919.8889 -26.28711,7.0586 55.83594,96.709 h 111.66992 l 7.06055,-26.2969 H 347.44195 Z"
+           style="fill:#5aba62;fill-opacity:1;stroke:#469f4e;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           sodipodi:nodetypes="ccccc"
+           inkscape:connector-curvature="0"
+           id="rect828-9-5-0-3"
+           d="m 359.87419,2067.7443 28.684,17.791 h -48.234 l -28.684,-17.791 z"
+           style="fill:#d8e0e3;fill-opacity:1;stroke:none;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           sodipodi:nodetypes="ccccc"
+           inkscape:connector-curvature="0"
+           id="rect911-0-6-0"
+           d="m 293.57421,2085.5353 h 94.98398 l -7.05727,26.2951 h -94.97243 z"
+           style="fill:#23a9f6;fill-opacity:1;stroke:#1b92e1;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+        <path
+           id="rect911-6-8-1-5-4"
+           d="m 459.91949,2145.2151 26.28711,-7.0586 -55.83594,-96.709 H 318.70074 l -7.06055,26.2968 h 103.54883 z"
+           style="fill:#ffcb00;fill-opacity:1;stroke:#fcb20f;stroke-width:2.0624;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+           inkscape:connector-curvature="0" />
+      </g>
+    </g>
+  </g>
+  <style
+     id="style2"
+     type="text/css">
+	.st0{fill:#FBBC05;}
+</style>
+</svg>
diff --git a/images/logo-elixir.svg b/images/logo-elixir.svg
new file mode 100644
index 0000000..af9d4b9
--- /dev/null
+++ b/images/logo-elixir.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   version="1.1"
+   id="LOGOS"
+   x="0px"
+   y="0px"
+   viewBox="0 0 159.43299 128"
+   xml:space="preserve"
+   sodipodi:docname="logo-elixir.svg"
+   width="159.433"
+   height="128"
+   inkscape:version="1.2 (56b05e47e7, 2022-06-09, custom)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><defs
+   id="defs261" /><sodipodi:namedview
+   id="namedview259"
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1.0"
+   inkscape:showpageshadow="2"
+   inkscape:pageopacity="0.0"
+   inkscape:pagecheckerboard="0"
+   inkscape:deskcolor="#d1d1d1"
+   showgrid="false"
+   inkscape:zoom="0.77122964"
+   inkscape:cx="-164.67209"
+   inkscape:cy="296.28011"
+   inkscape:window-width="1848"
+   inkscape:window-height="1016"
+   inkscape:window-x="72"
+   inkscape:window-y="27"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="LOGOS" />
+<style
+   type="text/css"
+   id="style242">
+	.st0{fill:#F47D20;}
+	.st1{fill:#4D4848;}
+</style>
+
+
+
+
+
+
+<g
+   id="g270"
+   transform="matrix(0.16776762,0,0,0.16776762,9.3730807,10.446892)"
+   style="stroke-width:5.96063"><path
+     class="st0"
+     d="m 756.4,33.1 c -23.7,0 -43,19.2 -43,43 0,7.2 1.8,14.3 5.2,20.6 -4.3,2.1 -14.2,7.1 -28.2,14.4 -7.6,-16 -26.6,-22.8 -42.6,-15.3 -16,7.5 -22.8,26.6 -15.3,42.6 0.5,1 1,1.9 1.5,2.8 -7.2,4 -14.6,8.1 -22.3,12.4 -7.6,-11.6 -23.2,-14.9 -34.8,-7.3 -10.8,7 -14.5,21.1 -8.6,32.5 -7.9,4.7 -15.9,9.6 -23.9,14.5 -7.7,-6.6 -19.3,-5.6 -25.9,2.1 -3.9,4.6 -5.3,10.8 -3.7,16.7 C 482.6,233 451.9,254.9 427,276.2 397.9,211.2 355.1,139.9 295.4,81.8 l -0.2,-0.2 c -7,-6.9 -12.8,-3.8 -4.7,4.7 l 0.3,0.3 c 50.6,54.5 90.4,133.3 117.5,205.2 -102.8,87.8 -216.1,257.9 -91.9,311.3 18.1,7.8 26.3,-7.1 18.5,-11.7 -1.4,-0.8 -56.7,-30.7 -21.5,-122.6 h 88.5 c 4.7,0.2 8.7,-3.6 8.9,-8.3 0.2,-4.7 -3.6,-8.7 -8.3,-8.9 -0.2,0 -0.4,0 -0.6,0 h -81.2 c 5.3,-11.1 11.1,-22 17.5,-32.5 h 63.7 c 4.7,-0.2 8.5,-4.1 8.3,-8.9 -0.2,-4.5 -3.8,-8.1 -8.3,-8.3 h -52.7 c 6.6,-9.8 13.9,-20.1 22.2,-30.9 h 30.5 c 4.7,-0.2 8.5,-4.1 8.3,-8.9 -0.2,-4.5 -3.8,-8.1 -8.3,-8.3 H 385 c 9.6,-11.7 20.3,-24 32,-36.9 l 0.3,-0.3 c 23.3,67.6 34.6,125.2 32.1,144.5 -1.4,10.5 -8.1,14.8 -10.8,16.1 -4.3,2.1 -6.1,8.1 0.7,10.1 21.7,6.4 35.1,-15.2 37.5,-32.5 2.4,-17.5 -9.5,-81.5 -40.9,-157.7 26.2,-26.1 56.5,-51 87.4,-73.9 8.8,5.1 20,2.1 25.1,-6.7 1.6,-2.8 2.5,-6 2.5,-9.2 0,-1.2 -0.1,-2.4 -0.4,-3.6 8.2,-5.7 16.3,-11.3 24.4,-16.6 10.9,8.6 26.6,6.8 35.3,-4 4.9,-6.1 6.6,-14.1 4.7,-21.7 8.1,-5 15.9,-9.8 23.4,-14.3 12.2,12.8 32.5,13.2 45.2,1 8.6,-8.3 12,-20.6 8.6,-32.1 13.7,-7.6 23.6,-13 28.5,-15.8 13.4,19.6 40.1,24.7 59.7,11.3 19.6,-13.4 24.7,-40.1 11.3,-59.7 -7.8,-11.6 -21,-18.6 -35.2,-18.7 z"
+     id="path244"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="m 267,377.4 c 1.6,-3.5 2.9,-7.2 4.5,-11.8 1.9,-5.2 3.4,-10.4 4.5,-15.8 l 16.3,-70.7 c 1.3,-7.2 2.4,-12.6 2.7,-15.8 0.5,-2.8 0.7,-5.7 0.5,-8.6 v -3.2 h 37 l -22.2,98.3 c -1.6,7 -2.7,12.3 -3.2,16.1 -0.7,3.8 -0.9,7.7 -0.8,11.5 z"
+     id="path246"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="M 174.8,377.4 H 65.2 c -11,0 -18.5,-2.4 -22.5,-7.5 -2.1,-2.9 -3.2,-6.9 -3.2,-12 0.1,-4.6 0.7,-9.2 1.9,-13.7 L 55,284.7 c 2.9,-12 7.2,-20.6 13.1,-25.7 6.2,-5.1 14.5,-7.5 25.7,-7.5 h 80.9 c 11.5,0 19,2.4 22.8,7.5 2.4,3 3.5,7 3.5,12.3 0,4.5 -0.7,9 -1.9,13.4 l -8.8,39.4 H 80.2 l -5.9,25.7 c -0.4,2 -0.7,4.1 -0.8,6.2 0,1.3 0,2.4 0.5,2.9 0.8,1.6 3.2,2.4 7,2.4 h 80.3 c 7,0 14,-0.7 20.9,-2.1 z m -8.6,-98.3 c 0.7,-2.1 1.1,-4.2 1.1,-6.4 0,-0.9 -0.2,-1.8 -0.5,-2.7 -1.1,-1.6 -3.7,-2.4 -7.5,-2.4 h -56.8 c -3.7,0 -6.4,0.8 -8,2.4 -1.9,1.6 -3.2,4.6 -3.7,9.1 L 83.9,308 h 75.5 z"
+     id="path248"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="m 202.1,377.4 c 1.7,-3.9 3.1,-8.1 4.8,-13.2 2,-5.8 3.6,-11.7 4.8,-17.7 l 28.1,-130.3 c 1.7,-8.1 2.8,-14.1 3.4,-17.7 0.3,-3.3 0.6,-7.8 0.6,-13.2 H 283 l -35,161.3 c -1.7,7.8 -2.8,13.8 -3.4,18 -0.7,4.3 -1,8.6 -0.8,12.9 z"
+     id="path250"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="m 480.7,377.4 c 1.6,-3.5 2.9,-7.2 4.5,-11.8 1.9,-5.1 3.4,-10.4 4.6,-15.8 l 16.3,-70.7 c 1.3,-7.2 2.4,-12.6 2.7,-15.8 0.5,-2.8 0.7,-5.7 0.5,-8.6 v -3.2 h 37 L 524,349.8 c -1.6,7 -2.7,12.3 -3.2,16.1 -0.7,3.8 -0.9,7.7 -0.8,11.5 z"
+     id="path252"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="m 654.1,272.7 c -4,-0.3 -8,-0.5 -12.6,-0.5 h -18 c -3.5,0 -6.4,0.8 -8,2.1 -2.1,1.6 -3.5,4.3 -4.6,8.3 l -15,67.2 c -2.7,10.4 -3.8,18.5 -3.8,24.6 v 2.9 H 553 c 1.6,-4 3.2,-7.8 4.3,-11.5 1.1,-3.5 2.4,-8.8 4.3,-16.1 L 577.7,279 c 1.9,-7 2.7,-12.3 3.5,-16.3 0.4,-3.7 0.6,-7.5 0.5,-11.2 h 32.9 l -1.3,12.8 c 1.8,-3.9 4.7,-7.3 8.3,-9.6 3.5,-2.1 8,-3.2 14.2,-3.2 H 668 l 7.8,13.9 c -1.7,1.5 -3.2,3.2 -4.5,5.1 -1.9,2.4 -3.2,3.5 -4,3.5 -4.9,-0.5 -9.2,-1 -13.2,-1.3 z"
+     id="path254"
+     style="stroke-width:5.96063" /><path
+     class="st1"
+     d="m 338.8,207.5 c 0,10 -8.1,18.1 -18.1,18.1 -10,0 -18.1,-8.1 -18.1,-18.1 0,-10 8.1,-18.1 18.1,-18.1 0,0 0,0 0,0 10,-0.1 18.1,8.1 18.1,18.1 0,0 0,0 0,0 z"
+     id="path256"
+     style="stroke-width:5.96063" /></g>
+</svg>
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..6dcc85e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,82 @@
+[build-system]
+build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+
+[tool.mypy]
+warn_return_any = true
+warn_unused_configs = true
+
+[tool.poetry]
+authors = ["Elixir Cloud AAI <cloud-service@elixir-europe.org>"]
+classifiers = [
+  "Development Status :: 4 - Beta",
+  "License :: OSI Approved :: Apache Software License",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.11",
+]
+description = "File Handler Utilizing TUS and Minio (S3 Storage) with DRS Filer Integration"
+license = "Apache-2.0"
+maintainers = ["Elixir Cloud AAI <cloud-service@elixir-europe.org>"]
+name = "tus_storagehandler"
+readme = "README.md"
+repository = "https://github.com/elixir-cloud-aai/tus-storagehandler"
+version = "0.1.0"
+
+[tool.poetry.dependencies]
+python = "^3.11"
+
+[tool.poetry.group.docs.dependencies]
+added-value = ">=0.24.0,<1.0.0"
+docutils = "0.20.1"
+furo = ">=2024.5.6,<2025.0.0"
+m2r2 = "^0.3.3.post2"
+sphinx = "^7.3.7"
+sphinx-autodoc-typehints = "^2.1.0"
+tomli = ">=2.0.1,<3.0.0"
+
+[tool.poetry.group.lint.dependencies]
+ruff = ">=0.4.10,<1.0.0"
+typos = ">=1.22.8,<2.0.0"
+
+[tool.poetry.group.misc.dependencies]
+cruft = ">=2.15.0,<3.0.0"
+jello = ">=1.6.0,<2.0.0"
+pre-commit = ">=3.7.1,<4.0.0"
+
+[tool.poetry.group.security.dependencies]
+bandit = ">=1.7.8,<2.0.0"
+safety = ">=3.2.0,<4.0.0"
+
+[tool.poetry.group.test.dependencies]
+pytest = ">=8.2.2,<9.0.0"
+pytest-cov = ">=5.0.0,<=6.0.0"
+
+[tool.poetry.group.types.dependencies]
+mypy = ">=1.10.0,<2.0.0"
+
+[tool.poetry.scripts]
+tus_storagehandler = "tus_storagehandler.main:main"
+
+[tool.ruff]
+indent-width = 2
+
+[tool.ruff.format]
+docstring-code-format = true
+indent-style = "space"
+line-ending = "lf"
+quote-style = "double"
+
+[tool.ruff.lint]
+select = [
+  "B", # flake8-bugbear
+  "D", # pydocstyle
+  "E", # pycodestyle
+  "F", # Pyflakes
+  "I", # isort
+  "PL", # pylint
+  "SIM", # flake8-simplify
+  "UP", # pyupgrade
+]
+
+[tool.ruff.lint.pydocstyle]
+convention = "google"
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..f1365f8
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+"""This package contains tests."""
diff --git a/tests/test_integration/__init__.py b/tests/test_integration/__init__.py
new file mode 100644
index 0000000..9a74cfd
--- /dev/null
+++ b/tests/test_integration/__init__.py
@@ -0,0 +1 @@
+"""This package contains integration tests."""
diff --git a/tests/test_integration/test_dummy.py b/tests/test_integration/test_dummy.py
new file mode 100644
index 0000000..1491684
--- /dev/null
+++ b/tests/test_integration/test_dummy.py
@@ -0,0 +1,10 @@
+"""Dummy test file to avoid the error."""
+
+
+# TODO: Remove this file when adding real tests.
+# This test is a placeholder for the integration tests. It is a workaround
+# to avoid the error "Module has no tests" when running the tests with pytest.
+# Cf. https://github.com/pytest-dev/pytest/issues/2393
+def test_placeholder():
+  """Dummy test to avoid the error."""
+  pass
diff --git a/tests/test_unit/__init__.py b/tests/test_unit/__init__.py
new file mode 100644
index 0000000..2f312a6
--- /dev/null
+++ b/tests/test_unit/__init__.py
@@ -0,0 +1 @@
+"""This package contains unit tests."""
diff --git a/tests/test_unit/test_dummy.py b/tests/test_unit/test_dummy.py
new file mode 100644
index 0000000..4443c40
--- /dev/null
+++ b/tests/test_unit/test_dummy.py
@@ -0,0 +1,10 @@
+"""Dummy test file to avoid the error."""
+
+
+# TODO: Remove this file when adding real tests.
+# This test is a placeholder for the unit tests. It is a workaround
+# to avoid the error "Module has no tests" when running the tests with pytest.
+# Cf. https://github.com/pytest-dev/pytest/issues/2393
+def test_placeholder():
+  """Dummy test to avoid the error."""
+  pass
diff --git a/tus_storagehandler/__init__.py b/tus_storagehandler/__init__.py
new file mode 100644
index 0000000..9f8346a
--- /dev/null
+++ b/tus_storagehandler/__init__.py
@@ -0,0 +1 @@
+"""This package contains module for the tus_storagehandler."""
diff --git a/tus_storagehandler/main.py b/tus_storagehandler/main.py
new file mode 100644
index 0000000..666c8b1
--- /dev/null
+++ b/tus_storagehandler/main.py
@@ -0,0 +1,10 @@
+"""Entry point for python_cookiecutter."""
+
+
+def main():
+  """Main entry point for tus_storagehandler."""
+  print("Hello from tus_storagehandler!")
+
+
+if __name__ == "__main__":
+  main()