mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Backend: Refactor middleware naming and improve code comments #5235
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
873454ddd2
commit
838adee3eb
54 changed files with 171 additions and 34 deletions
|
|
@ -15,12 +15,12 @@ import (
|
|||
// ClusterMetrics returns lightweight metrics about the cluster.
|
||||
//
|
||||
// @Summary temporary cluster metrics (counts only)
|
||||
// @Id ClusterMetrics
|
||||
// @Id ClusterMetrics
|
||||
// @Tags Cluster
|
||||
// @Produce json
|
||||
// @Success 200 {object} cluster.MetricsResponse
|
||||
// @Failure 401,403,429 {object} i18n.Response
|
||||
// @Router /api/v1/cluster/metrics [get]
|
||||
// @Router /api/v1/cluster/metrics [get]
|
||||
func ClusterMetrics(router *gin.RouterGroup) {
|
||||
router.GET("/cluster/metrics", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceCluster, acl.ActionView)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,6 @@ func OIDCLogin(router *gin.RouterGroup) {
|
|||
r.Success()
|
||||
|
||||
// Handle OIDC login request.
|
||||
provider.AuthCodeUrlHandler(c)
|
||||
provider.AuthURLHandler(c)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -773,6 +773,12 @@
|
|||
"UsageInfo": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"VisionFilter": {
|
||||
"type": "string"
|
||||
},
|
||||
"VisionSchedule": {
|
||||
"type": "string"
|
||||
},
|
||||
"WakeupInterval": {
|
||||
"$ref": "#/definitions/time.Duration"
|
||||
},
|
||||
|
|
@ -4460,14 +4466,16 @@
|
|||
"url",
|
||||
"images",
|
||||
"vision",
|
||||
"ollama"
|
||||
"ollama",
|
||||
"openai"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ApiFormatUrl",
|
||||
"ApiFormatImages",
|
||||
"ApiFormatVision",
|
||||
"ApiFormatOllama"
|
||||
"ApiFormatOllama",
|
||||
"ApiFormatOpenAI"
|
||||
]
|
||||
},
|
||||
"vision.ApiRequest": {
|
||||
|
|
@ -4704,12 +4712,26 @@
|
|||
},
|
||||
"vision.Model": {
|
||||
"properties": {
|
||||
"Run": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/vision.RunType"
|
||||
}
|
||||
],
|
||||
"description": "\"auto\", \"never\", \"manual\", \"always\", \"newly-indexed\", \"on-schedule\""
|
||||
},
|
||||
"default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"engine": {
|
||||
"$ref": "#/definitions/vision.ModelEngine"
|
||||
},
|
||||
"format": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -4722,6 +4744,12 @@
|
|||
"resolution": {
|
||||
"type": "integer"
|
||||
},
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"schemaFile": {
|
||||
"type": "string"
|
||||
},
|
||||
"service": {
|
||||
"$ref": "#/definitions/vision.Service"
|
||||
},
|
||||
|
|
@ -4740,19 +4768,77 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"vision.ModelEngine": {
|
||||
"enum": [
|
||||
"vision",
|
||||
"tensorflow",
|
||||
"local"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"EngineVision",
|
||||
"EngineTensorFlow",
|
||||
"EngineLocal"
|
||||
]
|
||||
},
|
||||
"vision.ModelType": {
|
||||
"enum": [
|
||||
"labels",
|
||||
"nsfw",
|
||||
"face",
|
||||
"caption"
|
||||
"caption",
|
||||
"generate"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-varnames": [
|
||||
"ModelTypeLabels",
|
||||
"ModelTypeNsfw",
|
||||
"ModelTypeFace",
|
||||
"ModelTypeCaption"
|
||||
"ModelTypeCaption",
|
||||
"ModelTypeGenerate"
|
||||
]
|
||||
},
|
||||
"vision.RunType": {
|
||||
"enum": [
|
||||
"",
|
||||
"never",
|
||||
"manual",
|
||||
"always",
|
||||
"newly-indexed",
|
||||
"on-demand",
|
||||
"on-schedule",
|
||||
"on-index"
|
||||
],
|
||||
"type": "string",
|
||||
"x-enum-comments": {
|
||||
"RunAlways": "Run manually, on-schedule, on-demand, and on-index.",
|
||||
"RunAuto": "Automatically decide when to run based on model type and configuration.",
|
||||
"RunManual": "Only run manually e.g. with the \"vision run\" command.",
|
||||
"RunNever": "Never run the model.",
|
||||
"RunNewlyIndexed": "Run manually amd for newly-indexed pictures.",
|
||||
"RunOnDemand": "Run manually, for newly-indexed pictures, and on configured schedule.",
|
||||
"RunOnIndex": "Run manually and on-index.",
|
||||
"RunOnSchedule": "Run manually and on-schedule."
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"Automatically decide when to run based on model type and configuration.",
|
||||
"Never run the model.",
|
||||
"Only run manually e.g. with the \"vision run\" command.",
|
||||
"Run manually, on-schedule, on-demand, and on-index.",
|
||||
"Run manually amd for newly-indexed pictures.",
|
||||
"Run manually, for newly-indexed pictures, and on configured schedule.",
|
||||
"Run manually and on-schedule.",
|
||||
"Run manually and on-index."
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RunAuto",
|
||||
"RunNever",
|
||||
"RunManual",
|
||||
"RunAlways",
|
||||
"RunNewlyIndexed",
|
||||
"RunOnDemand",
|
||||
"RunOnSchedule",
|
||||
"RunOnIndex"
|
||||
]
|
||||
},
|
||||
"vision.Service": {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ type Client struct {
|
|||
insecure bool
|
||||
}
|
||||
|
||||
// NewClient creates and returns a new OpenID Connect (OIDC) Relying Party Client based on the specified parameters.
|
||||
// NewClient creates a new OpenID Connect (OIDC) Relying Party (RP) client using the provided discovery URL,
|
||||
// client credentials, scopes, and site URL.
|
||||
func NewClient(issuerUri *url.URL, oidcClient, oidcSecret, oidcScopes, siteUrl string, insecure bool) (result *Client, err error) {
|
||||
if issuerUri == nil {
|
||||
err = errors.New("issuer uri required")
|
||||
|
|
@ -126,8 +127,8 @@ func NewClient(issuerUri *url.URL, oidcClient, oidcSecret, oidcScopes, siteUrl s
|
|||
}, nil
|
||||
}
|
||||
|
||||
// AuthCodeUrlHandler redirects a browser to the login page of the configured OIDC identity provider.
|
||||
func (c *Client) AuthCodeUrlHandler(ctx *gin.Context) {
|
||||
// AuthURLHandler redirects a browser to the login page of the configured OIDC identity provider.
|
||||
func (c *Client) AuthURLHandler(ctx *gin.Context) {
|
||||
handle := rp.AuthURLHandler(rnd.State, c)
|
||||
handle(ctx.Writer, ctx.Request)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func TestNewClient(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
})
|
||||
t.Run("EmptyRedirectUrl", func(t *testing.T) {
|
||||
t.Run("EmptyRedirectURL", func(t *testing.T) {
|
||||
uri, parseErr := url.Parse("http://dummy-oidc:9998")
|
||||
|
||||
assert.NoError(t, parseErr)
|
||||
|
|
|
|||
|
|
@ -7,13 +7,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
||||
// HttpClient represents a client that makes HTTP requests.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
// HttpClient returns an HTTP client tailored for OIDC requests. When debug is true, it wraps the
|
||||
// default transport with a LoggingRoundTripper and keeps a 30s timeout.
|
||||
func HttpClient(debug bool) *http.Client {
|
||||
if debug {
|
||||
return &http.Client{
|
||||
|
|
@ -25,7 +20,7 @@ func HttpClient(debug bool) *http.Client {
|
|||
return &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
|
||||
// LoggingRoundTripper specifies the http.RoundTripper interface.
|
||||
// LoggingRoundTripper wraps an http.RoundTripper and emits audit logs for OIDC requests.
|
||||
type LoggingRoundTripper struct {
|
||||
proxy http.RoundTripper
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
// RedirectURL returns the redirect URL for authentication via OIDC based on the specified site URL.
|
||||
// RedirectURL builds the OIDC redirect callback from the provided site URL.
|
||||
func RedirectURL(siteUrl string) (string, error) {
|
||||
if siteUrl == "" {
|
||||
return "", errors.New("site url required")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package photoprism
|
||||
|
||||
// Worker action identifiers shared across import/index pipelines.
|
||||
const (
|
||||
ActionIndex = "index"
|
||||
ActionAutoIndex = "autoindex"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
package backup
|
||||
|
||||
// SqlBackupFileNamePattern matches YYYY-MM-DD.sql database backup filenames.
|
||||
const SqlBackupFileNamePattern = "[2-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9].sql"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package photoprism
|
||||
|
||||
// CleanUpOptions controls cleanup worker behaviour.
|
||||
type CleanUpOptions struct {
|
||||
Dry bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
var conf *config.Config
|
||||
|
||||
// SetConfig initialises package-level access to the shared Config.
|
||||
func SetConfig(c *config.Config) {
|
||||
if c == nil {
|
||||
panic("config is missing")
|
||||
|
|
@ -14,6 +15,7 @@ func SetConfig(c *config.Config) {
|
|||
conf = c
|
||||
}
|
||||
|
||||
// Config returns the shared Config, panicking if it has not been set.
|
||||
func Config() *config.Config {
|
||||
if conf == nil {
|
||||
panic("config is missing")
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
// ConvertJob represents a single media conversion task.
|
||||
type ConvertJob struct {
|
||||
force bool
|
||||
file *MediaFile
|
||||
convert *Convert
|
||||
}
|
||||
|
||||
// ConvertWorker processes ConvertJob messages serially on a worker goroutine.
|
||||
func ConvertWorker(jobs <-chan ConvertJob) {
|
||||
convertErr := func(err error, job ConvertJob) {
|
||||
fileName := job.file.RelName(job.convert.conf.OriginalsPath())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/ffmpeg/encode"
|
||||
)
|
||||
|
||||
// Cached binary discovery results.
|
||||
var (
|
||||
YtDlpBin = ""
|
||||
FFmpegBin = ""
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ type Options struct {
|
|||
noInfoDownload bool
|
||||
}
|
||||
|
||||
// DownloadOptions controls how yt-dlp downloads media on behalf of PhotoPrism.
|
||||
type DownloadOptions struct {
|
||||
Filter string // Download format matched by filter (usually a format id or quality designator).
|
||||
AudioFormats string // --audio-formats Download audio using formats (best, aac, alac, flac, m4a, mp3, opus, vorbis, wav).
|
||||
|
|
@ -63,6 +64,7 @@ type DownloadOptions struct {
|
|||
Output string
|
||||
}
|
||||
|
||||
// DownloadWithOptions runs yt-dlp with the provided download options.
|
||||
func (result Metadata) DownloadWithOptions(
|
||||
ctx context.Context,
|
||||
options DownloadOptions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package dl
|
||||
|
||||
// Thumbnail models a thumbnail entry returned by yt-dlp metadata.
|
||||
type Thumbnail struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const (
|
|||
TypeChannel
|
||||
)
|
||||
|
||||
// TypeFromString translates string identifiers to download Type values.
|
||||
var TypeFromString = map[string]Type{
|
||||
"any": TypeAny,
|
||||
"single": TypeSingle,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func (w *Faces) Optimize() (result FacesOptimizeResult, err error) {
|
|||
return w.OptimizeFor("")
|
||||
}
|
||||
|
||||
// Optimize optimizes the face lookup table for the specified subj_uid or "" for all subjects
|
||||
// OptimizeFor optimizes the face lookup table for the given subject UID (or all when empty).
|
||||
func (w *Faces) OptimizeFor(subj_uid string) (result FacesOptimizeResult, err error) {
|
||||
if w.Disabled() {
|
||||
return result, fmt.Errorf("face recognition is disabled")
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package photoprism
|
|||
|
||||
import "github.com/photoprism/photoprism/internal/ai/face"
|
||||
|
||||
// FacesOptions controls face clustering jobs.
|
||||
type FacesOptions struct {
|
||||
Force bool
|
||||
Threshold int
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initCleanUp() {
|
|||
services.CleanUp = photoprism.NewCleanUp(Config())
|
||||
}
|
||||
|
||||
// CleanUp returns the singleton cleanup worker service instance.
|
||||
func CleanUp() *photoprism.CleanUp {
|
||||
onceCleanUp.Do(initCleanUp)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initConvert() {
|
|||
services.Convert = photoprism.NewConvert(Config())
|
||||
}
|
||||
|
||||
// Convert returns the singleton media conversion service instance.
|
||||
func Convert() *photoprism.Convert {
|
||||
onceConvert.Do(initConvert)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func initCoverCache() {
|
|||
services.CoverCache = gc.New(time.Hour, 10*time.Minute)
|
||||
}
|
||||
|
||||
// CoverCache returns the shared album cover cache instance.
|
||||
func CoverCache() *gc.Cache {
|
||||
onceCoverCache.Do(initCoverCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initFaces() {
|
|||
services.Faces = photoprism.NewFaces(Config())
|
||||
}
|
||||
|
||||
// Faces returns the singleton face-recognition service instance.
|
||||
func Faces() *photoprism.Faces {
|
||||
onceFaces.Do(initFaces)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initFiles() {
|
|||
services.Files = photoprism.NewFiles()
|
||||
}
|
||||
|
||||
// Files returns the shared indexed-files cache instance.
|
||||
func Files() *photoprism.Files {
|
||||
onceFiles.Do(initFiles)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func initFolderCache() {
|
|||
services.FolderCache = gc.New(time.Minute*15, 5*time.Minute)
|
||||
}
|
||||
|
||||
// FolderCache returns the shared folder listing cache instance.
|
||||
func FolderCache() *gc.Cache {
|
||||
onceFolderCache.Do(initFolderCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initImport() {
|
|||
services.Import = photoprism.NewImport(Config(), Index(), Convert())
|
||||
}
|
||||
|
||||
// Import returns the singleton import service instance.
|
||||
func Import() *photoprism.Import {
|
||||
onceImport.Do(initImport)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initMoments() {
|
|||
services.Moments = photoprism.NewMoments(Config())
|
||||
}
|
||||
|
||||
// Moments returns the singleton moments service instance.
|
||||
func Moments() *photoprism.Moments {
|
||||
onceMoments.Do(initMoments)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ func initOidc() {
|
|||
)
|
||||
}
|
||||
|
||||
// OIDC returns the singleton OIDC client instance.
|
||||
func OIDC() *oidc.Client {
|
||||
onceOidc.Do(initOidc)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initPhotos() {
|
|||
services.Photos = photoprism.NewPhotos()
|
||||
}
|
||||
|
||||
// Photos returns the shared map of indexed photos.
|
||||
func Photos() *photoprism.Photos {
|
||||
oncePhotos.Do(initPhotos)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initPlaces() {
|
|||
services.Places = photoprism.NewPlaces(Config())
|
||||
}
|
||||
|
||||
// Places returns the singleton place lookup service instance.
|
||||
func Places() *photoprism.Places {
|
||||
oncePlaces.Do(initPlaces)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initPurge() {
|
|||
services.Purge = photoprism.NewPurge(Config(), Files())
|
||||
}
|
||||
|
||||
// Purge returns the singleton purge worker instance.
|
||||
func Purge() *photoprism.Purge {
|
||||
oncePurge.Do(initPurge)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initQuery() {
|
|||
services.Query = query.New(Config().Db())
|
||||
}
|
||||
|
||||
// Query returns the singleton query helper instance.
|
||||
func Query() *query.Query {
|
||||
onceQuery.Do(initQuery)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ var services struct {
|
|||
JWTVerifier *clusterjwt.Verifier
|
||||
}
|
||||
|
||||
// SetConfig stores the shared Config for service constructors.
|
||||
func SetConfig(c *config.Config) {
|
||||
if c == nil {
|
||||
log.Panic("panic: argument is nil in get.SetConfig(c *config.Config)")
|
||||
|
|
@ -73,6 +74,7 @@ func SetConfig(c *config.Config) {
|
|||
photoprism.SetConfig(c)
|
||||
}
|
||||
|
||||
// Config returns the shared Config used by the service registry.
|
||||
func Config() *config.Config {
|
||||
if conf == nil {
|
||||
log.Panic("panic: conf is nil in get.Config()")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initSession() {
|
|||
services.Session = session.New(Config())
|
||||
}
|
||||
|
||||
// Session returns the singleton session manager instance.
|
||||
func Session() *session.Session {
|
||||
onceSession.Do(initSession)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ func initThumbCache() {
|
|||
services.ThumbCache = gc.New(time.Hour*24, 10*time.Minute)
|
||||
}
|
||||
|
||||
// ThumbCache returns the thumbnail cache used by the UI.
|
||||
func ThumbCache() *gc.Cache {
|
||||
onceThumbCache.Do(initThumbCache)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ func initThumbs() {
|
|||
services.Thumbs = photoprism.NewThumbs(Config())
|
||||
}
|
||||
|
||||
// Thumbs returns the singleton thumbs service instance.
|
||||
func Thumbs() *photoprism.Thumbs {
|
||||
onceThumbs.Do(initThumbs)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
// ImportJob describes a media import task pulled from the worker queue.
|
||||
type ImportJob struct {
|
||||
FileName string
|
||||
Related RelatedFiles
|
||||
|
|
@ -18,6 +19,7 @@ type ImportJob struct {
|
|||
Imp *Import
|
||||
}
|
||||
|
||||
// ImportWorker consumes ImportJob messages and performs the on-disk moves/copies plus indexing.
|
||||
func ImportWorker(jobs <-chan ImportJob) {
|
||||
for job := range jobs {
|
||||
var destMainFileName string
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package photoprism
|
||||
|
||||
// Indexing status strings emitted from index runs.
|
||||
const (
|
||||
IndexUpdated IndexStatus = "updated"
|
||||
IndexAdded IndexStatus = "added"
|
||||
|
|
@ -10,6 +11,7 @@ const (
|
|||
IndexFailed IndexStatus = "failed"
|
||||
)
|
||||
|
||||
// IndexStatus is the unified status type returned for each indexed file.
|
||||
type IndexStatus string
|
||||
|
||||
// IndexResult represents a media file indexing result.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ func (l Labels) Less(i, j int) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// AppendLabel adds a label if it has a name and returns the extended slice.
|
||||
func (l Labels) AppendLabel(label Label) Labels {
|
||||
if label.Name == "" {
|
||||
return l
|
||||
|
|
@ -52,6 +53,7 @@ func (l Labels) AppendLabel(label Label) Labels {
|
|||
return append(l, label)
|
||||
}
|
||||
|
||||
// Keywords flattens label names and categories into keyword tokens.
|
||||
func (l Labels) Keywords() (result []string) {
|
||||
for _, label := range l {
|
||||
result = append(result, txt.Keywords(label.Name)...)
|
||||
|
|
@ -64,6 +66,7 @@ func (l Labels) Keywords() (result []string) {
|
|||
return result
|
||||
}
|
||||
|
||||
// Title picks the best label name as title, using fallback when confidence is low.
|
||||
func (l Labels) Title(fallback string) string {
|
||||
fallbackRunes := len([]rune(fallback))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
_ "image/gif" // register GIF decoder
|
||||
_ "image/jpeg" // register JPEG decoder
|
||||
_ "image/png" // register PNG decoder
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
|
@ -18,9 +18,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
_ "golang.org/x/image/bmp" // register BMP decoder
|
||||
_ "golang.org/x/image/tiff" // register TIFF decoder
|
||||
_ "golang.org/x/image/webp" // register WebP decoder
|
||||
|
||||
"github.com/djherbis/times"
|
||||
"github.com/dustin/go-humanize"
|
||||
|
|
@ -220,6 +220,7 @@ func (m *MediaFile) TakenAt() (utc time.Time, local time.Time, source string) {
|
|||
return m.takenAt, m.takenAtLocal, m.takenAtSrc
|
||||
}
|
||||
|
||||
// HasTimeAndPlace reports whether both TakenAt and GPS coordinates are available.
|
||||
func (m *MediaFile) HasTimeAndPlace() bool {
|
||||
data := m.MetaData()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
var log = event.Log
|
||||
|
||||
// S is a helper alias for string slices used when composing logs and metadata.
|
||||
type S []string
|
||||
|
||||
// logWarn logs an error as warning and keeps quiet otherwise.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package photoprism
|
|||
|
||||
import "github.com/photoprism/photoprism/pkg/fs"
|
||||
|
||||
// PurgeOptions controls behaviour of the purge worker.
|
||||
type PurgeOptions struct {
|
||||
Path string
|
||||
Ignore fs.Done
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package photoprism
|
||||
|
||||
// ThumbsJob encapsulates thumbnail generation parameters for a media file.
|
||||
type ThumbsJob struct {
|
||||
mediaFile *MediaFile
|
||||
path string
|
||||
force bool
|
||||
}
|
||||
|
||||
// ThumbsWorker consumes thumbnail jobs and generates the requested previews.
|
||||
func ThumbsWorker(jobs <-chan ThumbsJob) {
|
||||
for job := range jobs {
|
||||
mf := job.mediaFile
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/service/http/header"
|
||||
)
|
||||
|
||||
// Api is a middleware that sets additional response headers when serving REST API requests.
|
||||
var Api = func(conf *config.Config) gin.HandlerFunc {
|
||||
// APIMiddleware returns a Gin middleware that applies API-specific headers and CORS handling.
|
||||
var APIMiddleware = func(conf *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Add a vary response header for authentication, if any.
|
||||
if c.GetHeader(header.XAuthToken) != "" {
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
registerApiDocs = api.GetDocs
|
||||
registerAPIDocs = api.GetDocs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Default authentication rate-limit parameters.
|
||||
const (
|
||||
DefaultAuthInterval = time.Second * 10 // average authentication errors per second
|
||||
DefaultAuthLimit = 60 // authentication failure burst rate limit (for access tokens)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package limiter
|
||||
|
||||
// DefaultIP is used when the real client IP cannot be determined.
|
||||
const (
|
||||
DefaultIP = "0.0.0.0"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Default login rate-limit parameters.
|
||||
const (
|
||||
DefaultLoginInterval = time.Minute // average failed logins per second
|
||||
DefaultLoginLimit = 10 // login failure burst rate limit (for passwords and 2FA)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package server
|
||||
|
||||
// Canonical HTTP and WebDAV method names reused across handlers.
|
||||
const (
|
||||
MethodHead = "HEAD"
|
||||
MethodGet = "GET"
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
// APIv1 is the router group that serves the version 1 REST API.
|
||||
var APIv1 *gin.RouterGroup
|
||||
var registerApiDocs func(router *gin.RouterGroup)
|
||||
var registerAPIDocs func(router *gin.RouterGroup)
|
||||
|
||||
// registerRoutes registers the routes for handling HTTP requests with the built-in web server.
|
||||
func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||
|
|
@ -35,8 +36,8 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
// Docs: https://pkg.go.dev/github.com/photoprism/photoprism/internal/api
|
||||
|
||||
// API Documentation.
|
||||
if registerApiDocs != nil {
|
||||
registerApiDocs(APIv1)
|
||||
if registerAPIDocs != nil {
|
||||
registerAPIDocs(APIv1)
|
||||
}
|
||||
|
||||
// User Sessions.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
// WebDAV mount points served by the application.
|
||||
const (
|
||||
WebDAVOriginals = "/originals"
|
||||
WebDAVImport = "/import"
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ func Start(ctx context.Context, conf *config.Config) {
|
|||
router.Use(Security(conf))
|
||||
|
||||
// Create REST API router group.
|
||||
APIv1 = router.Group(conf.BaseUri(config.ApiUri), Api(conf))
|
||||
APIv1 = router.Group(conf.BaseUri(config.ApiUri), APIMiddleware(conf))
|
||||
|
||||
// Initialize package extensions.
|
||||
Ext().Init(router, conf)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import (
|
|||
var webdavAuthExpiration = 5 * time.Minute
|
||||
var webdavAuthCache = gc.New(webdavAuthExpiration, webdavAuthExpiration)
|
||||
var webdavAuthMutex = sync.Mutex{}
|
||||
|
||||
// BasicAuthRealm is the challenge string returned for WebDAV Basic auth prompts.
|
||||
var BasicAuthRealm = "Basic realm=\"WebDAV Authorization Required\""
|
||||
|
||||
// WebDAVAuth checks authentication and authentication
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
// OAuth metadata published via the well-known discovery endpoints.
|
||||
var (
|
||||
OAuthResponseTypes = []string{"token"}
|
||||
OAuthGrantTypes = []string{"client_credentials"}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import (
|
|||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Remove temporary SQLite files before running the tests.
|
||||
fs.PurgeTestDbFiles(".", false)
|
||||
|
||||
// Run unit tests.
|
||||
code := m.Run()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue