package objectstorage import ( "fmt" "net/url" "strings" "github.com/geo-platform/tenant-api/internal/shared/config" ) func buildPublicURL(cfg config.ObjectStorageConfig, objectKey string, bucketInHost bool) (string, error) { objectKey = strings.TrimSpace(objectKey) if objectKey == "" { return "", fmt.Errorf("object key is required") } defaultScheme := "http" if cfg.UseSSL { defaultScheme = "https" } if publicBaseURL := strings.TrimSpace(cfg.PublicBaseURL); publicBaseURL != "" { baseURL, err := parseURLWithScheme(publicBaseURL, defaultScheme) if err != nil { return "", fmt.Errorf("parse public_base_url: %w", err) } baseURL.Path = joinEscapedPath(baseURL.Path, objectKey) baseURL.RawPath = "" return baseURL.String(), nil } endpoint := strings.TrimSpace(cfg.Endpoint) bucket := strings.TrimSpace(cfg.Bucket) if endpoint == "" || bucket == "" { return "", fmt.Errorf("%w: object storage endpoint or bucket is missing", ErrNotConfigured) } endpointURL, err := parseURLWithScheme(endpoint, defaultScheme) if err != nil { return "", fmt.Errorf("parse object storage endpoint: %w", err) } if bucketInHost { hostname := endpointURL.Hostname() if hostname == "" { return "", fmt.Errorf("invalid object storage endpoint host") } host := hostname if !strings.HasPrefix(host, bucket+".") { host = bucket + "." + host } if port := endpointURL.Port(); port != "" { host += ":" + port } endpointURL.Host = host endpointURL.Path = joinEscapedPath("", objectKey) endpointURL.RawPath = "" return endpointURL.String(), nil } endpointURL.Path = joinEscapedPath(endpointURL.Path, bucket, objectKey) endpointURL.RawPath = "" return endpointURL.String(), nil } func parseURLWithScheme(raw string, defaultScheme string) (*url.URL, error) { raw = strings.TrimSpace(raw) if raw == "" { return nil, fmt.Errorf("url is empty") } if !strings.Contains(raw, "://") { raw = defaultScheme + "://" + raw } parsed, err := url.Parse(raw) if err != nil { return nil, err } if parsed.Host == "" { return nil, fmt.Errorf("url host is empty") } return parsed, nil } func joinEscapedPath(basePath string, extra ...string) string { segments := make([]string, 0) appendPath := func(value string) { value = strings.TrimSpace(value) value = strings.Trim(value, "/") if value == "" { return } for _, segment := range strings.Split(value, "/") { segment = strings.TrimSpace(segment) if segment == "" { continue } segments = append(segments, url.PathEscape(segment)) } } appendPath(basePath) for _, value := range extra { appendPath(value) } if len(segments) == 0 { return "/" } return "/" + strings.Join(segments, "/") }