v91.0 Ana Sayfa

API Dokümantasyonu

Hospital Storage, AWS S3 protokolüyle tam uyumlu bir nesne depolama API'si sunar. Mevcut S3 istemcileri, SDK'lar ve araçlar doğrudan çalışır — yalnızca endpoint URL'i değiştirin.

Base URL
http://host:8080
Auth
AWS Sig V4
Region
tr
Service
s3
Her yanıt X-Request-ID, x-amz-request-id ve x-amz-id-2 header'larını içerir. Destek taleplerinde bu değerleri paylaşın.

Kimlik Doğrulama

Tüm korumalı endpoint'ler AWS Signature V4 gerektirir. Sunucu saati ile ±15 dakika tolerans uygulanır.

Authorization Header
Authorization: AWS4-HMAC-SHA256
  Credential=<ACCESS_KEY>/<YYYYMMDD>/tr/s3/aws4_request,
  SignedHeaders=host;x-amz-content-sha256;x-amz-date,
  Signature=<hex_signature>
Zorunlu Header'lar
HeaderAçıklama
AuthorizationAWS4-HMAC-SHA256 formatında imza
X-Amz-DateISO8601 — örn: 20240115T120000Z
X-Amz-Content-Sha256Body SHA-256 hash'i
HostSunucu adresi
AWS CLI Kurulumu
# Bir kez çalıştır
aws configure set aws_access_key_id     ERISIM_ANAHTARINIZ
aws configure set aws_secret_access_key GIZLI_ANAHTARINIZ
aws configure set default.region        tr

aws s3 ls --endpoint-url http://localhost:8080

Hata Yanıtları

{ "error": { "code": "NoSuchBucket", "message": "bucket bulunamadi" } }
HTTPKodAçıklama
400InvalidRequestEksik veya hatalı parametre
403AccessDeniedGeçersiz imza veya yetersiz yetki
404NoSuchBucket / NoSuchKeyKaynak bulunamadı
409BucketAlreadyExists / ObjectLockedÇakışma veya WORM kilidi
429RateLimitExceeded100 req/s tenant limiti aşıldı
503StorageFull / ServiceUnavailableDisk >%95 veya bağımlılık hatası
503LeaderElectionInProgressCluster lider seçimi — 2s bekle, tekrar dene

Roller

super_admin
Tüm tenant ve sistem yönetimi
tenant_admin
Kendi tenant, kullanıcı, bucket
operator
Bucket & nesne CRUD, lifecycle
doctor
Tıbbi veri okuma ve yazma
backup_svc
Servis hesabı, yedekleme
technician
Salt okunur teknik destek
viewer
Yalnızca GET / HEAD
Rate limit: tenant başına 100 req/s, burst 200. Aşıldığında Retry-After header'ı gelir.

Bucket İşlemleri

GET/Bucket listele
Response 200
{ "buckets": [{ "name": "xray-arsiv", "created": "2024-01-15T10:00:00Z" }] }
PUT/{bucket}Bucket oluştur

Bucket adı: [a-z0-9][a-z0-9-]{1,61}[a-z0-9]

QueryAçıklama
?corsCORS konfigürasyonu (body: JSON)
?policyBucket policy (body: JSON)
AWS CLI
aws s3 mb s3://xray-arsiv --endpoint-url http://localhost:8080
DELETE/{bucket}Bucket sil (boş olmalı)
Response 204
(boş body)
GET/{bucket}Nesneleri listele (v2)
QueryVarsayılanAçıklama
prefixÖn ek filtresi: 2024/xray/
delimiterKlasör ayracı: /
max-keys1000Maks nesne sayısı
continuation-tokenSayfalama token'ı
?uploadsAktif multipart upload'ları listele
Response 200
{ "objects": [{ "key": "2024/hasta_001.dcm", "size": 2048576, "etag": "\"d41d...\"" }] }
PUT/{bucket}/wormWORM yapılandır
COMPLIANCE modunda etkinleştirilen WORM kapatılamaz.
Request Body
{ "enabled": true, "mode": "COMPLIANCE", "retention_days": 2555 }
PUT/{bucket}/lifecycleOtomatik silme kuralı
Request Body
{ "rules": [{ "id": "7-yil", "expiration_days": 2555, "enabled": true }] }

Nesne İşlemleri

PUT/{bucket}/{key}Nesne yükle

Anahtar slash içerebilir: 2024/xray/hasta.dcm. Tüm nesneler AES-256-GCM ile otomatik şifrelenir.

HeaderZorunlulukAçıklama
Content-LengthzorunluBody boyutu (byte)
Content-TypeopsiyonelÖrn: application/dicom
x-amz-meta-*opsiyonelÖzel metadata: x-amz-meta-patient-id: 123
Response 200
{ "etag": "\"d41d8cd...\"", "version_id": "v1710076920-abc123" }
AWS CLI
aws s3 cp hasta.dcm s3://xray-arsiv/2024/hasta.dcm --endpoint-url http://localhost:8080
GET/{bucket}/{key}Nesne indir
QueryAçıklama
?versionId=...Belirli bir versiyonu indir
?versions=trueTüm versiyonları listele
Response Headers
HeaderAçıklama
ETagNesne MD5 hash'i
x-amz-version-idVersion ID
x-amz-server-side-encryptionAES256
x-amz-meta-*Yüklenirken eklenen özel metadata
HEAD/{bucket}/{key}Nesne meta verisi (body yok)

Body olmadan tüm metadata header'larını döner. Büyük dosyaların varlık kontrolü için idealdir.

DELETE/{bucket}/{key}Nesne sil

WORM/Object Lock aktif nesneler silinemez — ObjectLocked hatası döner.

QueryAçıklama
?versionId=...Belirli bir versiyonu sil
POST/{bucket}?deleteToplu nesne silme
Request Body (XML)
<Delete><Object><Key>2024/hasta_001.dcm</Key></Object></Delete>
GET/admin/presignPresign URL oluştur
QueryZorunlulukAçıklama
bucketzorunluBucket adı
keyzorunluNesne anahtarı
expiresopsiyonelSaniye — varsayılan 300, maks 604800
Response 200
{ "url": "http://host:8080/presign/eyJ...", "expires_at": "2024-03-10T15:30:00Z" }

Multipart Upload

5 GB üzeri dosyalar için. Her parça en az 5 MB olmalı (son parça hariç).

POST/{bucket}/{key}?uploads1 — Başlat
Response
{ "upload_id": "mpu-abc123" }
PUT/{bucket}/{key}?partNumber=N&uploadId=...2 — Parça yükle
Response
{ "etag": "\"abc123\"", "part_number": 1 }
POST/{bucket}/{key}?uploadId=...3 — Tamamla
Body
{ "parts": [{ "part_number": 1, "etag": "\"abc\"" }] }
DELETE/{bucket}/{key}?uploadId=...İptal et

Upload'u iptal eder, yüklenen tüm parçaları siler.

Sistem Endpoint'leri

GET/healthSistem durumu — auth gerekmez
Response 200 / 503
{ "status": "ok", "version": "v91.0", "is_leader": true,
  "checks": { "redis": "ok", "postgres": "ok", "disk_used_pct": "34.2%", "nats": "ok" } }
GET/metricsPrometheus metrikleri — auth gerekmez

Prometheus text formatında sistem metrikleri. Grafana ile doğrudan kullanılabilir.

Admin Endpoint'leri

GET/admin/whoamiDoğrulanan kullanıcı
{ "access_key": "admin", "role": "super_admin", "tenant_id": "system" }
GET/admin/audit/exportKVKK audit export (CSV / JSON)
QueryVarsayılanAçıklama
formatcsvcsv veya json
from30 gün önceRFC3339: 2024-01-01T00:00:00Z
toşimdiRFC3339
tenantTenant filtresi (super_admin)
actionPUT, GET, DELETE, LOGIN...
limit10000Maks 50000
GET/admin/gc/statusGC istatistikleri
{ "total_runs": 48, "deleted_bytes_human": "2.3 GB", "last_run_at": "2024-03-10T14:00:00Z" }
POST/admin/rebuildRedis'i yeniden oluştur — super_admin
Yalnızca Redis veri kaybında kullanın. Disk sidecar'larından metadata yeniden oluşturur.
POST/admin/rotate-keysMaster key rotasyonu — super_admin
Request Body
{ "old_master_key_hex": "<64 karakter hex>" }

Tüm nesne DEK'lerini eski key ile çözer, yeni key ile şifreler. Yeni key sunucu yeniden başlatılmadan önce yerinde olmalıdır.

GET/admin/usage?tenant_id=...Tenant depolama kullanımı
{ "tenant_id": "t1", "total_bytes": 1073741824, "object_count": 245, "bucket_count": 3, "quota_gb": 100 }

Tenant (Kurum) Yönetimi

super_admin: tüm tenant'ları yönetir. tenant_admin: sadece kendi kurumunu görür.

GET/admin/tenantsTenant listele
[{ "id": "t1", "name": "Antalya Devlet", "slug": "antalya-devlet", "status": "active", "quota_gb": 100 }]
POST/admin/tenantsTenant oluştur — super_admin
Request Body
{ "name": "Yeni Hastane", "slug": "yeni-hastane", "quota_gb": 50 }
PUT/admin/tenants?id=...Tenant güncelle
{ "name": "Güncel Ad", "quota_gb": 200 }
PUT/admin/tenants/statusTenant durumu değiştir (active/suspended)
{ "tenant_id": "t1", "status": "suspended" }
POST/admin/tenants/approveBaşvuru onayla
{ "registration_id": "reg_abc123" }
GET/admin/registrationsBekleyen başvuruları listele

Self-servis kayıt (POST /api/register) sonrası admin onayı bekleyen başvurular.

Kullanıcı Yönetimi

Roller: super_admintenant_adminoperatordoctortechnicianbackup_svcviewer

GET/admin/users?tenant_id=...Kullanıcı listele
[{ "access_key": "dr.ahmet", "name": "Dr. Ahmet", "role": "doctor", "is_active": true }]
POST/admin/usersKullanıcı oluştur
Request Body
{ "tenant_id": "t1", "access_key": "dr.ahmet", "secret_key": "GucluSifre123456",
  "role": "doctor", "name": "Dr. Ahmet Yılmaz", "email": "[email protected]" }
Şifre kuralları: en az 16 karakter, en az 1 büyük harf, 1 küçük harf, 1 rakam.
PUT/admin/users?id=...Kullanıcı güncelle (ad, rol, durum)
{ "name": "Dr. Ahmet Y.", "role": "operator", "is_active": true }
DELETE/admin/users?id=...Kullanıcı sil

Kullanıcı kalıcı olarak silinir. Silinmiş kullanıcının verileri tenant'ta kalır.

POST/admin/users/reset-secretŞifre sıfırla — super_admin
{ "user_id": "dr.ahmet", "new_secret": "YeniGucluSifre123" }
POST/admin/users/change-passwordKendi şifresini değiştir
{ "access_key": "dr.ahmet", "current_secret": "EskiSifre123456!", "new_secret": "YeniSifre789012!" }

Güvenlik

2FA / TOTP

POST/admin/2fa/setupTOTP kurulum başlat (QR URI döner)
{ "qr_uri": "otpauth://totp/HospitalStorage:admin?secret=...", "secret": "JBSWY3DPEHPK3PXP" }
POST/admin/2fa/verify6 haneli kod doğrula, 2FA aktifleştir
{ "code": "123456" }
// Response: backup codes listesi (tek seferlik)
POST/admin/2fa/disable2FA kapat
{ "access_key": "dr.ahmet" }
GET/admin/2fa/status2FA durumu sorgula
{ "enabled": true, "verified": true, "backup_codes_remaining": 8 }

Acil Durum Modu — super_admin

POST/admin/emergency/read-onlySistemi salt-okunur moda al

Tüm yazma işlemleri (PUT, DELETE) reddedilir. Okuma devam eder. Güvenlik ihlali şüphesinde kullanın.

POST/admin/emergency/lockdownTam kilitleme — sadece /health yanıt verir
Tüm API operasyonları durur. Yalnızca aktif saldırı altında kullanın.
POST/admin/emergency/resumeNormal moda dön

Read-only veya lockdown modundan normal operasyona geçiş.

GET/admin/emergency/statusMevcut acil durum modu
{ "mode": "normal" }  // "normal", "read_only", "lockdown"

IP Erişim Kuralları — super_admin

GET/admin/ip-rulesIP kurallarını listele
[{ "id": "r1", "tenant_id": "t1", "cidr": "10.0.0.0/24", "type": "whitelist" }]
POST/admin/ip-rulesIP kuralı ekle
{ "tenant_id": "t1", "cidr": "192.168.1.0/24", "type": "whitelist" }

type: whitelist (sadece bu IP'ler erişir) veya blacklist (bu IP'ler engellenir).

DELETE/admin/ip-rules/{id}IP kuralı sil

Kural kalıcı olarak silinir.

KVKK Uyumluluk

Veri İhlali Bildirimi

POST/admin/compliance/breach-reportİhlal raporu oluştur
{ "title": "Yetkisiz erişim tespiti", "severity": "high",
  "detection_method": "anomaly", "description": "..." }

KVKK Madde 12: İhlal tespitinden itibaren 72 saat içinde bildirim zorunlu.

GET/admin/compliance/breach-reportsİhlal raporlarını listele

Tüm breach raporları: severity (low/medium/high/critical), status (open/investigating/contained/resolved/reported).

POST/admin/compliance/breach-notify/{id}KVKK kuruluna bildirim gönder

Raporu "reported" durumuna geçirir ve bildirim tarihini kaydeder.

Veri Silme Talebi (Right to Erasure)

POST/admin/compliance/erasure-requestSilme talebi oluştur
{ "tenant_id": "t1", "requester": "Hasta Mehmet",
  "data_subject": "TC:12345678901", "reason": "KVKK talep" }
GET/admin/compliance/erasure-requestsSilme taleplerini listele

Durumlar: pendingapprovedprocessingcompleted

POST/admin/compliance/erasure-approve/{id}Silme talebini onayla ve çalıştır

Prefix scan → obje silme → audit anonimleştirme → sonuç raporu. Geri alınamaz.

IAM & Erişim Kuralları

POST/admin/iam/policiesIAM politikası ekle
{ "user_id": "dr.ahmet", "policy": {
    "Effect": "Allow",
    "Action": ["s3:GetObject"],
    "Resource": ["arn:aws:s3:::radyoloji/*"]
  } }

AWS IAM uyumlu JSON politika. Deny her zaman Allow'u override eder.

GET/admin/iam/policiesIAM politikalarını listele

Tenant'a ait tüm kullanıcı politikaları.

DELETE/admin/iam/policies/{id}IAM politikası sil

Politika kalıcı olarak silinir.

Hasta Erişim Kuralları

POST/admin/access-rulesErişim kuralı ekle
{ "user_id": "dr.ahmet", "patient_tc": "12345678901", "access_type": "read" }
GET/admin/access-rulesErişim kurallarını listele

Hangi doktorun hangi hastanın dosyalarına erişebildiğini gösterir.

GET/admin/patient-access-logHasta erişim logu (KVKK)

Hangi doktor, hangi hastanın dosyasına, ne zaman erişti — KVKK denetim raporu için.

Domain & Altyapı

GET/admin/domainsCustom domain'leri listele
[{ "domain": "dosya.hastane.com", "tenant_id": "t1", "is_verified": true }]
POST/admin/domainsCustom domain ekle
{ "domain": "dosya.hastane.com", "tenant_id": "t1" }

DNS TXT kaydı ile doğrulama gerekir.

POST/admin/domains/verifyDomain DNS doğrulaması
{ "domain": "dosya.hastane.com" }
GET/admin/shardmapShard map (tenant → node eşleme)

Multi-node yapıda hangi tenant'ın hangi node'da olduğunu gösterir.

GET/admin/pluginsYüklü plugin'leri listele

Healthcare plugin framework — aktif eklentiler.

GET/admin/dlqDead Letter Queue görüntüle

Başarısız event'ler (NATS publish hataları). Manuel retry veya silme.

Hasta Portalı

Hastalar kendi dosyalarını görebilir, paylaşım linki oluşturabilir.

POST/api/patients/registerHasta kaydı — auth gerekmez
{ "tc_no": "12345678901", "name": "Mehmet Yılmaz", "password": "..." }
POST/api/patients/loginHasta girişi — auth gerekmez
{ "tc_no": "12345678901", "password": "..." }
GET/api/patients/search?q=...Hasta ara (doktor)

TC no veya ad ile hasta arama. Doctor+ rolü gerekir.

GET/api/patients/{id}/filesHastanın dosyalarını listele

Hasta ID veya TC no ile dosya listesi.

POST/api/patient-linksHasta paylaşım linki oluştur
{ "patient_id": "p1", "tenant_id": "t1", "expires_secs": 86400, "max_views": 5 }

Token-based link: /patient/view/{token} — auth gerekmez, süreli erişim.

POST/api/registerKurum başvurusu (self-servis) — auth gerekmez
{ "hospital_name": "Yeni Hastane", "contact_name": "Ali", "email": "[email protected]", "phone": "05551234567" }

Başvuru admin onayı bekler (POST /admin/tenants/approve).

AWS CLI

# Konfigürasyon
aws configure set aws_access_key_id     ERISIM_ANAHTARINIZ
aws configure set aws_secret_access_key GIZLI_ANAHTARINIZ
aws configure set default.region        tr

# Kısayol
alias s3h="aws s3 --endpoint-url http://localhost:8080"

# Bucket
s3h ls                                                   # listele
s3h mb s3://xray-arsiv                                   # oluştur

# Nesne
s3h cp hasta.dcm s3://xray-arsiv/2024/hasta.dcm          # yükle
s3h cp s3://xray-arsiv/2024/hasta.dcm lokal.dcm          # indir
s3h ls s3://xray-arsiv/2024/                             # listele
s3h rm s3://xray-arsiv/2024/hasta.dcm                    # sil
s3h sync ./dosyalar/ s3://xray-arsiv/2024/               # senkronize et

# Presign URL (5 dakika)
aws s3 presign s3://xray-arsiv/2024/hasta.dcm \
  --expires-in 300 --endpoint-url http://localhost:8080

Python — boto3

import boto3
from botocore.client import Config

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:8080",
    aws_access_key_id="ERISIM_ANAHTARINIZ",
    aws_secret_access_key="GIZLI_ANAHTARINIZ",
    region_name="tr",
    config=Config(signature_version="s3v4")
)

# Yükle
with open("hasta.dcm", "rb") as f:
    s3.put_object(Bucket="xray-arsiv", Key="2024/hasta.dcm", Body=f,
                  ContentType="application/dicom",
                  Metadata={"patient-id": "12345678901"})

# Listele
resp = s3.list_objects_v2(Bucket="xray-arsiv", Prefix="2024/")
for obj in resp.get("Contents", []):
    print(obj["Key"], obj["Size"])

# Presign URL
url = s3.generate_presigned_url("get_object",
    Params={"Bucket": "xray-arsiv", "Key": "2024/hasta.dcm"},
    ExpiresIn=3600)

# Multipart (>5 GB)
mpu = s3.create_multipart_upload(Bucket="xray-arsiv", Key="buyuk.dcm")
parts = []
for i, chunk in enumerate(chunks, 1):
    p = s3.upload_part(Bucket="xray-arsiv", Key="buyuk.dcm",
                       UploadId=mpu["UploadId"], PartNumber=i, Body=chunk)
    parts.append({"PartNumber": i, "ETag": p["ETag"]})
s3.complete_multipart_upload(Bucket="xray-arsiv", Key="buyuk.dcm",
    UploadId=mpu["UploadId"], MultipartUpload={"Parts": parts})

Go — aws-sdk-go-v2

package main

import (
    "context"; "os"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/credentials"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func newClient() *s3.Client {
    cfg, _ := config.LoadDefaultConfig(context.Background(),
        config.WithRegion("tr"),
        config.WithCredentialsProvider(
            credentials.NewStaticCredentialsProvider("ANAHTAR", "GIZLI", ""),
        ),
    )
    return s3.NewFromConfig(cfg, func(o *s3.Options) {
        o.BaseEndpoint = aws.String("http://localhost:8080")
        o.UsePathStyle = true
    })
}

func main() {
    client, ctx := newClient(), context.Background()

    // Yükle
    f, _ := os.Open("hasta.dcm"); defer f.Close()
    client.PutObject(ctx, &s3.PutObjectInput{
        Bucket: aws.String("xray-arsiv"),
        Key:    aws.String("2024/hasta.dcm"),
        Body:   f,
    })

    // Listele
    out, _ := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
        Bucket: aws.String("xray-arsiv"),
        Prefix: aws.String("2024/"),
    })
    for _, o := range out.Contents { println(*o.Key) }
}