Pkg: Add service/cluster package & rename media/http → service/http #98

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-09-13 12:58:28 +02:00
parent 3f11165f61
commit 023fbe3a1d
154 changed files with 354 additions and 236 deletions

View File

@@ -18,7 +18,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/tensorflow"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// Model represents a TensorFlow classification model.

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"strings"
"github.com/photoprism/photoprism/pkg/clusters"
"github.com/photoprism/photoprism/pkg/vector/alg"
)
// Embedding represents a face embedding.
@@ -54,7 +54,7 @@ func (m Embedding) Dist(other Embedding) float64 {
return -1
}
return clusters.EuclideanDist(m, other)
return alg.EuclideanDist(m, other)
}
// Magnitude returns the face embedding vector length (magnitude).

View File

@@ -7,7 +7,7 @@ import (
"github.com/montanaflynn/stats"
"github.com/photoprism/photoprism/pkg/clusters"
"github.com/photoprism/photoprism/pkg/vector/alg"
)
// Embeddings represents a face embedding cluster.
@@ -166,7 +166,7 @@ func EmbeddingsMidpoint(embeddings Embeddings) (result Embedding, radius float64
// Radius is the max embedding distance + 0.01 from result.
for _, emb := range embeddings {
if d := clusters.EuclideanDist(result, emb); d > radius {
if d := alg.EuclideanDist(result, emb); d > radius {
radius = d + 0.01
}
}

View File

@@ -12,8 +12,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// Model uses TensorFlow to label drawing, hentai, neutral, porn and sexy images.

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// PerformApiRequest performs a Vision API request and returns the result.

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
func TestNewApiRequest(t *testing.T) {

View File

@@ -8,8 +8,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// ApiResponseOllama represents a Ollama API service response.

View File

@@ -15,8 +15,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
type Files = []string

View File

@@ -11,7 +11,7 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
var (

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/nsfw"
"github.com/photoprism/photoprism/internal/ai/tensorflow"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
var modelMutex = sync.Mutex{}

View File

@@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
func TestModel(t *testing.T) {

View File

@@ -2,7 +2,7 @@ package vision
import (
"github.com/photoprism/photoprism/internal/ai/tensorflow"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// Default computer vision model configuration.

View File

@@ -1,7 +1,7 @@
package vision
import (
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// Service represents a remote computer vision service configuration.

View File

@@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
//go:embed embed/video.mp4

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Auth checks if the user is authorized to access a resource with the given permission

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/auth/session"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestAuth(t *testing.T) {

View File

@@ -3,7 +3,7 @@ package api
import (
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ClientIP returns the client IP address from the request context or a placeholder if it is unknown.

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// AddCountHeader adds the actual result count to the response.

View File

@@ -17,7 +17,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Ensure assets path is set so TestMain in this package can initialize config.

View File

@@ -12,8 +12,8 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
type ThumbCache struct {

View File

@@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestAddVideoCacheHeader(t *testing.T) {

View File

@@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ClusterGetTheme returns custom theme files as zip, if available.
@@ -44,13 +44,13 @@ func ClusterGetTheme(router *gin.RouterGroup) {
conf := get.Config()
// Abort if this is not a portal server.
if !conf.ClusterPortal() {
if !conf.IsPortal() {
AbortFeatureDisabled(c)
return
}
clientIp := ClientIP(c)
themePath := conf.ClusterThemePath()
themePath := conf.PortalThemePath()
// Resolve symbolic links.
if resolved, err := filepath.EvalSymlinks(themePath); err != nil {

View File

@@ -12,14 +12,15 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/cluster"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestClusterGetTheme(t *testing.T) {
t.Run("FeatureDisabled", func(t *testing.T) {
app, router, conf := NewApiTest()
// Ensure portal feature flag is disabled.
conf.Options().ClusterPortal = false
conf.Options().NodeType = cluster.Instance
ClusterGetTheme(router)
r := PerformRequest(app, http.MethodGet, "/api/v1/cluster/theme")
@@ -29,7 +30,7 @@ func TestClusterGetTheme(t *testing.T) {
t.Run("NotFound", func(t *testing.T) {
app, router, conf := NewApiTest()
// Enable portal feature flag for this endpoint.
conf.Options().ClusterPortal = true
conf.Options().NodeType = cluster.Portal
ClusterGetTheme(router)
missing := filepath.Join(os.TempDir(), "photoprism-test-missing-theme")
@@ -47,7 +48,7 @@ func TestClusterGetTheme(t *testing.T) {
t.Run("Success", func(t *testing.T) {
app, router, conf := NewApiTest()
// Enable portal feature flag for this endpoint.
conf.Options().ClusterPortal = true
conf.Options().NodeType = cluster.Portal
ClusterGetTheme(router)
tempTheme, err := os.MkdirTemp("", "pp-theme-*")
@@ -102,7 +103,7 @@ func TestClusterGetTheme(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
app, router, conf := NewApiTest()
// Enable portal feature flag for this endpoint.
conf.Options().ClusterPortal = true
conf.Options().NodeType = cluster.Portal
ClusterGetTheme(router)
// Create an empty temporary theme directory (no includable files).

View File

@@ -13,7 +13,7 @@ import (
swagger "github.com/swaggo/gin-swagger"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
//go:embed swagger.json

View File

@@ -15,7 +15,7 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// GetMetrics provides a Prometheus-compatible metrics stream for monitoring.

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// OAuthAuthorize should gather consent and authorization from resource owners when using the

View File

@@ -14,8 +14,8 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// OAuthRevoke takes an access token and deletes it. A client may only delete its own tokens.

View File

@@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestOAuthRevoke(t *testing.T) {

View File

@@ -16,7 +16,7 @@ import (
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// OAuthToken creates a new access token for clients that authenticate with valid OAuth2 client credentials.

View File

@@ -11,7 +11,7 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestOAuthToken(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// OAuthUserinfo should return information about the authenticated user,

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// OIDCLogin redirects a browser to the login page of the configured OpenID Connect provider, if any.

View File

@@ -16,8 +16,8 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/time/tz"
"github.com/photoprism/photoprism/pkg/time/unix"
"github.com/photoprism/photoprism/pkg/txt"

View File

@@ -12,7 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -13,7 +13,7 @@ import (
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// CreateSession creates a new client session and returns it as JSON if authentication was successful.

View File

@@ -3,7 +3,7 @@ package api
import (
"net/http"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/gin-gonic/gin"

View File

@@ -8,8 +8,8 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// GetSession returns the session data as JSON if authentication was successful.

View File

@@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/thumb/avatar"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// UploadUserAvatar updates the avatar image of the currently authenticated user.

View File

@@ -17,8 +17,8 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// CreateUserPasscode sets up a new two-factor authentication passcode.

View File

@@ -13,9 +13,9 @@ import (
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// GetVideo returns a video, optionally limited to a byte range for streaming.

View File

@@ -9,8 +9,8 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestGetVideo(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// PostVisionCaption returns a suitable caption for an image.

View File

@@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// PostVisionFace returns the embeddings of a face.

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
func TestPostVisionFace(t *testing.T) {

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// PostVisionLabels returns suitable labels for an image.

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
func TestPostVisionLabels(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// PostVisionNsfw checks the specified images for inappropriate content.

View File

@@ -10,7 +10,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
func TestPostVisionNsfw(t *testing.T) {

View File

@@ -22,8 +22,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// DownloadCommand configures the command name, flags, and action.

View File

@@ -63,6 +63,11 @@ func (c *Config) AuthMode() string {
}
}
// AuthSecret returns the key for signing authentication tokens, if specified.
func (c *Config) AuthSecret() string {
return c.options.AuthSecret
}
// Public checks if app runs in public mode and requires no authentication.
func (c *Config) Public() bool {
return c.AuthMode() == AuthModePublic

View File

@@ -51,6 +51,15 @@ func TestAuthMode(t *testing.T) {
c.options.Debug = false
}
func TestAuthSecret(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, "", c.AuthSecret())
c.options.AuthSecret = "341e1657d37759410de1ae628b95dbaa"
assert.Equal(t, "341e1657d37759410de1ae628b95dbaa", c.AuthSecret())
c.options.AuthSecret = ""
assert.Equal(t, "", c.AuthSecret())
}
func TestConfig_AdminPassword(t *testing.T) {
c := NewConfig(CliTestContext())

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestConfig_CdnUrl(t *testing.T) {

View File

@@ -1,19 +1,41 @@
package config
import (
"os"
"path/filepath"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/service/cluster"
)
// NodeName returns the human-readable instance name, if specified.
// NodeName returns the unique name of this node within the cluster (lowercase letters and numbers only).
func (c *Config) NodeName() string {
return c.options.NodeName
return clean.TypeLowerDash(c.options.NodeName)
}
// NodeSecret returns the node's authentication secret, if specified.
// NodeType returns the type of this node for cluster operation (portal, instance, service).
func (c *Config) NodeType() string {
switch c.options.NodeType {
case cluster.Portal, cluster.Instance, cluster.Service:
return c.options.NodeType
default:
return cluster.Instance
}
}
// NodeSecret returns the private node key for intra-cluster communication.
func (c *Config) NodeSecret() string {
if c.options.NodeSecret != "" {
return c.options.NodeSecret
} else if fileName := FlagFilePath("NODE_SECRET"); fileName == "" {
return ""
} else if b, err := os.ReadFile(fileName); err != nil || len(b) == 0 {
log.Warnf("config: failed to read node secret from %s (%s)", fileName, err)
return ""
} else {
return string(b)
}
}
// PortalUrl returns the URL of the cluster portal server, if configured.
@@ -21,35 +43,39 @@ func (c *Config) PortalUrl() string {
return c.options.PortalUrl
}
// PortalClient returns the portal client ID, if configured.
func (c *Config) PortalClient() string {
return c.options.PortalClient
// PortalToken returns the token required to access the portal API endpoints.
func (c *Config) PortalToken() string {
if c.options.PortalToken != "" {
return c.options.PortalToken
} else if fileName := FlagFilePath("PORTAL_TOKEN"); fileName == "" {
return ""
} else if b, err := os.ReadFile(fileName); err != nil || len(b) == 0 {
log.Warnf("config: failed to read portal token from %s (%s)", fileName, err)
return ""
} else {
return string(b)
}
// PortalSecret returns the portal client secret, if configured.
func (c *Config) PortalSecret() string {
return c.options.PortalSecret
}
// ClusterPortal returns true if this instance should act as a cluster portal.
func (c *Config) ClusterPortal() bool {
return c.options.ClusterPortal
return c.IsPortal()
}
// ClusterNode returns true if this instance should be configured as a cluster node.
func (c *Config) ClusterNode() bool {
return c.options.ClusterNode
// IsPortal returns true if the configured node type is "portal".
func (c *Config) IsPortal() bool {
return c.NodeType() == cluster.Portal
}
// ClusterConfigPath returns the path to portal config files.
func (c *Config) ClusterConfigPath() string {
// PortalConfigPath returns the path to the default configuration for cluster nodes.
func (c *Config) PortalConfigPath() string {
return filepath.Join(c.ConfigPath(), fs.ClusterDir)
}
// ClusterThemePath returns the path to the shared theme files.
func (c *Config) ClusterThemePath() string {
// PortalThemePath returns the path to the theme files for cluster nodes to use.
func (c *Config) PortalThemePath() string {
// Prefer the cluster-specific theme directory if it exists.
if dir := filepath.Join(c.ClusterConfigPath(), fs.ThemeDir); fs.PathExists(dir) {
if dir := filepath.Join(c.PortalConfigPath(), fs.ThemeDir); fs.PathExists(dir) {
return dir
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/service/cluster"
)
func TestConfig_Cluster(t *testing.T) {
@@ -16,13 +17,13 @@ func TestConfig_Cluster(t *testing.T) {
// Defaults
assert.False(t, c.ClusterPortal())
assert.False(t, c.ClusterNode())
assert.False(t, c.IsPortal())
// Toggle values
c.options.ClusterPortal = true
c.options.ClusterNode = true
c.Options().NodeType = string(cluster.Portal)
assert.True(t, c.ClusterPortal())
assert.True(t, c.ClusterNode())
assert.True(t, c.IsPortal())
c.Options().NodeType = ""
})
t.Run("Paths", func(t *testing.T) {
@@ -32,22 +33,22 @@ func TestConfig_Cluster(t *testing.T) {
tempCfg := t.TempDir()
c.options.ConfigPath = tempCfg
// ClusterConfigPath always points to a "cluster" subfolder under ConfigPath.
// PortalConfigPath always points to a "cluster" subfolder under ConfigPath.
expectedCluster := filepath.Join(c.ConfigPath(), fs.ClusterDir)
assert.Equal(t, expectedCluster, c.ClusterConfigPath())
assert.Equal(t, expectedCluster, c.PortalConfigPath())
// ClusterThemePath falls back to ThemePath if cluster dir does not exist.
// PortalThemePath falls back to ThemePath if cluster dir does not exist.
expectedTheme := filepath.Join(c.ConfigPath(), fs.ThemeDir)
assert.Equal(t, expectedTheme, c.ClusterThemePath())
assert.Equal(t, expectedTheme, c.PortalThemePath())
// When only the cluster directory exists (without a theme subfolder), it still falls back to ThemePath.
assert.NoError(t, os.MkdirAll(expectedCluster, 0o755))
assert.Equal(t, expectedTheme, c.ClusterThemePath())
assert.Equal(t, expectedTheme, c.PortalThemePath())
// When the cluster theme directory exists, ClusterThemePath returns it.
// When the cluster theme directory exists, PortalThemePath returns it.
expectedClusterTheme := filepath.Join(expectedCluster, fs.ThemeDir)
assert.NoError(t, os.MkdirAll(expectedClusterTheme, 0o755))
assert.Equal(t, expectedClusterTheme, c.ClusterThemePath())
assert.Equal(t, expectedClusterTheme, c.PortalThemePath())
})
t.Run("PortalAndSecrets", func(t *testing.T) {
@@ -55,19 +56,16 @@ func TestConfig_Cluster(t *testing.T) {
// Defaults
assert.Equal(t, "", c.PortalUrl())
assert.Equal(t, "", c.PortalClient())
assert.Equal(t, "", c.PortalSecret())
assert.Equal(t, "", c.PortalToken())
assert.Equal(t, "", c.NodeSecret())
// Set and read back values
c.options.PortalUrl = "https://portal.example.test"
c.options.PortalClient = "client-id"
c.options.PortalSecret = "client-secret"
c.options.PortalToken = "portal-token"
c.options.NodeSecret = "node-secret"
assert.Equal(t, "https://portal.example.test", c.PortalUrl())
assert.Equal(t, "client-id", c.PortalClient())
assert.Equal(t, "client-secret", c.PortalSecret())
assert.Equal(t, "portal-token", c.PortalToken())
assert.Equal(t, "node-secret", c.NodeSecret())
})
@@ -79,12 +77,65 @@ func TestConfig_Cluster(t *testing.T) {
// ThemePath should be absolute.
assert.True(t, filepath.IsAbs(c.ThemePath()))
// ClusterThemePath should be absolute (fallback case).
assert.True(t, filepath.IsAbs(c.ClusterThemePath()))
// PortalThemePath should be absolute (fallback case).
assert.True(t, filepath.IsAbs(c.PortalThemePath()))
// Create cluster theme directory and verify again.
clusterTheme := filepath.Join(c.ClusterConfigPath(), fs.ThemeDir)
clusterTheme := filepath.Join(c.PortalConfigPath(), fs.ThemeDir)
assert.NoError(t, os.MkdirAll(clusterTheme, 0o755))
assert.True(t, filepath.IsAbs(c.ClusterThemePath()))
assert.True(t, filepath.IsAbs(c.PortalThemePath()))
})
t.Run("NodeName", func(t *testing.T) {
c := NewConfig(CliTestContext())
c.options.NodeName = " Client Credentials幸"
assert.Equal(t, "client-credentials", c.NodeName())
c.options.NodeName = ""
assert.Equal(t, "", c.NodeName())
})
t.Run("NodeTypeValues", func(t *testing.T) {
c := NewConfig(CliTestContext())
// Default / unknown → node
c.options.NodeType = ""
assert.Equal(t, string(cluster.Instance), c.NodeType())
c.options.NodeType = "unknown"
assert.Equal(t, string(cluster.Instance), c.NodeType())
// Explicit values
c.options.NodeType = string(cluster.Instance)
assert.Equal(t, string(cluster.Instance), c.NodeType())
c.options.NodeType = string(cluster.Portal)
assert.Equal(t, string(cluster.Portal), c.NodeType())
c.options.NodeType = string(cluster.Service)
assert.Equal(t, string(cluster.Service), c.NodeType())
})
t.Run("SecretsFromFiles", func(t *testing.T) {
c := NewConfig(CliTestContext())
// Create temp secret/token files.
dir := t.TempDir()
nsFile := filepath.Join(dir, "node_secret")
tkFile := filepath.Join(dir, "portal_token")
assert.NoError(t, os.WriteFile(nsFile, []byte("s3cr3t"), 0o600))
assert.NoError(t, os.WriteFile(tkFile, []byte("t0k3n"), 0o600))
// Clear inline values so file-based lookup is used.
c.options.NodeSecret = ""
c.options.PortalToken = ""
// Point env vars at the files and verify.
t.Setenv("PHOTOPRISM_NODE_SECRET_FILE", nsFile)
t.Setenv("PHOTOPRISM_PORTAL_TOKEN_FILE", tkFile)
assert.Equal(t, "s3cr3t", c.NodeSecret())
assert.Equal(t, "t0k3n", c.PortalToken())
// Empty / missing should yield empty strings.
t.Setenv("PHOTOPRISM_NODE_SECRET_FILE", filepath.Join(dir, "missing"))
t.Setenv("PHOTOPRISM_PORTAL_TOKEN_FILE", filepath.Join(dir, "missing"))
assert.Equal(t, "", c.NodeSecret())
assert.Equal(t, "", c.PortalToken())
})
}

View File

@@ -9,8 +9,8 @@ import (
"github.com/photoprism/photoprism/internal/config/ttl"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
const (

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/config/ttl"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -16,8 +16,8 @@ import (
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
"github.com/photoprism/photoprism/pkg/time/tz"
"github.com/photoprism/photoprism/pkg/txt"
)
@@ -32,6 +32,12 @@ var Flags = CliFlags{
Value: "password",
EnvVars: EnvVars("AUTH_MODE"),
}}, {
Flag: &cli.StringFlag{
Name: "auth-secret",
Usage: "secret `KEY` for signing authentication tokens",
EnvVars: EnvVars("AUTH_SECRET"),
Hidden: true,
}}, {
Flag: &cli.BoolFlag{
Name: "public",
Aliases: []string{"p"},
@@ -593,11 +599,16 @@ var Flags = CliFlags{
}}, {
Flag: &cli.StringFlag{
Name: "site-url",
Aliases: []string{"url"},
Usage: "public site `URL`",
Usage: "public site `URL` used to build links and determine HTTPS/TLS; must include scheme (http/https)",
Value: "http://localhost:2342/",
EnvVars: EnvVars("SITE_URL"),
}}, {
Flag: &cli.StringFlag{
Name: "internal-url",
Usage: "internal server `URL` for service-to-service communication and local networking*optional*",
Value: "",
EnvVars: EnvVars("INTERNAL_URL"),
}}, {
Flag: &cli.StringFlag{
Name: "site-author",
Usage: "site `OWNER`, copyright, or artist",
@@ -631,12 +642,6 @@ var Flags = CliFlags{
Usage: "sharing preview image `URL`",
EnvVars: EnvVars("SITE_PREVIEW"),
}}, {
Flag: &cli.StringFlag{
Name: "internal-url",
Usage: "internal site `URL` to be used for local networking*optional*",
Value: "",
EnvVars: EnvVars("INTERNAL_URL"),
}}, {
Flag: &cli.StringFlag{
Name: "cdn-url",
Usage: "content delivery network `URL`",
@@ -665,6 +670,35 @@ var Flags = CliFlags{
EnvVars: EnvVars("CORS_METHODS"),
Value: header.DefaultAccessControlAllowMethods,
}}, {
Flag: &cli.StringFlag{
Name: "node-name",
Usage: "cluster node `NAME` (lowercase letters, digits, hyphens; 163 chars)",
EnvVars: EnvVars("NODE_NAME"),
}}, {
Flag: &cli.StringFlag{
Name: "node-type",
Usage: "cluster node `TYPE` (portal, instance, service)",
EnvVars: EnvVars("NODE_TYPE"),
Hidden: true,
}}, {
Flag: &cli.StringFlag{
Name: "node-secret",
Usage: "private `KEY` to secure intra-cluster communication*optional*",
EnvVars: EnvVars("NODE_SECRET"),
Hidden: true,
}}, {
Flag: &cli.StringFlag{
Name: "portal-url",
Usage: "base `URL` of the cluster portal e.g. https://portal.example.com",
EnvVars: EnvVars("PORTAL_URL"),
Hidden: true,
}, Tags: []string{Pro}}, {
Flag: &cli.StringFlag{
Name: "portal-token",
Usage: "access TOKEN for nodes to register and synchronize with the portal",
EnvVars: EnvVars("PORTAL_TOKEN"),
Hidden: true,
}, Tags: []string{Pro}}, {
Flag: &cli.StringFlag{
Name: "https-proxy",
Usage: "proxy server `URL` to be used for outgoing connections*optional*",
@@ -1108,46 +1142,6 @@ var Flags = CliFlags{
Value: face.MatchDist,
EnvVars: EnvVars("FACE_MATCH_DIST"),
}}, {
Flag: &cli.StringFlag{
Name: "node-name",
Usage: "unique `NAME` for this cluster node (lowercase, no spaces or special characters)",
EnvVars: EnvVars("NODE_NAME"),
}}, {
Flag: &cli.StringFlag{
Name: "node-secret",
Usage: "unique `SECRET` for authenticating this cluster node",
EnvVars: EnvVars("NODE_SECRET"),
}}, {
Flag: &cli.StringFlag{
Name: "portal-url",
Usage: "base `URL` of the cluster portal server e.g. https://portal.example.com",
EnvVars: EnvVars("PORTAL_URL"),
Hidden: true,
}, Tags: []string{Pro}}, {
/* Flag: &cli.StringFlag{
Name: "portal-client",
Usage: "OAuth2 client `ID` for joining a cluster*optional*",
EnvVars: EnvVars("PORTAL_CLIENT"),
Hidden: true,
}, Tags: []string{Pro}}, {*/
Flag: &cli.StringFlag{
Name: "portal-token",
Usage: "access `TOKEN` for authenticating to the portal server (may be shared between nodes)",
EnvVars: EnvVars("PORTAL_TOKEN"),
Hidden: true,
}, Tags: []string{Pro}}, {
Flag: &cli.BoolFlag{
Name: "cluster-node",
Usage: "runs this instance as a cluster node and joins the configured portal",
EnvVars: EnvVars("CLUSTER_NODE"),
Hidden: true,
}, Tags: []string{Pro}}, {
Flag: &cli.BoolFlag{
Name: "cluster-portal",
Usage: "runs this instance as a cluster portal for orchestrating nodes",
EnvVars: EnvVars("CLUSTER_PORTAL"),
Hidden: true,
}, Tags: []string{Pro}}, {
Flag: &cli.StringFlag{
Name: "pid-filename",
Usage: "process id `FILENAME`*daemon-mode only*",

View File

@@ -25,6 +25,7 @@ type Options struct {
Copyright string `json:"-"`
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
AuthMode string `yaml:"AuthMode" json:"-" flag:"auth-mode"`
AuthSecret string `yaml:"AuthSecret" json:"-" flag:"auth-secret"`
Public bool `yaml:"Public" json:"-" flag:"public"`
NoHub bool `yaml:"-" json:"-" flag:"no-hub"`
AdminUser string `yaml:"AdminUser" json:"-" flag:"admin-user"`
@@ -130,18 +131,24 @@ type Options struct {
LegalUrl string `yaml:"LegalUrl" json:"LegalUrl" flag:"legal-url"`
WallpaperUri string `yaml:"WallpaperUri" json:"WallpaperUri" flag:"wallpaper-uri"`
SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"`
InternalUrl string `yaml:"InternalUrl" json:"InternalUrl" flag:"internal-url"`
SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"`
SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"`
SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"`
SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"`
SiteFavicon string `yaml:"SiteFavicon" json:"SiteFavicon" flag:"site-favicon"`
SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"`
InternalUrl string `yaml:"InternalUrl" json:"InternalUrl" flag:"internal-url"`
CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"`
CdnVideo bool `yaml:"CdnVideo" json:"CdnVideo" flag:"cdn-video"`
CORSOrigin string `yaml:"CORSOrigin" json:"-" flag:"cors-origin"`
CORSHeaders string `yaml:"CORSHeaders" json:"-" flag:"cors-headers"`
CORSMethods string `yaml:"CORSMethods" json:"-" flag:"cors-methods"`
NodeName string `yaml:"NodeName" json:"-" flag:"node-name"`
NodeType string `yaml:"NodeType" json:"-" flag:"node-type"`
NodeSecret string `yaml:"NodeSecret" json:"-" flag:"node-secret"`
PortalUrl string `yaml:"PortalUrl" json:"-" flag:"portal-url"`
PortalClient string `yaml:"PortalClient" json:"-" flag:"portal-client"`
PortalToken string `yaml:"PortalToken" json:"-" flag:"portal-token"`
HttpsProxy string `yaml:"HttpsProxy" json:"HttpsProxy" flag:"https-proxy"`
HttpsProxyInsecure bool `yaml:"HttpsProxyInsecure" json:"HttpsProxyInsecure" flag:"https-proxy-insecure"`
TrustedPlatform string `yaml:"TrustedPlatform" json:"-" flag:"trusted-platform"`
@@ -218,13 +225,6 @@ type Options struct {
FaceClusterCore int `yaml:"-" json:"-" flag:"face-cluster-core"`
FaceClusterDist float64 `yaml:"-" json:"-" flag:"face-cluster-dist"`
FaceMatchDist float64 `yaml:"-" json:"-" flag:"face-match-dist"`
NodeName string `yaml:"NodeName" json:"-" flag:"node-name"`
NodeSecret string `yaml:"NodeSecret" json:"-" flag:"node-secret"`
PortalUrl string `yaml:"PortalUrl" json:"-" flag:"portal-url"`
PortalClient string `yaml:"PortalClient" json:"-" flag:"portal-client"`
PortalSecret string `yaml:"PortalSecret" json:"-" flag:"portal-secret"`
ClusterNode bool `yaml:"ClusterNode" json:"-" flag:"cluster-node"`
ClusterPortal bool `yaml:"ClusterPortal" json:"-" flag:"cluster-portal"`
PIDFilename string `yaml:"PIDFilename" json:"-" flag:"pid-filename"`
LogFilename string `yaml:"LogFilename" json:"-" flag:"log-filename"`
DetachServer bool `yaml:"DetachServer" json:"-" flag:"detach-server"`

View File

@@ -7,7 +7,7 @@ import (
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Icons represents a list of app icons.

View File

@@ -152,6 +152,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
// Site Infos.
{"site-url", c.SiteUrl()},
{"internal-url", c.InternalUrl()},
{"site-https", fmt.Sprintf("%t", c.SiteHttps())},
{"site-domain", c.SiteDomain()},
{"site-author", c.SiteAuthor()},
@@ -160,7 +161,15 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"site-description", c.SiteDescription()},
{"site-favicon", c.SiteFavicon()},
{"site-preview", c.SitePreview()},
{"internal-url", c.InternalUrl()},
// Cluster Configuration.
{"node-name", c.NodeName()},
{"node-type", c.NodeType()},
{"node-secret", fmt.Sprintf("%s", strings.Repeat("*", utf8.RuneCountInString(c.NodeSecret())))},
{"portal-url", c.PortalUrl()},
{"portal-token", fmt.Sprintf("%s", strings.Repeat("*", utf8.RuneCountInString(c.PortalToken())))},
{"portal-config-path", c.PortalConfigPath()},
{"portal-theme-path", c.PortalThemePath()},
// CDN and Cross-Origin Resource Sharing (CORS).
{"cdn-url", c.CdnUrl("/")},
@@ -272,17 +281,6 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"face-cluster-dist", fmt.Sprintf("%f", c.FaceClusterDist())},
{"face-match-dist", fmt.Sprintf("%f", c.FaceMatchDist())},
// Cluster Configuration.
{"node-name", c.NodeName()},
{"node-secret", fmt.Sprintf("%s", strings.Repeat("*", utf8.RuneCountInString(c.NodeSecret())))},
{"portal-url", c.PortalUrl()},
{"portal-client", c.PortalClient()},
{"portal-secret", fmt.Sprintf("%s", strings.Repeat("*", utf8.RuneCountInString(c.PortalSecret())))},
{"cluster-node", fmt.Sprintf("%t", c.ClusterNode())},
{"cluster-portal", fmt.Sprintf("%t", c.ClusterPortal())},
{"cluster-config-path", c.ClusterConfigPath()},
{"cluster-theme-path", c.ClusterThemePath()},
// Daemon Mode.
{"pid-filename", c.PIDFilename()},
{"log-filename", c.LogFilename()},

View File

@@ -25,6 +25,7 @@ var OptionsReportSections = []ReportSection{
{Start: "PHOTOPRISM_READONLY", Title: "Feature Flags"},
{Start: "PHOTOPRISM_DEFAULT_LOCALE", Title: "Customization"},
{Start: "PHOTOPRISM_SITE_URL", Title: "Site Information"},
{Start: "PHOTOPRISM_NODE_NAME", Title: "Cluster Configuration"},
{Start: "PHOTOPRISM_HTTPS_PROXY", Title: "Proxy Server"},
{Start: "PHOTOPRISM_DISABLE_TLS", Title: "Web Server"},
{Start: "PHOTOPRISM_DATABASE_DRIVER", Title: "Database Connection"},
@@ -35,7 +36,6 @@ var OptionsReportSections = []ReportSection{
{Start: "PHOTOPRISM_VISION_YAML", Title: "Computer Vision"},
{Start: "PHOTOPRISM_FACE_SIZE", Title: "Face Recognition",
Info: faceFlagsInfo},
{Start: "PHOTOPRISM_NODE_NAME", Title: "Cluster Configuration"},
{Start: "PHOTOPRISM_PID_FILENAME", Title: "Daemon Mode",
Info: "If you start the server as a *daemon* in the background, you can additionally specify a filename for the log and the process ID:"},
}
@@ -52,6 +52,7 @@ var YamlReportSections = []ReportSection{
{Start: "ReadOnly", Title: "Feature Flags"},
{Start: "DefaultLocale", Title: "Customization"},
{Start: "SiteUrl", Title: "Site Information"},
{Start: "NodeName", Title: "Cluster Configuration"},
{Start: "HttpsProxy", Title: "Proxy Server"},
{Start: "DisableTLS", Title: "Web Server"},
{Start: "DatabaseDriver", Title: "Database Connection"},
@@ -60,7 +61,6 @@ var YamlReportSections = []ReportSection{
{Start: "ThumbLibrary", Title: "Preview Images"},
{Start: "JpegQuality", Title: "Image Quality"},
{Start: "VisionYaml", Title: "Computer Vision"},
{Start: "NodeName", Title: "Cluster Configuration"},
{Start: "PIDFilename", Title: "Daemon Mode",
Info: "If you start the server as a *daemon* in the background, you can additionally specify a filename for the log and the process ID:"},
}

View File

@@ -18,8 +18,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/list"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/time/unix"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/photoprism/photoprism/pkg/txt/report"

View File

@@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -10,8 +10,8 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/time/unix"
"github.com/photoprism/photoprism/pkg/txt/report"
)

View File

@@ -11,9 +11,9 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/colors"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/projection"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestFile_RegenerateIndex(t *testing.T) {

View File

@@ -8,8 +8,8 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestPhoto_Ids(t *testing.T) {

View File

@@ -13,10 +13,10 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/projection"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/time/tz"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -16,7 +16,7 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ToImage converts a media file to a directly supported image file format.

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/face"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/entity/query"
"github.com/photoprism/photoprism/pkg/clusters"
"github.com/photoprism/photoprism/pkg/vector/alg"
)
// Cluster clusters indexed face embeddings.
@@ -37,10 +37,10 @@ func (w *Faces) Cluster(opt FacesOptions) (added entity.Faces, err error) {
log.Debugf("faces: at least %d samples needed for clustering", opt.SampleThreshold())
return added, nil
} else {
var c clusters.HardClusterer
var c alg.HardClusterer
// See https://dl.photoprism.app/research/ for research on face clustering algorithms.
if c, err = clusters.DBSCAN(face.ClusterCore, face.ClusterDist, w.conf.IndexWorkers(), clusters.EuclideanDist); err != nil {
if c, err = alg.DBSCAN(face.ClusterCore, face.ClusterDist, w.conf.IndexWorkers(), alg.EuclideanDist); err != nil {
return added, err
} else if err = c.Learn(embeddings.Float64()); err != nil {
return added, err

View File

@@ -32,8 +32,8 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -12,9 +12,9 @@ import (
"github.com/photoprism/photoprism/internal/meta"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/projection"
"github.com/photoprism/photoprism/pkg/media/video"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/time/tz"
)

View File

@@ -14,7 +14,7 @@ import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestMediaFile_Ok(t *testing.T) {

View File

@@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Api is a middleware that sets additional response headers when serving REST API requests.

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/i18n"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// registerStaticRoutes adds routes for serving static content and templates.

View File

@@ -8,7 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// registerWebAppRoutes adds routes for the web user interface.

View File

@@ -5,7 +5,7 @@ import (
"github.com/photoprism/photoprism/internal/api"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Security is a middleware that adds security-related headers to the server's response.

View File

@@ -20,7 +20,7 @@ import (
"github.com/photoprism/photoprism/internal/server/process"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -6,7 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Static is a middleware that adds static content-related headers to the server's response.

View File

@@ -16,7 +16,7 @@ import (
"github.com/photoprism/photoprism/internal/workers/auto"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -20,8 +20,8 @@ import (
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Use auth cache to improve WebDAV performance. It has a standard expiration time of about 5 minutes.

View File

@@ -7,8 +7,8 @@ import (
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/server/limiter"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// WebDAVAuthSession returns the client session that belongs to the auth token provided, or returns nil if it was not found.

View File

@@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestWebDAVAuth(t *testing.T) {

View File

@@ -22,7 +22,7 @@ import (
"github.com/photoprism/photoprism/internal/service/hub/places"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
type Status string

View File

@@ -9,7 +9,7 @@ import (
"time"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/txt"
)

View File

@@ -6,7 +6,7 @@ import (
"net/http"
"time"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// GetRequest fetches the cell ID data from the service URL.

View File

@@ -13,8 +13,8 @@ import (
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// SetUserImageURL sets a new user avatar URL.

View File

@@ -8,7 +8,7 @@ import (
"github.com/disintegration/imaging"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/stretchr/testify/assert"
)

View File

@@ -7,7 +7,7 @@ import (
"github.com/disintegration/imaging"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/stretchr/testify/assert"
)

View File

@@ -7,7 +7,7 @@ import (
"github.com/disintegration/imaging"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/stretchr/testify/assert"
)

View File

@@ -3,7 +3,7 @@ package clean
import (
"strings"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ContentType normalizes media content type strings, see https://en.wikipedia.org/wiki/Media_type.

View File

@@ -1,7 +1,7 @@
package fs
import (
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// TypeAnimated maps animated file types to their mime type.

View File

@@ -7,7 +7,7 @@ import (
"github.com/gabriel-vasile/mimetype"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
const (

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestDetectMimeType(t *testing.T) {

View File

@@ -13,8 +13,8 @@ import (
"github.com/gabriel-vasile/mimetype"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/media/http/scheme"
"github.com/photoprism/photoprism/pkg/service/http/header"
"github.com/photoprism/photoprism/pkg/service/http/scheme"
)
// DataUrl generates a data URL of the binary data from the specified io.Reader.

View File

@@ -7,7 +7,7 @@ import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ContentType returns a normalized video content type strings based on the video file type and codec.

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestContentType(t *testing.T) {

View File

@@ -5,7 +5,7 @@ import (
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// Info represents video file information.

View File

@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestInfo(t *testing.T) {

View File

@@ -11,7 +11,7 @@ import (
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// ProbeFile returns information for the given filename.

View File

@@ -9,7 +9,7 @@ import (
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/media"
"github.com/photoprism/photoprism/pkg/media/http/header"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
func TestProbeFile(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More