mirror of
https://github.com/tronbyt/server.git
synced 2025-12-19 08:25:46 +01:00
hore: enable more linters and fix findings
This commit is contained in:
25
.golangci.yml
Normal file
25
.golangci.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: "2"
|
||||
|
||||
linters:
|
||||
default: fast
|
||||
enable:
|
||||
- errcheck
|
||||
- govet
|
||||
- ineffassign
|
||||
- modernize
|
||||
- staticcheck
|
||||
- unused
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- funcorder
|
||||
- funlen
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- lll
|
||||
- maintidx
|
||||
- mnd
|
||||
- nestif
|
||||
- nlreturn
|
||||
- testpackage
|
||||
- wsl
|
||||
@@ -63,7 +63,7 @@ The core functionality involves serving WebP images to devices, generated by ren
|
||||
## Development Conventions
|
||||
|
||||
* **Formatting:** Use `go fmt ./...` (or `goimports`).
|
||||
* **Linting:** Use `go vet`, `golangci-lint`.
|
||||
* **Linting:** Use `golangci-lint`.
|
||||
* **Logging:** Use `log/slog` for structured logging.
|
||||
* **Templates:** Use `{{ t .Localizer "MessageID" }}` for translated strings.
|
||||
* **Database:** Use GORM for DB interactions. Ensure migrations are updated in `cmd/migrate` or auto-migrated in `server.go` if appropriate.
|
||||
|
||||
@@ -46,6 +46,7 @@ func main() {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return os.Chown(path, uid, gid)
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -40,6 +40,7 @@ func runHealthCheck(url string) error {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,6 +81,7 @@ func openDB(dsn, logLevel string) (*gorm.DB, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return db, err
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,7 @@ func ListSystemApps(dataDir string) ([]AppMetadata, error) {
|
||||
apps[i].Supports2x = true
|
||||
}
|
||||
found = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -199,6 +200,7 @@ func scanSystemApps(dataDir string) ([]AppMetadata, error) {
|
||||
apps = append(apps, app)
|
||||
}
|
||||
}
|
||||
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
@@ -271,5 +273,6 @@ func ListUserApps(dataDir, username string) ([]AppMetadata, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
@@ -27,12 +27,14 @@ func VerifyPassword(hashStr, password string) (bool, bool, error) {
|
||||
// 1. Check for Argon2id (Standard)
|
||||
if strings.HasPrefix(hashStr, "$argon2id") {
|
||||
valid, err := verifyArgon2id(hashStr, password)
|
||||
|
||||
return valid, false, err // Valid and current
|
||||
}
|
||||
|
||||
// 2. Check for Scrypt (Legacy)
|
||||
if strings.HasPrefix(hashStr, "scrypt:") {
|
||||
valid, err := verifyScrypt(hashStr, password)
|
||||
|
||||
return valid, true, err // Valid but legacy
|
||||
}
|
||||
|
||||
@@ -42,11 +44,13 @@ func VerifyPassword(hashStr, password string) (bool, bool, error) {
|
||||
if err == nil {
|
||||
return true, true, nil // Valid but legacy
|
||||
}
|
||||
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(hashStr, "pbkdf2:sha256:") {
|
||||
valid, err := verifyPbkdf2(hashStr, password)
|
||||
|
||||
return valid, true, err // Valid but legacy
|
||||
}
|
||||
|
||||
|
||||
@@ -9,24 +9,24 @@ import (
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
DBDSN string `env:"DB_DSN" envDefault:"data/tronbyt.db"`
|
||||
DataDir string `env:"DATA_DIR" envDefault:"data"`
|
||||
Production string `env:"PRODUCTION" envDefault:"1"`
|
||||
DBDSN string `env:"DB_DSN" envDefault:"data/tronbyt.db"`
|
||||
DataDir string `env:"DATA_DIR" envDefault:"data"`
|
||||
Production string `env:"PRODUCTION" envDefault:"1"`
|
||||
EnableUserRegistration string `env:"ENABLE_USER_REGISTRATION" envDefault:"1"`
|
||||
MaxUsers int `env:"MAX_USERS" envDefault:"0"`
|
||||
SingleUserAutoLogin string `env:"SINGLE_USER_AUTO_LOGIN" envDefault:"0"`
|
||||
SystemAppsRepo string `env:"SYSTEM_APPS_REPO" envDefault:"https://github.com/tronbyt/apps.git"`
|
||||
MaxUsers int `env:"MAX_USERS" envDefault:"0"`
|
||||
SingleUserAutoLogin string `env:"SINGLE_USER_AUTO_LOGIN" envDefault:"0"`
|
||||
SystemAppsRepo string `env:"SYSTEM_APPS_REPO" envDefault:"https://github.com/tronbyt/apps.git"`
|
||||
RedisURL string `env:"REDIS_URL"`
|
||||
Host string `env:"TRONBYT_HOST" envDefault:""`
|
||||
Port string `env:"TRONBYT_PORT" envDefault:"8000"`
|
||||
Host string `env:"TRONBYT_HOST" envDefault:""`
|
||||
Port string `env:"TRONBYT_PORT" envDefault:"8000"`
|
||||
UnixSocket string `env:"TRONBYT_UNIX_SOCKET"`
|
||||
SSLKeyFile string `env:"TRONBYT_SSL_KEYFILE"`
|
||||
SSLCertFile string `env:"TRONBYT_SSL_CERTFILE"`
|
||||
TrustedProxies string `env:"TRONBYT_TRUSTED_PROXIES" envDefault:"*"`
|
||||
LogLevel string `env:"LOG_LEVEL" envDefault:"INFO"`
|
||||
TrustedProxies string `env:"TRONBYT_TRUSTED_PROXIES" envDefault:"*"`
|
||||
LogLevel string `env:"LOG_LEVEL" envDefault:"INFO"`
|
||||
}
|
||||
|
||||
// TemplateConfig holds configuration values needed in templates
|
||||
// TemplateConfig holds configuration values needed in templates.
|
||||
type TemplateConfig struct {
|
||||
EnableUserRegistration string
|
||||
SingleUserAutoLogin string
|
||||
|
||||
@@ -8,41 +8,41 @@ import (
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// GORMSlogLogger wraps slog for GORM logging
|
||||
// GORMSlogLogger wraps slog for GORM logging.
|
||||
type GORMSlogLogger struct {
|
||||
LogLevel logger.LogLevel
|
||||
SlowThreshold time.Duration
|
||||
}
|
||||
|
||||
// LogMode log mode
|
||||
// LogMode log mode.
|
||||
func (l *GORMSlogLogger) LogMode(level logger.LogLevel) logger.Interface {
|
||||
newLogger := *l
|
||||
newLogger.LogLevel = level
|
||||
return &newLogger
|
||||
}
|
||||
|
||||
// Info prints info
|
||||
// Info prints info.
|
||||
func (l *GORMSlogLogger) Info(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Info {
|
||||
slog.InfoContext(ctx, msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warn prints warn messages
|
||||
// Warn prints warn messages.
|
||||
func (l *GORMSlogLogger) Warn(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Warn {
|
||||
slog.WarnContext(ctx, msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error prints error messages
|
||||
// Error prints error messages.
|
||||
func (l *GORMSlogLogger) Error(ctx context.Context, msg string, data ...any) {
|
||||
if l.LogLevel >= logger.Error {
|
||||
slog.ErrorContext(ctx, msg, data...)
|
||||
}
|
||||
}
|
||||
|
||||
// Trace prints trace messages
|
||||
// Trace prints trace messages.
|
||||
func (l *GORMSlogLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||
if l.LogLevel <= logger.Silent {
|
||||
return
|
||||
@@ -65,7 +65,7 @@ func (l *GORMSlogLogger) Trace(ctx context.Context, begin time.Time, fc func() (
|
||||
}
|
||||
}
|
||||
|
||||
// NewGORMSlogLogger creates a new GORM logger that uses slog
|
||||
// NewGORMSlogLogger creates a new GORM logger that uses slog.
|
||||
func NewGORMSlogLogger(slogLevel logger.LogLevel, slowThreshold time.Duration) logger.Interface {
|
||||
return &GORMSlogLogger{
|
||||
LogLevel: slogLevel,
|
||||
|
||||
@@ -84,6 +84,7 @@ func (p *ProtocolType) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
*p = ProtocolType(strings.ToUpper(s))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -98,19 +99,22 @@ const (
|
||||
|
||||
type Brightness int
|
||||
|
||||
// Value implements the driver.Valuer interface for database storage.
|
||||
func (b Brightness) Value() (driver.Value, error) {
|
||||
return int64(b), nil
|
||||
}
|
||||
|
||||
func (b *Brightness) Scan(value interface{}) error {
|
||||
func (b *Brightness) Scan(value any) error {
|
||||
if val, ok := value.(int64); ok {
|
||||
*b = Brightness(val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("failed to scan Brightness")
|
||||
}
|
||||
|
||||
// Percent returns brightness as 0.0-1.0
|
||||
// Percent returns brightness as 0.0-1.0.
|
||||
func (b Brightness) Percent() float64 {
|
||||
if b < 0 {
|
||||
return 0.0
|
||||
@@ -121,12 +125,12 @@ func (b Brightness) Percent() float64 {
|
||||
return float64(b) / 100.0
|
||||
}
|
||||
|
||||
// Uint8 returns brightness as 0-255
|
||||
// Uint8 returns brightness as 0-255.
|
||||
func (b Brightness) Uint8() uint8 {
|
||||
return uint8(b.Percent() * 255.0)
|
||||
}
|
||||
|
||||
// UIScale returns 0-5
|
||||
// UIScale returns 0-5.
|
||||
func (b Brightness) UIScale(customScale map[int]int) int {
|
||||
v := int(b)
|
||||
if customScale != nil {
|
||||
@@ -190,7 +194,7 @@ func (b Brightness) UIScale(customScale map[int]int) int {
|
||||
return 5
|
||||
}
|
||||
|
||||
// BrightnessFromUIScale converts a UI scale value (0-5) to Brightness percentage
|
||||
// BrightnessFromUIScale converts a UI scale value (0-5) to Brightness percentage.
|
||||
func BrightnessFromUIScale(uiValue int, customScale map[int]int) Brightness {
|
||||
if customScale != nil {
|
||||
if val, ok := customScale[uiValue]; ok {
|
||||
@@ -213,7 +217,7 @@ func BrightnessFromUIScale(uiValue int, customScale map[int]int) Brightness {
|
||||
return Brightness(20) // Default
|
||||
}
|
||||
|
||||
// ParseCustomBrightnessScale parses a comma-separated string into a map
|
||||
// ParseCustomBrightnessScale parses a comma-separated string into a map.
|
||||
func ParseCustomBrightnessScale(scaleStr string) map[int]int {
|
||||
if scaleStr == "" {
|
||||
return nil
|
||||
@@ -268,7 +272,7 @@ func (l DeviceLocation) Value() (driver.Value, error) {
|
||||
return json.Marshal(l)
|
||||
}
|
||||
|
||||
func (l *DeviceLocation) Scan(value interface{}) error {
|
||||
func (l *DeviceLocation) Scan(value any) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -296,7 +300,7 @@ func (i DeviceInfo) Value() (driver.Value, error) {
|
||||
return json.Marshal(i)
|
||||
}
|
||||
|
||||
func (i *DeviceInfo) Scan(value interface{}) error {
|
||||
func (i *DeviceInfo) Scan(value any) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -311,15 +315,14 @@ func (i *DeviceInfo) Scan(value interface{}) error {
|
||||
return json.Unmarshal(bytes, i)
|
||||
}
|
||||
|
||||
// JSONMap is a helper for storing arbitrary JSON in the DB
|
||||
// JSONMap is a helper for storing arbitrary JSON in the DB.
|
||||
type JSONMap map[string]any
|
||||
|
||||
func (j JSONMap) Value() (driver.Value, error) {
|
||||
|
||||
return json.Marshal(j)
|
||||
}
|
||||
|
||||
func (j *JSONMap) Scan(value interface{}) error {
|
||||
func (j *JSONMap) Scan(value any) error {
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("type assertion to []byte failed")
|
||||
@@ -327,14 +330,14 @@ func (j *JSONMap) Scan(value interface{}) error {
|
||||
return json.Unmarshal(bytes, j)
|
||||
}
|
||||
|
||||
// StringSlice is a helper for storing []string as JSON
|
||||
// StringSlice is a helper for storing []string as JSON.
|
||||
type StringSlice []string
|
||||
|
||||
func (s StringSlice) Value() (driver.Value, error) {
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func (s *StringSlice) Scan(value interface{}) error {
|
||||
func (s *StringSlice) Scan(value any) error {
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("type assertion to []byte failed")
|
||||
|
||||
@@ -76,6 +76,7 @@ func getFirmwareFilename(deviceType string, swapColors bool) string {
|
||||
if swapColors {
|
||||
return "tidbyt-gen1_swap.bin"
|
||||
}
|
||||
|
||||
return "tidbyt-gen1.bin"
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,7 @@ func updateFirmwareData(data []byte) ([]byte, error) {
|
||||
|
||||
// 1. Calculate Checksum (XOR sum of data + 0xEF)
|
||||
checksum := byte(0xEF)
|
||||
for i := 0; i < dataLen; i++ {
|
||||
for i := range dataLen {
|
||||
checksum ^= data[i]
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ func GetRepoInfo(path string, remoteURL string) (*RepoInfo, error) {
|
||||
branchName = ref.Name().Short()
|
||||
return fmt.Errorf("found") // Hack to exit ForEach
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil && err.Error() != "found" {
|
||||
@@ -84,6 +85,7 @@ func EnsureRepo(path string, url string, update bool) error {
|
||||
Progress: os.Stdout,
|
||||
Depth: 1,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -105,6 +107,7 @@ func EnsureRepo(path string, url string, update bool) error {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return fmt.Errorf("failed to remove old repo: %w", err)
|
||||
}
|
||||
|
||||
return EnsureRepo(path, url, update)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,12 +103,12 @@ type LegacyApp struct {
|
||||
ColorFilter *string `json:"color_filter"`
|
||||
}
|
||||
|
||||
// Wrapper for the JSON blob structure in DB
|
||||
// UserDataBlob is a wrapper for the JSON blob structure in DB.
|
||||
type UserDataBlob struct {
|
||||
Users map[string]LegacyUser `json:"users"`
|
||||
}
|
||||
|
||||
// ParseBool handles boolean polymorphism (bool or int 0/1)
|
||||
// ParseBool handles boolean polymorphism (bool or int 0/1).
|
||||
func ParseBool(val any) bool {
|
||||
if v, ok := val.(bool); ok {
|
||||
return v
|
||||
@@ -119,10 +119,11 @@ func ParseBool(val any) bool {
|
||||
if v, ok := val.(float64); ok {
|
||||
return int(v) != 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Helper to handle Brightness polymorphism (int or object with value)
|
||||
// ParseBrightness is a helper to handle Brightness polymorphism (int or object with value).
|
||||
func ParseBrightness(val any) int {
|
||||
if v, ok := val.(float64); ok {
|
||||
return int(v)
|
||||
@@ -135,6 +136,7 @@ func ParseBrightness(val any) int {
|
||||
return int(v)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -148,7 +150,7 @@ func ParseTimeStr(val any) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseDuration parses ISO8601 duration (PT1.5S) or numeric seconds into int64 nanoseconds
|
||||
// ParseDuration parses ISO8601 duration (PT1.5S) or numeric seconds into int64 nanoseconds.
|
||||
func ParseDuration(val any) int64 {
|
||||
if val == nil {
|
||||
return 0
|
||||
|
||||
@@ -71,6 +71,7 @@ func MigrateLegacyDB(oldDBPath, newDBLocation, dataDir string) error {
|
||||
}
|
||||
|
||||
slog.Info("Migration complete.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -109,6 +110,7 @@ func readLegacyUsers(dbPath string) ([]legacy.LegacyUser, error) {
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
@@ -344,6 +346,7 @@ func migrateDirectories(oldDBPath, newDataDir string) error {
|
||||
return fmt.Errorf("failed to move %s: %w", srcPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ func (s *Server) handleDots(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get device webp directory (from server.go)
|
||||
// getDeviceWebpDir is a helper to get device webp directory (from server.go).
|
||||
func (s *Server) getDeviceWebPDir(deviceID string) string {
|
||||
path := filepath.Join(s.DataDir, "webp", deviceID)
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
|
||||
@@ -69,7 +69,7 @@ func newTestServerAPI(t *testing.T) *Server {
|
||||
return s
|
||||
}
|
||||
|
||||
// Helper to create a request with API key
|
||||
// Helper to create a request with API key.
|
||||
func newAPIRequest(method, path, apiKey string, body []byte) *http.Request {
|
||||
req := httptest.NewRequest(method, path, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
@@ -80,7 +80,7 @@ func newAPIRequest(method, path, apiKey string, body []byte) *http.Request {
|
||||
func TestHandleDots(t *testing.T) {
|
||||
s := newTestServerAPI(t)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/dots?w=2&h=1&r=0.75", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/dots?w=2&h=1&r=0.75", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s.ServeHTTP(rr, req)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// handleNextApp is the handler for GET /{id}/next
|
||||
// handleNextApp is the handler for GET /{id}/next.
|
||||
func (s *Server) handleNextApp(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestHandleNextApp(t *testing.T) {
|
||||
t.Fatalf("Failed to save pushed image: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "/testdevice/next", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/testdevice/next", nil)
|
||||
req.SetPathValue("id", "testdevice")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *Server) UpdateFirmwareBinaries() error {
|
||||
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (s *Server) UpdateFirmwareBinaries() error {
|
||||
slog.Info("Downloading firmware", "asset", asset.Name)
|
||||
|
||||
// Use API URL with Accept header to get binary
|
||||
dReq, _ := http.NewRequest("GET", asset.URL, nil)
|
||||
dReq, _ := http.NewRequest(http.MethodGet, asset.URL, nil)
|
||||
dReq.Header.Set("Accept", "application/octet-stream")
|
||||
if token != "" {
|
||||
dReq.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestHandleFirmwareGenerateGet(t *testing.T) {
|
||||
var device data.Device
|
||||
s.DB.First(&device, "id = ?", "testdevice")
|
||||
|
||||
req := httptest.NewRequest("GET", "/devices/testdevice/firmware", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/devices/testdevice/firmware", nil)
|
||||
ctx := context.WithValue(req.Context(), userContextKey, &user)
|
||||
ctx = context.WithValue(ctx, deviceContextKey, &device)
|
||||
req = req.WithContext(ctx)
|
||||
@@ -69,7 +69,7 @@ func TestHandleFirmwareGeneratePost(t *testing.T) {
|
||||
form.Add("wifi_password", "TestPass")
|
||||
form.Add("img_url", "http://example.com/image")
|
||||
|
||||
req := httptest.NewRequest("POST", "/devices/testdevice/firmware", strings.NewReader(form.Encode()))
|
||||
req := httptest.NewRequest(http.MethodPost, "/devices/testdevice/firmware", strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
ctx := context.WithValue(req.Context(), userContextKey, &user)
|
||||
|
||||
@@ -170,7 +170,7 @@ func (s *Server) RequireApp(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// UserFromContext retrieves the User from the context
|
||||
// UserFromContext retrieves the User from the context.
|
||||
func UserFromContext(ctx context.Context) (*data.User, error) {
|
||||
u, ok := ctx.Value(userContextKey).(*data.User)
|
||||
if !ok {
|
||||
@@ -179,7 +179,7 @@ func UserFromContext(ctx context.Context) (*data.User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// DeviceFromContext retrieves the Device from the context
|
||||
// DeviceFromContext retrieves the Device from the context.
|
||||
func DeviceFromContext(ctx context.Context) (*data.Device, error) {
|
||||
d, ok := ctx.Value(deviceContextKey).(*data.Device)
|
||||
if !ok {
|
||||
@@ -188,7 +188,7 @@ func DeviceFromContext(ctx context.Context) (*data.Device, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// AppFromContext retrieves the App from the context
|
||||
// AppFromContext retrieves the App from the context.
|
||||
func AppFromContext(ctx context.Context) (*data.App, error) {
|
||||
a, ok := ctx.Value(appContextKey).(*data.App)
|
||||
if !ok {
|
||||
@@ -197,7 +197,7 @@ func AppFromContext(ctx context.Context) (*data.App, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Helper to get Context objects without error checking (panics if missing, use only within middleware)
|
||||
// GetUser is a helper to get Context objects without error checking (panics if missing, use only within middleware).
|
||||
func GetUser(r *http.Request) *data.User {
|
||||
u, _ := UserFromContext(r.Context())
|
||||
if u == nil {
|
||||
@@ -222,7 +222,7 @@ func GetApp(r *http.Request) *data.App {
|
||||
return a
|
||||
}
|
||||
|
||||
// Helper to wrap APIAuthMiddleware for ServeMux which expects generic handler
|
||||
// APIAuth is a helper to wrap APIAuthMiddleware for ServeMux which expects generic handler.
|
||||
func (s *Server) APIAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return s.APIAuthMiddleware(next).ServeHTTP
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func createExpandedAppsList(device *data.Device, apps []data.App) []data.App {
|
||||
return apps
|
||||
}
|
||||
|
||||
var expanded []data.App
|
||||
expanded := make([]data.App, 0, len(device.Apps)*10) // Pre-allocate to a reasonable size
|
||||
for i, app := range apps {
|
||||
expanded = append(expanded, app)
|
||||
// Add interstitial after each regular app, except the last one
|
||||
|
||||
@@ -13,12 +13,14 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -48,7 +50,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// AppManifest reflects a subset of manifest.yaml for internal updates
|
||||
// AppManifest reflects a subset of manifest.yaml for internal updates.
|
||||
type AppManifest struct {
|
||||
Broken *bool `yaml:"broken,omitempty"`
|
||||
BrokenReason *string `yaml:"brokenReason,omitempty"`
|
||||
@@ -145,7 +147,7 @@ type CreateDeviceFormData struct {
|
||||
LocationJSON string
|
||||
}
|
||||
|
||||
// Map template names to their file paths relative to web/templates
|
||||
// Map template names to their file paths relative to web/templates.
|
||||
var templateFiles = map[string]string{
|
||||
"index": "manager/index.html",
|
||||
"adminindex": "manager/adminindex.html",
|
||||
@@ -403,10 +405,7 @@ func NewServer(db *gorm.DB, cfg *config.Settings) *Server {
|
||||
if length < 0 {
|
||||
length = 0
|
||||
}
|
||||
end := start + length
|
||||
if end > len(s) {
|
||||
end = len(s)
|
||||
}
|
||||
end := min(start+length, len(s))
|
||||
if start > len(s) {
|
||||
return ""
|
||||
}
|
||||
@@ -418,12 +417,7 @@ func NewServer(db *gorm.DB, cfg *config.Settings) *Server {
|
||||
return args
|
||||
},
|
||||
"contains": func(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(slice, item)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -673,7 +667,7 @@ func (s *Server) renderTemplate(w http.ResponseWriter, r *http.Request, name str
|
||||
}
|
||||
}
|
||||
|
||||
// localizeOrID helper to safely localize or return ID
|
||||
// localizeOrID helper to safely localize or return ID.
|
||||
func (s *Server) localizeOrID(localizer *i18n.Localizer, messageID string) string {
|
||||
config := &i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
@@ -694,7 +688,7 @@ func (s *Server) getLocalizer(r *http.Request) *i18n.Localizer {
|
||||
return i18n.NewLocalizer(s.Bundle, accept, language.English.String())
|
||||
}
|
||||
|
||||
// getDeviceTypeChoices returns a map of device type values to display names
|
||||
// getDeviceTypeChoices returns a map of device type values to display names.
|
||||
func (s *Server) getDeviceTypeChoices(localizer *i18n.Localizer) map[string]string {
|
||||
choices := make(map[string]string)
|
||||
|
||||
@@ -723,7 +717,7 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("handleIndex called")
|
||||
user := GetUser(r)
|
||||
|
||||
var devicesWithUI []DeviceWithUIScale
|
||||
devicesWithUI := make([]DeviceWithUIScale, 0, len(user.Devices)) // Pre-allocate
|
||||
|
||||
for i := range user.Devices {
|
||||
device := &user.Devices[i]
|
||||
@@ -841,8 +835,8 @@ func (s *Server) getRealIP(r *http.Request) string {
|
||||
isTrusted = true
|
||||
} else {
|
||||
// Simple check for comma-separated list of IPs
|
||||
proxies := strings.Split(trustedProxies, ",")
|
||||
for _, proxy := range proxies {
|
||||
proxies := strings.SplitSeq(trustedProxies, ",")
|
||||
for proxy := range proxies {
|
||||
if strings.TrimSpace(proxy) == remoteIP {
|
||||
isTrusted = true
|
||||
break
|
||||
@@ -1081,7 +1075,7 @@ func (s *Server) duplicateAppToDeviceLogic(r *http.Request, user *data.User, sou
|
||||
// Generate a unique iname for the duplicate on the target device
|
||||
var newIname string
|
||||
maxAttempts := 900 // Max 900 attempts for 3-digit numbers
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
for range maxAttempts {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(900)) // 0-899
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate random number for iname: %w", err)
|
||||
@@ -1117,9 +1111,7 @@ func (s *Server) duplicateAppToDeviceLogic(r *http.Request, user *data.User, sou
|
||||
// Deep copy Config map if it exists
|
||||
if originalApp.Config != nil {
|
||||
newConfig := make(data.JSONMap)
|
||||
for k, v := range originalApp.Config {
|
||||
newConfig[k] = v
|
||||
}
|
||||
maps.Copy(newConfig, originalApp.Config)
|
||||
duplicatedApp.Config = newConfig
|
||||
}
|
||||
|
||||
@@ -1362,7 +1354,7 @@ func (s *Server) handleAddAppPost(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Generate iname (random 3-digit string, matching Python version)
|
||||
var iname string
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
// Random integer between 100 and 999
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(900))
|
||||
if err != nil {
|
||||
@@ -1906,6 +1898,7 @@ func (s *Server) handleMoveApp(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) handleDuplicateApp(w http.ResponseWriter, r *http.Request) {
|
||||
user := GetUser(r)
|
||||
device := GetDevice(r)
|
||||
@@ -1913,7 +1906,7 @@ func (s *Server) handleDuplicateApp(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Generate new iname (random 3-digit string)
|
||||
var newIname string
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := range 100 {
|
||||
// Random integer between 100 and 999
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(900))
|
||||
if err != nil {
|
||||
@@ -2683,7 +2676,7 @@ func (s *Server) checkForUpdates() {
|
||||
func (s *Server) doUpdateCheck() {
|
||||
url := "https://api.github.com/repos/tronbyt/server/releases/latest"
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
slog.Debug("Failed to create HTTP request for update check", "error", err)
|
||||
return
|
||||
|
||||
@@ -38,7 +38,7 @@ func newTestServer(t *testing.T) *Server {
|
||||
func TestHealthCheck(t *testing.T) {
|
||||
s := newTestServer(t)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/health", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s.ServeHTTP(rr, req)
|
||||
@@ -65,7 +65,7 @@ func TestLoginRedirectToRegisterIfNoUsers(t *testing.T) {
|
||||
t.Fatalf("Expected 0 users, got %d", count)
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", "/auth/login", nil)
|
||||
req, _ := http.NewRequest(http.MethodGet, "/auth/login", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
s.ServeHTTP(rr, req)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
)
|
||||
|
||||
// Wrapper for data.User to satisfy webauthn.User interface
|
||||
// WebAuthnUser is a wrapper for data.User to satisfy webauthn.User interface.
|
||||
type WebAuthnUser struct {
|
||||
User *data.User
|
||||
Credentials []webauthn.Credential
|
||||
@@ -93,7 +93,7 @@ func (s *Server) handleWebAuthnRegisterBegin(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
var credentials []webauthn.Credential
|
||||
credentials := make([]webauthn.Credential, 0, len(user.Credentials))
|
||||
for _, cred := range user.Credentials {
|
||||
idBytes, err := base64.URLEncoding.DecodeString(cred.ID)
|
||||
if err != nil {
|
||||
@@ -313,7 +313,7 @@ func (s *Server) handleWebAuthnLoginFinish(w http.ResponseWriter, r *http.Reques
|
||||
s.DB.Where("user_id = ?", cred.UserID).Find(&userCreds)
|
||||
cred.User.Credentials = userCreds // Attach credentials to user object
|
||||
|
||||
var waCredentials []webauthn.Credential
|
||||
waCredentials := make([]webauthn.Credential, 0, 1) // Expecting 1 or 0
|
||||
for _, c := range userCreds {
|
||||
idBytes, _ := base64.URLEncoding.DecodeString(c.ID)
|
||||
aaguid, _ := hex.DecodeString(c.Authenticator)
|
||||
|
||||
@@ -70,10 +70,7 @@ func (s *Server) wsWriteLoop(ctx context.Context, conn *websocket.Conn, initialD
|
||||
var timeoutSec int
|
||||
if device.Info.ProtocolVersion != nil {
|
||||
// New firmware: wait longer to allow buffering/ack
|
||||
timeoutSec = dwell * 2
|
||||
if timeoutSec < 25 {
|
||||
timeoutSec = 25
|
||||
}
|
||||
timeoutSec = max(dwell*2, 25)
|
||||
} else {
|
||||
// Old firmware: wait exactly dwell time
|
||||
timeoutSec = dwell
|
||||
|
||||
Reference in New Issue
Block a user