mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-27 17:56:36 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a06d9d21e4 | ||
|
|
cbecbd088f | ||
|
|
3c42a713ce | ||
|
|
e7e0176316 | ||
|
|
0551502586 | ||
|
|
5251cd9799 | ||
|
|
673e5841aa | ||
|
|
dc6558522e | ||
|
|
724c41cb7a | ||
|
|
fc52bd4efb | ||
|
|
2701754e73 | ||
|
|
3700bd942d | ||
|
|
2b5401dd2f | ||
|
|
95e9af4bbf | ||
|
|
0c039cc88c | ||
|
|
192f71a13c | ||
|
|
f90f21b620 | ||
|
|
d71966f996 | ||
|
|
cad80e7d74 | ||
|
|
832b7fbff4 | ||
|
|
e3905cf315 |
106
.github/workflows/pr-quality.yml
vendored
Normal file
106
.github/workflows/pr-quality.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
name: PR Quality
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pr-quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: peakoss/anti-slop@v0
|
||||||
|
with:
|
||||||
|
# General Settings
|
||||||
|
max-failures: 4
|
||||||
|
|
||||||
|
# PR Branch Checks
|
||||||
|
allowed-target-branches: "main"
|
||||||
|
blocked-target-branches: ""
|
||||||
|
allowed-source-branches: ""
|
||||||
|
blocked-source-branches: ""
|
||||||
|
|
||||||
|
# PR Quality Checks
|
||||||
|
max-negative-reactions: 0
|
||||||
|
require-maintainer-can-modify: true
|
||||||
|
|
||||||
|
# PR Title Checks
|
||||||
|
require-conventional-title: true
|
||||||
|
|
||||||
|
# PR Description Checks
|
||||||
|
require-description: true
|
||||||
|
max-description-length: 2500
|
||||||
|
max-emoji-count: 0
|
||||||
|
max-code-references: 0
|
||||||
|
require-linked-issue: false
|
||||||
|
blocked-terms: ""
|
||||||
|
blocked-issue-numbers: ""
|
||||||
|
|
||||||
|
# PR Template Checks
|
||||||
|
require-pr-template: true
|
||||||
|
strict-pr-template-sections: ""
|
||||||
|
optional-pr-template-sections: "Issues"
|
||||||
|
max-additional-pr-template-sections: 3
|
||||||
|
|
||||||
|
# Commit Message Checks
|
||||||
|
max-commit-message-length: 500
|
||||||
|
require-conventional-commits: false
|
||||||
|
require-commit-author-match: true
|
||||||
|
blocked-commit-authors: ""
|
||||||
|
|
||||||
|
# File Checks
|
||||||
|
allowed-file-extensions: ""
|
||||||
|
allowed-paths: ""
|
||||||
|
blocked-paths: |
|
||||||
|
SECURITY.md
|
||||||
|
LICENSE
|
||||||
|
require-final-newline: false
|
||||||
|
max-added-comments: 0
|
||||||
|
|
||||||
|
# User Checks
|
||||||
|
detect-spam-usernames: true
|
||||||
|
min-account-age: 30
|
||||||
|
max-daily-forks: 7
|
||||||
|
min-profile-completeness: 4
|
||||||
|
|
||||||
|
# Merge Checks
|
||||||
|
min-repo-merged-prs: 0
|
||||||
|
min-repo-merge-ratio: 0
|
||||||
|
min-global-merge-ratio: 30
|
||||||
|
global-merge-ratio-exclude-own: false
|
||||||
|
|
||||||
|
# Exemptions
|
||||||
|
exempt-draft-prs: false
|
||||||
|
exempt-bots: |
|
||||||
|
actions-user
|
||||||
|
dependabot[bot]
|
||||||
|
renovate[bot]
|
||||||
|
github-actions[bot]
|
||||||
|
exempt-users: ""
|
||||||
|
exempt-author-association: "OWNER,MEMBER,COLLABORATOR"
|
||||||
|
exempt-label: "quality/exempt"
|
||||||
|
exempt-pr-label: ""
|
||||||
|
exempt-all-milestones: false
|
||||||
|
exempt-all-pr-milestones: false
|
||||||
|
exempt-milestones: ""
|
||||||
|
exempt-pr-milestones: ""
|
||||||
|
|
||||||
|
# PR Success Actions
|
||||||
|
success-add-pr-labels: "quality/verified"
|
||||||
|
|
||||||
|
# PR Failure Actions
|
||||||
|
failure-remove-pr-labels: ""
|
||||||
|
failure-remove-all-pr-labels: true
|
||||||
|
failure-add-pr-labels: "quality/rejected"
|
||||||
|
failure-pr-message: |
|
||||||
|
This PR did not pass quality checks so it will be closed.
|
||||||
|
See the [workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}) for details on which checks failed.
|
||||||
|
|
||||||
|
If you believe this is a mistake please let us know.
|
||||||
|
|
||||||
|
close-pr: true
|
||||||
|
lock-pr: false
|
||||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
|||||||
|
## v2.5.0
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- better error messages when there's another instance of Pocket ID running ([#1370](https://github.com/pocket-id/pocket-id/pull/1370) by @ItalyPaleAle)
|
||||||
|
- move tooltip inside of form input to prevent shifting ([#1369](https://github.com/pocket-id/pocket-id/pull/1369) by @GameTec-live)
|
||||||
|
- derive LDAP admin access from group membership ([#1374](https://github.com/pocket-id/pocket-id/pull/1374) by @kmendell)
|
||||||
|
- avoid fmt.Sprintf on custom GeoLiteDBUrl without %s placeholder ([#1384](https://github.com/pocket-id/pocket-id/pull/1384) by @choyri)
|
||||||
|
- show a warning when SQLite DB is stored on NFS/SMB/FUSE ([#1381](https://github.com/pocket-id/pocket-id/pull/1381) by @ItalyPaleAle)
|
||||||
|
- empty background restore after reboot ([#1379](https://github.com/pocket-id/pocket-id/pull/1379) by @taoso)
|
||||||
|
- allow one-char username on signup ([#1378](https://github.com/pocket-id/pocket-id/pull/1378) by @taoso)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- allow use of svg, png, and ico images types for favicon ([#1289](https://github.com/pocket-id/pocket-id/pull/1289) by @taoso)
|
||||||
|
- allow clearing background image ([#1290](https://github.com/pocket-id/pocket-id/pull/1290) by @taoso)
|
||||||
|
- add `token_endpoint_auth_methods_supported` to `.well-known` ([#1388](https://github.com/pocket-id/pocket-id/pull/1388) by @owenvoke)
|
||||||
|
- add TRUSTED_PLATFORM environment variable for gin ([#1372](https://github.com/pocket-id/pocket-id/pull/1372) by @choyri)
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- add pr quality action ([e3905cf](https://github.com/pocket-id/pocket-id/commit/e3905cf3159fe0370778b0d7d3be64b4246d19be) by @stonith404)
|
||||||
|
- separate querying LDAP and updating DB during sync ([#1371](https://github.com/pocket-id/pocket-id/pull/1371) by @ItalyPaleAle)
|
||||||
|
- bump google.golang.org/grpc from 1.79.1 to 1.79.3 in /backend in the go_modules group across 1 directory ([#1391](https://github.com/pocket-id/pocket-id/pull/1391) by @dependabot[bot])
|
||||||
|
- Improve Latvian translations in lv.json ([#1382](https://github.com/pocket-id/pocket-id/pull/1382) by @Raito00)
|
||||||
|
- ignore linter on app image bootstrap ([5251cd9](https://github.com/pocket-id/pocket-id/commit/5251cd97994177c96cb6f9ab3f88ca31367b5b55) by @kmendell)
|
||||||
|
- upgrade dependencies ([e7e0176](https://github.com/pocket-id/pocket-id/commit/e7e0176316857186b9683e2f0cb0686189f86cfb) by @kmendell)
|
||||||
|
- upgrade dependencies ([3c42a71](https://github.com/pocket-id/pocket-id/commit/3c42a713ce91b4061ffcf86d92cbb19294359ff8) by @kmendell)
|
||||||
|
|
||||||
|
**Full Changelog**: https://github.com/pocket-id/pocket-id/compare/v2.4.0...v2.5.0
|
||||||
|
|
||||||
## v2.4.0
|
## v2.4.0
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ require (
|
|||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||||
google.golang.org/grpc v1.79.1 // indirect
|
google.golang.org/grpc v1.79.3 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.68.0 // indirect
|
modernc.org/libc v1.68.0 // indirect
|
||||||
|
|||||||
@@ -470,8 +470,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:
|
|||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
@@ -20,6 +21,8 @@ import (
|
|||||||
|
|
||||||
// initApplicationImages copies the images from the embedded directory to the storage backend
|
// initApplicationImages copies the images from the embedded directory to the storage backend
|
||||||
// and returns a map containing the detected file extensions in the application-images directory.
|
// and returns a map containing the detected file extensions in the application-images directory.
|
||||||
|
//
|
||||||
|
//nolint:gocognit
|
||||||
func initApplicationImages(ctx context.Context, fileStorage storage.FileStorage) (map[string]string, error) {
|
func initApplicationImages(ctx context.Context, fileStorage storage.FileStorage) (map[string]string, error) {
|
||||||
// Previous versions of images
|
// Previous versions of images
|
||||||
// If these are found, they are deleted
|
// If these are found, they are deleted
|
||||||
@@ -76,6 +79,18 @@ func initApplicationImages(ctx context.Context, fileStorage storage.FileStorage)
|
|||||||
dstNameToExt[nameWithoutExt] = ext
|
dstNameToExt[nameWithoutExt] = ext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initedPath := path.Join("application-images", ".inited")
|
||||||
|
if _, _, err := fileStorage.Open(ctx, initedPath); err == nil {
|
||||||
|
return dstNameToExt, nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("failed to read .inited: %w", err)
|
||||||
|
} else {
|
||||||
|
err := fileStorage.Save(ctx, initedPath, strings.NewReader(""))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store .inited: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy images from the images directory to the application-images directory if they don't already exist
|
// Copy images from the images directory to the application-images directory if they don't already exist
|
||||||
for _, sourceFile := range sourceFiles {
|
for _, sourceFile := range sourceFiles {
|
||||||
if sourceFile.IsDir() {
|
if sourceFile.IsDir() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/job"
|
"github.com/pocket-id/pocket-id/backend/internal/job"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/service"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
)
|
)
|
||||||
@@ -60,7 +62,9 @@ func Bootstrap(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
waitUntil, err := svc.appLockService.Acquire(ctx, false)
|
waitUntil, err := svc.appLockService.Acquire(ctx, false)
|
||||||
if err != nil {
|
if errors.Is(err, service.ErrLockUnavailable) {
|
||||||
|
return errors.New("it appears that there's already one instance of Pocket ID running; running multiple replicas of Pocket ID is currently not supported")
|
||||||
|
} else if err != nil {
|
||||||
return fmt.Errorf("failed to acquire application lock: %w", err)
|
return fmt.Errorf("failed to acquire application lock: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ func NewDatabase() (db *gorm.DB, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run migrations
|
// Run migrations
|
||||||
if err := utils.MigrateDatabase(sqlDb); err != nil {
|
err = utils.MigrateDatabase(sqlDb)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,10 @@ func NewDatabase() (db *gorm.DB, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ConnectDatabase() (db *gorm.DB, err error) {
|
func ConnectDatabase() (db *gorm.DB, err error) {
|
||||||
var dialector gorm.Dialector
|
var (
|
||||||
|
dialector gorm.Dialector
|
||||||
|
sqliteNetworkFilesystem bool
|
||||||
|
)
|
||||||
|
|
||||||
// Choose the correct database provider
|
// Choose the correct database provider
|
||||||
var onConnFn func(conn *sql.DB)
|
var onConnFn func(conn *sql.DB)
|
||||||
@@ -63,6 +67,14 @@ func ConnectDatabase() (db *gorm.DB, err error) {
|
|||||||
if err := ensureSqliteDatabaseDir(dbPath); err != nil {
|
if err := ensureSqliteDatabaseDir(dbPath); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqliteNetworkFilesystem, err = utils.IsNetworkedFileSystem(filepath.Dir(dbPath))
|
||||||
|
if err != nil {
|
||||||
|
// Log the error only
|
||||||
|
slog.Warn("Failed to detect filesystem type for the SQLite database directory", slog.String("path", filepath.Dir(dbPath)), slog.Any("error", err))
|
||||||
|
} else if sqliteNetworkFilesystem {
|
||||||
|
slog.Warn("⚠️⚠️⚠️ SQLite databases should not be stored on a networked file system like NFS, SMB, or FUSE, as there's a risk of crashes and even database corruption", slog.String("path", filepath.Dir(dbPath)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before we connect, also make sure that there's a temporary folder for SQLite to write its data
|
// Before we connect, also make sure that there's a temporary folder for SQLite to write its data
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ func initRouter(db *gorm.DB, svc *services) (utils.Service, error) {
|
|||||||
_ = r.SetTrustedProxies(nil)
|
_ = r.SetTrustedProxies(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if common.EnvConfig.TrustedPlatform != "" {
|
||||||
|
r.TrustedPlatform = common.EnvConfig.TrustedPlatform
|
||||||
|
}
|
||||||
|
|
||||||
if common.EnvConfig.TracingEnabled {
|
if common.EnvConfig.TracingEnabled {
|
||||||
r.Use(otelgin.Middleware(common.Name))
|
r.Use(otelgin.Middleware(common.Name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,11 +119,10 @@ func acquireImportLock(ctx context.Context, db *gorm.DB, force bool) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
waitUntil, err := appLockService.Acquire(opCtx, force)
|
waitUntil, err := appLockService.Acquire(opCtx, force)
|
||||||
if err != nil {
|
if errors.Is(err, service.ErrLockUnavailable) {
|
||||||
if errors.Is(err, service.ErrLockUnavailable) {
|
//nolint:staticcheck
|
||||||
//nolint:staticcheck
|
return errors.New("Pocket ID must be stopped before importing data; please stop the running instance or run with --forcefully-acquire-lock to terminate the other instance")
|
||||||
return errors.New("Pocket ID must be stopped before importing data; please stop the running instance or run with --forcefully-acquire-lock to terminate the other instance")
|
} else if err != nil {
|
||||||
}
|
|
||||||
return fmt.Errorf("failed to acquire application lock: %w", err)
|
return fmt.Errorf("failed to acquire application lock: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type EnvConfigSchema struct {
|
|||||||
DbProvider DbProvider
|
DbProvider DbProvider
|
||||||
DbConnectionString string `env:"DB_CONNECTION_STRING" options:"file"`
|
DbConnectionString string `env:"DB_CONNECTION_STRING" options:"file"`
|
||||||
TrustProxy bool `env:"TRUST_PROXY"`
|
TrustProxy bool `env:"TRUST_PROXY"`
|
||||||
|
TrustedPlatform string `env:"TRUSTED_PLATFORM"`
|
||||||
AuditLogRetentionDays int `env:"AUDIT_LOG_RETENTION_DAYS"`
|
AuditLogRetentionDays int `env:"AUDIT_LOG_RETENTION_DAYS"`
|
||||||
AnalyticsDisabled bool `env:"ANALYTICS_DISABLED"`
|
AnalyticsDisabled bool `env:"ANALYTICS_DISABLED"`
|
||||||
AllowDowngrade bool `env:"ALLOW_DOWNGRADE"`
|
AllowDowngrade bool `env:"ALLOW_DOWNGRADE"`
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -34,6 +36,7 @@ func NewAppImagesController(
|
|||||||
group.PUT("/application-images/favicon", authMiddleware.Add(), controller.updateFaviconHandler)
|
group.PUT("/application-images/favicon", authMiddleware.Add(), controller.updateFaviconHandler)
|
||||||
group.PUT("/application-images/default-profile-picture", authMiddleware.Add(), controller.updateDefaultProfilePicture)
|
group.PUT("/application-images/default-profile-picture", authMiddleware.Add(), controller.updateDefaultProfilePicture)
|
||||||
|
|
||||||
|
group.DELETE("/application-images/background", authMiddleware.Add(), controller.deleteBackgroundImageHandler)
|
||||||
group.DELETE("/application-images/default-profile-picture", authMiddleware.Add(), controller.deleteDefaultProfilePicture)
|
group.DELETE("/application-images/default-profile-picture", authMiddleware.Add(), controller.deleteDefaultProfilePicture)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +195,27 @@ func (c *AppImagesController) updateBackgroundImageHandler(ctx *gin.Context) {
|
|||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteBackgroundImageHandler godoc
|
||||||
|
// @Summary Delete background image
|
||||||
|
// @Description Delete the application background image
|
||||||
|
// @Tags Application Images
|
||||||
|
// @Success 204 "No Content"
|
||||||
|
// @Router /api/application-images/background [delete]
|
||||||
|
func (c *AppImagesController) deleteBackgroundImageHandler(ctx *gin.Context) {
|
||||||
|
if err := c.appImagesService.DeleteImage(ctx.Request.Context(), "background"); err != nil {
|
||||||
|
_ = ctx.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// updateFaviconHandler godoc
|
// updateFaviconHandler godoc
|
||||||
// @Summary Update favicon
|
// @Summary Update favicon
|
||||||
// @Description Update the application favicon
|
// @Description Update the application favicon
|
||||||
// @Tags Application Images
|
// @Tags Application Images
|
||||||
// @Accept multipart/form-data
|
// @Accept multipart/form-data
|
||||||
// @Param file formData file true "Favicon file (.ico)"
|
// @Param file formData file true "Favicon file (.svg/.png/.ico)"
|
||||||
// @Success 204 "No Content"
|
// @Success 204 "No Content"
|
||||||
// @Router /api/application-images/favicon [put]
|
// @Router /api/application-images/favicon [put]
|
||||||
func (c *AppImagesController) updateFaviconHandler(ctx *gin.Context) {
|
func (c *AppImagesController) updateFaviconHandler(ctx *gin.Context) {
|
||||||
@@ -208,8 +226,9 @@ func (c *AppImagesController) updateFaviconHandler(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileType := utils.GetFileExtension(file.Filename)
|
fileType := utils.GetFileExtension(file.Filename)
|
||||||
if fileType != "ico" {
|
mimeType := utils.GetImageMimeType(strings.ToLower(fileType))
|
||||||
_ = ctx.Error(&common.WrongFileTypeError{ExpectedFileType: ".ico"})
|
if !slices.Contains([]string{"image/svg+xml", "image/png", "image/x-icon"}, mimeType) {
|
||||||
|
_ = ctx.Error(&common.WrongFileTypeError{ExpectedFileType: ".svg or .png or .ico"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ func (wkc *WellKnownController) computeOIDCConfiguration() ([]byte, error) {
|
|||||||
"id_token_signing_alg_values_supported": []string{alg.String()},
|
"id_token_signing_alg_values_supported": []string{alg.String()},
|
||||||
"authorization_response_iss_parameter_supported": true,
|
"authorization_response_iss_parameter_supported": true,
|
||||||
"code_challenge_methods_supported": []string{"plain", "S256"},
|
"code_challenge_methods_supported": []string{"plain", "S256"},
|
||||||
|
"token_endpoint_auth_methods_supported": []string{"client_secret_basic", "client_secret_post", "none"},
|
||||||
}
|
}
|
||||||
return json.Marshal(config)
|
return json.Marshal(config)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dto
|
package dto
|
||||||
|
|
||||||
type SignUpDto struct {
|
type SignUpDto struct {
|
||||||
Username string `json:"username" binding:"required,username,min=2,max=50" unorm:"nfc"`
|
Username string `json:"username" binding:"required,username,min=1,max=50" unorm:"nfc"`
|
||||||
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
|
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
|
||||||
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
|
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
|
||||||
LastName string `json:"lastName" binding:"max=50" unorm:"nfc"`
|
LastName string `json:"lastName" binding:"max=50" unorm:"nfc"`
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type UserDto struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserCreateDto struct {
|
type UserCreateDto struct {
|
||||||
Username string `json:"username" binding:"required,username,min=2,max=50" unorm:"nfc"`
|
Username string `json:"username" binding:"required,username,min=1,max=50" unorm:"nfc"`
|
||||||
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
|
Email *string `json:"email" binding:"omitempty,email" unorm:"nfc"`
|
||||||
EmailVerified bool `json:"emailVerified"`
|
EmailVerified bool `json:"emailVerified"`
|
||||||
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
|
FirstName string `json:"firstName" binding:"max=50" unorm:"nfc"`
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import (
|
|||||||
// [a-zA-Z0-9] : The username must start with an alphanumeric character
|
// [a-zA-Z0-9] : The username must start with an alphanumeric character
|
||||||
// [a-zA-Z0-9_.@-]* : The rest of the username can contain alphanumeric characters, dots, underscores, hyphens, and "@" symbols
|
// [a-zA-Z0-9_.@-]* : The rest of the username can contain alphanumeric characters, dots, underscores, hyphens, and "@" symbols
|
||||||
// [a-zA-Z0-9]$ : The username must end with an alphanumeric character
|
// [a-zA-Z0-9]$ : The username must end with an alphanumeric character
|
||||||
var validateUsernameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.@-]*[a-zA-Z0-9]$")
|
// (...)? : This allows single-character usernames (just one alphanumeric character)
|
||||||
|
var validateUsernameRegex = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9_.@-]*[a-zA-Z0-9])?$")
|
||||||
|
|
||||||
var validateClientIDRegex = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
|
var validateClientIDRegex = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ func TestValidateUsername(t *testing.T) {
|
|||||||
{"starts with symbol", ".username", false},
|
{"starts with symbol", ".username", false},
|
||||||
{"ends with non-alphanumeric", "username-", false},
|
{"ends with non-alphanumeric", "username-", false},
|
||||||
{"contains space", "user name", false},
|
{"contains space", "user name", false},
|
||||||
|
{"valid single char", "a", true},
|
||||||
{"empty", "", false},
|
{"empty", "", false},
|
||||||
{"only special chars", "-._@", false},
|
{"only special chars", "-._@", false},
|
||||||
{"valid long", "a1234567890_b.c-d@e", true},
|
{"valid long", "a1234567890_b.c-d@e", true},
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ func (s *AppLockService) Acquire(ctx context.Context, force bool) (waitUntil tim
|
|||||||
|
|
||||||
var prevLock lockValue
|
var prevLock lockValue
|
||||||
if prevLockRaw != "" {
|
if prevLockRaw != "" {
|
||||||
if err := prevLock.Unmarshal(prevLockRaw); err != nil {
|
err = prevLock.Unmarshal(prevLockRaw)
|
||||||
|
if err != nil {
|
||||||
return time.Time{}, fmt.Errorf("decode existing lock value: %w", err)
|
return time.Time{}, fmt.Errorf("decode existing lock value: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +143,8 @@ func (s *AppLockService) Acquire(ctx context.Context, force bool) (waitUntil tim
|
|||||||
return time.Time{}, fmt.Errorf("lock acquisition failed: %w", res.Error)
|
return time.Time{}, fmt.Errorf("lock acquisition failed: %w", res.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit().Error; err != nil {
|
err = tx.Commit().Error
|
||||||
|
if err != nil {
|
||||||
return time.Time{}, fmt.Errorf("commit lock acquisition: %w", err)
|
return time.Time{}, fmt.Errorf("commit lock acquisition: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -112,7 +113,11 @@ func (s *GeoLiteService) UpdateDatabase(parentCtx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Updating GeoLite2 City database")
|
slog.Info("Updating GeoLite2 City database")
|
||||||
downloadUrl := fmt.Sprintf(common.EnvConfig.GeoLiteDBUrl, common.EnvConfig.MaxMindLicenseKey)
|
|
||||||
|
downloadUrl := common.EnvConfig.GeoLiteDBUrl
|
||||||
|
if strings.Contains(downloadUrl, "%s") {
|
||||||
|
downloadUrl = fmt.Sprintf(downloadUrl, common.EnvConfig.MaxMindLicenseKey)
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Minute)
|
ctx, cancel := context.WithTimeout(parentCtx, 10*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type LdapService struct {
|
|||||||
userService *UserService
|
userService *UserService
|
||||||
groupService *UserGroupService
|
groupService *UserGroupService
|
||||||
fileStorage storage.FileStorage
|
fileStorage storage.FileStorage
|
||||||
|
clientFactory func() (ldapClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type savePicture struct {
|
type savePicture struct {
|
||||||
@@ -43,8 +44,33 @@ type savePicture struct {
|
|||||||
picture string
|
picture string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ldapDesiredUser struct {
|
||||||
|
ldapID string
|
||||||
|
input dto.UserCreateDto
|
||||||
|
picture string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ldapDesiredGroup struct {
|
||||||
|
ldapID string
|
||||||
|
input dto.UserGroupCreateDto
|
||||||
|
memberUsernames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ldapDesiredState struct {
|
||||||
|
users []ldapDesiredUser
|
||||||
|
userIDs map[string]struct{}
|
||||||
|
groups []ldapDesiredGroup
|
||||||
|
groupIDs map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ldapClient interface {
|
||||||
|
Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error)
|
||||||
|
Bind(username, password string) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
func NewLdapService(db *gorm.DB, httpClient *http.Client, appConfigService *AppConfigService, userService *UserService, groupService *UserGroupService, fileStorage storage.FileStorage) *LdapService {
|
func NewLdapService(db *gorm.DB, httpClient *http.Client, appConfigService *AppConfigService, userService *UserService, groupService *UserGroupService, fileStorage storage.FileStorage) *LdapService {
|
||||||
return &LdapService{
|
service := &LdapService{
|
||||||
db: db,
|
db: db,
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
appConfigService: appConfigService,
|
appConfigService: appConfigService,
|
||||||
@@ -52,9 +78,12 @@ func NewLdapService(db *gorm.DB, httpClient *http.Client, appConfigService *AppC
|
|||||||
groupService: groupService,
|
groupService: groupService,
|
||||||
fileStorage: fileStorage,
|
fileStorage: fileStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service.clientFactory = service.createClient
|
||||||
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LdapService) createClient() (*ldap.Conn, error) {
|
func (s *LdapService) createClient() (ldapClient, error) {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
|
||||||
if !dbConfig.LdapEnabled.IsTrue() {
|
if !dbConfig.LdapEnabled.IsTrue() {
|
||||||
@@ -79,24 +108,33 @@ func (s *LdapService) createClient() (*ldap.Conn, error) {
|
|||||||
|
|
||||||
func (s *LdapService) SyncAll(ctx context.Context) error {
|
func (s *LdapService) SyncAll(ctx context.Context) error {
|
||||||
// Setup LDAP connection
|
// Setup LDAP connection
|
||||||
client, err := s.createClient()
|
client, err := s.clientFactory()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create LDAP client: %w", err)
|
return fmt.Errorf("failed to create LDAP client: %w", err)
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// Start a transaction
|
// First, we fetch all users and group from LDAP, which is our "desired state"
|
||||||
tx := s.db.Begin()
|
desiredState, err := s.fetchDesiredState(ctx, client)
|
||||||
defer func() {
|
if err != nil {
|
||||||
tx.Rollback()
|
return fmt.Errorf("failed to fetch LDAP state: %w", err)
|
||||||
}()
|
}
|
||||||
|
|
||||||
savePictures, deleteFiles, err := s.SyncUsers(ctx, tx, client)
|
// Start a transaction
|
||||||
|
tx := s.db.WithContext(ctx).Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return fmt.Errorf("failed to begin database transaction: %w", tx.Error)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Reconcile users
|
||||||
|
savePictures, deleteFiles, err := s.reconcileUsers(ctx, tx, desiredState.users, desiredState.userIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to sync users: %w", err)
|
return fmt.Errorf("failed to sync users: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.SyncGroups(ctx, tx, client)
|
// Reconcile groups
|
||||||
|
err = s.reconcileGroups(ctx, tx, desiredState.groups, desiredState.groupIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to sync groups: %w", err)
|
return fmt.Errorf("failed to sync groups: %w", err)
|
||||||
}
|
}
|
||||||
@@ -129,10 +167,59 @@ func (s *LdapService) SyncAll(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit
|
func (s *LdapService) fetchDesiredState(ctx context.Context, client ldapClient) (ldapDesiredState, error) {
|
||||||
func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB, client *ldap.Conn) error {
|
// Fetch users first so we can use their DNs when resolving group members
|
||||||
|
users, userIDs, usernamesByDN, err := s.fetchUsersFromLDAP(ctx, client)
|
||||||
|
if err != nil {
|
||||||
|
return ldapDesiredState{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then fetch groups to complete the desired LDAP state snapshot
|
||||||
|
groups, groupIDs, err := s.fetchGroupsFromLDAP(ctx, client, usernamesByDN)
|
||||||
|
if err != nil {
|
||||||
|
return ldapDesiredState{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply user admin flags from the desired group membership snapshot.
|
||||||
|
// This intentionally uses the configured group member attribute rather than
|
||||||
|
// relying on a user-side reverse-membership attribute such as memberOf.
|
||||||
|
s.applyAdminGroupMembership(users, groups)
|
||||||
|
|
||||||
|
return ldapDesiredState{
|
||||||
|
users: users,
|
||||||
|
userIDs: userIDs,
|
||||||
|
groups: groups,
|
||||||
|
groupIDs: groupIDs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) applyAdminGroupMembership(desiredUsers []ldapDesiredUser, desiredGroups []ldapDesiredGroup) {
|
||||||
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
if dbConfig.LdapAdminGroupName.Value == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
adminUsernames := make(map[string]struct{})
|
||||||
|
for _, group := range desiredGroups {
|
||||||
|
if group.input.Name != dbConfig.LdapAdminGroupName.Value {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, username := range group.memberUsernames {
|
||||||
|
adminUsernames[username] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range desiredUsers {
|
||||||
|
_, isAdmin := adminUsernames[desiredUsers[i].input.Username]
|
||||||
|
desiredUsers[i].input.IsAdmin = desiredUsers[i].input.IsAdmin || isAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) fetchGroupsFromLDAP(ctx context.Context, client ldapClient, usernamesByDN map[string]string) (desiredGroups []ldapDesiredGroup, ldapGroupIDs map[string]struct{}, err error) {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
|
||||||
|
// Query LDAP for all groups we want to manage
|
||||||
searchAttrs := []string{
|
searchAttrs := []string{
|
||||||
dbConfig.LdapAttributeGroupName.Value,
|
dbConfig.LdapAttributeGroupName.Value,
|
||||||
dbConfig.LdapAttributeGroupUniqueIdentifier.Value,
|
dbConfig.LdapAttributeGroupUniqueIdentifier.Value,
|
||||||
@@ -149,90 +236,42 @@ func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB, client *ldap.
|
|||||||
)
|
)
|
||||||
result, err := client.Search(searchReq)
|
result, err := client.Search(searchReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to query LDAP: %w", err)
|
return nil, nil, fmt.Errorf("failed to query LDAP groups: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a mapping for groups that exist
|
// Build the in-memory desired state for groups
|
||||||
ldapGroupIDs := make(map[string]struct{}, len(result.Entries))
|
ldapGroupIDs = make(map[string]struct{}, len(result.Entries))
|
||||||
|
desiredGroups = make([]ldapDesiredGroup, 0, len(result.Entries))
|
||||||
|
|
||||||
for _, value := range result.Entries {
|
for _, value := range result.Entries {
|
||||||
ldapId := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeGroupUniqueIdentifier.Value))
|
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeGroupUniqueIdentifier.Value))
|
||||||
|
|
||||||
// Skip groups without a valid LDAP ID
|
// Skip groups without a valid LDAP ID
|
||||||
if ldapId == "" {
|
if ldapID == "" {
|
||||||
slog.Warn("Skipping LDAP group without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeGroupUniqueIdentifier.Value))
|
slog.Warn("Skipping LDAP group without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeGroupUniqueIdentifier.Value))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapGroupIDs[ldapId] = struct{}{}
|
ldapGroupIDs[ldapID] = struct{}{}
|
||||||
|
|
||||||
// Try to find the group in the database
|
|
||||||
var databaseGroup model.UserGroup
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Where("ldap_id = ?", ldapId).
|
|
||||||
First(&databaseGroup).
|
|
||||||
Error
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
// This could error with ErrRecordNotFound and we want to ignore that here
|
|
||||||
return fmt.Errorf("failed to query for LDAP group ID '%s': %w", ldapId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get group members and add to the correct Group
|
// Get group members and add to the correct Group
|
||||||
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
||||||
membersUserId := make([]string, 0, len(groupMembers))
|
memberUsernames := make([]string, 0, len(groupMembers))
|
||||||
for _, member := range groupMembers {
|
for _, member := range groupMembers {
|
||||||
username := getDNProperty(dbConfig.LdapAttributeUserUsername.Value, member)
|
username := s.resolveGroupMemberUsername(ctx, client, member, usernamesByDN)
|
||||||
|
|
||||||
// If username extraction fails, try to query LDAP directly for the user
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
// Query LDAP to get the user by their DN
|
|
||||||
userSearchReq := ldap.NewSearchRequest(
|
|
||||||
member,
|
|
||||||
ldap.ScopeBaseObject,
|
|
||||||
0, 0, 0, false,
|
|
||||||
"(objectClass=*)",
|
|
||||||
[]string{dbConfig.LdapAttributeUserUsername.Value, dbConfig.LdapAttributeUserUniqueIdentifier.Value},
|
|
||||||
[]ldap.Control{},
|
|
||||||
)
|
|
||||||
|
|
||||||
userResult, err := client.Search(userSearchReq)
|
|
||||||
if err != nil || len(userResult.Entries) == 0 {
|
|
||||||
slog.WarnContext(ctx, "Could not resolve group member DN", slog.String("member", member), slog.Any("error", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
username = userResult.Entries[0].GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value)
|
|
||||||
if username == "" {
|
|
||||||
slog.WarnContext(ctx, "Could not extract username from group member DN", slog.String("member", member))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
username = norm.NFC.String(username)
|
|
||||||
|
|
||||||
var databaseUser model.User
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Where("username = ? AND ldap_id IS NOT NULL", username).
|
|
||||||
First(&databaseUser).
|
|
||||||
Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
// The user collides with a non-LDAP user, so we skip it
|
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
|
||||||
return fmt.Errorf("failed to query for existing user '%s': %w", username, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
membersUserId = append(membersUserId, databaseUser.ID)
|
memberUsernames = append(memberUsernames, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
syncGroup := dto.UserGroupCreateDto{
|
syncGroup := dto.UserGroupCreateDto{
|
||||||
Name: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
Name: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
||||||
FriendlyName: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
FriendlyName: value.GetAttributeValue(dbConfig.LdapAttributeGroupName.Value),
|
||||||
LdapID: ldapId,
|
LdapID: ldapID,
|
||||||
}
|
}
|
||||||
dto.Normalize(syncGroup)
|
dto.Normalize(&syncGroup)
|
||||||
|
|
||||||
err = syncGroup.Validate()
|
err = syncGroup.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,66 +279,21 @@ func (s *LdapService) SyncGroups(ctx context.Context, tx *gorm.DB, client *ldap.
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if databaseGroup.ID == "" {
|
desiredGroups = append(desiredGroups, ldapDesiredGroup{
|
||||||
newGroup, err := s.groupService.createInternal(ctx, syncGroup, tx)
|
ldapID: ldapID,
|
||||||
if err != nil {
|
input: syncGroup,
|
||||||
return fmt.Errorf("failed to create group '%s': %w", syncGroup.Name, err)
|
memberUsernames: memberUsernames,
|
||||||
}
|
})
|
||||||
|
|
||||||
_, err = s.groupService.updateUsersInternal(ctx, newGroup.ID, membersUserId, tx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to sync users for group '%s': %w", syncGroup.Name, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = s.groupService.updateInternal(ctx, databaseGroup.ID, syncGroup, true, tx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update group '%s': %w", syncGroup.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.groupService.updateUsersInternal(ctx, databaseGroup.ID, membersUserId, tx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to sync users for group '%s': %w", syncGroup.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all LDAP groups from the database
|
return desiredGroups, ldapGroupIDs, nil
|
||||||
var ldapGroupsInDb []model.UserGroup
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Find(&ldapGroupsInDb, "ldap_id IS NOT NULL").
|
|
||||||
Select("ldap_id").
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to fetch groups from database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete groups that no longer exist in LDAP
|
|
||||||
for _, group := range ldapGroupsInDb {
|
|
||||||
if _, exists := ldapGroupIDs[*group.LdapID]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Delete(&model.UserGroup{}, "ldap_id = ?", group.LdapID).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete group '%s': %w", group.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Deleted group", slog.String("group", group.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit
|
func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient) (desiredUsers []ldapDesiredUser, ldapUserIDs map[string]struct{}, usernamesByDN map[string]string, err error) {
|
||||||
func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.Conn) (savePictures []savePicture, deleteFiles []string, err error) {
|
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
|
||||||
|
// Query LDAP for all users we want to manage
|
||||||
searchAttrs := []string{
|
searchAttrs := []string{
|
||||||
"memberOf",
|
|
||||||
"sn",
|
"sn",
|
||||||
"cn",
|
"cn",
|
||||||
dbConfig.LdapAttributeUserUniqueIdentifier.Value,
|
dbConfig.LdapAttributeUserUniqueIdentifier.Value,
|
||||||
@@ -323,59 +317,29 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
|||||||
|
|
||||||
result, err := client.Search(searchReq)
|
result, err := client.Search(searchReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to query LDAP: %w", err)
|
return nil, nil, nil, fmt.Errorf("failed to query LDAP users: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a mapping for users that exist
|
// Build the in-memory desired state for users and a DN lookup for group membership resolution
|
||||||
ldapUserIDs := make(map[string]struct{}, len(result.Entries))
|
ldapUserIDs = make(map[string]struct{}, len(result.Entries))
|
||||||
savePictures = make([]savePicture, 0, len(result.Entries))
|
usernamesByDN = make(map[string]string, len(result.Entries))
|
||||||
|
desiredUsers = make([]ldapDesiredUser, 0, len(result.Entries))
|
||||||
|
|
||||||
for _, value := range result.Entries {
|
for _, value := range result.Entries {
|
||||||
ldapId := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
username := norm.NFC.String(value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value))
|
||||||
|
if normalizedDN := normalizeLDAPDN(value.DN); normalizedDN != "" && username != "" {
|
||||||
|
usernamesByDN[normalizedDN] = username
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
||||||
|
|
||||||
// Skip users without a valid LDAP ID
|
// Skip users without a valid LDAP ID
|
||||||
if ldapId == "" {
|
if ldapID == "" {
|
||||||
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapUserIDs[ldapId] = struct{}{}
|
ldapUserIDs[ldapID] = struct{}{}
|
||||||
|
|
||||||
// Get the user from the database
|
|
||||||
var databaseUser model.User
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Where("ldap_id = ?", ldapId).
|
|
||||||
First(&databaseUser).
|
|
||||||
Error
|
|
||||||
|
|
||||||
// If a user is found (even if disabled), enable them since they're now back in LDAP
|
|
||||||
if databaseUser.ID != "" && databaseUser.Disabled {
|
|
||||||
err = tx.
|
|
||||||
WithContext(ctx).
|
|
||||||
Model(&model.User{}).
|
|
||||||
Where("id = ?", databaseUser.ID).
|
|
||||||
Update("disabled", false).
|
|
||||||
Error
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("failed to enable user %s: %w", databaseUser.Username, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
// This could error with ErrRecordNotFound and we want to ignore that here
|
|
||||||
return nil, nil, fmt.Errorf("failed to query for LDAP user ID '%s': %w", ldapId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user is admin by checking if they are in the admin group
|
|
||||||
isAdmin := false
|
|
||||||
for _, group := range value.GetAttributeValues("memberOf") {
|
|
||||||
if getDNProperty(dbConfig.LdapAttributeGroupName.Value, group) == dbConfig.LdapAdminGroupName.Value {
|
|
||||||
isAdmin = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newUser := dto.UserCreateDto{
|
newUser := dto.UserCreateDto{
|
||||||
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value),
|
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value),
|
||||||
@@ -384,15 +348,17 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
|||||||
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.Value),
|
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.Value),
|
||||||
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.Value),
|
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.Value),
|
||||||
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.Value),
|
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.Value),
|
||||||
IsAdmin: isAdmin,
|
// Admin status is computed after groups are loaded so it can use the
|
||||||
LdapID: ldapId,
|
// configured group member attribute instead of a hard-coded memberOf.
|
||||||
|
IsAdmin: false,
|
||||||
|
LdapID: ldapID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUser.DisplayName == "" {
|
if newUser.DisplayName == "" {
|
||||||
newUser.DisplayName = strings.TrimSpace(newUser.FirstName + " " + newUser.LastName)
|
newUser.DisplayName = strings.TrimSpace(newUser.FirstName + " " + newUser.LastName)
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.Normalize(newUser)
|
dto.Normalize(&newUser)
|
||||||
|
|
||||||
err = newUser.Validate()
|
err = newUser.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -400,53 +366,201 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := databaseUser.ID
|
desiredUsers = append(desiredUsers, ldapDesiredUser{
|
||||||
if databaseUser.ID == "" {
|
ldapID: ldapID,
|
||||||
createdUser, err := s.userService.createUserInternal(ctx, newUser, true, tx)
|
input: newUser,
|
||||||
if errors.Is(err, &common.AlreadyInUseError{}) {
|
picture: value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.Value),
|
||||||
slog.Warn("Skipping creating LDAP user", slog.String("username", newUser.Username), slog.Any("error", err))
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return desiredUsers, ldapUserIDs, usernamesByDN, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client ldapClient, member string, usernamesByDN map[string]string) string {
|
||||||
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
|
||||||
|
// First try the DN cache we built while loading users
|
||||||
|
username, exists := usernamesByDN[normalizeLDAPDN(member)]
|
||||||
|
if exists && username != "" {
|
||||||
|
return username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try to extract the username directly from the DN
|
||||||
|
username = getDNProperty(dbConfig.LdapAttributeUserUsername.Value, member)
|
||||||
|
if username != "" {
|
||||||
|
return norm.NFC.String(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As a fallback, query LDAP for the referenced entry
|
||||||
|
userSearchReq := ldap.NewSearchRequest(
|
||||||
|
member,
|
||||||
|
ldap.ScopeBaseObject,
|
||||||
|
0, 0, 0, false,
|
||||||
|
"(objectClass=*)",
|
||||||
|
[]string{dbConfig.LdapAttributeUserUsername.Value},
|
||||||
|
[]ldap.Control{},
|
||||||
|
)
|
||||||
|
|
||||||
|
userResult, err := client.Search(userSearchReq)
|
||||||
|
if err != nil || len(userResult.Entries) == 0 {
|
||||||
|
slog.WarnContext(ctx, "Could not resolve group member DN", slog.String("member", member), slog.Any("error", err))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
username = userResult.Entries[0].GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value)
|
||||||
|
if username == "" {
|
||||||
|
slog.WarnContext(ctx, "Could not extract username from group member DN", slog.String("member", member))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return norm.NFC.String(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) reconcileGroups(ctx context.Context, tx *gorm.DB, desiredGroups []ldapDesiredGroup, ldapGroupIDs map[string]struct{}) error {
|
||||||
|
// Load the current LDAP-managed state from the database
|
||||||
|
ldapGroupsInDB, ldapGroupsByID, err := s.loadLDAPGroupsInDB(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch groups from database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ldapUsersByUsername, err := s.loadLDAPUsersInDB(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch users from database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply creates and updates to match the desired LDAP group state
|
||||||
|
for _, desiredGroup := range desiredGroups {
|
||||||
|
memberUserIDs := make([]string, 0, len(desiredGroup.memberUsernames))
|
||||||
|
for _, username := range desiredGroup.memberUsernames {
|
||||||
|
databaseUser, exists := ldapUsersByUsername[username]
|
||||||
|
if !exists {
|
||||||
|
// The user collides with a non-LDAP user or was skipped during user sync, so we ignore it
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("error creating user '%s': %w", newUser.Username, err)
|
|
||||||
}
|
|
||||||
userID = createdUser.ID
|
|
||||||
} else {
|
|
||||||
_, err = s.userService.updateUserInternal(ctx, databaseUser.ID, newUser, false, true, tx)
|
|
||||||
if errors.Is(err, &common.AlreadyInUseError{}) {
|
|
||||||
slog.Warn("Skipping updating LDAP user", slog.String("username", newUser.Username), slog.Any("error", err))
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("error updating user '%s': %w", newUser.Username, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memberUserIDs = append(memberUserIDs, databaseUser.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save profile picture
|
databaseGroup := ldapGroupsByID[desiredGroup.ldapID]
|
||||||
pictureString := value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.Value)
|
if databaseGroup.ID == "" {
|
||||||
if pictureString != "" {
|
newGroup, err := s.groupService.createInternal(ctx, desiredGroup.input, tx)
|
||||||
// Storage operations must be executed outside of a transaction
|
if err != nil {
|
||||||
savePictures = append(savePictures, savePicture{
|
return fmt.Errorf("failed to create group '%s': %w", desiredGroup.input.Name, err)
|
||||||
userID: databaseUser.ID,
|
}
|
||||||
username: userID,
|
ldapGroupsByID[desiredGroup.ldapID] = newGroup
|
||||||
picture: pictureString,
|
|
||||||
})
|
_, err = s.groupService.updateUsersInternal(ctx, newGroup.ID, memberUserIDs, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to sync users for group '%s': %w", desiredGroup.input.Name, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.groupService.updateInternal(ctx, databaseGroup.ID, desiredGroup.input, true, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update group '%s': %w", desiredGroup.input.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.groupService.updateUsersInternal(ctx, databaseGroup.ID, memberUserIDs, tx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to sync users for group '%s': %w", desiredGroup.input.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all LDAP users from the database
|
// Delete groups that are no longer present in LDAP
|
||||||
var ldapUsersInDb []model.User
|
for _, group := range ldapGroupsInDB {
|
||||||
err = tx.
|
if group.LdapID == nil {
|
||||||
WithContext(ctx).
|
continue
|
||||||
Find(&ldapUsersInDb, "ldap_id IS NOT NULL").
|
}
|
||||||
Select("id, username, ldap_id, disabled").
|
|
||||||
Error
|
if _, exists := ldapGroupIDs[*group.LdapID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Delete(&model.UserGroup{}, "ldap_id = ?", *group.LdapID).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete group '%s': %w", group.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Deleted group", slog.String("group", group.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocognit
|
||||||
|
func (s *LdapService) reconcileUsers(ctx context.Context, tx *gorm.DB, desiredUsers []ldapDesiredUser, ldapUserIDs map[string]struct{}) (savePictures []savePicture, deleteFiles []string, err error) {
|
||||||
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
|
|
||||||
|
// Load the current LDAP-managed state from the database
|
||||||
|
ldapUsersInDB, ldapUsersByID, _, err := s.loadLDAPUsersInDB(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to fetch users from database: %w", err)
|
return nil, nil, fmt.Errorf("failed to fetch users from database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark users as disabled or delete users that no longer exist in LDAP
|
// Apply creates and updates to match the desired LDAP user state
|
||||||
deleteFiles = make([]string, 0, len(ldapUserIDs))
|
savePictures = make([]savePicture, 0, len(desiredUsers))
|
||||||
for _, user := range ldapUsersInDb {
|
|
||||||
// Skip if the user ID exists in the fetched LDAP results
|
for _, desiredUser := range desiredUsers {
|
||||||
|
databaseUser := ldapUsersByID[desiredUser.ldapID]
|
||||||
|
|
||||||
|
// If a user is found (even if disabled), enable them since they're now back in LDAP.
|
||||||
|
if databaseUser.ID != "" && databaseUser.Disabled {
|
||||||
|
err = tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Model(&model.User{}).
|
||||||
|
Where("id = ?", databaseUser.ID).
|
||||||
|
Update("disabled", false).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to enable user %s: %w", databaseUser.Username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseUser.Disabled = false
|
||||||
|
ldapUsersByID[desiredUser.ldapID] = databaseUser
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := databaseUser.ID
|
||||||
|
if databaseUser.ID == "" {
|
||||||
|
createdUser, err := s.userService.createUserInternal(ctx, desiredUser.input, true, tx)
|
||||||
|
if errors.Is(err, &common.AlreadyInUseError{}) {
|
||||||
|
slog.Warn("Skipping creating LDAP user", slog.String("username", desiredUser.input.Username), slog.Any("error", err))
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error creating user '%s': %w", desiredUser.input.Username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID = createdUser.ID
|
||||||
|
ldapUsersByID[desiredUser.ldapID] = createdUser
|
||||||
|
} else {
|
||||||
|
_, err = s.userService.updateUserInternal(ctx, databaseUser.ID, desiredUser.input, false, true, tx)
|
||||||
|
if errors.Is(err, &common.AlreadyInUseError{}) {
|
||||||
|
slog.Warn("Skipping updating LDAP user", slog.String("username", desiredUser.input.Username), slog.Any("error", err))
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error updating user '%s': %w", desiredUser.input.Username, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if desiredUser.picture != "" {
|
||||||
|
savePictures = append(savePictures, savePicture{
|
||||||
|
userID: userID,
|
||||||
|
username: desiredUser.input.Username,
|
||||||
|
picture: desiredUser.picture,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable or delete users that are no longer present in LDAP
|
||||||
|
deleteFiles = make([]string, 0, len(ldapUsersInDB))
|
||||||
|
for _, user := range ldapUsersInDB {
|
||||||
|
if user.LdapID == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, exists := ldapUserIDs[*user.LdapID]; exists {
|
if _, exists := ldapUserIDs[*user.LdapID]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -458,29 +572,73 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Disabled user", slog.String("username", user.Username))
|
slog.Info("Disabled user", slog.String("username", user.Username))
|
||||||
} else {
|
continue
|
||||||
err = s.userService.deleteUserInternal(ctx, tx, user.ID, true)
|
|
||||||
if err != nil {
|
|
||||||
target := &common.LdapUserUpdateError{}
|
|
||||||
if errors.As(err, &target) {
|
|
||||||
return nil, nil, fmt.Errorf("failed to delete user %s: LDAP user must be disabled before deletion", user.Username)
|
|
||||||
}
|
|
||||||
return nil, nil, fmt.Errorf("failed to delete user %s: %w", user.Username, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("Deleted user", slog.String("username", user.Username))
|
|
||||||
|
|
||||||
// Storage operations must be executed outside of a transaction
|
|
||||||
deleteFiles = append(deleteFiles, path.Join("profile-pictures", user.ID+".png"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = s.userService.deleteUserInternal(ctx, tx, user.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
target := &common.LdapUserUpdateError{}
|
||||||
|
if errors.As(err, &target) {
|
||||||
|
return nil, nil, fmt.Errorf("failed to delete user %s: LDAP user must be disabled before deletion", user.Username)
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("failed to delete user %s: %w", user.Username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Deleted user", slog.String("username", user.Username))
|
||||||
|
deleteFiles = append(deleteFiles, path.Join("profile-pictures", user.ID+".png"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return savePictures, deleteFiles, nil
|
return savePictures, deleteFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) loadLDAPUsersInDB(ctx context.Context, tx *gorm.DB) (users []model.User, byLdapID map[string]model.User, byUsername map[string]model.User, err error) {
|
||||||
|
// Load all LDAP-managed users and index them by LDAP ID and by username
|
||||||
|
err = tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Select("id, username, ldap_id, disabled").
|
||||||
|
Where("ldap_id IS NOT NULL").
|
||||||
|
Find(&users).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
byLdapID = make(map[string]model.User, len(users))
|
||||||
|
byUsername = make(map[string]model.User, len(users))
|
||||||
|
for _, user := range users {
|
||||||
|
byLdapID[*user.LdapID] = user
|
||||||
|
byUsername[user.Username] = user
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, byLdapID, byUsername, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LdapService) loadLDAPGroupsInDB(ctx context.Context, tx *gorm.DB) ([]model.UserGroup, map[string]model.UserGroup, error) {
|
||||||
|
var groups []model.UserGroup
|
||||||
|
|
||||||
|
// Load all LDAP-managed groups and index them by LDAP ID
|
||||||
|
err := tx.
|
||||||
|
WithContext(ctx).
|
||||||
|
Select("id, name, ldap_id").
|
||||||
|
Where("ldap_id IS NOT NULL").
|
||||||
|
Find(&groups).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsByID := make(map[string]model.UserGroup, len(groups))
|
||||||
|
for _, group := range groups {
|
||||||
|
groupsByID[*group.LdapID] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, groupsByID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId string, pictureString string) error {
|
func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId string, pictureString string) error {
|
||||||
var reader io.ReadSeeker
|
var reader io.ReadSeeker
|
||||||
|
|
||||||
|
// Accept either a URL, a base64-encoded payload, or raw binary data
|
||||||
_, err := url.ParseRequestURI(pictureString)
|
_, err := url.ParseRequestURI(pictureString)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx, cancel := context.WithTimeout(parentCtx, 15*time.Second)
|
ctx, cancel := context.WithTimeout(parentCtx, 15*time.Second)
|
||||||
@@ -522,6 +680,31 @@ func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeLDAPDN returns a canonical lowercase form of a DN for use as a map key.
|
||||||
|
// Different LDAP servers may format the same DN with varying attribute type casing (e.g. "CN=" vs "cn=") or extra whitespace (e.g. "dc=example, dc=com").
|
||||||
|
// Without normalization, cache lookups in usernamesByDN would miss when a member attribute value uses a different format than the DN returned in the search entry
|
||||||
|
//
|
||||||
|
// ldap.ParseDN is used instead of simple lowercasing because it correctly handles multi-valued RDNs (joined with "+") and strips inter-component whitespace.
|
||||||
|
// If parsing fails for any reason, we fall back to a simple lowercase+trim.
|
||||||
|
func normalizeLDAPDN(dn string) string {
|
||||||
|
parsed, err := ldap.ParseDN(dn)
|
||||||
|
if err != nil {
|
||||||
|
return strings.ToLower(strings.TrimSpace(dn))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct the DN in a canonical form: lowercase type=lowercase value, with RDN components separated by "," and multi-value attributes by "+"
|
||||||
|
parts := make([]string, 0, len(parsed.RDNs))
|
||||||
|
for _, rdn := range parsed.RDNs {
|
||||||
|
attrs := make([]string, 0, len(rdn.Attributes))
|
||||||
|
for _, attr := range rdn.Attributes {
|
||||||
|
attrs = append(attrs, strings.ToLower(attr.Type)+"="+strings.ToLower(attr.Value))
|
||||||
|
}
|
||||||
|
parts = append(parts, strings.Join(attrs, "+"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// getDNProperty returns the value of a property from a LDAP identifier
|
// getDNProperty returns the value of a property from a LDAP identifier
|
||||||
// See: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names
|
// See: https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names
|
||||||
func getDNProperty(property string, str string) string {
|
func getDNProperty(property string, str string) string {
|
||||||
|
|||||||
@@ -1,9 +1,368 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
||||||
|
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type fakeLDAPClient struct {
|
||||||
|
searchFn func(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeLDAPClient) Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
|
if c.searchFn == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.searchFn(searchRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeLDAPClient) Bind(_, _ string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeLDAPClient) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLdapServiceSyncAllReconcilesUsersAndGroups(t *testing.T) {
|
||||||
|
service, db := newTestLdapService(t, newFakeLDAPClient(
|
||||||
|
ldapSearchResult(
|
||||||
|
ldapEntry("uid=alice,ou=people,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"u-alice"},
|
||||||
|
"uid": {"alice"},
|
||||||
|
"mail": {"alice@example.com"},
|
||||||
|
"givenName": {"Alice"},
|
||||||
|
"sn": {"Jones"},
|
||||||
|
"displayName": {""},
|
||||||
|
}),
|
||||||
|
ldapEntry("uid=bob,ou=people,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"u-bob"},
|
||||||
|
"uid": {"bob"},
|
||||||
|
"mail": {"bob@example.com"},
|
||||||
|
"givenName": {"Bob"},
|
||||||
|
"sn": {"Brown"},
|
||||||
|
"displayName": {""},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ldapSearchResult(
|
||||||
|
ldapEntry("cn=admins,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-admins"},
|
||||||
|
"cn": {"admins"},
|
||||||
|
"member": {"uid=alice,ou=people,dc=example,dc=com"},
|
||||||
|
}),
|
||||||
|
ldapEntry("cn=team,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-team"},
|
||||||
|
"cn": {"team"},
|
||||||
|
"member": {
|
||||||
|
"UID=Alice, OU=People, DC=example, DC=com",
|
||||||
|
"uid=bob, ou=people, dc=example, dc=com",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
aliceLdapID := "u-alice"
|
||||||
|
missingLdapID := "u-missing"
|
||||||
|
teamLdapID := "g-team"
|
||||||
|
oldGroupLdapID := "g-old"
|
||||||
|
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Username: "alice-old",
|
||||||
|
Email: new("alice-old@example.com"),
|
||||||
|
EmailVerified: true,
|
||||||
|
FirstName: "Old",
|
||||||
|
LastName: "Name",
|
||||||
|
DisplayName: "Old Name",
|
||||||
|
LdapID: &aliceLdapID,
|
||||||
|
Disabled: true,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
require.NoError(t, db.Create(&model.User{
|
||||||
|
Username: "missing",
|
||||||
|
Email: new("missing@example.com"),
|
||||||
|
EmailVerified: true,
|
||||||
|
FirstName: "Missing",
|
||||||
|
LastName: "User",
|
||||||
|
DisplayName: "Missing User",
|
||||||
|
LdapID: &missingLdapID,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
require.NoError(t, db.Create(&model.UserGroup{
|
||||||
|
Name: "team-old",
|
||||||
|
FriendlyName: "team-old",
|
||||||
|
LdapID: &teamLdapID,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
require.NoError(t, db.Create(&model.UserGroup{
|
||||||
|
Name: "old-group",
|
||||||
|
FriendlyName: "old-group",
|
||||||
|
LdapID: &oldGroupLdapID,
|
||||||
|
}).Error)
|
||||||
|
|
||||||
|
require.NoError(t, service.SyncAll(t.Context()))
|
||||||
|
|
||||||
|
var alice model.User
|
||||||
|
require.NoError(t, db.First(&alice, "ldap_id = ?", aliceLdapID).Error)
|
||||||
|
assert.Equal(t, "alice", alice.Username)
|
||||||
|
assert.Equal(t, new("alice@example.com"), alice.Email)
|
||||||
|
assert.Equal(t, "Alice", alice.FirstName)
|
||||||
|
assert.Equal(t, "Jones", alice.LastName)
|
||||||
|
assert.Equal(t, "Alice Jones", alice.DisplayName)
|
||||||
|
assert.True(t, alice.IsAdmin)
|
||||||
|
assert.False(t, alice.Disabled)
|
||||||
|
|
||||||
|
var bob model.User
|
||||||
|
require.NoError(t, db.First(&bob, "ldap_id = ?", "u-bob").Error)
|
||||||
|
assert.Equal(t, "bob", bob.Username)
|
||||||
|
assert.Equal(t, "Bob Brown", bob.DisplayName)
|
||||||
|
|
||||||
|
var missing model.User
|
||||||
|
require.NoError(t, db.First(&missing, "ldap_id = ?", missingLdapID).Error)
|
||||||
|
assert.True(t, missing.Disabled)
|
||||||
|
|
||||||
|
var oldGroupCount int64
|
||||||
|
require.NoError(t, db.Model(&model.UserGroup{}).Where("ldap_id = ?", oldGroupLdapID).Count(&oldGroupCount).Error)
|
||||||
|
assert.Zero(t, oldGroupCount)
|
||||||
|
|
||||||
|
var team model.UserGroup
|
||||||
|
require.NoError(t, db.Preload("Users").First(&team, "ldap_id = ?", teamLdapID).Error)
|
||||||
|
assert.Equal(t, "team", team.Name)
|
||||||
|
assert.Equal(t, "team", team.FriendlyName)
|
||||||
|
assert.ElementsMatch(t, []string{"alice", "bob"}, usernames(team.Users))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLdapServiceSyncAllHandlesDuplicateLDAPIDsInSingleRun(t *testing.T) {
|
||||||
|
service, db := newTestLdapService(t, newFakeLDAPClient(
|
||||||
|
ldapSearchResult(
|
||||||
|
ldapEntry("uid=alice,ou=people,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"u-dup"},
|
||||||
|
"uid": {"alice"},
|
||||||
|
"mail": {"alice@example.com"},
|
||||||
|
"givenName": {"Alice"},
|
||||||
|
"sn": {"Doe"},
|
||||||
|
"displayName": {"Alice Doe"},
|
||||||
|
}),
|
||||||
|
ldapEntry("uid=alice,ou=people,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"u-dup"},
|
||||||
|
"uid": {"alice"},
|
||||||
|
"mail": {"alice@example.com"},
|
||||||
|
"givenName": {"Alicia"},
|
||||||
|
"sn": {"Doe"},
|
||||||
|
"displayName": {"Alicia Doe"},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ldapSearchResult(
|
||||||
|
ldapEntry("cn=team,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-dup"},
|
||||||
|
"cn": {"team"},
|
||||||
|
"member": {"uid=alice,ou=people,dc=example,dc=com"},
|
||||||
|
}),
|
||||||
|
ldapEntry("cn=team,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-dup"},
|
||||||
|
"cn": {"team-renamed"},
|
||||||
|
"member": {"uid=alice,ou=people,dc=example,dc=com"},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
require.NoError(t, service.SyncAll(t.Context()))
|
||||||
|
|
||||||
|
var users []model.User
|
||||||
|
require.NoError(t, db.Find(&users, "ldap_id = ?", "u-dup").Error)
|
||||||
|
require.Len(t, users, 1)
|
||||||
|
assert.Equal(t, "alice", users[0].Username)
|
||||||
|
assert.Equal(t, "Alicia", users[0].FirstName)
|
||||||
|
assert.Equal(t, "Alicia Doe", users[0].DisplayName)
|
||||||
|
|
||||||
|
var groups []model.UserGroup
|
||||||
|
require.NoError(t, db.Preload("Users").Find(&groups, "ldap_id = ?", "g-dup").Error)
|
||||||
|
require.Len(t, groups, 1)
|
||||||
|
assert.Equal(t, "team-renamed", groups[0].Name)
|
||||||
|
assert.Equal(t, "team-renamed", groups[0].FriendlyName)
|
||||||
|
assert.ElementsMatch(t, []string{"alice"}, usernames(groups[0].Users))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLdapServiceSyncAllSetsAdminFromGroupMembership(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
appConfig *model.AppConfig
|
||||||
|
groupEntry *ldap.Entry
|
||||||
|
groupName string
|
||||||
|
groupLookup string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "memberOf missing on user",
|
||||||
|
appConfig: defaultTestLDAPAppConfig(),
|
||||||
|
groupEntry: ldapEntry("cn=admins,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-admins"},
|
||||||
|
"cn": {"admins"},
|
||||||
|
"member": {"uid=testadmin,ou=people,dc=example,dc=com"},
|
||||||
|
}),
|
||||||
|
groupName: "admins",
|
||||||
|
groupLookup: "g-admins",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "configured group name attribute differs from DN RDN",
|
||||||
|
appConfig: func() *model.AppConfig {
|
||||||
|
cfg := defaultTestLDAPAppConfig()
|
||||||
|
cfg.LdapAttributeGroupName = model.AppConfigVariable{Value: "displayName"}
|
||||||
|
cfg.LdapAdminGroupName = model.AppConfigVariable{Value: "pocketid.admin"}
|
||||||
|
return cfg
|
||||||
|
}(),
|
||||||
|
groupEntry: ldapEntry("cn=admins,ou=groups,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"g-display-admins"},
|
||||||
|
"cn": {"admins"},
|
||||||
|
"displayName": {"pocketid.admin"},
|
||||||
|
"member": {"uid=testadmin,ou=people,dc=example,dc=com"},
|
||||||
|
}),
|
||||||
|
groupName: "pocketid.admin",
|
||||||
|
groupLookup: "g-display-admins",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
service, db := newTestLdapServiceWithAppConfig(t, tt.appConfig, newFakeLDAPClient(
|
||||||
|
ldapSearchResult(
|
||||||
|
ldapEntry("uid=testadmin,ou=people,dc=example,dc=com", map[string][]string{
|
||||||
|
"entryUUID": {"u-testadmin"},
|
||||||
|
"uid": {"testadmin"},
|
||||||
|
"mail": {"testadmin@example.com"},
|
||||||
|
"givenName": {"Test"},
|
||||||
|
"sn": {"Admin"},
|
||||||
|
"displayName": {""},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
ldapSearchResult(tt.groupEntry),
|
||||||
|
))
|
||||||
|
|
||||||
|
require.NoError(t, service.SyncAll(t.Context()))
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
require.NoError(t, db.First(&user, "ldap_id = ?", "u-testadmin").Error)
|
||||||
|
assert.True(t, user.IsAdmin)
|
||||||
|
|
||||||
|
var group model.UserGroup
|
||||||
|
require.NoError(t, db.Preload("Users").First(&group, "ldap_id = ?", tt.groupLookup).Error)
|
||||||
|
assert.Equal(t, tt.groupName, group.Name)
|
||||||
|
assert.ElementsMatch(t, []string{"testadmin"}, usernames(group.Users))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestLdapService(t *testing.T, client ldapClient) (*LdapService, *gorm.DB) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return newTestLdapServiceWithAppConfig(t, defaultTestLDAPAppConfig(), client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestLdapServiceWithAppConfig(t *testing.T, appConfigModel *model.AppConfig, client ldapClient) (*LdapService, *gorm.DB) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
db := testutils.NewDatabaseForTest(t)
|
||||||
|
|
||||||
|
fileStorage, err := storage.NewDatabaseStorage(db)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
appConfig := NewTestAppConfigService(appConfigModel)
|
||||||
|
|
||||||
|
groupService := NewUserGroupService(db, appConfig, nil)
|
||||||
|
userService := NewUserService(
|
||||||
|
db,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
appConfig,
|
||||||
|
NewCustomClaimService(db),
|
||||||
|
NewAppImagesService(map[string]string{}, fileStorage),
|
||||||
|
nil,
|
||||||
|
fileStorage,
|
||||||
|
)
|
||||||
|
|
||||||
|
service := NewLdapService(db, &http.Client{}, appConfig, userService, groupService, fileStorage)
|
||||||
|
service.clientFactory = func() (ldapClient, error) {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, db
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultTestLDAPAppConfig() *model.AppConfig {
|
||||||
|
return &model.AppConfig{
|
||||||
|
RequireUserEmail: model.AppConfigVariable{Value: "false"},
|
||||||
|
LdapEnabled: model.AppConfigVariable{Value: "true"},
|
||||||
|
LdapBase: model.AppConfigVariable{Value: "dc=example,dc=com"},
|
||||||
|
LdapUserSearchFilter: model.AppConfigVariable{Value: "(objectClass=person)"},
|
||||||
|
LdapUserGroupSearchFilter: model.AppConfigVariable{Value: "(objectClass=groupOfNames)"},
|
||||||
|
LdapAttributeUserUniqueIdentifier: model.AppConfigVariable{Value: "entryUUID"},
|
||||||
|
LdapAttributeUserUsername: model.AppConfigVariable{Value: "uid"},
|
||||||
|
LdapAttributeUserEmail: model.AppConfigVariable{Value: "mail"},
|
||||||
|
LdapAttributeUserFirstName: model.AppConfigVariable{Value: "givenName"},
|
||||||
|
LdapAttributeUserLastName: model.AppConfigVariable{Value: "sn"},
|
||||||
|
LdapAttributeUserDisplayName: model.AppConfigVariable{Value: "displayName"},
|
||||||
|
LdapAttributeUserProfilePicture: model.AppConfigVariable{Value: "jpegPhoto"},
|
||||||
|
LdapAttributeGroupMember: model.AppConfigVariable{Value: "member"},
|
||||||
|
LdapAttributeGroupUniqueIdentifier: model.AppConfigVariable{Value: "entryUUID"},
|
||||||
|
LdapAttributeGroupName: model.AppConfigVariable{Value: "cn"},
|
||||||
|
LdapAdminGroupName: model.AppConfigVariable{Value: "admins"},
|
||||||
|
LdapSoftDeleteUsers: model.AppConfigVariable{Value: "true"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeLDAPClient(userResult, groupResult *ldap.SearchResult) ldapClient {
|
||||||
|
return &fakeLDAPClient{
|
||||||
|
searchFn: func(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
||||||
|
switch searchRequest.Filter {
|
||||||
|
case "(objectClass=person)":
|
||||||
|
return userResult, nil
|
||||||
|
case "(objectClass=groupOfNames)":
|
||||||
|
return groupResult, nil
|
||||||
|
default:
|
||||||
|
return &ldap.SearchResult{}, nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ldapSearchResult(entries ...*ldap.Entry) *ldap.SearchResult {
|
||||||
|
return &ldap.SearchResult{Entries: entries}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ldapEntry(dn string, attrs map[string][]string) *ldap.Entry {
|
||||||
|
entry := &ldap.Entry{
|
||||||
|
DN: dn,
|
||||||
|
Attributes: make([]*ldap.EntryAttribute, 0, len(attrs)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, values := range attrs {
|
||||||
|
entry.Attributes = append(entry.Attributes, &ldap.EntryAttribute{
|
||||||
|
Name: name,
|
||||||
|
Values: values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func usernames(users []model.User) []string {
|
||||||
|
result := make([]string, 0, len(users))
|
||||||
|
for _, user := range users {
|
||||||
|
result = append(result, user.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDNProperty(t *testing.T) {
|
func TestGetDNProperty(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -64,10 +423,58 @@ func TestGetDNProperty(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := getDNProperty(tt.property, tt.dn)
|
result := getDNProperty(tt.property, tt.dn)
|
||||||
if result != tt.expectedResult {
|
assert.Equalf(t, tt.expectedResult, result, "getDNProperty(%q, %q)", tt.property, tt.dn)
|
||||||
t.Errorf("getDNProperty(%q, %q) = %q, want %q",
|
})
|
||||||
tt.property, tt.dn, result, tt.expectedResult)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeLDAPDN(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "already normalized",
|
||||||
|
input: "cn=alice,dc=example,dc=com",
|
||||||
|
expected: "cn=alice,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uppercase attribute types",
|
||||||
|
input: "CN=Alice,DC=example,DC=com",
|
||||||
|
expected: "cn=alice,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "spaces after commas",
|
||||||
|
input: "cn=alice, dc=example, dc=com",
|
||||||
|
expected: "cn=alice,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uppercase types and spaces",
|
||||||
|
input: "CN=Alice, DC=example, DC=com",
|
||||||
|
expected: "cn=alice,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi-valued RDN",
|
||||||
|
input: "cn=alice+uid=a123,dc=example,dc=com",
|
||||||
|
expected: "cn=alice+uid=a123,dc=example,dc=com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid DN falls back to lowercase+trim",
|
||||||
|
input: " NOT A VALID DN ",
|
||||||
|
expected: "not a valid dn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
input: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := normalizeLDAPDN(tt.input)
|
||||||
|
assert.Equalf(t, tt.expected, result, "normalizeLDAPDN(%q)", tt.input)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,9 +505,7 @@ func TestConvertLdapIdToString(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := convertLdapIdToString(tt.input)
|
got := convertLdapIdToString(tt.input)
|
||||||
if got != tt.expected {
|
assert.Equal(t, tt.expected, got)
|
||||||
t.Errorf("Expected %q, got %q", tt.expected, got)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +129,10 @@ func (s *UserGroupService) createInternal(ctx context.Context, input dto.UserGro
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +181,10 @@ func (s *UserGroupService) updateInternal(ctx context.Context, id string, input
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +247,10 @@ func (s *UserGroupService) updateUsersInternal(ctx context.Context, id string, u
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,6 +327,9 @@ func (s *UserGroupService) UpdateAllowedOidcClient(ctx context.Context, id strin
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return group, nil
|
return group, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,7 +225,10 @@ func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userI
|
|||||||
return fmt.Errorf("failed to delete user: %w", err)
|
return fmt.Errorf("failed to delete user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +313,10 @@ func (s *UserService) createUserInternal(ctx context.Context, input dto.UserCrea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +462,10 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +524,10 @@ func (s *UserService) UpdateUserGroups(ctx context.Context, id string, userGroup
|
|||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +588,10 @@ func (s *UserService) disableUserInternal(ctx context.Context, tx *gorm.DB, user
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.scimService.ScheduleSync()
|
if s.scimService != nil {
|
||||||
|
s.scimService.ScheduleSync()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
backend/internal/utils/networked_filesystem_linux.go
Normal file
35
backend/internal/utils/networked_filesystem_linux.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filesystem magic values from Linux's include/uapi/linux/magic.h, used by statfs(2).
|
||||||
|
const (
|
||||||
|
nfsSuperMagic = 0x6969
|
||||||
|
smbSuperMagic = 0x517b
|
||||||
|
cifsSuperMagic = 0xff534d42
|
||||||
|
fuseSuperMagic = 0x65735546
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNetworkedFileSystem reports whether path is on a filesystem that is known to be unsafe for SQLite, specifically NFS, SMB/CIFS, or FUSE mounts.
|
||||||
|
func IsNetworkedFileSystem(path string) (bool, error) {
|
||||||
|
var statfs syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(path, &statfs)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("error executing statfs syscall: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statfs_t.Type is arch-dependent (for example, int32 on some systems and int64 on others).
|
||||||
|
// Normalize through uint32 first so signed values still preserve the Linux bit pattern for magic numbers such as CIFS (0xff534d42), then compare in a wide unsigned form.
|
||||||
|
//nolint:gosec
|
||||||
|
switch uint64(uint32(statfs.Type)) {
|
||||||
|
case nfsSuperMagic, smbSuperMagic, cifsSuperMagic, fuseSuperMagic:
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
8
backend/internal/utils/networked_filesystem_nonlinux.go
Normal file
8
backend/internal/utils/networked_filesystem_nonlinux.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
// IsNetworkedFileSystem returns false on non-Linux systems because this detection is only used for Linux-specific statfs(2) filesystem magic values.
|
||||||
|
func IsNetworkedFileSystem(string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -102,7 +102,7 @@
|
|||||||
"see_your_recent_account_activities": "See your account activities within the configured retention period.",
|
"see_your_recent_account_activities": "See your account activities within the configured retention period.",
|
||||||
"time": "Time",
|
"time": "Time",
|
||||||
"event": "Event",
|
"event": "Event",
|
||||||
"approximate_location": "Approximate Location",
|
"approximate_location": "Omtrentlig plassering",
|
||||||
"ip_address": "IP adresse",
|
"ip_address": "IP adresse",
|
||||||
"device": "Enhet",
|
"device": "Enhet",
|
||||||
"client": "Klient",
|
"client": "Klient",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pocket-id-frontend",
|
"name": "pocket-id-frontend",
|
||||||
"version": "2.4.0",
|
"version": "2.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
<script module lang="ts">
|
||||||
|
// Persist the last failing background image URL across route remounts so
|
||||||
|
// login pages without a background do not briefly retry the image and stutter.
|
||||||
|
let persistedMissingBackgroundImageUrl: string | undefined;
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate } from '$app/navigation';
|
import { afterNavigate } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
@@ -17,13 +23,32 @@
|
|||||||
showAlternativeSignInMethodButton?: boolean;
|
showAlternativeSignInMethodButton?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
let missingBackgroundImageUrl = $state<string | undefined>(persistedMissingBackgroundImageUrl);
|
||||||
|
let loadedBackgroundImageUrl = $state<string | undefined>();
|
||||||
let isInitialLoad = $state(false);
|
let isInitialLoad = $state(false);
|
||||||
let animate = $derived(isInitialLoad && !$appConfigStore.disableAnimations);
|
let backgroundImageUrl = $derived(cachedBackgroundImage.getUrl());
|
||||||
|
let imageError = $derived(missingBackgroundImageUrl === backgroundImageUrl);
|
||||||
|
let imageLoaded = $derived(loadedBackgroundImageUrl === backgroundImageUrl);
|
||||||
|
let animate = $derived(isInitialLoad && imageLoaded && !$appConfigStore.disableAnimations);
|
||||||
|
|
||||||
afterNavigate((e) => {
|
afterNavigate((e) => {
|
||||||
isInitialLoad = !e?.from?.url;
|
isInitialLoad = !e?.from?.url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onBackgroundImageLoad() {
|
||||||
|
loadedBackgroundImageUrl = backgroundImageUrl;
|
||||||
|
if (persistedMissingBackgroundImageUrl === backgroundImageUrl) {
|
||||||
|
persistedMissingBackgroundImageUrl = undefined;
|
||||||
|
missingBackgroundImageUrl = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBackgroundImageError() {
|
||||||
|
loadedBackgroundImageUrl = undefined;
|
||||||
|
persistedMissingBackgroundImageUrl = backgroundImageUrl;
|
||||||
|
missingBackgroundImageUrl = backgroundImageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
const isDesktop = new MediaQuery('min-width: 1024px');
|
const isDesktop = new MediaQuery('min-width: 1024px');
|
||||||
let alternativeSignInButton = $state({
|
let alternativeSignInButton = $state({
|
||||||
href: '/login/alternative',
|
href: '/login/alternative',
|
||||||
@@ -46,9 +71,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isDesktop.current}
|
{#if isDesktop.current}
|
||||||
<div class="h-screen items-center overflow-hidden text-center">
|
<div class="h-screen items-center overflow-hidden text-center flex justify-center">
|
||||||
<div
|
<div
|
||||||
class="relative z-10 flex h-full w-[650px] 2xl:w-[800px] p-16 {cn(
|
class="flex h-full w-[650px] 2xl:w-[800px] p-16 {cn(
|
||||||
showAlternativeSignInMethodButton && 'pb-0'
|
showAlternativeSignInMethodButton && 'pb-0'
|
||||||
)}"
|
)}"
|
||||||
>
|
>
|
||||||
@@ -69,16 +94,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Background image -->
|
{#if !imageError}
|
||||||
<div class="absolute top-0 right-0 left-500px bottom-0 z-0 overflow-hidden rounded-[40px] m-6">
|
<!-- Background image -->
|
||||||
<img
|
<div class="m-6 flex h-[calc(100vh-3rem)] overflow-hidden rounded-[40px]">
|
||||||
src={cachedBackgroundImage.getUrl()}
|
<img
|
||||||
class="{cn(
|
src={backgroundImageUrl}
|
||||||
animate && 'animate-bg-zoom'
|
class="h-full object-cover {cn(animate && 'animate-bg-zoom')}"
|
||||||
)} h-screen object-cover w-[calc(100vw-650px)] 2xl:w-[calc(100vw-800px)]"
|
alt={m.login_background()}
|
||||||
alt={m.login_background()}
|
onload={onBackgroundImageLoad}
|
||||||
/>
|
onerror={onBackgroundImageError}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ export default class AppConfigService extends APIService {
|
|||||||
cachedBackgroundImage.bustCache();
|
cachedBackgroundImage.bustCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
deleteBackgroundImage = async () => {
|
||||||
|
await this.api.delete(`/application-images/background`);
|
||||||
|
cachedBackgroundImage.bustCache();
|
||||||
|
};
|
||||||
|
|
||||||
deleteDefaultProfilePicture = async () => {
|
deleteDefaultProfilePicture = async () => {
|
||||||
await this.api.delete('/application-images/default-profile-picture');
|
await this.api.delete('/application-images/default-profile-picture');
|
||||||
cachedDefaultProfilePicture.bustCache();
|
cachedDefaultProfilePicture.bustCache();
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const callbackUrlSchema = z
|
|||||||
|
|
||||||
export const usernameSchema = z
|
export const usernameSchema = z
|
||||||
.string()
|
.string()
|
||||||
.min(2)
|
.min(1)
|
||||||
.max(30)
|
.max(30)
|
||||||
.regex(/^[a-zA-Z0-9]/, m.username_must_start_with())
|
.regex(/^[a-zA-Z0-9]/, m.username_must_start_with())
|
||||||
.regex(/[a-zA-Z0-9]$/, m.username_must_end_with())
|
.regex(/[a-zA-Z0-9]$/, m.username_must_end_with())
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
logoDark: File | undefined,
|
logoDark: File | undefined,
|
||||||
logoEmail: File | undefined,
|
logoEmail: File | undefined,
|
||||||
defaultProfilePicture: File | null | undefined,
|
defaultProfilePicture: File | null | undefined,
|
||||||
backgroundImage: File | undefined,
|
backgroundImage: File | null | undefined,
|
||||||
favicon: File | undefined
|
favicon: File | undefined
|
||||||
) {
|
) {
|
||||||
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
||||||
@@ -68,9 +68,12 @@
|
|||||||
? appConfigService.updateDefaultProfilePicture(defaultProfilePicture)
|
? appConfigService.updateDefaultProfilePicture(defaultProfilePicture)
|
||||||
: Promise.resolve();
|
: Promise.resolve();
|
||||||
|
|
||||||
const backgroundImagePromise = backgroundImage
|
const backgroundImagePromise =
|
||||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
backgroundImage === null
|
||||||
: Promise.resolve();
|
? appConfigService.deleteBackgroundImage()
|
||||||
|
: backgroundImage
|
||||||
|
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
lightLogoPromise,
|
lightLogoPromise,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
logoDark: File | undefined,
|
logoDark: File | undefined,
|
||||||
logoEmail: File | undefined,
|
logoEmail: File | undefined,
|
||||||
defaultProfilePicture: File | null | undefined,
|
defaultProfilePicture: File | null | undefined,
|
||||||
backgroundImage: File | undefined,
|
backgroundImage: File | null | undefined,
|
||||||
favicon: File | undefined
|
favicon: File | undefined
|
||||||
) => void;
|
) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -26,10 +26,11 @@
|
|||||||
let logoDark = $state<File | undefined>();
|
let logoDark = $state<File | undefined>();
|
||||||
let logoEmail = $state<File | undefined>();
|
let logoEmail = $state<File | undefined>();
|
||||||
let defaultProfilePicture = $state<File | null | undefined>();
|
let defaultProfilePicture = $state<File | null | undefined>();
|
||||||
let backgroundImage = $state<File | undefined>();
|
let backgroundImage = $state<File | null | undefined>();
|
||||||
let favicon = $state<File | undefined>();
|
let favicon = $state<File | undefined>();
|
||||||
|
|
||||||
let defaultProfilePictureSet = $state(true);
|
let defaultProfilePictureSet = $state(true);
|
||||||
|
let backgroundImageSet = $state(true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-8">
|
<div class="flex flex-col gap-8">
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
label={m.favicon()}
|
label={m.favicon()}
|
||||||
bind:image={favicon}
|
bind:image={favicon}
|
||||||
imageURL="/api/application-images/favicon"
|
imageURL="/api/application-images/favicon"
|
||||||
accept="image/x-icon"
|
accept="image/svg+xml, image/png, image/x-icon"
|
||||||
/>
|
/>
|
||||||
<ApplicationImage
|
<ApplicationImage
|
||||||
id="logo-light"
|
id="logo-light"
|
||||||
@@ -77,10 +78,12 @@
|
|||||||
/>
|
/>
|
||||||
<ApplicationImage
|
<ApplicationImage
|
||||||
id="background-image"
|
id="background-image"
|
||||||
imageClass="h-[350px] max-w-[500px]"
|
imageClass="max-h-[350px] max-w-[500px]"
|
||||||
label={m.background_image()}
|
label={m.background_image()}
|
||||||
|
isResetable
|
||||||
bind:image={backgroundImage}
|
bind:image={backgroundImage}
|
||||||
imageURL={cachedBackgroundImage.getUrl()}
|
imageURL={cachedBackgroundImage.getUrl()}
|
||||||
|
isImageSet={backgroundImageSet}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import FormInput from '$lib/components/form/form-input.svelte';
|
import FormInput from '$lib/components/form/form-input.svelte';
|
||||||
import SwitchWithLabel from '$lib/components/form/switch-with-label.svelte';
|
import SwitchWithLabel from '$lib/components/form/switch-with-label.svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import { Input } from '$lib/components/ui/input';
|
||||||
import { Toggle } from '$lib/components/ui/toggle';
|
import { Toggle } from '$lib/components/ui/toggle';
|
||||||
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
@@ -76,34 +77,37 @@
|
|||||||
<fieldset disabled={inputDisabled}>
|
<fieldset disabled={inputDisabled}>
|
||||||
<div class="grid grid-cols-1 items-start gap-5 md:grid-cols-2">
|
<div class="grid grid-cols-1 items-start gap-5 md:grid-cols-2">
|
||||||
<FormInput label={m.username()} bind:input={$inputs.username} />
|
<FormInput label={m.username()} bind:input={$inputs.username} />
|
||||||
<div class="flex items-end">
|
<FormInput label={m.email()} bind:input={$inputs.email} labelFor="email">
|
||||||
<FormInput
|
<div class="flex items-end">
|
||||||
inputClass="rounded-r-none border-r-0"
|
<Input
|
||||||
label={m.email()}
|
id="email"
|
||||||
bind:input={$inputs.email}
|
class="rounded-r-none border-r-0"
|
||||||
/>
|
aria-invalid={!!$inputs.email.error}
|
||||||
<Tooltip.Provider>
|
bind:value={$inputs.email.value}
|
||||||
{@const label = $inputs.emailVerified.value
|
/>
|
||||||
? m.mark_as_unverified()
|
<Tooltip.Provider>
|
||||||
: m.mark_as_verified()}
|
{@const label = $inputs.emailVerified.value
|
||||||
<Tooltip.Root>
|
? m.mark_as_unverified()
|
||||||
<Tooltip.Trigger>
|
: m.mark_as_verified()}
|
||||||
<Toggle
|
<Tooltip.Root>
|
||||||
bind:pressed={$inputs.emailVerified.value}
|
<Tooltip.Trigger>
|
||||||
aria-label={label}
|
<Toggle
|
||||||
class="h-9 border-input bg-yellow-100 dark:bg-yellow-950 data-[state=on]:bg-green-100 dark:data-[state=on]:bg-green-950 rounded-l-none border px-2 py-1 shadow-xs flex items-center hover:data-[state=on]:bg-accent"
|
bind:pressed={$inputs.emailVerified.value}
|
||||||
>
|
aria-label={label}
|
||||||
{#if $inputs.emailVerified.value}
|
class="h-9 border-input bg-yellow-100 dark:bg-yellow-950 data-[state=on]:bg-green-100 dark:data-[state=on]:bg-green-950 rounded-l-none border px-2 py-1 shadow-xs flex items-center hover:data-[state=on]:bg-accent"
|
||||||
<LucideMailCheck class="text-green-500 dark:text-green-600 size-5" />
|
>
|
||||||
{:else}
|
{#if $inputs.emailVerified.value}
|
||||||
<LucideMailWarning class="text-yellow-500 dark:text-yellow-600 size-5" />
|
<LucideMailCheck class="text-green-500 dark:text-green-600 size-5" />
|
||||||
{/if}
|
{:else}
|
||||||
</Toggle>
|
<LucideMailWarning class="text-yellow-500 dark:text-yellow-600 size-5" />
|
||||||
</Tooltip.Trigger>
|
{/if}
|
||||||
<Tooltip.Content>{label}</Tooltip.Content>
|
</Toggle>
|
||||||
</Tooltip.Root>
|
</Tooltip.Trigger>
|
||||||
</Tooltip.Provider>
|
<Tooltip.Content>{label}</Tooltip.Content>
|
||||||
</div>
|
</Tooltip.Root>
|
||||||
|
</Tooltip.Provider>
|
||||||
|
</div>
|
||||||
|
</FormInput>
|
||||||
<FormInput label={m.first_name()} oninput={onNameInput} bind:input={$inputs.firstName} />
|
<FormInput label={m.first_name()} oninput={onNameInput} bind:input={$inputs.firstName} />
|
||||||
<FormInput label={m.last_name()} oninput={onNameInput} bind:input={$inputs.lastName} />
|
<FormInput label={m.last_name()} oninput={onNameInput} bind:input={$inputs.lastName} />
|
||||||
<FormInput
|
<FormInput
|
||||||
|
|||||||
372
pnpm-lock.yaml
generated
372
pnpm-lock.yaml
generated
@@ -5,14 +5,21 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
|
'@isaacs/brace-expansion': '>=5.0.1'
|
||||||
cookie@<0.7.0: '>=0.7.0'
|
cookie@<0.7.0: '>=0.7.0'
|
||||||
devalue: ^5.6.2
|
devalue: '>=5.6.4'
|
||||||
glob@>=11.0.0 <11.1.0: '>=11.1.0'
|
glob@>=11.0.0 <11.1.0: '>=11.1.0'
|
||||||
js-yaml@>=4.0.0 <4.1.1: '>=4.1.1'
|
js-yaml@>=4.0.0 <4.1.1: '>=4.1.1'
|
||||||
|
next: '>=16.1.7'
|
||||||
valibot@>=0.31.0 <1.2.0: '>=1.2.0'
|
valibot@>=0.31.0 <1.2.0: '>=1.2.0'
|
||||||
validator@<13.15.20: '>=13.15.20'
|
validator@<13.15.20: '>=13.15.20'
|
||||||
'@isaacs/brace-expansion': '>=5.0.1'
|
flatted: '>=3.4.2'
|
||||||
next: '>=16.1.5'
|
socket.io-parser: '>=4.2.6'
|
||||||
|
kysely: '>=0.28.14'
|
||||||
|
minimatch: '>=10.2.3'
|
||||||
|
picomatch: '>=4.0.4'
|
||||||
|
effect: '>=3.20.0'
|
||||||
|
yaml: '>=2.8.3'
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
@@ -59,7 +66,7 @@ importers:
|
|||||||
version: 13.2.2
|
version: 13.2.2
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
version: 4.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.13.5
|
specifier: ^1.13.5
|
||||||
version: 1.13.5
|
version: 1.13.5
|
||||||
@@ -77,10 +84,10 @@ importers:
|
|||||||
version: 1.5.4
|
version: 1.5.4
|
||||||
runed:
|
runed:
|
||||||
specifier: ^0.37.1
|
specifier: ^0.37.1
|
||||||
version: 0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(zod@4.3.6)
|
version: 0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(zod@4.3.6)
|
||||||
sveltekit-superforms:
|
sveltekit-superforms:
|
||||||
specifier: ^2.30.0
|
specifier: ^2.30.0
|
||||||
version: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)
|
version: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.5.0
|
specifier: ^3.5.0
|
||||||
version: 3.5.0
|
version: 3.5.0
|
||||||
@@ -105,13 +112,13 @@ importers:
|
|||||||
version: 0.559.0(svelte@5.53.6)
|
version: 0.559.0(svelte@5.53.6)
|
||||||
'@sveltejs/adapter-static':
|
'@sveltejs/adapter-static':
|
||||||
specifier: ^3.0.10
|
specifier: ^3.0.10
|
||||||
version: 3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))
|
version: 3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))
|
||||||
'@sveltejs/kit':
|
'@sveltejs/kit':
|
||||||
specifier: ^2.53.4
|
specifier: ^2.53.4
|
||||||
version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
version: 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
'@sveltejs/vite-plugin-svelte':
|
'@sveltejs/vite-plugin-svelte':
|
||||||
specifier: ^6.2.4
|
specifier: ^6.2.4
|
||||||
version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
version: 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^9.6.1
|
specifier: ^9.6.1
|
||||||
version: 9.6.1
|
version: 9.6.1
|
||||||
@@ -123,7 +130,7 @@ importers:
|
|||||||
version: 1.5.6
|
version: 1.5.6
|
||||||
bits-ui:
|
bits-ui:
|
||||||
specifier: ^2.16.2
|
specifier: ^2.16.2
|
||||||
version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)
|
version: 2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.3
|
specifier: ^9.39.3
|
||||||
version: 9.39.3(jiti@2.6.1)
|
version: 9.39.3(jiti@2.6.1)
|
||||||
@@ -135,7 +142,7 @@ importers:
|
|||||||
version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.6)
|
version: 3.15.0(eslint@9.39.3(jiti@2.6.1))(svelte@5.53.6)
|
||||||
formsnap:
|
formsnap:
|
||||||
specifier: ^2.0.1
|
specifier: ^2.0.1
|
||||||
version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3))
|
version: 2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3))
|
||||||
globals:
|
globals:
|
||||||
specifier: ^16.5.0
|
specifier: ^16.5.0
|
||||||
version: 16.5.0
|
version: 16.5.0
|
||||||
@@ -159,7 +166,7 @@ importers:
|
|||||||
version: 5.53.6
|
version: 5.53.6
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3)
|
version: 4.4.3(picomatch@4.0.4)(svelte@5.53.6)(typescript@5.9.3)
|
||||||
svelte-sonner:
|
svelte-sonner:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7(svelte@5.53.6)
|
version: 1.0.7(svelte@5.53.6)
|
||||||
@@ -183,10 +190,10 @@ importers:
|
|||||||
version: 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.56.0(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.3.1
|
specifier: ^7.3.1
|
||||||
version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
version: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
vite-plugin-compression:
|
vite-plugin-compression:
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
version: 0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -262,8 +269,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@emnapi/runtime@1.7.1':
|
'@emnapi/runtime@1.9.1':
|
||||||
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
|
resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
|
||||||
|
|
||||||
'@esbuild/aix-ppc64@0.25.12':
|
'@esbuild/aix-ppc64@0.25.12':
|
||||||
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||||
@@ -805,8 +812,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||||
engines: {node: '>=18.18'}
|
engines: {node: '>=18.18'}
|
||||||
|
|
||||||
'@img/colour@1.0.0':
|
'@img/colour@1.1.0':
|
||||||
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
'@img/sharp-darwin-arm64@0.34.5':
|
'@img/sharp-darwin-arm64@0.34.5':
|
||||||
@@ -1009,57 +1016,57 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
svelte: ^5
|
svelte: ^5
|
||||||
|
|
||||||
'@next/env@16.1.6':
|
'@next/env@16.2.1':
|
||||||
resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==}
|
resolution: {integrity: sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@16.1.6':
|
'@next/swc-darwin-arm64@16.2.1':
|
||||||
resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==}
|
resolution: {integrity: sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-darwin-x64@16.1.6':
|
'@next/swc-darwin-x64@16.2.1':
|
||||||
resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==}
|
resolution: {integrity: sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@16.1.6':
|
'@next/swc-linux-arm64-gnu@16.2.1':
|
||||||
resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==}
|
resolution: {integrity: sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@16.1.6':
|
'@next/swc-linux-arm64-musl@16.2.1':
|
||||||
resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==}
|
resolution: {integrity: sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@16.1.6':
|
'@next/swc-linux-x64-gnu@16.2.1':
|
||||||
resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==}
|
resolution: {integrity: sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@16.1.6':
|
'@next/swc-linux-x64-musl@16.2.1':
|
||||||
resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==}
|
resolution: {integrity: sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@16.1.6':
|
'@next/swc-win32-arm64-msvc@16.2.1':
|
||||||
resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==}
|
resolution: {integrity: sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@16.1.6':
|
'@next/swc-win32-x64-msvc@16.2.1':
|
||||||
resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==}
|
resolution: {integrity: sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1747,9 +1754,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
|
||||||
|
|
||||||
balanced-match@4.0.4:
|
balanced-match@4.0.4:
|
||||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -1758,8 +1762,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
|
||||||
engines: {node: ^4.5.0 || >= 5.9}
|
engines: {node: ^4.5.0 || >= 5.9}
|
||||||
|
|
||||||
baseline-browser-mapping@2.9.19:
|
baseline-browser-mapping@2.10.11:
|
||||||
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
|
resolution: {integrity: sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
bits-ui@2.16.2:
|
bits-ui@2.16.2:
|
||||||
@@ -1769,11 +1774,8 @@ packages:
|
|||||||
'@internationalized/date': ^3.8.1
|
'@internationalized/date': ^3.8.1
|
||||||
svelte: ^5.33.0
|
svelte: ^5.33.0
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@5.0.5:
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
|
||||||
|
|
||||||
brace-expansion@5.0.3:
|
|
||||||
resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==}
|
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
@@ -1795,8 +1797,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001762:
|
caniuse-lite@1.0.30001781:
|
||||||
resolution: {integrity: sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==}
|
resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
@@ -1860,9 +1862,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==}
|
resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
|
||||||
|
|
||||||
conf@15.0.2:
|
conf@15.0.2:
|
||||||
resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==}
|
resolution: {integrity: sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -1959,8 +1958,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
devalue@5.6.3:
|
devalue@5.6.4:
|
||||||
resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==}
|
resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==}
|
||||||
|
|
||||||
dijkstrajs@1.0.3:
|
dijkstrajs@1.0.3:
|
||||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
@@ -1993,8 +1992,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
effect@3.19.19:
|
effect@3.21.0:
|
||||||
resolution: {integrity: sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg==}
|
resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==}
|
||||||
|
|
||||||
emoji-regex@10.6.0:
|
emoji-regex@10.6.0:
|
||||||
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
|
||||||
@@ -2153,7 +2152,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
picomatch: ^3 || ^4
|
picomatch: '>=4.0.4'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -2179,8 +2178,8 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
flatted@3.3.3:
|
flatted@3.4.2:
|
||||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
follow-redirects@1.15.11:
|
follow-redirects@1.15.11:
|
||||||
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||||
@@ -2407,9 +2406,9 @@ packages:
|
|||||||
known-css-properties@0.37.0:
|
known-css-properties@0.37.0:
|
||||||
resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==}
|
resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==}
|
||||||
|
|
||||||
kysely@0.27.6:
|
kysely@0.28.14:
|
||||||
resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==}
|
resolution: {integrity: sha512-SU3lgh0rPvq7upc6vvdVrCsSMUG1h3ChvHVOY7wJ2fw4C9QEB7X3d5eyYEyULUX7UQtxZJtZXGuT6U2US72UYA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
leac@0.6.0:
|
leac@0.6.0:
|
||||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||||
@@ -2564,17 +2563,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
minimatch@10.2.2:
|
minimatch@10.2.4:
|
||||||
resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==}
|
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
minimatch@3.1.3:
|
|
||||||
resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==}
|
|
||||||
|
|
||||||
minimatch@9.0.6:
|
|
||||||
resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==}
|
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
|
||||||
|
|
||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
@@ -2610,8 +2602,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
next@16.1.6:
|
next@16.2.1:
|
||||||
resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==}
|
resolution: {integrity: sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2711,8 +2703,8 @@ packages:
|
|||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
picomatch@4.0.3:
|
picomatch@4.0.4:
|
||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pkg-types@2.3.0:
|
pkg-types@2.3.0:
|
||||||
@@ -2997,8 +2989,8 @@ packages:
|
|||||||
socket.io-adapter@2.5.6:
|
socket.io-adapter@2.5.6:
|
||||||
resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==}
|
resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==}
|
||||||
|
|
||||||
socket.io-parser@4.2.5:
|
socket.io-parser@4.2.6:
|
||||||
resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==}
|
resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
socket.io@4.8.3:
|
socket.io@4.8.3:
|
||||||
@@ -3019,7 +3011,7 @@ packages:
|
|||||||
sqlite-wasm-kysely@0.3.0:
|
sqlite-wasm-kysely@0.3.0:
|
||||||
resolution: {integrity: sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==}
|
resolution: {integrity: sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
kysely: '*'
|
kysely: '>=0.28.14'
|
||||||
|
|
||||||
stdin-discarder@0.2.2:
|
stdin-discarder@0.2.2:
|
||||||
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
|
||||||
@@ -3307,7 +3299,7 @@ packages:
|
|||||||
sugarss: ^5.0.0
|
sugarss: ^5.0.0
|
||||||
terser: ^5.16.0
|
terser: ^5.16.0
|
||||||
tsx: ^4.8.1
|
tsx: ^4.8.1
|
||||||
yaml: ^2.4.2
|
yaml: '>=2.8.3'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -3377,12 +3369,8 @@ packages:
|
|||||||
y18n@4.0.3:
|
y18n@4.0.3:
|
||||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||||
|
|
||||||
yaml@1.10.2:
|
yaml@2.8.3:
|
||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
yaml@2.8.1:
|
|
||||||
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
|
|
||||||
engines: {node: '>= 14.6'}
|
engines: {node: '>= 14.6'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -3476,7 +3464,7 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.27.1
|
'@babel/helper-string-parser': 7.27.1
|
||||||
'@babel/helper-validator-identifier': 7.28.5
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
|
||||||
'@emnapi/runtime@1.7.1':
|
'@emnapi/runtime@1.9.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
@@ -3726,7 +3714,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/object-schema': 2.1.7
|
'@eslint/object-schema': 2.1.7
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 3.1.3
|
minimatch: 10.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3747,7 +3735,7 @@ snapshots:
|
|||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
import-fresh: 3.3.1
|
import-fresh: 3.3.1
|
||||||
js-yaml: 4.1.1
|
js-yaml: 4.1.1
|
||||||
minimatch: 3.1.3
|
minimatch: 10.2.4
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -3794,7 +3782,7 @@ snapshots:
|
|||||||
|
|
||||||
'@humanwhocodes/retry@0.4.3': {}
|
'@humanwhocodes/retry@0.4.3': {}
|
||||||
|
|
||||||
'@img/colour@1.0.0':
|
'@img/colour@1.1.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-darwin-arm64@0.34.5':
|
'@img/sharp-darwin-arm64@0.34.5':
|
||||||
@@ -3879,7 +3867,7 @@ snapshots:
|
|||||||
|
|
||||||
'@img/sharp-wasm32@0.34.5':
|
'@img/sharp-wasm32@0.34.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/runtime': 1.7.1
|
'@emnapi/runtime': 1.9.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-arm64@0.34.5':
|
'@img/sharp-win32-arm64@0.34.5':
|
||||||
@@ -3921,8 +3909,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@lix-js/sdk': 0.4.7
|
'@lix-js/sdk': 0.4.7
|
||||||
'@sinclair/typebox': 0.31.28
|
'@sinclair/typebox': 0.31.28
|
||||||
kysely: 0.27.6
|
kysely: 0.28.14
|
||||||
sqlite-wasm-kysely: 0.3.0(kysely@0.27.6)
|
sqlite-wasm-kysely: 0.3.0(kysely@0.28.14)
|
||||||
uuid: 13.0.0
|
uuid: 13.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@@ -3962,8 +3950,8 @@ snapshots:
|
|||||||
dedent: 1.5.1
|
dedent: 1.5.1
|
||||||
human-id: 4.1.3
|
human-id: 4.1.3
|
||||||
js-sha256: 0.11.1
|
js-sha256: 0.11.1
|
||||||
kysely: 0.27.6
|
kysely: 0.28.14
|
||||||
sqlite-wasm-kysely: 0.3.0(kysely@0.27.6)
|
sqlite-wasm-kysely: 0.3.0(kysely@0.28.14)
|
||||||
uuid: 10.0.0
|
uuid: 10.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@@ -3974,30 +3962,30 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
|
|
||||||
'@next/env@16.1.6': {}
|
'@next/env@16.2.1': {}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@16.1.6':
|
'@next/swc-darwin-arm64@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-darwin-x64@16.1.6':
|
'@next/swc-darwin-x64@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@16.1.6':
|
'@next/swc-linux-arm64-gnu@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@16.1.6':
|
'@next/swc-linux-arm64-musl@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@16.1.6':
|
'@next/swc-linux-x64-gnu@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@16.1.6':
|
'@next/swc-linux-x64-musl@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@16.1.6':
|
'@next/swc-win32-arm64-msvc@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@16.1.6':
|
'@next/swc-win32-x64-msvc@16.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@playwright/test@1.57.0':
|
'@playwright/test@1.57.0':
|
||||||
@@ -4095,7 +4083,7 @@ snapshots:
|
|||||||
|
|
||||||
'@react-email/preview-server@5.0.7(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
'@react-email/preview-server@5.0.7(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 16.1.6(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
next: 16.2.1(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- '@opentelemetry/api'
|
- '@opentelemetry/api'
|
||||||
@@ -4251,19 +4239,19 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.16.0
|
acorn: 8.16.0
|
||||||
|
|
||||||
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))':
|
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
|
|
||||||
'@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))':
|
'@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
'@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0)
|
'@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0)
|
||||||
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
'@types/cookie': 0.6.0
|
'@types/cookie': 0.6.0
|
||||||
acorn: 8.16.0
|
acorn: 8.16.0
|
||||||
cookie: 1.1.1
|
cookie: 1.1.1
|
||||||
devalue: 5.6.3
|
devalue: 5.6.4
|
||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
kleur: 4.1.5
|
kleur: 4.1.5
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
@@ -4271,26 +4259,26 @@ snapshots:
|
|||||||
set-cookie-parser: 3.0.1
|
set-cookie-parser: 3.0.1
|
||||||
sirv: 3.0.2
|
sirv: 3.0.2
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))':
|
'@sveltejs/vite-plugin-svelte-inspector@5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
obug: 2.1.1
|
obug: 2.1.1
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
|
|
||||||
'@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))':
|
'@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/vite-plugin-svelte-inspector': 5.0.2(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
deepmerge: 4.3.1
|
deepmerge: 4.3.1
|
||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
obug: 2.1.1
|
obug: 2.1.1
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
vitefu: 1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
vitefu: 1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4361,12 +4349,12 @@ snapshots:
|
|||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.0
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.0
|
||||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.0
|
'@tailwindcss/oxide-win32-x64-msvc': 4.2.0
|
||||||
|
|
||||||
'@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))':
|
'@tailwindcss/vite@4.2.0(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.2.0
|
'@tailwindcss/node': 4.2.0
|
||||||
'@tailwindcss/oxide': 4.2.0
|
'@tailwindcss/oxide': 4.2.0
|
||||||
tailwindcss: 4.2.0
|
tailwindcss: 4.2.0
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
|
|
||||||
'@types/adm-zip@0.5.7':
|
'@types/adm-zip@0.5.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4497,7 +4485,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.56.0
|
'@typescript-eslint/types': 8.56.0
|
||||||
'@typescript-eslint/visitor-keys': 8.56.0
|
'@typescript-eslint/visitor-keys': 8.56.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 9.0.6
|
minimatch: 10.2.4
|
||||||
semver: 7.7.4
|
semver: 7.7.4
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
@@ -4615,33 +4603,26 @@ snapshots:
|
|||||||
|
|
||||||
axobject-query@4.1.0: {}
|
axobject-query@4.1.0: {}
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
|
||||||
|
|
||||||
balanced-match@4.0.4: {}
|
balanced-match@4.0.4: {}
|
||||||
|
|
||||||
base64id@2.0.0: {}
|
base64id@2.0.0: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.9.19: {}
|
baseline-browser-mapping@2.10.11: {}
|
||||||
|
|
||||||
bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6):
|
bits-ui@2.16.2(@internationalized/date@3.11.0)(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/core': 1.7.4
|
'@floating-ui/core': 1.7.4
|
||||||
'@floating-ui/dom': 1.7.5
|
'@floating-ui/dom': 1.7.5
|
||||||
'@internationalized/date': 3.11.0
|
'@internationalized/date': 3.11.0
|
||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)
|
runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)
|
svelte-toolbelt: 0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)
|
||||||
tabbable: 6.4.0
|
tabbable: 6.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@sveltejs/kit'
|
- '@sveltejs/kit'
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
brace-expansion@5.0.5:
|
||||||
dependencies:
|
|
||||||
balanced-match: 1.0.2
|
|
||||||
concat-map: 0.0.1
|
|
||||||
|
|
||||||
brace-expansion@5.0.3:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 4.0.4
|
balanced-match: 4.0.4
|
||||||
|
|
||||||
@@ -4660,7 +4641,7 @@ snapshots:
|
|||||||
camelcase@8.0.0:
|
camelcase@8.0.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
caniuse-lite@1.0.30001762: {}
|
caniuse-lite@1.0.30001781: {}
|
||||||
|
|
||||||
chalk@4.1.2:
|
chalk@4.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4723,8 +4704,6 @@ snapshots:
|
|||||||
core-util-is: 1.0.3
|
core-util-is: 1.0.3
|
||||||
esprima: 4.0.1
|
esprima: 4.0.1
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
|
||||||
|
|
||||||
conf@15.0.2:
|
conf@15.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 8.18.0
|
ajv: 8.18.0
|
||||||
@@ -4793,7 +4772,7 @@ snapshots:
|
|||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
devalue@5.6.3: {}
|
devalue@5.6.4: {}
|
||||||
|
|
||||||
dijkstrajs@1.0.3: {}
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
@@ -4830,7 +4809,7 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
|
|
||||||
effect@3.19.19:
|
effect@3.21.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/spec': 1.1.0
|
'@standard-schema/spec': 1.1.0
|
||||||
fast-check: 3.23.2
|
fast-check: 3.23.2
|
||||||
@@ -5037,7 +5016,7 @@ snapshots:
|
|||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
json-stable-stringify-without-jsonify: 1.0.1
|
json-stable-stringify-without-jsonify: 1.0.1
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
minimatch: 3.1.3
|
minimatch: 10.2.4
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
optionator: 0.9.4
|
optionator: 0.9.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -5086,9 +5065,9 @@ snapshots:
|
|||||||
|
|
||||||
fast-uri@3.1.0: {}
|
fast-uri@3.1.0: {}
|
||||||
|
|
||||||
fdir@6.5.0(picomatch@4.0.3):
|
fdir@6.5.0(picomatch@4.0.4):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
|
|
||||||
file-entry-cache@8.0.0:
|
file-entry-cache@8.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5106,12 +5085,12 @@ snapshots:
|
|||||||
|
|
||||||
flat-cache@4.0.1:
|
flat-cache@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flatted: 3.3.3
|
flatted: 3.4.2
|
||||||
keyv: 4.5.4
|
keyv: 4.5.4
|
||||||
|
|
||||||
flat@6.0.1: {}
|
flat@6.0.1: {}
|
||||||
|
|
||||||
flatted@3.3.3: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
follow-redirects@1.15.11: {}
|
follow-redirects@1.15.11: {}
|
||||||
|
|
||||||
@@ -5123,11 +5102,11 @@ snapshots:
|
|||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
|
||||||
formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)):
|
formsnap@2.0.1(svelte@5.53.6)(sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
svelte-toolbelt: 0.5.0(svelte@5.53.6)
|
svelte-toolbelt: 0.5.0(svelte@5.53.6)
|
||||||
sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)
|
sveltekit-superforms: 2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3)
|
||||||
|
|
||||||
fs-extra@10.1.0:
|
fs-extra@10.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5175,7 +5154,7 @@ snapshots:
|
|||||||
|
|
||||||
glob@13.0.0:
|
glob@13.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 10.2.2
|
minimatch: 10.2.4
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
path-scurry: 2.0.1
|
path-scurry: 2.0.1
|
||||||
|
|
||||||
@@ -5308,7 +5287,7 @@ snapshots:
|
|||||||
|
|
||||||
known-css-properties@0.37.0: {}
|
known-css-properties@0.37.0: {}
|
||||||
|
|
||||||
kysely@0.27.6: {}
|
kysely@0.28.14: {}
|
||||||
|
|
||||||
leac@0.6.0: {}
|
leac@0.6.0: {}
|
||||||
|
|
||||||
@@ -5421,17 +5400,9 @@ snapshots:
|
|||||||
|
|
||||||
mimic-function@5.0.1: {}
|
mimic-function@5.0.1: {}
|
||||||
|
|
||||||
minimatch@10.2.2:
|
minimatch@10.2.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.3
|
brace-expansion: 5.0.5
|
||||||
|
|
||||||
minimatch@3.1.3:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 1.1.12
|
|
||||||
|
|
||||||
minimatch@9.0.6:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 5.0.3
|
|
||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
@@ -5455,25 +5426,25 @@ snapshots:
|
|||||||
|
|
||||||
negotiator@0.6.3: {}
|
negotiator@0.6.3: {}
|
||||||
|
|
||||||
next@16.1.6(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
next@16.2.1(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 16.1.6
|
'@next/env': 16.2.1
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
baseline-browser-mapping: 2.9.19
|
baseline-browser-mapping: 2.10.11
|
||||||
caniuse-lite: 1.0.30001762
|
caniuse-lite: 1.0.30001781
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
react-dom: 19.2.3(react@19.2.3)
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
styled-jsx: 5.1.6(react@19.2.3)
|
styled-jsx: 5.1.6(react@19.2.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 16.1.6
|
'@next/swc-darwin-arm64': 16.2.1
|
||||||
'@next/swc-darwin-x64': 16.1.6
|
'@next/swc-darwin-x64': 16.2.1
|
||||||
'@next/swc-linux-arm64-gnu': 16.1.6
|
'@next/swc-linux-arm64-gnu': 16.2.1
|
||||||
'@next/swc-linux-arm64-musl': 16.1.6
|
'@next/swc-linux-arm64-musl': 16.2.1
|
||||||
'@next/swc-linux-x64-gnu': 16.1.6
|
'@next/swc-linux-x64-gnu': 16.2.1
|
||||||
'@next/swc-linux-x64-musl': 16.1.6
|
'@next/swc-linux-x64-musl': 16.2.1
|
||||||
'@next/swc-win32-arm64-msvc': 16.1.6
|
'@next/swc-win32-arm64-msvc': 16.2.1
|
||||||
'@next/swc-win32-x64-msvc': 16.1.6
|
'@next/swc-win32-x64-msvc': 16.2.1
|
||||||
'@playwright/test': 1.57.0
|
'@playwright/test': 1.57.0
|
||||||
sharp: 0.34.5
|
sharp: 0.34.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5564,7 +5535,7 @@ snapshots:
|
|||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
picomatch@4.0.4: {}
|
||||||
|
|
||||||
pkg-types@2.3.0:
|
pkg-types@2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5585,7 +5556,7 @@ snapshots:
|
|||||||
postcss-load-config@3.1.4(postcss@8.5.6):
|
postcss-load-config@3.1.4(postcss@8.5.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
lilconfig: 2.1.0
|
lilconfig: 2.1.0
|
||||||
yaml: 1.10.2
|
yaml: 2.8.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
|
|
||||||
@@ -5748,23 +5719,23 @@ snapshots:
|
|||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
|
|
||||||
runed@0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6):
|
runed@0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
lz-string: 1.5.0
|
lz-string: 1.5.0
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
|
|
||||||
runed@0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(zod@4.3.6):
|
runed@0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(zod@4.3.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
lz-string: 1.5.0
|
lz-string: 1.5.0
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
|
||||||
sade@1.8.1:
|
sade@1.8.1:
|
||||||
@@ -5785,7 +5756,7 @@ snapshots:
|
|||||||
|
|
||||||
sharp@0.34.5:
|
sharp@0.34.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@img/colour': 1.0.0
|
'@img/colour': 1.1.0
|
||||||
detect-libc: 2.1.2
|
detect-libc: 2.1.2
|
||||||
semver: 7.7.4
|
semver: 7.7.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -5840,7 +5811,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
socket.io-parser@4.2.5:
|
socket.io-parser@4.2.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@socket.io/component-emitter': 3.1.2
|
'@socket.io/component-emitter': 3.1.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -5855,7 +5826,7 @@ snapshots:
|
|||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
engine.io: 6.6.5
|
engine.io: 6.6.5
|
||||||
socket.io-adapter: 2.5.6
|
socket.io-adapter: 2.5.6
|
||||||
socket.io-parser: 4.2.5
|
socket.io-parser: 4.2.6
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -5872,10 +5843,10 @@ snapshots:
|
|||||||
source-map@0.6.1:
|
source-map@0.6.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
sqlite-wasm-kysely@0.3.0(kysely@0.27.6):
|
sqlite-wasm-kysely@0.3.0(kysely@0.28.14):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sqlite.org/sqlite-wasm': 3.48.0-build4
|
'@sqlite.org/sqlite-wasm': 3.48.0-build4
|
||||||
kysely: 0.27.6
|
kysely: 0.28.14
|
||||||
|
|
||||||
stdin-discarder@0.2.2: {}
|
stdin-discarder@0.2.2: {}
|
||||||
|
|
||||||
@@ -5925,11 +5896,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|
||||||
svelte-check@4.4.3(picomatch@4.0.3)(svelte@5.53.6)(typescript@5.9.3):
|
svelte-check@4.4.3(picomatch@4.0.4)(svelte@5.53.6)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
chokidar: 4.0.3
|
chokidar: 4.0.3
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
sade: 1.8.1
|
sade: 1.8.1
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
@@ -5953,10 +5924,10 @@ snapshots:
|
|||||||
runed: 0.28.0(svelte@5.53.6)
|
runed: 0.28.0(svelte@5.53.6)
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
|
|
||||||
svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6):
|
svelte-toolbelt@0.10.6(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)
|
runed: 0.35.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)
|
||||||
style-to-object: 1.0.14
|
style-to-object: 1.0.14
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -5986,7 +5957,7 @@ snapshots:
|
|||||||
aria-query: 5.3.1
|
aria-query: 5.3.1
|
||||||
axobject-query: 4.1.0
|
axobject-query: 4.1.0
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
devalue: 5.6.3
|
devalue: 5.6.4
|
||||||
esm-env: 1.2.2
|
esm-env: 1.2.2
|
||||||
esrap: 2.2.3
|
esrap: 2.2.3
|
||||||
is-reference: 3.0.3
|
is-reference: 3.0.3
|
||||||
@@ -5994,10 +5965,10 @@ snapshots:
|
|||||||
magic-string: 0.30.21
|
magic-string: 0.30.21
|
||||||
zimmerframe: 1.1.4
|
zimmerframe: 1.1.4
|
||||||
|
|
||||||
sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3):
|
sveltekit-superforms@2.30.0(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(@types/json-schema@7.0.15)(svelte@5.53.6)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
'@sveltejs/kit': 2.53.4(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.6)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.53.6)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))
|
||||||
devalue: 5.6.3
|
devalue: 5.6.4
|
||||||
memoize-weak: 1.0.2
|
memoize-weak: 1.0.2
|
||||||
svelte: 5.53.6
|
svelte: 5.53.6
|
||||||
ts-deepmerge: 7.0.3
|
ts-deepmerge: 7.0.3
|
||||||
@@ -6009,7 +5980,7 @@ snapshots:
|
|||||||
'@vinejs/vine': 3.0.1
|
'@vinejs/vine': 3.0.1
|
||||||
arktype: 2.1.29
|
arktype: 2.1.29
|
||||||
class-validator: 0.14.3
|
class-validator: 0.14.3
|
||||||
effect: 3.19.19
|
effect: 3.21.0
|
||||||
joi: 17.13.3
|
joi: 17.13.3
|
||||||
json-schema-to-ts: 3.1.1
|
json-schema-to-ts: 3.1.1
|
||||||
superstruct: 2.0.2
|
superstruct: 2.0.2
|
||||||
@@ -6055,8 +6026,8 @@ snapshots:
|
|||||||
|
|
||||||
tinyglobby@0.2.15:
|
tinyglobby@0.2.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
|
|
||||||
toposort@2.0.2:
|
toposort@2.0.2:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -6128,7 +6099,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
acorn: 8.16.0
|
acorn: 8.16.0
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
webpack-virtual-modules: 0.6.2
|
webpack-virtual-modules: 0.6.2
|
||||||
|
|
||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
@@ -6153,20 +6124,20 @@ snapshots:
|
|||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
vite-plugin-compression@0.5.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1):
|
vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.3
|
esbuild: 0.27.3
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.4
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
rollup: 4.59.0
|
rollup: 4.59.0
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
@@ -6177,11 +6148,11 @@ snapshots:
|
|||||||
lightningcss: 1.31.1
|
lightningcss: 1.31.1
|
||||||
terser: 5.44.0
|
terser: 5.44.0
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
yaml: 2.8.1
|
yaml: 2.8.3
|
||||||
|
|
||||||
vitefu@1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
vitefu@1.1.1(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)
|
vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2: {}
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
||||||
@@ -6205,10 +6176,7 @@ snapshots:
|
|||||||
|
|
||||||
y18n@4.0.3: {}
|
y18n@4.0.3: {}
|
||||||
|
|
||||||
yaml@1.10.2: {}
|
yaml@2.8.3: {}
|
||||||
|
|
||||||
yaml@2.8.1:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
yargs-parser@18.1.3:
|
yargs-parser@18.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -10,9 +10,16 @@ onlyBuiltDependencies:
|
|||||||
overrides:
|
overrides:
|
||||||
'@isaacs/brace-expansion': '>=5.0.1'
|
'@isaacs/brace-expansion': '>=5.0.1'
|
||||||
cookie@<0.7.0: '>=0.7.0'
|
cookie@<0.7.0: '>=0.7.0'
|
||||||
devalue: ^5.6.2
|
devalue: '>=5.6.4'
|
||||||
glob@>=11.0.0 <11.1.0: '>=11.1.0'
|
glob@>=11.0.0 <11.1.0: '>=11.1.0'
|
||||||
js-yaml@>=4.0.0 <4.1.1: '>=4.1.1'
|
js-yaml@>=4.0.0 <4.1.1: '>=4.1.1'
|
||||||
next: '>=16.1.5'
|
next: '>=16.1.7'
|
||||||
valibot@>=0.31.0 <1.2.0: '>=1.2.0'
|
valibot@>=0.31.0 <1.2.0: '>=1.2.0'
|
||||||
validator@<13.15.20: '>=13.15.20'
|
validator@<13.15.20: '>=13.15.20'
|
||||||
|
flatted: '>=3.4.2'
|
||||||
|
'socket.io-parser': '>=4.2.6'
|
||||||
|
kysely: '>=0.28.14'
|
||||||
|
minimatch: '>=10.2.3'
|
||||||
|
picomatch: '>=4.0.4'
|
||||||
|
effect: '>=3.20.0'
|
||||||
|
yaml: '>=2.8.3'
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ test('Change Locale', async ({ page }) => {
|
|||||||
// Check if the validation messages are translated because they are provided by Zod
|
// Check if the validation messages are translated because they are provided by Zod
|
||||||
await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill('');
|
await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill('');
|
||||||
await page.getByRole('button', { name: 'Opslaan' }).click();
|
await page.getByRole('button', { name: 'Opslaan' }).click();
|
||||||
await expect(page.getByText('Te kort: verwacht dat string >=2 tekens heeft')).toBeVisible();
|
await expect(page.getByText('Te kort: verwacht dat string >=1 tekens heeft')).toBeVisible();
|
||||||
|
|
||||||
// Clear all cookies and sign in again to check if the language is still set to Dutch
|
// Clear all cookies and sign in again to check if the language is still set to Dutch
|
||||||
await page.context().clearCookies();
|
await page.context().clearCookies();
|
||||||
@@ -76,7 +76,7 @@ test('Change Locale', async ({ page }) => {
|
|||||||
|
|
||||||
await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill('');
|
await page.getByRole('textbox', { name: 'Gebruikersnaam' }).fill('');
|
||||||
await page.getByRole('button', { name: 'Opslaan' }).click();
|
await page.getByRole('button', { name: 'Opslaan' }).click();
|
||||||
await expect(page.getByText('Te kort: verwacht dat string >=2 tekens heeft')).toBeVisible();
|
await expect(page.getByText('Te kort: verwacht dat string >=1 tekens heeft')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Add passkey to an account', async ({ page }) => {
|
test('Add passkey to an account', async ({ page }) => {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function compareExports(dir1: string, dir2: string): void {
|
|||||||
const hashes2 = hashAllFiles(dir2);
|
const hashes2 = hashAllFiles(dir2);
|
||||||
|
|
||||||
const files1 = Object.keys(hashes1).sort();
|
const files1 = Object.keys(hashes1).sort();
|
||||||
const files2 = Object.keys(hashes2).sort();
|
const files2 = Object.keys(hashes2).sort().filter(p => !p.includes('.inited'));
|
||||||
expect(files2).toEqual(files1);
|
expect(files2).toEqual(files1);
|
||||||
|
|
||||||
for (const file of files1) {
|
for (const file of files1) {
|
||||||
|
|||||||
Reference in New Issue
Block a user