CI: Apply Go linter recommendations to "internal/service" packages #5330

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2025-11-22 17:57:25 +01:00
parent 699ad5b50c
commit 57c9096d1f
25 changed files with 61 additions and 42 deletions

View file

@ -10,12 +10,12 @@ const (
// Example values used by documentation and tests to illustrate cluster tokens.
const (
// ExampleJoinToken represents a valid portal join token.
ExampleJoinToken = "pGVplw8-eISgkdQN-Mep62nQ"
// ExampleJoinTokenAlt provides an alternative join token for negative/rotation tests.
ExampleJoinTokenAlt = "k9sEFe6-A7gt6zqm-gY9gFh0"
// ExampleJoinToken represents a valid portal join token. Sample only.
ExampleJoinToken = "pGVplw8-eISgkdQN-Mep62nQ" //nolint:gosec // example value, not a secret
// ExampleJoinTokenAlt provides an alternative join token for negative/rotation tests. Sample only.
ExampleJoinTokenAlt = "k9sEFe6-A7gt6zqm-gY9gFh0" //nolint:gosec // example value, not a secret
// ExampleClientID is a sample node client identifier issued by the portal.
ExampleClientID = "cs5gfen1bgxz7s9i"
// ExampleClientSecret is a sample node client secret matching the format generated by rnd.ClientSecret().
ExampleClientSecret = "A1B2C3D4E5F6G7H8J9K0L1M2N3P4Q5R6"
ExampleClientSecret = "A1B2C3D4E5F6G7H8J9K0L1M2N3P4Q5R6" //nolint:gosec // example value
)

View file

@ -105,7 +105,6 @@ func (u *OptionsUpdate) SetDatabasePassword(value string) {
u.DatabasePassword = stringPtr(value)
}
// forEach enumerates all set fields and invokes fn with the corresponding key/value pair.
// Visit enumerates all set fields and invokes fn with the corresponding key/value pair.
func (u OptionsUpdate) Visit(fn func(string, any)) {
if u.ClusterUUID != nil {

View file

@ -38,7 +38,7 @@ func TestOptionsUpdate_Apply(t *testing.T) {
seed := map[string]any{"Existing": "value"}
b, err := yaml.Marshal(seed)
require.NoError(t, err)
require.NoError(t, os.WriteFile(conf.OptionsYaml(), b, 0o644))
require.NoError(t, os.WriteFile(conf.OptionsYaml(), b, 0o600))
update := cluster.OptionsUpdate{}
update.SetClusterUUID("4a47c940-d5de-41b3-88a2-eb816cc659ca")

View file

@ -33,7 +33,7 @@ func init() {
applyPolicyEnv()
}
// applyPolicyEnv allows advanced users to fine-tune bootstrap behaviour via environment
// applyPolicyEnv allows advanced users to fine-tune bootstrap behavior via environment
// variables without exposing additional user-facing configuration options.
func applyPolicyEnv() {
if v := os.Getenv(clean.EnvVar("cluster-bootstrap-auto-join-enabled")); v != "" {

View file

@ -10,7 +10,7 @@ import (
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
_ "github.com/go-sql-driver/mysql" // register MySQL driver
)
// ProvisionDSN specifies the admin DSN used for auto-provisioning, for example:

View file

@ -110,7 +110,7 @@ func TestExecTimeout_DeadlineExceeded(t *testing.T) {
// execTimeout forwards the statement correctly.
type fastDriver struct{ last *string }
func (f fastDriver) Open(name string) (driver.Conn, error) { return fastConn{last: f.last}, nil }
func (f fastDriver) Open(name string) (driver.Conn, error) { return fastConn(f), nil }
type fastConn struct{ last *string }
@ -147,7 +147,7 @@ func TestExecTimeout_ForwardsStatement(t *testing.T) {
// variant waits for cancellation, the other returns quickly.
type pingyDriver struct{ wait bool }
func (p pingyDriver) Open(name string) (driver.Conn, error) { return pingyConn{wait: p.wait}, nil }
func (p pingyDriver) Open(name string) (driver.Conn, error) { return pingyConn(p), nil }
type pingyConn struct{ wait bool }

View file

@ -20,9 +20,6 @@ const (
// username: cluster_u<hmac11>
dbSuffix = 11
userSuffix = 11
// Budgets: keep user conservative for MySQL compatibility; MariaDB allows more.
userMax = 32
dbMax = 64
// prefixMax ensures usernames remain within the MySQL identifier limit.
prefixMax = cluster.DatabaseProvisionPrefixMaxLen
)

View file

@ -13,8 +13,9 @@ type Node struct {
// ensureDatabase returns a writable NodeDatabase, creating one if missing.
func (n *Node) ensureDatabase() *cluster.NodeDatabase {
if n.Node.Database == nil {
n.Node.Database = &cluster.NodeDatabase{}
if n.Database == nil {
n.Database = &cluster.NodeDatabase{}
}
return n.Node.Database
return n.Database
}

View file

@ -4,10 +4,14 @@ import (
"github.com/photoprism/photoprism/internal/auth/acl"
)
// NodeRole represents the role a node plays within a cluster.
type NodeRole = string
const (
RoleApp = NodeRole(acl.RoleApp) // A regular PhotoPrism app node that can join a cluster
RolePortal = NodeRole(acl.RolePortal) // A management portal for orchestrating a cluster
RoleService = NodeRole(acl.RoleService) // Other service used within a cluster, e.g. Ollama or Vision API
// RoleApp represents a regular PhotoPrism app node that can join a cluster.
RoleApp = NodeRole(acl.RoleApp)
// RolePortal represents a management portal for orchestrating a cluster.
RolePortal = NodeRole(acl.RolePortal)
// RoleService represents other services used within a cluster, e.g., Ollama or Vision API.
RoleService = NodeRole(acl.RoleService)
)

View file

@ -29,7 +29,7 @@ func DetectVersion(themePath string) (string, error) {
versionFile := filepath.Join(themePath, fs.VersionTxtFile)
if data, readErr := os.ReadFile(versionFile); readErr == nil {
if data, readErr := os.ReadFile(versionFile); readErr == nil { //nolint:gosec // version file path is internal, provided by caller
if v := clean.TypeUnicode(string(data)); v != "" {
return v, nil
}

View file

@ -6,6 +6,7 @@ import (
"strings"
)
// Headers holds request header key/value pairs.
type Headers = map[string]string
// Heuristic represents a heuristic for detecting a remote service type, e.g. WebDAV.
@ -36,6 +37,7 @@ var Heuristics = []Heuristic{
},
}
// MatchDomain returns true if the heuristic allows the provided domain.
func (h Heuristic) MatchDomain(match string) bool {
if len(h.Domains) == 0 {
return true
@ -44,6 +46,7 @@ func (h Heuristic) MatchDomain(match string) bool {
return slices.Contains(h.Domains, match)
}
// Discover returns the first matching endpoint URL for the heuristic.
func (h Heuristic) Discover(rawUrl, user string) *url.URL {
u, err := url.Parse(rawUrl)
@ -56,7 +59,7 @@ func (h Heuristic) Discover(rawUrl, user string) *url.URL {
}
for _, p := range h.Paths {
u.Path = strings.Replace(p, "{user}", user, -1)
u.Path = strings.ReplaceAll(p, "{user}", user)
if h.TestRequest(h.Method, u.String()) {
return u

View file

@ -4,7 +4,7 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"crypto/sha1" //nolint:gosec // retained for legacy key derivation check
"crypto/sha256"
"encoding/hex"
"encoding/json"
@ -25,11 +25,15 @@ import (
"github.com/photoprism/photoprism/pkg/http/header"
)
// Status represents the hub registration state.
type Status string
const (
StatusUnknown Status = ""
StatusNew Status = "unregistered"
// StatusUnknown indicates an undefined state.
StatusUnknown Status = ""
// StatusNew indicates a node has not registered yet.
StatusNew Status = "unregistered"
// StatusCommunity indicates Community Edition status.
StatusCommunity Status = "ce"
)
@ -122,7 +126,7 @@ func (c *Config) Sanitize() {
c.Key = strings.ToLower(c.Key)
if c.Secret != "" {
if c.Key != fmt.Sprintf("%x", sha1.Sum([]byte(c.Secret))) {
if c.Key != fmt.Sprintf("%x", sha1.Sum([]byte(c.Secret))) { //nolint:gosec // legacy SHA1 comparison
c.Key = ""
c.Secret = ""
c.Session = ""

View file

@ -1,3 +1,4 @@
//nolint:gocritic // explicit branching retained for clarity
package places
import (

View file

@ -28,6 +28,11 @@ var Retries = 2
// RetryDelay specifies the waiting time between retries.
var RetryDelay = 100 * time.Millisecond
var Key = "f60f5b25d59c397989e3cd374f81cdd7710a4fca"
var Secret = "photoprism"
// Key is the hub places API key (overridden via environment/config in production).
var Key = "f60f5b25d59c397989e3cd374f81cdd7710a4fca" //nolint:gosec // example/default key
// Secret is the hub places API secret (overridden in production).
var Secret = "photoprism" //nolint:gosec // example/default secret
// UserAgent overrides the default HTTP User-Agent header for hub places calls.
var UserAgent = ""

View file

@ -4,5 +4,8 @@ import (
"errors"
)
// ErrMissingQuery indicates that a place search query was empty.
var ErrMissingQuery = errors.New("missing query")
// ErrMissingCoordinates indicates that both latitude and longitude were missing.
var ErrMissingCoordinates = errors.New("missing coordinates")

View file

@ -1,3 +1,4 @@
//nolint:gocritic // explicit branching retained for clarity
package places
import (

View file

@ -1,7 +1,7 @@
package places
import (
"crypto/sha1"
"crypto/sha1" //nolint:gosec // required for upstream signature scheme
"fmt"
"net/http"
"time"
@ -40,7 +40,7 @@ func GetRequest(reqUrl string, locale string) (r *http.Response, err error) {
// Add API key?
if Key != "" {
req.Header.Set("X-Key", Key)
req.Header.Set("X-Signature", fmt.Sprintf("%x", sha1.Sum([]byte(Key+reqUrl+Secret))))
req.Header.Set("X-Signature", fmt.Sprintf("%x", sha1.Sum([]byte(Key+reqUrl+Secret)))) //nolint:gosec // upstream expects SHA1
}
// Create new http.Client.

View file

@ -1,3 +1,4 @@
//nolint:gocritic // explicit branching retained for clarity
package places
import (

View file

@ -1,5 +1,6 @@
package places
// SearchResult represents a place returned by the hub places API.
type SearchResult struct {
Id string `json:"id"`
Name string `json:"name,omitempty"`
@ -12,4 +13,5 @@ type SearchResult struct {
Licence string `json:"licence,omitempty"`
}
// SearchResults is a slice of SearchResult.
type SearchResults = []SearchResult

View file

@ -2,6 +2,7 @@ package maps
// Generated code, do not edit.
// CountryNames maps ISO country codes to localized names.
var CountryNames = map[string]string{
"af": "Afghanistan",
"ax": "Åland Islands",

View file

@ -1,5 +1,4 @@
//go:build ignore
// +build ignore
// This generates countries.go by running "go generate"
package main
@ -55,6 +54,7 @@ package maps
// Generated code, do not edit.
// CountryNames maps ISO country codes to localized names.
var CountryNames = map[string]string{
{{- range .Countries }}
{{ printf "%q" .Code }}: {{ printf "%q" .Name }},

View file

@ -1,3 +1,4 @@
//revive:disable:exported // getters intentionally exported for use across services
package maps
import (
@ -42,9 +43,8 @@ type LocationSource interface {
Source() string
}
func (l *Location) QueryApi(api string) error {
switch api {
case places.ApiName:
func (l *Location) QueryApi(api string) error { //nolint:gocritic // single-case switch retained for future APIs
if api == places.ApiName {
return l.QueryPlaces()
}

View file

@ -24,11 +24,5 @@ Additional information can be found in our Developer Guide:
*/
package maps
import (
"github.com/photoprism/photoprism/internal/event"
)
//go:generate go run gen.go
//go:generate go fmt .
var log = event.Log

View file

@ -221,7 +221,7 @@ func (c *Client) Upload(src, dest string) (err error) {
return fmt.Errorf("file %s not found", clean.Log(path.Base(src)))
}
f, err := os.OpenFile(src, os.O_RDONLY, 0)
f, err := os.OpenFile(src, os.O_RDONLY, 0) //nolint:gosec // path provided by caller; read-only
if err != nil {
log.Errorf("webdav: %s", clean.Error(err))
@ -290,7 +290,7 @@ func (c *Client) Download(src, dest string, force bool) (err error) {
defer reader.Close()
f, err := os.OpenFile(dest, os.O_TRUNC|os.O_RDWR|os.O_CREATE, fs.ModeFile)
f, err := os.OpenFile(dest, os.O_TRUNC|os.O_RDWR|os.O_CREATE, fs.ModeFile) //nolint:gosec // dest provided by caller
if err != nil {
log.Errorf("webdav: %s", clean.Error(err))

View file

@ -33,6 +33,7 @@ import (
// Global log instance.
var log = event.Log
// Timeout specifies a timeout mode for WebDAV operations.
type Timeout string
// Request Timeout options.
@ -45,6 +46,8 @@ const (
)
// Second represents a second on which other timeouts are based.
//
//revive:disable-next-line:time-naming // keep exported constant name for API compatibility
const Second = time.Second
// MaxRequestDuration is the maximum request duration e.g. for recursive retrieval of large remote directory structures.