/
t3
/
gateway-server
Обзор
Документация
Войти
/
t3
/
gateway-server
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/api/objects_handler.go
150 строк
5 KB
Ivan
end-to-end logging
25 июл 2026, 00:10
25 июл 2026, 00:10
663e207
Код
Авторство
О чём код?
package api import ( "log/slog" "net/http" "strings" "gitverse.ru/t3/gateway-server/internal/auth" "gitverse.ru/t3/gateway-server/internal/client" ) type ObjectsHandler struct { s3Client *client.S3Client iamClient *client.IAMClient logger *slog.Logger } func NewObjectsHandler(s3Client *client.S3Client, iamClient *client.IAMClient, logger *slog.Logger) *ObjectsHandler { return &ObjectsHandler{s3Client: s3Client, iamClient: iamClient, logger: logger} } func (h *ObjectsHandler) ListObjects(w http.ResponseWriter, r *http.Request) { claims := auth.ClaimsFromContext(r.Context()) if claims == nil { writeError(w, http.StatusUnauthorized, "unauthorized", "no session") return } bucket := r.PathValue("bucket") prefix := r.URL.Query().Get("prefix") resource := "arn:t3:s3:::" + bucket + "/*" check, err := h.iamClient.AuthCheck(r.Context(), claims.AccessKey, claims.AccessSecret, "s3:ListBucket", resource) if err != nil || !check.Allowed { writeError(w, http.StatusForbidden, "forbidden", "no permission to list objects") return } objects, err := h.s3Client.ListObjects(r.Context(), bucket, prefix, claims.AccessKey, claims.AccessSecret) if err != nil { h.logger.Error("list objects failed", "error", err, "bucket", bucket) writeError(w, http.StatusBadGateway, "bad_gateway", "S3 service error") return } result := make([]ObjectResponse, 0, len(objects)) for _, o := range objects { result = append(result, ObjectResponse{ Key: o.Key, Size: o.Size, LastModified: o.LastModified, ETag: o.ETag, }) } writeJSON(w, http.StatusOK, result) } // PresignObject dispatches to PresignDownload or PresignUpload based on the // captured {key...} suffix. This is necessary because Go's ServeMux requires // {...} wildcards to be the last segment of the pattern, but we need to match // both /objects/{key...}/presign-download and /objects/{key...}/presign-upload // while still allowing multi-segment keys. func (h *ObjectsHandler) PresignObject(w http.ResponseWriter, r *http.Request) { bucket := r.PathValue("bucket") key := r.PathValue("key") switch { case strings.HasSuffix(key, "/presign-download"): h.presignDownload(w, r, bucket, strings.TrimSuffix(key, "/presign-download")) case strings.HasSuffix(key, "/presign-upload"): h.presignUpload(w, r, bucket, strings.TrimSuffix(key, "/presign-upload")) default: writeError(w, http.StatusNotFound, "not_found", "unknown object action") } } func (h *ObjectsHandler) PresignDownload(w http.ResponseWriter, r *http.Request) { h.presignDownload(w, r, r.PathValue("bucket"), r.PathValue("key")) } func (h *ObjectsHandler) presignDownload(w http.ResponseWriter, r *http.Request, bucket, key string) { claims := auth.ClaimsFromContext(r.Context()) if claims == nil { writeError(w, http.StatusUnauthorized, "unauthorized", "no session") return } resource := "arn:t3:s3:::" + bucket + "/" + key check, err := h.iamClient.AuthCheck(r.Context(), claims.AccessKey, claims.AccessSecret, "s3:GetObject", resource) if err != nil || !check.Allowed { writeError(w, http.StatusForbidden, "forbidden", "no permission to download object") return } url := h.s3Client.PresignDownloadURL(bucket, key, claims.AccessKey, claims.AccessSecret) if url == "" { writeError(w, http.StatusInternalServerError, "internal_error", "failed to generate presigned URL") return } writeJSON(w, http.StatusOK, PresignResponse{URL: url, Method: "GET"}) } func (h *ObjectsHandler) PresignUpload(w http.ResponseWriter, r *http.Request) { h.presignUpload(w, r, r.PathValue("bucket"), r.PathValue("key")) } func (h *ObjectsHandler) presignUpload(w http.ResponseWriter, r *http.Request, bucket, key string) { claims := auth.ClaimsFromContext(r.Context()) if claims == nil { writeError(w, http.StatusUnauthorized, "unauthorized", "no session") return } resource := "arn:t3:s3:::" + bucket + "/" + key check, err := h.iamClient.AuthCheck(r.Context(), claims.AccessKey, claims.AccessSecret, "s3:PutObject", resource) if err != nil || !check.Allowed { writeError(w, http.StatusForbidden, "forbidden", "no permission to upload object") return } url := h.s3Client.PresignUploadURL(bucket, key, claims.AccessKey, claims.AccessSecret) if url == "" { writeError(w, http.StatusInternalServerError, "internal_error", "failed to generate presigned upload URL") return } writeJSON(w, http.StatusOK, PresignResponse{URL: url, Method: "PUT"}) } func (h *ObjectsHandler) DeleteObject(w http.ResponseWriter, r *http.Request) { claims := auth.ClaimsFromContext(r.Context()) if claims == nil { writeError(w, http.StatusUnauthorized, "unauthorized", "no session") return } bucket := r.PathValue("bucket") key := r.PathValue("key") resource := "arn:t3:s3:::" + bucket + "/" + key check, err := h.iamClient.AuthCheck(r.Context(), claims.AccessKey, claims.AccessSecret, "s3:DeleteObject", resource) if err != nil || !check.Allowed { writeError(w, http.StatusForbidden, "forbidden", "no permission to delete object") return } if err := h.s3Client.DeleteObject(r.Context(), bucket, key, claims.AccessKey, claims.AccessSecret); err != nil { h.logger.Error("delete object failed", "error", err, "bucket", bucket, "key", key) writeError(w, http.StatusBadGateway, "bad_gateway", "S3 service error") return } w.WriteHeader(http.StatusNoContent) }