44406b72db
Moteva-style AI design workbench replica. Home prompt creates a project; the project page provides an infinite canvas with node drag, zoom/pan, a design chat panel, history replay, image asset upload, and project save/regenerate. - Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage; sky-valley/pi agent runtime adapter. - Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n, shadcn/ui components, auth (OTP/Turnstile/Google/WeChat). - Deploy: Docker Compose and k3s manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
46 lines
1.1 KiB
Go
46 lines
1.1 KiB
Go
package objectstorage
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
func objectKeyFromPublicURL(publicBase string, value string) (string, bool) {
|
|
value = strings.TrimSpace(value)
|
|
publicBase = strings.TrimRight(strings.TrimSpace(publicBase), "/")
|
|
if value == "" || publicBase == "" {
|
|
return "", false
|
|
}
|
|
|
|
if strings.HasPrefix(value, publicBase+"/") {
|
|
return unescapeObjectKey(strings.TrimPrefix(strings.Split(value, "?")[0], publicBase+"/"))
|
|
}
|
|
|
|
parsedValue, err := url.Parse(value)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
parsedBase, err := url.Parse(publicBase)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
basePath := strings.TrimRight(parsedBase.EscapedPath(), "/")
|
|
valuePath := parsedValue.EscapedPath()
|
|
if basePath == "" || !strings.HasPrefix(valuePath, basePath+"/") {
|
|
return "", false
|
|
}
|
|
return unescapeObjectKey(strings.TrimPrefix(valuePath, basePath+"/"))
|
|
}
|
|
|
|
func unescapeObjectKey(value string) (string, bool) {
|
|
value = strings.TrimLeft(strings.TrimSpace(value), "/")
|
|
if value == "" {
|
|
return "", false
|
|
}
|
|
objectKey, err := url.PathUnescape(value)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
return objectKey, objectKey != ""
|
|
}
|