diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 2f3a86eb..55bf1674 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -2,7 +2,9 @@
"name": "pocket-id",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
- "ghcr.io/devcontainers/features/go:1": {}
+ "ghcr.io/devcontainers/features/go:1": {
+ "version": "1.26"
+ }
},
"customizations": {
"vscode": {
diff --git a/.github/workflows/backend-linter.yml b/.github/workflows/backend-linter.yml
index 35aae402..4d6a5a8d 100644
--- a/.github/workflows/backend-linter.yml
+++ b/.github/workflows/backend-linter.yml
@@ -33,9 +33,9 @@ jobs:
go-version-file: backend/go.mod
- name: Run Golangci-lint
- uses: golangci/golangci-lint-action@v8.0.0
+ uses: golangci/golangci-lint-action@v9.0.0
with:
- version: v2.4.0
+ version: v2.9.0
args: --build-tags=exclude_frontend
working-directory: backend
only-new-issues: ${{ github.event_name == 'pull_request' }}
diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml
index ef128ddc..9c5a1bd5 100644
--- a/.github/workflows/build-next.yml
+++ b/.github/workflows/build-next.yml
@@ -28,7 +28,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
- node-version: 22
+ node-version: 24
- name: Setup Go
uses: actions/setup-go@v6
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 5e2913ff..4d26371f 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -76,7 +76,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
- node-version: 22
+ node-version: 24
- name: Cache Playwright Browsers
uses: actions/cache@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4c9ca81a..1f5a5669 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -22,7 +22,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
- node-version: 22
+ node-version: 24
- uses: actions/setup-go@v6
with:
go-version-file: "backend/go.mod"
diff --git a/.github/workflows/svelte-check.yml b/.github/workflows/svelte-check.yml
index c03fe966..f2f4cfc3 100644
--- a/.github/workflows/svelte-check.yml
+++ b/.github/workflows/svelte-check.yml
@@ -44,7 +44,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v5
with:
- node-version: 22
+ node-version: 24
- name: Install dependencies
run: pnpm --filter pocket-id-frontend install --frozen-lockfile
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3702ae7d..7d4ef66c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,7 +21,6 @@ Before you submit the pull request for review please ensure that
```
Where `TYPE` can be:
-
- **feat** - is a new feature
- **doc** - documentation only changes
- **fix** - a bug fix
@@ -51,8 +50,8 @@ If you use [Dev Containers](https://code.visualstudio.com/docs/remote/containers
If you don't use Dev Containers, you need to install the following tools manually:
-- [Node.js](https://nodejs.org/en/download/) >= 22
-- [Go](https://golang.org/doc/install) >= 1.25
+- [Node.js](https://nodejs.org/en/download/) >= 24
+- [Go](https://golang.org/doc/install) >= 1.26
- [Git](https://git-scm.com/downloads)
### 2. Setup
diff --git a/backend/go.mod b/backend/go.mod
index d56298d5..9013dc1e 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -1,6 +1,6 @@
module github.com/pocket-id/pocket-id/backend
-go 1.25.0
+go 1.26.0
require (
github.com/aws/aws-sdk-go-v2 v1.41.1
diff --git a/backend/internal/bootstrap/observability_boostrap.go b/backend/internal/bootstrap/observability_boostrap.go
index 7228b36b..6c89861e 100644
--- a/backend/internal/bootstrap/observability_boostrap.go
+++ b/backend/internal/bootstrap/observability_boostrap.go
@@ -118,11 +118,10 @@ func initOtelLogging(ctx context.Context, resource *resource.Resource) error {
// Set the logger provider globally
globallog.SetLoggerProvider(provider)
- // Wrap the handler in a "fanout" one
- handler = utils.LogFanoutHandler{
+ handler = slog.NewMultiHandler(
handler,
otelslog.NewHandler(common.Name, otelslog.WithLoggerProvider(provider)),
- }
+ )
// Set the default slog to send logs to OTel and add the app name
log := slog.New(handler).
diff --git a/backend/internal/common/env_config.go b/backend/internal/common/env_config.go
index dfd45a0e..93d68dbb 100644
--- a/backend/internal/common/env_config.go
+++ b/backend/internal/common/env_config.go
@@ -106,7 +106,7 @@ func defaultConfig() EnvConfigSchema {
func parseEnvConfig() error {
parsers := map[reflect.Type]env.ParserFunc{
- reflect.TypeOf([]byte{}): func(value string) (interface{}, error) {
+ reflect.TypeFor[[]byte](): func(value string) (any, error) {
return []byte(value), nil
},
}
@@ -184,8 +184,8 @@ func ValidateEnvConfig(config *EnvConfigSchema) error {
}
// Validate LOCAL_IPV6_RANGES
- ranges := strings.Split(config.LocalIPv6Ranges, ",")
- for _, rangeStr := range ranges {
+ ranges := strings.SplitSeq(config.LocalIPv6Ranges, ",")
+ for rangeStr := range ranges {
rangeStr = strings.TrimSpace(rangeStr)
if rangeStr == "" {
continue
@@ -235,9 +235,9 @@ func prepareEnvConfig(config *EnvConfigSchema) error {
fieldType := typ.Field(i)
optionsTag := fieldType.Tag.Get("options")
- options := strings.Split(optionsTag, ",")
+ options := strings.SplitSeq(optionsTag, ",")
- for _, option := range options {
+ for option := range options {
switch option {
case "toLower":
if field.Kind() == reflect.String {
diff --git a/backend/internal/dto/dto_mapper_test.go b/backend/internal/dto/dto_mapper_test.go
index 43b86b14..f244b9ee 100644
--- a/backend/internal/dto/dto_mapper_test.go
+++ b/backend/internal/dto/dto_mapper_test.go
@@ -9,7 +9,6 @@ import (
"github.com/pocket-id/pocket-id/backend/internal/model"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
- "github.com/pocket-id/pocket-id/backend/internal/utils"
)
type sourceStruct struct {
@@ -60,11 +59,11 @@ type embeddedStruct struct {
func TestMapStruct(t *testing.T) {
src := sourceStruct{
AString: "abcd",
- AStringPtr: utils.Ptr("xyz"),
+ AStringPtr: new("xyz"),
ABool: true,
- ABoolPtr: utils.Ptr(false),
+ ABoolPtr: new(false),
ACustomDateTime: datatype.DateTime(time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC)),
- ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))),
+ ACustomDateTimePtr: new(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))),
ANilStringPtr: nil,
ASlice: []string{"a", "b", "c"},
AMap: map[string]int{
@@ -80,8 +79,8 @@ func TestMapStruct(t *testing.T) {
Bar: 111,
},
- StringPtrToString: utils.Ptr("foobar"),
- EmptyStringPtrToString: utils.Ptr(""),
+ StringPtrToString: new("foobar"),
+ EmptyStringPtrToString: new(""),
NilStringPtrToString: nil,
IntToInt64: 99,
AuditLogEventToString: model.AuditLogEventAccountCreated,
@@ -118,11 +117,11 @@ func TestMapStructList(t *testing.T) {
sources := []sourceStruct{
{
AString: "first",
- AStringPtr: utils.Ptr("one"),
+ AStringPtr: new("one"),
ABool: true,
- ABoolPtr: utils.Ptr(false),
+ ABoolPtr: new(false),
ACustomDateTime: datatype.DateTime(time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC)),
- ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))),
+ ACustomDateTimePtr: new(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))),
ASlice: []string{"a", "b"},
AMap: map[string]int{
"a": 1,
@@ -136,11 +135,11 @@ func TestMapStructList(t *testing.T) {
},
{
AString: "second",
- AStringPtr: utils.Ptr("two"),
+ AStringPtr: new("two"),
ABool: false,
- ABoolPtr: utils.Ptr(true),
+ ABoolPtr: new(true),
ACustomDateTime: datatype.DateTime(time.Date(2026, 6, 7, 8, 9, 10, 0, time.UTC)),
- ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2023, 6, 7, 8, 9, 10, 0, time.UTC))),
+ ACustomDateTimePtr: new(datatype.DateTime(time.Date(2023, 6, 7, 8, 9, 10, 0, time.UTC))),
ASlice: []string{"c", "d", "e"},
AMap: map[string]int{
"c": 3,
diff --git a/backend/internal/dto/dto_normalize.go b/backend/internal/dto/dto_normalize.go
index d366f1d3..b73910c4 100644
--- a/backend/internal/dto/dto_normalize.go
+++ b/backend/internal/dto/dto_normalize.go
@@ -12,7 +12,7 @@ import (
// Normalize iterates through an object and performs Unicode normalization on all string fields with the `unorm` tag.
func Normalize(obj any) {
v := reflect.ValueOf(obj)
- if v.Kind() != reflect.Ptr || v.IsNil() {
+ if v.Kind() != reflect.Pointer || v.IsNil() {
return
}
v = v.Elem()
@@ -21,7 +21,7 @@ func Normalize(obj any) {
if v.Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
- if elem.Kind() == reflect.Ptr && !elem.IsNil() && elem.Elem().Kind() == reflect.Struct {
+ if elem.Kind() == reflect.Pointer && !elem.IsNil() && elem.Elem().Kind() == reflect.Struct {
Normalize(elem.Interface())
} else if elem.Kind() == reflect.Struct && elem.CanAddr() {
Normalize(elem.Addr().Interface())
diff --git a/backend/internal/dto/scim_dto.go b/backend/internal/dto/scim_dto.go
index dc98e1be..c04c8948 100644
--- a/backend/internal/dto/scim_dto.go
+++ b/backend/internal/dto/scim_dto.go
@@ -67,7 +67,7 @@ type ScimResourceData struct {
type ScimResourceMeta struct {
Location string `json:"location,omitempty"`
ResourceType string `json:"resourceType,omitempty"`
- Created time.Time `json:"created,omitempty"`
+ Created time.Time `json:"created"`
LastModified time.Time `json:"lastModified,omitempty"`
Version string `json:"version,omitempty"`
}
diff --git a/backend/internal/dto/user_dto_test.go b/backend/internal/dto/user_dto_test.go
index a037218f..251dfece 100644
--- a/backend/internal/dto/user_dto_test.go
+++ b/backend/internal/dto/user_dto_test.go
@@ -3,7 +3,6 @@ package dto
import (
"testing"
- "github.com/pocket-id/pocket-id/backend/internal/utils"
"github.com/stretchr/testify/require"
)
@@ -17,7 +16,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
name: "valid input",
input: UserCreateDto{
Username: "testuser",
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "John",
LastName: "Doe",
DisplayName: "John Doe",
@@ -27,7 +26,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
{
name: "missing username",
input: UserCreateDto{
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "John",
LastName: "Doe",
DisplayName: "John Doe",
@@ -37,7 +36,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
{
name: "missing display name",
input: UserCreateDto{
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "John",
LastName: "Doe",
},
@@ -47,7 +46,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
name: "username contains invalid characters",
input: UserCreateDto{
Username: "test/ser",
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "John",
LastName: "Doe",
DisplayName: "John Doe",
@@ -58,7 +57,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
name: "invalid email",
input: UserCreateDto{
Username: "testuser",
- Email: utils.Ptr("not-an-email"),
+ Email: new("not-an-email"),
FirstName: "John",
LastName: "Doe",
DisplayName: "John Doe",
@@ -69,7 +68,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
name: "first name too short",
input: UserCreateDto{
Username: "testuser",
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "",
LastName: "Doe",
DisplayName: "John Doe",
@@ -80,7 +79,7 @@ func TestUserCreateDto_Validate(t *testing.T) {
name: "last name too long",
input: UserCreateDto{
Username: "testuser",
- Email: utils.Ptr("test@example.com"),
+ Email: new("test@example.com"),
FirstName: "John",
LastName: "abcdfghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz",
DisplayName: "John Doe",
diff --git a/backend/internal/model/app_config_test.go b/backend/internal/model/app_config_test.go
index 7290617b..aac70a62 100644
--- a/backend/internal/model/app_config_test.go
+++ b/backend/internal/model/app_config_test.go
@@ -70,13 +70,12 @@ func TestAppConfigVariable_AsMinutesDuration(t *testing.T) {
// - dto.AppConfigDto should not include "internal" fields from model.AppConfig
// This test is primarily meant to catch discrepancies between the two structs as fields are added or removed over time
func TestAppConfigStructMatchesUpdateDto(t *testing.T) {
- appConfigType := reflect.TypeOf(model.AppConfig{})
- updateDtoType := reflect.TypeOf(dto.AppConfigUpdateDto{})
+ appConfigType := reflect.TypeFor[model.AppConfig]()
+ updateDtoType := reflect.TypeFor[dto.AppConfigUpdateDto]()
// Process AppConfig fields
appConfigFields := make(map[string]string)
- for i := 0; i < appConfigType.NumField(); i++ {
- field := appConfigType.Field(i)
+ for field := range appConfigType.Fields() {
if field.Tag.Get("key") == "" {
// Skip internal fields
continue
@@ -91,9 +90,7 @@ func TestAppConfigStructMatchesUpdateDto(t *testing.T) {
// Process AppConfigUpdateDto fields
dtoFields := make(map[string]string)
- for i := 0; i < updateDtoType.NumField(); i++ {
- field := updateDtoType.Field(i)
-
+ for field := range updateDtoType.Fields() {
// Extract the json name from the tag (takes the part before any binding constraints)
jsonTag := field.Tag.Get("json")
jsonName, _, _ := strings.Cut(jsonTag, ",")
diff --git a/backend/internal/model/webauthn.go b/backend/internal/model/webauthn.go
index 4cd19520..60f1913f 100644
--- a/backend/internal/model/webauthn.go
+++ b/backend/internal/model/webauthn.go
@@ -58,7 +58,7 @@ type ReauthenticationToken struct {
type AuthenticatorTransportList []protocol.AuthenticatorTransport //nolint:recvcheck
// Scan and Value methods for GORM to handle the custom type
-func (atl *AuthenticatorTransportList) Scan(value interface{}) error {
+func (atl *AuthenticatorTransportList) Scan(value any) error {
return utils.UnmarshalJSONFromDatabase(atl, value)
}
@@ -69,7 +69,7 @@ func (atl AuthenticatorTransportList) Value() (driver.Value, error) {
type CredentialParameters []protocol.CredentialParameter //nolint:recvcheck
// Scan and Value methods for GORM to handle the custom type
-func (cp *CredentialParameters) Scan(value interface{}) error {
+func (cp *CredentialParameters) Scan(value any) error {
return utils.UnmarshalJSONFromDatabase(cp, value)
}
diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go
index bc8de9f5..0d175a92 100644
--- a/backend/internal/service/api_key_service.go
+++ b/backend/internal/service/api_key_service.go
@@ -170,7 +170,7 @@ func (s *ApiKeyService) ValidateApiKey(ctx context.Context, apiKey string) (mode
Clauses(clause.Returning{}).
Where("key = ? AND expires_at > ?", hashedKey, datatype.DateTime(now)).
Updates(&model.ApiKey{
- LastUsedAt: utils.Ptr(datatype.DateTime(now)),
+ LastUsedAt: new(datatype.DateTime(now)),
}).
Preload("User").
First(&key).
diff --git a/backend/internal/service/app_config_service.go b/backend/internal/service/app_config_service.go
index e05427f6..613fdcf8 100644
--- a/backend/internal/service/app_config_service.go
+++ b/backend/internal/service/app_config_service.go
@@ -186,8 +186,7 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon
rt := reflect.ValueOf(input).Type()
rv := reflect.ValueOf(input)
dbUpdate := make([]model.AppConfigVariable, 0, rt.NumField())
- for i := range rt.NumField() {
- field := rt.Field(i)
+ for field := range rt.Fields() {
value := rv.FieldByName(field.Name).String()
// Get the value of the json tag, taking only what's before the comma
diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go
index 4e65064d..7df15681 100644
--- a/backend/internal/service/e2etest_service.go
+++ b/backend/internal/service/e2etest_service.go
@@ -81,7 +81,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
ID: "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e",
},
Username: "tim",
- Email: utils.Ptr("tim.cook@test.com"),
+ Email: new("tim.cook@test.com"),
EmailVerified: true,
FirstName: "Tim",
LastName: "Cook",
@@ -93,7 +93,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
ID: "1cd19686-f9a6-43f4-a41f-14a0bf5b4036",
},
Username: "craig",
- Email: utils.Ptr("craig.federighi@test.com"),
+ Email: new("craig.federighi@test.com"),
EmailVerified: false,
FirstName: "Craig",
LastName: "Federighi",
@@ -105,7 +105,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
ID: "d9256384-98ad-49a7-bc58-99ad0b4dc23c",
},
Username: "eddy",
- Email: utils.Ptr("eddy.cue@test.com"),
+ Email: new("eddy.cue@test.com"),
FirstName: "Eddy",
LastName: "Cue",
DisplayName: "Eddy Cue",
@@ -171,12 +171,12 @@ func (s *TestService) SeedDatabase(baseURL string) error {
ID: "3654a746-35d4-4321-ac61-0bdcff2b4055",
},
Name: "Nextcloud",
- LaunchURL: utils.Ptr("https://nextcloud.local"),
+ LaunchURL: new("https://nextcloud.local"),
Secret: "$2a$10$9dypwot8nGuCjT6wQWWpJOckZfRprhe2EkwpKizxS/fpVHrOLEJHC", // w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY
CallbackURLs: model.UrlList{"http://nextcloud/auth/callback"},
LogoutCallbackURLs: model.UrlList{"http://nextcloud/auth/logout/callback"},
- ImageType: utils.StringPointer("png"),
- CreatedByID: utils.Ptr(users[0].ID),
+ ImageType: new("png"),
+ CreatedByID: new(users[0].ID),
},
{
Base: model.Base{
@@ -185,7 +185,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
Name: "Immich",
Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x
CallbackURLs: model.UrlList{"http://immich/auth/callback"},
- CreatedByID: utils.Ptr(users[1].ID),
+ CreatedByID: new(users[1].ID),
IsGroupRestricted: true,
AllowedUserGroups: []model.UserGroup{
userGroups[1],
@@ -200,7 +200,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
CallbackURLs: model.UrlList{"http://tailscale/auth/callback"},
LogoutCallbackURLs: model.UrlList{"http://tailscale/auth/logout/callback"},
IsGroupRestricted: true,
- CreatedByID: utils.Ptr(users[0].ID),
+ CreatedByID: new(users[0].ID),
},
{
Base: model.Base{
@@ -209,7 +209,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
Name: "Federated",
Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x
CallbackURLs: model.UrlList{"http://federated/auth/callback"},
- CreatedByID: utils.Ptr(users[1].ID),
+ CreatedByID: new(users[1].ID),
AllowedUserGroups: []model.UserGroup{},
Credentials: model.OidcClientCredentials{
FederatedIdentities: []model.OidcClientFederatedIdentity{
@@ -229,7 +229,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
Name: "SCIM Client",
Secret: "$2a$10$h4wfa8gI7zavDAxwzSq1sOwYU4e8DwK1XZ8ZweNnY5KzlJ3Iz.qdK", // nQbiuMRG7FpdK2EnDd5MBivWQeKFXohn
CallbackURLs: model.UrlList{"http://scimclient/auth/callback"},
- CreatedByID: utils.Ptr(users[0].ID),
+ CreatedByID: new(users[0].ID),
IsGroupRestricted: true,
AllowedUserGroups: []model.UserGroup{
userGroups[0],
@@ -458,7 +458,7 @@ func (s *TestService) SeedDatabase(baseURL string) error {
{
Key: jwkutils.PrivateKeyDBKey,
// {"alg":"RS256","d":"mvMDWSdPPvcum0c0iEHE2gbqtV2NKMmLwrl9E6K7g8lTV95SePLnW_bwyMPV7EGp7PQk3l17I5XRhFjze7GqTnFIOgKzMianPs7jv2ELtBMGK0xOPATgu1iGb70xZ6vcvuEfRyY3dJ0zr4jpUdVuXwKmx9rK4IdZn2dFCKfvSuspqIpz11RhF1ALrqDLkxGVv7ZwNh0_VhJZU9hcjG5l6xc7rQEKpPRkZp0IdjkGS8Z0FskoVaiRIWAbZuiVFB9WCW8k1czC4HQTPLpII01bUQx2ludbm0UlXRgVU9ptUUbU7GAImQqTOW8LfPGklEvcgzlIlR_oqw4P9yBxLi-yMQ","dp":"pvNCSnnhbo8Igw9psPR-DicxFnkXlu_ix4gpy6efTrxA-z1VDFDioJ814vKQNioYDzpyAP1gfMPhRkvG_q0hRZsJah3Sb9dfA-WkhSWY7lURQP4yIBTMU0PF_rEATuS7lRciYk1SOx5fqXZd3m_LP0vpBC4Ujlq6NAq6CIjCnms","dq":"TtUVGCCkPNgfOLmkYXu7dxxUCV5kB01-xAEK2OY0n0pG8vfDophH4_D_ZC7nvJ8J9uDhs_3JStexq1lIvaWtG99RNTChIEDzpdn6GH9yaVcb_eB4uJjrNm64FhF8PGCCwxA-xMCZMaARKwhMB2_IOMkxUbWboL3gnhJ2rDO_QO0","e":"AQAB","kid":"8uHDw3M6rf8","kty":"RSA","n":"yaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC-585UXacoJ0chUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl_4EDDTO8HwawTjwkPoQlRzeByhlvGPVvwgB3Fn93B8QJ_cZhXKxJvjjrC_8Pk76heC_ntEMru71Ix77BoC3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeOZl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJw","p":"_Yylc9e07CKdqNRD2EosMC2mrhrEa9j5oY_l00Qyy4-jmCA59Q9viyqvveRo0U7cRvFA5BWgWN6GGLh1DG3X-QBqVr0dnk3uzbobb55RYUXyPLuBZI2q6w2oasbiDwPdY7KpkVv_H-bpITQlyDvO8hhucA6rUV7F6KTQVz8M3Ms","q":"y5p3hch-7jJ21TkAhp_Vk1fLCAuD4tbErwQs2of9ja8sB4iJOs5Wn6HD3P7Mc8Plye7qaLHvzc8I5g0tPKWvC0DPd_FLPXiWwMVAzee3NUX_oGeJNOQp11y1w_KqdO9qZqHSEPZ3NcFL_SZMFgggxhM1uzRiPzsVN0lnD_6prZU","qi":"2Grt6uXHm61ji3xSdkBWNtUnj19vS1-7rFJp5SoYztVQVThf_W52BAiXKBdYZDRVoItC_VS2NvAOjeJjhYO_xQ_q3hK7MdtuXfEPpLnyXKkmWo3lrJ26wbeF6l05LexCkI7ShsOuSt-dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI","use":"sig"}
- Value: utils.Ptr("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"),
+ Value: new("7d/5hl7diJ2rnFL14hEAQf9tzpu29aqXQ8jpJ2iqqKUNFZpdOkEpud0CmRv4H3r8yyk2u/Gqqj9klSy58DJkYXGF5PAYgLyoBIb7L3JXWRbxg4cQ3QJCug13l2OTmpAKoVc+rmX8c3j3h1sNqyJ+7Ql5sS0jSeyiYgIsFNCdnK5alBDyvtcpe/QDpklmP4JCeVpvmf2rLGplk3g5UO5ydJ8UiDXxfDmi+gF6NKJvrGnnah8Ar3G/x88z+tTJtp0DIQFwxXwUM2XZqzEVGm8K2r0w5o9/Keh6bBBaiuH2C78ZOaijGV3DovhR+e9J0cYUYGwT42MZMx9fSWQ/lvWGGnf+Uq3MXJfjWSREfhkp8KTQwR9F7+dnVJWswOEk7jPR8I7hCWTMxJyvaFX3wgAXIVmhrgXZQQbYOqTt56IoqUl0xOJku8dA8opg2UcLlmmuOh6+hfkXKsiiS/H/9c1BVIGj1fCOiT6IePh4wKKSTbwJnPD5EKmdJpgTsUpjcDnXQKY4ReO0UpdRdKxwRDDLeQuG6j+ljGxR9GPudCU9Nmci6rFVI6n5LWYkQxBA1O73RpmXRZPDzntDfpXMEonkmSvOoxaCK2Id7CRKMdqvR0kEouwnhk5WSFtsfi3sA0pkXzPFxwZeWM8vFtbffZOZzXaOhxCOfcj1NClZohlZhyc4jvkxmrpY7PSaAzih0AmHI7y0LYFi6fZu/K4EheVa1+KF55nWZ8ARikHMWKAKkyExkTak7xyN884TDmzURRaPlQg4jzQte5WMNjAG/hlHibdMBNvgwiYd49ZxteJ8ABdbiXVRl+2JGbdjl2ubpQZwOn7bJKlqO56bIwsZ+e4+pXsuOGdBahkHrUjtMEmH3DZbGc6CJLbcmdhdpApLQRRcLAazxJhzAwJ47FRYsHsj57LnYNvmcKdIxw8rxCdLUuzz95uw0T3ankEO5J9sjem+HMEuKdwXK1UcuOn2rjR8Sd/BuvQmeso27dFbPXqXYNS90Ml45YyTvcKSiopD181oZR703TFUSpR7dsiqROMr+p/2jN9h6a8WbQ8xpksyclaQByY/M77AssbXnG6wfhRsntNIINCZLbBnjXOyz6ZHIC5K4tSTdcnWaiYPeRPQmnw9UUvHAcNU2yMWsy0eU377yDS0WstTxOdQutTdkczl8kv5Lo26JiEK7mSIuRK19ffF9Zz8FG8+eKv5zdyIPjyQRDYBysUoDv5huKe2eoxJu/MWS2Pql/ZtUGeD6Ozm3mCvh0vQ9ceagBkY6Ocm3du0ziAKP29Ri0mjg4DizVorbLzsh+EQH/s2Pi9MnjUZDlEmuLl2Xfp7/w4j/8u0N0tVR70VDFuGdKpTjFY3vS8EJrPtyMTM51x1D9rb8gIql8aR/rJw4YF+huxg1mv5n6+tGVqg5msbPmF12eJijP4lkmaRwIpLW5pJTtaDkUj7uOeu1mm4k+Dt5nh0/0jPHzrv6bcTCcbV7UjMHDoTXXqEpFAAJ66rHR7zdAJu+YKsnTIZyLmOpcowq7LL8G9qTvV0OSpyQWUIavRSgbDHFqEqRs+JU94jAzkq8nCY5MTd9m5sIv9InfdT3k+pwpsE/FKge8nghFLtbUrafGkzTky8SE2druvVcIvbfXMfLIKRUYjJgnWc0gQzF5J6pzXM7D2r/RG6JDzASqjlbURq6v9bhNerlOVdMujWKEEVcKWIzlbt4RkihRjM8AUqIZQOyicGQ+4yfIjAHw5viuABONYs3OIWULnFqJxdvS9rNKhfxSjIq9cfqyzevq2xrRoMXEonobh6M3bD2Vang8OAeVeD1OXWPERi4pepCYFS9RJ/Xa/UWxptsqSNuGcb3fAzQSmLpXLGdWRoKXvSe7EYgc0bGcLOjSTu5RURKo+EF9i4KT9EJauf6VXw5dTf/CCIJRXE1bWzXhSCFYntohYhX2ldOCDYpi/jFBC6Vtkw0ud3/xq8Nmhd5gUk+SpngByCZH3Pm3H+jvlbMpiqkDkm1v74hDX13Xhrcw2eWyuqKBVoRCCniUvwpYNbGvBfjC6Hcizv0Aybciwj+4nybt5EPoEUm6S6Gs7fG7QpPdvrzpAxX70MlmdkF/gwyuhbEeJhLK+WL7qAsN5CvHPzVbsIf90x+nGTtMJPgpxVr0tJMj+vprXV4WxutfARBiOnqe58MhA857sd+MzKBgKnoLOBRTiC3qc/0/ULwbG2HCCD7nmwzz7M4nUuMvo8rgS7z0BF68OClT8X3JwSXbL5Wg=="),
},
}
diff --git a/backend/internal/service/export_service.go b/backend/internal/service/export_service.go
index 67d18111..1ce5b259 100644
--- a/backend/internal/service/export_service.go
+++ b/backend/internal/service/export_service.go
@@ -129,39 +129,39 @@ func (s *ExportService) getScanValuesForTable(cols []string, types utils.DBSchem
case "boolean", "bool":
var x bool
if types[col].Nullable {
- res[i] = utils.Ptr(utils.Ptr(x))
+ res[i] = new(new(x))
} else {
- res[i] = utils.Ptr(x)
+ res[i] = new(x)
}
case "blob", "bytea", "jsonb":
// Treat jsonb columns as binary too
var x []byte
if types[col].Nullable {
- res[i] = utils.Ptr(utils.Ptr(x))
+ res[i] = new(new(x))
} else {
- res[i] = utils.Ptr(x)
+ res[i] = new(x)
}
case "timestamp", "timestamptz", "timestamp with time zone", "datetime":
var x datatype.DateTime
if types[col].Nullable {
- res[i] = utils.Ptr(utils.Ptr(x))
+ res[i] = new(new(x))
} else {
- res[i] = utils.Ptr(x)
+ res[i] = new(x)
}
case "integer", "int", "bigint":
var x int64
if types[col].Nullable {
- res[i] = utils.Ptr(utils.Ptr(x))
+ res[i] = new(new(x))
} else {
- res[i] = utils.Ptr(x)
+ res[i] = new(x)
}
default:
// Treat everything else as a string (including the "numeric" type)
var x string
if types[col].Nullable {
- res[i] = utils.Ptr(utils.Ptr(x))
+ res[i] = new(new(x))
} else {
- res[i] = utils.Ptr(x)
+ res[i] = new(x)
}
}
}
diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go
index 5b08f1c7..3adba7e0 100644
--- a/backend/internal/service/jwt_service_test.go
+++ b/backend/internal/service/jwt_service_test.go
@@ -20,7 +20,6 @@ import (
"github.com/pocket-id/pocket-id/backend/internal/common"
"github.com/pocket-id/pocket-id/backend/internal/model"
- "github.com/pocket-id/pocket-id/backend/internal/utils"
jwkutils "github.com/pocket-id/pocket-id/backend/internal/utils/jwk"
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
)
@@ -305,7 +304,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "user123"},
- Email: utils.Ptr("user@example.com"),
+ Email: new("user@example.com"),
IsAdmin: false,
}
@@ -341,7 +340,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
adminUser := model.User{
Base: model.Base{ID: "admin123"},
- Email: utils.Ptr("admin@example.com"),
+ Email: new("admin@example.com"),
IsAdmin: true,
}
@@ -393,7 +392,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "eddsauser123"},
- Email: utils.Ptr("eddsauser@example.com"),
+ Email: new("eddsauser@example.com"),
IsAdmin: true,
}
@@ -430,7 +429,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "ecdsauser123"},
- Email: utils.Ptr("ecdsauser@example.com"),
+ Email: new("ecdsauser@example.com"),
IsAdmin: true,
}
@@ -467,7 +466,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "rsauser123"},
- Email: utils.Ptr("rsauser@example.com"),
+ Email: new("rsauser@example.com"),
IsAdmin: true,
}
@@ -502,7 +501,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
t.Run("generates and verifies ID token with standard claims", func(t *testing.T) {
service, _, _ := setupJwtService(t, mockConfig)
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "user123",
"name": "Test User",
"email": "user@example.com",
@@ -539,7 +538,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
t.Run("can accept expired tokens if told so", func(t *testing.T) {
service, _, _ := setupJwtService(t, mockConfig)
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "user123",
"name": "Test User",
"email": "user@example.com",
@@ -587,7 +586,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
t.Run("generates and verifies ID token with nonce", func(t *testing.T) {
service, _, _ := setupJwtService(t, mockConfig)
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "user456",
"name": "Another User",
}
@@ -612,7 +611,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
t.Run("fails verification with incorrect issuer", func(t *testing.T) {
service, _, _ := setupJwtService(t, mockConfig)
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "user789",
}
tokenString, err := service.GenerateIDToken(userClaims, "client-789", "")
@@ -634,7 +633,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
require.True(t, ok)
assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original")
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "eddsauser456",
"name": "EdDSA User",
"email": "eddsauser@example.com",
@@ -672,7 +671,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
require.True(t, ok)
assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original")
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "ecdsauser456",
"email": "ecdsauser@example.com",
}
@@ -709,7 +708,7 @@ func TestGenerateVerifyIdToken(t *testing.T) {
require.True(t, ok)
assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original")
- userClaims := map[string]interface{}{
+ userClaims := map[string]any{
"sub": "rsauser456",
"name": "RSA User",
"email": "rsauser@example.com",
@@ -742,7 +741,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "user123"},
- Email: utils.Ptr("user@example.com"),
+ Email: new("user@example.com"),
}
const clientID = "test-client-123"
@@ -825,7 +824,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "eddsauser789"},
- Email: utils.Ptr("eddsaoauth@example.com"),
+ Email: new("eddsaoauth@example.com"),
}
const clientID = "eddsa-oauth-client"
@@ -862,7 +861,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "ecdsauser789"},
- Email: utils.Ptr("ecdsaoauth@example.com"),
+ Email: new("ecdsaoauth@example.com"),
}
const clientID = "ecdsa-oauth-client"
@@ -899,7 +898,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) {
user := model.User{
Base: model.Base{ID: "rsauser789"},
- Email: utils.Ptr("rsaoauth@example.com"),
+ Email: new("rsaoauth@example.com"),
}
const clientID = "rsa-oauth-client"
diff --git a/backend/internal/service/ldap_service.go b/backend/internal/service/ldap_service.go
index 01a8ff3d..ac6042b2 100644
--- a/backend/internal/service/ldap_service.go
+++ b/backend/internal/service/ldap_service.go
@@ -529,7 +529,7 @@ func getDNProperty(property string, str string) string {
// First we split at the comma
property = strings.ToLower(property)
l := len(property) + 1
- for _, v := range strings.Split(str, ",") {
+ for v := range strings.SplitSeq(str, ",") {
v = strings.TrimSpace(v)
if len(v) > l && strings.ToLower(v)[0:l] == property+"=" {
return v[l:]
diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go
index f633c151..828e0710 100644
--- a/backend/internal/service/oidc_service.go
+++ b/backend/internal/service/oidc_service.go
@@ -731,7 +731,7 @@ func (s *OidcService) CreateClient(ctx context.Context, input dto.OidcClientCrea
Base: model.Base{
ID: input.ID,
},
- CreatedByID: utils.Ptr(userID),
+ CreatedByID: new(userID),
}
updateOIDCClientModelFromDto(&client, &input.OidcClientUpdateDto)
diff --git a/backend/internal/service/scim_service.go b/backend/internal/service/scim_service.go
index 1d8a8736..976e2cd5 100644
--- a/backend/internal/service/scim_service.go
+++ b/backend/internal/service/scim_service.go
@@ -11,6 +11,7 @@ import (
"net/http"
"net/url"
"path"
+ "slices"
"strconv"
"strings"
"time"
@@ -243,7 +244,7 @@ func (s *ScimService) SyncServiceProvider(ctx context.Context, serviceProviderID
return errors.Join(errs...)
}
- provider.LastSyncedAt = utils.Ptr(datatype.DateTime(time.Now()))
+ provider.LastSyncedAt = new(datatype.DateTime(time.Now()))
if err := s.db.WithContext(ctx).Save(&provider).Error; err != nil {
return err
}
@@ -788,10 +789,8 @@ func ensureScimStatus(
resp *http.Response,
provider model.ScimServiceProvider,
allowedStatuses ...int) error {
- for _, status := range allowedStatuses {
- if resp.StatusCode == status {
- return nil
- }
+ if slices.Contains(allowedStatuses, resp.StatusCode) {
+ return nil
}
body := readScimErrorBody(resp.Body)
diff --git a/backend/internal/service/user_group_service.go b/backend/internal/service/user_group_service.go
index cc6cb7eb..e55c9085 100644
--- a/backend/internal/service/user_group_service.go
+++ b/backend/internal/service/user_group_service.go
@@ -162,7 +162,7 @@ func (s *UserGroupService) updateInternal(ctx context.Context, id string, input
group.Name = input.Name
group.FriendlyName = input.FriendlyName
- group.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now()))
+ group.UpdatedAt = new(datatype.DateTime(time.Now()))
err = tx.
WithContext(ctx).
@@ -228,7 +228,7 @@ func (s *UserGroupService) updateUsersInternal(ctx context.Context, id string, u
}
// Save the updated group
- group.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now()))
+ group.UpdatedAt = new(datatype.DateTime(time.Now()))
err = tx.
WithContext(ctx).
diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go
index b02dcab3..f0ad2369 100644
--- a/backend/internal/service/user_service.go
+++ b/backend/internal/service/user_service.go
@@ -435,7 +435,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
}
}
- user.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now()))
+ user.UpdatedAt = new(datatype.DateTime(time.Now()))
err = tx.
WithContext(ctx).
@@ -501,9 +501,9 @@ func (s *UserService) UpdateUserGroups(ctx context.Context, id string, userGroup
}
// Update the UpdatedAt field for all affected groups
- now := time.Now()
+ now := datatype.DateTime(time.Now())
for _, group := range groups {
- group.UpdatedAt = utils.Ptr(datatype.DateTime(now))
+ group.UpdatedAt = &now
err = tx.WithContext(ctx).Save(&group).Error
if err != nil {
return model.User{}, err
@@ -636,7 +636,7 @@ func (s *UserService) VerifyEmail(ctx context.Context, userID string, token stri
}
user.EmailVerified = true
- user.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now()))
+ user.UpdatedAt = new(datatype.DateTime(time.Now()))
err = tx.WithContext(ctx).Save(&user).Error
if err != nil {
return err
diff --git a/backend/internal/utils/callback_url_util_test.go b/backend/internal/utils/callback_url_util_test.go
index 9691bdb4..959a8789 100644
--- a/backend/internal/utils/callback_url_util_test.go
+++ b/backend/internal/utils/callback_url_util_test.go
@@ -414,10 +414,10 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) {
expectMatch: true,
},
{
- name: "IPv6 loopback without brackets in input",
- urls: []string{"http://[::1]/callback"},
- inputCallbackURL: "http://::1:8080/callback",
- expectedURL: "http://::1:8080/callback",
+ name: "IPv6 loopback with wildcard path",
+ urls: []string{"http://[::1]/auth/*"},
+ inputCallbackURL: "http://[::1]:8080/auth/callback",
+ expectedURL: "http://[::1]:8080/auth/callback",
expectMatch: true,
},
{
@@ -462,6 +462,13 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) {
expectedURL: "http://127.0.0.1:8080/callback",
expectMatch: true,
},
+ {
+ name: "wildcard matches IPv6 loopback",
+ urls: []string{"*"},
+ inputCallbackURL: "http://[::1]:8080/callback",
+ expectedURL: "http://[::1]:8080/callback",
+ expectMatch: true,
+ },
}
for _, tt := range tests {
diff --git a/backend/internal/utils/ip_util.go b/backend/internal/utils/ip_util.go
index be2ab83c..4136658f 100644
--- a/backend/internal/utils/ip_util.go
+++ b/backend/internal/utils/ip_util.go
@@ -87,9 +87,9 @@ func listContainsIP(ipNets []*net.IPNet, ip net.IP) bool {
func loadLocalIPv6Ranges() {
localIPv6Ranges = nil
- ranges := strings.Split(common.EnvConfig.LocalIPv6Ranges, ",")
+ ranges := strings.SplitSeq(common.EnvConfig.LocalIPv6Ranges, ",")
- for _, rangeStr := range ranges {
+ for rangeStr := range ranges {
rangeStr = strings.TrimSpace(rangeStr)
if rangeStr == "" {
continue
diff --git a/backend/internal/utils/json_util.go b/backend/internal/utils/json_util.go
index f7dd55f4..476fdd9a 100644
--- a/backend/internal/utils/json_util.go
+++ b/backend/internal/utils/json_util.go
@@ -42,7 +42,7 @@ func (d *JSONDuration) UnmarshalJSON(b []byte) error {
}
}
-func UnmarshalJSONFromDatabase(data interface{}, value any) error {
+func UnmarshalJSONFromDatabase(data any, value any) error {
switch v := value.(type) {
case []byte:
return json.Unmarshal(v, data)
diff --git a/backend/internal/utils/list_request_util.go b/backend/internal/utils/list_request_util.go
index e52773bc..b0bb34c8 100644
--- a/backend/internal/utils/list_request_util.go
+++ b/backend/internal/utils/list_request_util.go
@@ -43,7 +43,7 @@ func ParseListRequestOptions(ctx *gin.Context) (listRequestOptions ListRequestOp
return listRequestOptions
}
-func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result interface{}) (PaginationResponse, error) {
+func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result any) (PaginationResponse, error) {
meta := extractModelMetadata(result)
query = applyFilters(params.Filters, query, meta)
@@ -52,7 +52,7 @@ func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result int
return Paginate(params.Pagination.Page, params.Pagination.Limit, query, result)
}
-func Paginate(page int, pageSize int, query *gorm.DB, result interface{}) (PaginationResponse, error) {
+func Paginate(page int, pageSize int, query *gorm.DB, result any) (PaginationResponse, error) {
if page < 1 {
page = 1
}
@@ -117,8 +117,8 @@ func parseNestedFilters(ctx *gin.Context) map[string][]any {
// Keys can be "filters[field]" or "filters[field][0]"
raw := strings.TrimPrefix(key, "filters[")
// Take everything up to the first closing bracket
- if idx := strings.IndexByte(raw, ']'); idx != -1 {
- field := raw[:idx]
+ if before, _, ok := strings.Cut(raw, "]"); ok {
+ field := before
for _, v := range values {
result[field] = append(result[field], ConvertStringToType(v))
}
@@ -165,12 +165,12 @@ func applySorting(sortColumn string, sortDirection string, query *gorm.DB, meta
}
// extractModelMetadata extracts FieldMeta from the model struct using reflection
-func extractModelMetadata(model interface{}) map[string]FieldMeta {
+func extractModelMetadata(model any) map[string]FieldMeta {
meta := make(map[string]FieldMeta)
// Unwrap pointers and slices to get the element struct type
t := reflect.TypeOf(model)
- for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
+ for t.Kind() == reflect.Pointer || t.Kind() == reflect.Slice {
t = t.Elem()
if t == nil {
return meta
@@ -180,8 +180,7 @@ func extractModelMetadata(model interface{}) map[string]FieldMeta {
// recursive parser that merges fields from embedded structs
var parseStruct func(reflect.Type)
parseStruct = func(st reflect.Type) {
- for i := 0; i < st.NumField(); i++ {
- field := st.Field(i)
+ for field := range st.Fields() {
ft := field.Type
// If the field is an embedded/anonymous struct, recurse into it
diff --git a/backend/internal/utils/ptr_util.go b/backend/internal/utils/ptr_util.go
index dae0bb5b..6c5ec1cb 100644
--- a/backend/internal/utils/ptr_util.go
+++ b/backend/internal/utils/ptr_util.go
@@ -1,10 +1,5 @@
package utils
-// Ptr returns a pointer to the given value.
-func Ptr[T any](v T) *T {
- return &v
-}
-
// PtrOrNil returns a pointer to v if v is not the zero value of its type,
// otherwise it returns nil.
func PtrOrNil[T comparable](v T) *T {
diff --git a/backend/internal/utils/slogfanout.go b/backend/internal/utils/slogfanout.go
deleted file mode 100644
index ac6777d7..00000000
--- a/backend/internal/utils/slogfanout.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package utils
-
-import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "slices"
-)
-
-// This file contains code adapted from https://github.com/samber/slog-multi
-// Source: https://github.com/samber/slog-multi/blob/ced84707f45ec9848138349ed58de178eedaa6f2/pipe.go
-// Copyright (C) 2023 Samuel Berthe
-// License: MIT (https://github.com/samber/slog-multi/blob/ced84707f45ec9848138349ed58de178eedaa6f2/LICENSE)
-
-// LogFanoutHandler is a slog.Handler that sends logs to multiple destinations
-type LogFanoutHandler []slog.Handler
-
-// Implements slog.Handler
-func (h LogFanoutHandler) Enabled(ctx context.Context, l slog.Level) bool {
- for i := range h {
- if h[i].Enabled(ctx, l) {
- return true
- }
- }
-
- return false
-}
-
-// Implements slog.Handler
-func (h LogFanoutHandler) Handle(ctx context.Context, r slog.Record) error {
- errs := make([]error, 0)
- for i := range h {
- if h[i].Enabled(ctx, r.Level) {
- err := try(func() error {
- return h[i].Handle(ctx, r.Clone())
- })
- if err != nil {
- errs = append(errs, err)
- }
- }
- }
-
- return errors.Join(errs...)
-}
-
-// Implements slog.Handler
-func (h LogFanoutHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
- res := make(LogFanoutHandler, len(h))
- for i, v := range h {
- res[i] = v.WithAttrs(slices.Clone(attrs))
- }
- return res
-}
-
-// Implements slog.Handler
-func (h LogFanoutHandler) WithGroup(name string) slog.Handler {
- // https://cs.opensource.google/go/x/exp/+/46b07846:slog/handler.go;l=247
- if name == "" {
- return h
- }
-
- res := make(LogFanoutHandler, len(h))
- for i, v := range h {
- res[i] = v.WithGroup(name)
- }
- return res
-}
-
-func try(callback func() error) (err error) {
- defer func() {
- r := recover()
- if r != nil {
- if e, ok := r.(error); ok {
- err = e
- } else {
- err = fmt.Errorf("unexpected error: %+v", r)
- }
- }
- }()
-
- err = callback()
-
- return
-}
diff --git a/backend/internal/utils/string_util.go b/backend/internal/utils/string_util.go
index b20f083f..13281903 100644
--- a/backend/internal/utils/string_util.go
+++ b/backend/internal/utils/string_util.go
@@ -70,11 +70,6 @@ func GetHostnameFromURL(rawURL string) string {
return parsedURL.Hostname()
}
-// StringPointer creates a string pointer from a string value
-func StringPointer(s string) *string {
- return &s
-}
-
func CapitalizeFirstLetter(str string) string {
if str == "" {
return ""
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 2dd396b6..3a1f38d0 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -4,7 +4,7 @@
ARG BUILD_TAGS=""
# Stage 1: Build Frontend
-FROM node:22-alpine AS frontend-builder
+FROM node:24-alpine AS frontend-builder
RUN corepack enable
WORKDIR /build
@@ -18,7 +18,7 @@ COPY ./frontend ./frontend/
RUN BUILD_OUTPUT_PATH=dist pnpm --filter pocket-id-frontend run build
# Stage 2: Build Backend
-FROM golang:1.25-alpine AS backend-builder
+FROM golang:1.26-alpine AS backend-builder
ARG BUILD_TAGS
WORKDIR /build
COPY ./backend/go.mod ./backend/go.sum ./
diff --git a/frontend/src/lib/components/form/form-input.svelte b/frontend/src/lib/components/form/form-input.svelte
index e3a995be..ab76404c 100644
--- a/frontend/src/lib/components/form/form-input.svelte
+++ b/frontend/src/lib/components/form/form-input.svelte
@@ -86,6 +86,6 @@
{/if}
{/if}
{#if input?.error}
- {input.error}
+ {input.error}
{/if}
diff --git a/package.json b/package.json
index 0f7cc837..e3b3e5a2 100644
--- a/package.json
+++ b/package.json
@@ -12,5 +12,5 @@
"test": "pnpm --filter pocket-id-tests test",
"format": "pnpm --filter pocket-id-frontend format"
},
- "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
+ "packageManager": "pnpm@10.30.1+sha512.3590e550d5384caa39bd5c7c739f72270234b2f6059e13018f975c313b1eb9fefcc09714048765d4d9efe961382c312e624572c0420762bdc5d5940cdf9be73a"
}