diff --git a/.DS_Store b/.DS_Store index 417ec8c..ab838e9 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/auth/auth.go b/auth/auth.go index 4439143..505585f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -29,6 +29,7 @@ var secrets struct { } const sessionTTL = 7 * 24 * time.Hour +const minPasswordLength = 8 type AuthParams struct { SessionCookie *http.Cookie `cookie:"session"` @@ -96,13 +97,63 @@ func sessionToken(p *AuthParams) (uuid.UUID, bool) { // pepper secret using SHA-256, producing a fixed-size digest. This both mixes // in the pepper and keeps the input to bcrypt within its 72-byte limit // regardless of the original password's length. -func pepperedPassword(password string) []byte { +func pepperedPassword(password string) ([]byte, error) { + if strings.TrimSpace(secrets.PasswordPepper) == "" { + return nil, &errs.Error{Code: errs.Internal, Message: "password pepper is not configured"} + } sum := sha256.Sum256([]byte(password + secrets.PasswordPepper)) - return sum[:] + return sum[:], nil +} + +func validatePassword(password string) error { + if strings.TrimSpace(password) == "" { + return &errs.Error{Code: errs.InvalidArgument, Message: "password is required"} + } + if len(password) < minPasswordLength { + return &errs.Error{Code: errs.InvalidArgument, Message: "password must be at least 8 characters long"} + } + return nil +} + +// ValidatePassword exposes the shared password rules to other services. +func ValidatePassword(password string) error { + return validatePassword(password) +} + +func authenticatedUserID() (uuid.UUID, error) { + uid, ok := auth.UserID() + if !ok { + return uuid.Nil, &errs.Error{Code: errs.Unauthenticated, Message: "missing auth"} + } + return parseUserID(uid) +} + +func parseUserID(uid auth.UID) (uuid.UUID, error) { + userID, err := uuid.FromString(string(uid)) + if err != nil { + return uuid.Nil, errs.WrapCode(err, errs.Internal, "invalid user id") + } + return userID, nil +} + +func verifyPassword(hash, password string) error { + peppered, err := pepperedPassword(password) + if err != nil { + return err + } + return bcrypt.CompareHashAndPassword([]byte(hash), peppered) +} + +func isPasswordReuse(currentHash, newPassword string) bool { + return verifyPassword(currentHash, newPassword) == nil } func hashPassword(password string) (string, error) { - hash, err := bcrypt.GenerateFromPassword(pepperedPassword(password), bcrypt.DefaultCost) + peppered, err := pepperedPassword(password) + if err != nil { + return "", err + } + hash, err := bcrypt.GenerateFromPassword(peppered, bcrypt.DefaultCost) if err != nil { return "", err } diff --git a/auth/credentials.go b/auth/credentials.go index 1aa1432..de50434 100644 --- a/auth/credentials.go +++ b/auth/credentials.go @@ -4,11 +4,9 @@ import ( "context" "errors" - "encore.dev/beta/auth" "encore.dev/beta/errs" "encore.dev/storage/sqldb" "encore.dev/types/uuid" - "golang.org/x/crypto/bcrypt" ) type RegisterParams struct { @@ -16,6 +14,16 @@ type RegisterParams struct { Password string `json:"password" encore:"sensitive"` } +func (p *RegisterParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "register params are required"} + } + if p.UserID == uuid.Nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "user id is required"} + } + return validatePassword(p.Password) +} + // Register stores the initial password hash for a newly created profile. // Called directly (service-to-service) by profiles.Insert during registration, // since that flow needs to know immediately whether credential setup succeeded. @@ -35,6 +43,23 @@ func Register(ctx context.Context, p *RegisterParams) error { return nil } +func (p *SetPasswordParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "password params are required"} + } + if err := validatePassword(p.CurrentPassword); err != nil { + return err + } + return validatePassword(p.Password) +} + +func (p *SetUserPasswordParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "user password params are required"} + } + return validatePassword(p.Password) +} + type SetPasswordParams struct { CurrentPassword string `json:"current_password" encore:"sensitive"` Password string `json:"password" encore:"sensitive"` @@ -49,40 +74,11 @@ type SetUserPasswordParams struct { // //encore:api auth method=PUT path=/auth/password func SetPassword(ctx context.Context, p *SetPasswordParams) error { - uid, ok := auth.UserID() - if !ok { - return &errs.Error{Code: errs.Unauthenticated, Message: "missing auth"} - } - userID, err := uuid.FromString(string(uid)) + userID, err := authenticatedUserID() if err != nil { - return errs.WrapCode(err, errs.Internal, "invalid user id") + return err } - - var currentHash string - err = db.QueryRow(ctx, ` - SELECT password_hash FROM credentials WHERE user_id = $1 - `, userID).Scan(¤tHash) - if errors.Is(err, sqldb.ErrNoRows) { - return &errs.Error{Code: errs.NotFound, Message: "credentials not found"} - } - if err != nil { - return errs.WrapCode(err, errs.Internal, "failed to fetch credentials") - } - if err := bcrypt.CompareHashAndPassword([]byte(currentHash), pepperedPassword(p.CurrentPassword)); err != nil { - return &errs.Error{Code: errs.Unauthenticated, Message: "invalid current password"} - } - - hash, err := hashPassword(p.Password) - if err != nil { - return errs.WrapCode(err, errs.Internal, "failed to hash password") - } - _, err = db.Exec(ctx, ` - UPDATE credentials SET password_hash = $2, updated_at = NOW() WHERE user_id = $1 - `, userID, hash) - if err != nil { - return errs.WrapCode(err, errs.Internal, "failed to update password") - } - return nil + return setPasswordWithCurrentCheck(ctx, userID, p.CurrentPassword, p.Password) } // SetUserPassword changes a user's password from trusted internal flows. @@ -92,13 +88,77 @@ func SetUserPassword(ctx context.Context, userID uuid.UUID, p *SetUserPasswordPa if p.UserID != uuid.Nil && p.UserID != userID { return &errs.Error{Code: errs.InvalidArgument, Message: "user id mismatch"} } + return replacePassword(ctx, userID, p.Password) +} - hash, err := hashPassword(p.Password) +func setPasswordWithCurrentCheck(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error { + if err := validatePassword(newPassword); err != nil { + return err + } + + tx, err := db.Begin(ctx) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to begin password update transaction") + } + defer func() { + if err != nil { + _ = tx.Rollback() + } + }() + + var currentHash string + err = tx.QueryRow(ctx, ` + SELECT password_hash FROM credentials WHERE user_id = $1 FOR UPDATE + `, userID).Scan(¤tHash) + if errors.Is(err, sqldb.ErrNoRows) { + return &errs.Error{Code: errs.NotFound, Message: "credentials not found"} + } + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to fetch credentials") + } + if err = verifyPassword(currentHash, currentPassword); err != nil { + return &errs.Error{Code: errs.Unauthenticated, Message: "invalid current password"} + } + if isPasswordReuse(currentHash, newPassword) { + return &errs.Error{Code: errs.InvalidArgument, Message: "new password must be different from the current password"} + } + + hash, err := hashPassword(newPassword) if err != nil { return errs.WrapCode(err, errs.Internal, "failed to hash password") } + _, err = tx.Exec(ctx, ` + UPDATE credentials SET password_hash = $2, updated_at = NOW() WHERE user_id = $1 + `, userID, hash) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to update password") + } + if err = tx.Commit(); err != nil { + return errs.WrapCode(err, errs.Internal, "failed to commit password update") + } + return nil +} - res, err := db.Exec(ctx, ` +func replacePassword(ctx context.Context, userID uuid.UUID, newPassword string) error { + if err := validatePassword(newPassword); err != nil { + return err + } + + tx, err := db.Begin(ctx) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to begin password update transaction") + } + defer func() { + if err != nil { + _ = tx.Rollback() + } + }() + + hash, err := hashPassword(newPassword) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to hash password") + } + res, err := tx.Exec(ctx, ` UPDATE credentials SET password_hash = $2, updated_at = NOW() WHERE user_id = $1 `, userID, hash) if err != nil { @@ -107,5 +167,8 @@ func SetUserPassword(ctx context.Context, userID uuid.UUID, p *SetUserPasswordPa if res.RowsAffected() == 0 { return &errs.Error{Code: errs.NotFound, Message: "credentials not found"} } + if err = tx.Commit(); err != nil { + return errs.WrapCode(err, errs.Internal, "failed to commit password update") + } return nil } diff --git a/auth/login.go b/auth/login.go index e50dcab..a4887e3 100644 --- a/auth/login.go +++ b/auth/login.go @@ -7,10 +7,10 @@ import ( "encore.dev/beta/auth" "encore.dev/beta/errs" + "encore.dev/cron" "encore.dev/pubsub" "encore.dev/storage/sqldb" "encore.dev/types/uuid" - "golang.org/x/crypto/bcrypt" ) type LoginParams struct { @@ -18,6 +18,16 @@ type LoginParams struct { Password string `json:"password" encore:"sensitive"` } +func (p *LoginParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "login params are required"} + } + if p.UserID == uuid.Nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "user id is required"} + } + return validatePassword(p.Password) +} + type LoginResponse struct { Token uuid.UUID `json:"token"` ExpiresAt time.Time `json:"expires_at"` @@ -41,7 +51,7 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) { if err != nil { return nil, errs.WrapCode(err, errs.Internal, "failed to fetch credentials") } - if err := bcrypt.CompareHashAndPassword([]byte(hash), pepperedPassword(p.Password)); err != nil { + if err := verifyPassword(hash, p.Password); err != nil { return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid credentials"} } @@ -59,6 +69,42 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) { return &LoginResponse{Token: token, ExpiresAt: expiresAt}, nil } +var _ = cron.NewJob("cleanup-expired-sessions", cron.JobConfig{ + Title: "Cleanup expired sessions", + Every: 1 * cron.Hour, + Endpoint: CleanupExpiredSessions, +}) + +var _ = cron.NewJob("cleanup-older-login-results", cron.JobConfig{ + Title: "Cleanup stale login results", + Every: 6 * cron.Hour, + Endpoint: CleanupLoginResults, +}) + +// CleanupExpiredSessions removes expired sessions from the database. +// +//encore:api private +func CleanupExpiredSessions(ctx context.Context) error { + _, err := db.Exec(ctx, `DELETE FROM sessions WHERE expires_at <= NOW()`) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to purge expired sessions") + } + return nil +} + +// CleanupLoginResults removes stale async auth correlation rows that are no longer needed. +// +//encore:api private +func CleanupLoginResults(ctx context.Context) error { + _, err := db.Exec(ctx, ` + DELETE FROM login_results WHERE created_at < NOW() - INTERVAL '24 hours' + `) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to purge stale login results") + } + return nil +} + // Logout revokes the session used to authenticate the current request. // //encore:api auth method=POST path=/auth/logout @@ -114,6 +160,25 @@ func handleLoginRequested(ctx context.Context, ev *LoginRequested) error { result.Token = resp.Token result.ExpiresAt = resp.ExpiresAt } + if err := storeLoginResult(ctx, result); err != nil { + return err + } _, err := LoginResults.Publish(ctx, result) return err } + +func storeLoginResult(ctx context.Context, result *LoginCompleted) error { + _, err := db.Exec(ctx, ` + INSERT INTO login_results (request_id, ok, token, expires_at) + VALUES ($1, $2, $3, $4) + ON CONFLICT (request_id) DO UPDATE SET + ok = EXCLUDED.ok, + token = EXCLUDED.token, + expires_at = EXCLUDED.expires_at, + updated_at = NOW() + `, result.RequestID, result.OK, result.Token, result.ExpiresAt) + if err != nil { + return errs.WrapCode(err, errs.Internal, "failed to store login result") + } + return nil +} diff --git a/auth/migrations/2_create_login_results.up.sql b/auth/migrations/2_create_login_results.up.sql new file mode 100644 index 0000000..d500533 --- /dev/null +++ b/auth/migrations/2_create_login_results.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE login_results ( + request_id UUID PRIMARY KEY, + ok BOOLEAN NOT NULL, + token UUID, + expires_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX login_results_created_at_idx ON login_results (created_at); diff --git a/auth/migrations/3_add_expired_sessions_idx.up.sql b/auth/migrations/3_add_expired_sessions_idx.up.sql new file mode 100644 index 0000000..fa8f66e --- /dev/null +++ b/auth/migrations/3_add_expired_sessions_idx.up.sql @@ -0,0 +1 @@ +CREATE INDEX sessions_expires_at_idx ON sessions (expires_at); diff --git a/auth/password_validation_test.go b/auth/password_validation_test.go new file mode 100644 index 0000000..1cd3e01 --- /dev/null +++ b/auth/password_validation_test.go @@ -0,0 +1,111 @@ +package auth + +import ( + "testing" + + authpkg "encore.dev/beta/auth" + "encore.dev/beta/errs" + "encore.dev/types/uuid" +) + +func TestValidatePasswordRejectsShortAndEmptyValues(t *testing.T) { + for _, tc := range []struct { + name string + password string + }{ + {name: "empty", password: ""}, + {name: "spaces", password: " "}, + {name: "short", password: "short"}, + } { + t.Run(tc.name, func(t *testing.T) { + err := validatePassword(tc.password) + if err == nil { + t.Fatalf("validatePassword(%q) = nil, want error", tc.password) + } + e, ok := err.(*errs.Error) + if !ok { + t.Fatalf("validatePassword(%q) returned %T, want *errs.Error", tc.password, err) + } + if e.Code != errs.InvalidArgument { + t.Fatalf("validatePassword(%q) returned code %v, want %v", tc.password, e.Code, errs.InvalidArgument) + } + }) + } +} + +func TestLoginParamsValidate(t *testing.T) { + uid, err := uuid.NewV4() + if err != nil { + t.Fatalf("uuid.NewV4() failed: %v", err) + } + + if err := (&LoginParams{UserID: uuid.Nil, Password: "validpass"}).Validate(); err == nil { + t.Fatal("Validate() should reject nil user id") + } + if err := (&LoginParams{UserID: uid, Password: "short"}).Validate(); err == nil { + t.Fatal("Validate() should reject short password") + } + if err := (&LoginParams{UserID: uid, Password: "validpass"}).Validate(); err != nil { + t.Fatalf("Validate() returned unexpected error: %v", err) + } +} + +func TestRegisterParamsValidate(t *testing.T) { + uid, err := uuid.NewV4() + if err != nil { + t.Fatalf("uuid.NewV4() failed: %v", err) + } + + if err := (&RegisterParams{UserID: uuid.Nil, Password: "validpass"}).Validate(); err == nil { + t.Fatal("Validate() should reject nil user id") + } + if err := (&RegisterParams{UserID: uid, Password: "weak"}).Validate(); err == nil { + t.Fatal("Validate() should reject weak password") + } +} + +func TestPepperedPasswordRequiresConfiguredSecret(t *testing.T) { + old := secrets.PasswordPepper + secrets.PasswordPepper = "" + defer func() { secrets.PasswordPepper = old }() + + if _, err := pepperedPassword("validpass"); err == nil { + t.Fatal("pepperedPassword() should fail when PasswordPepper is empty") + } +} + +func TestPasswordReuseDetection(t *testing.T) { + old := secrets.PasswordPepper + secrets.PasswordPepper = "pepper-test" + defer func() { secrets.PasswordPepper = old }() + + hash, err := hashPassword("this-is-a-valid-password") + if err != nil { + t.Fatalf("hashPassword() failed: %v", err) + } + if !isPasswordReuse(hash, "this-is-a-valid-password") { + t.Fatal("isPasswordReuse() should return true for the same password") + } + if isPasswordReuse(hash, "another-very-different-pass") { + t.Fatal("isPasswordReuse() should return false for a different password") + } +} + +func TestParseUserID(t *testing.T) { + uid, err := uuid.NewV4() + if err != nil { + t.Fatalf("uuid.NewV4() failed: %v", err) + } + + parsed, err := parseUserID(authpkg.UID(uid.String())) + if err != nil { + t.Fatalf("parseUserID() returned unexpected error: %v", err) + } + if parsed != uid { + t.Fatalf("parseUserID() mismatch: got %v, want %v", parsed, uid) + } + + if _, err := parseUserID(authpkg.UID("not-a-uuid")); err == nil { + t.Fatal("parseUserID() should reject invalid UUID strings") + } +} diff --git a/frontend/.editorconfig b/frontend/.editorconfig deleted file mode 100644 index f654551..0000000 --- a/frontend/.editorconfig +++ /dev/null @@ -1,7 +0,0 @@ -[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}] -charset = utf-8 -indent_size = 2 -indent_style = space -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 54404c5..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -.DS_Store -.thumbs.db -node_modules - -# .env files -.env* - -# Quasar core related directories -.quasar -/dist -/quasar.config.*.temporary.compiled* - -# Cordova related directories and files -/src-cordova/node_modules -/src-cordova/platforms -/src-cordova/plugins -/src-cordova/www - -# Capacitor related directories and files -/src-capacitor/www -/src-capacitor/node_modules - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json deleted file mode 100644 index f7f50b4..0000000 --- a/frontend/.vscode/extensions.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "recommendations": [ - "editorconfig.editorconfig", - "vue.volar", - "wayou.vscode-todo-highlight" - ], - "unwantedRecommendations": [ - "octref.vetur", - "hookyqr.beautify", - "dbaeumer.jshint", - "ms-vscode.vscode-typescript-tslint-plugin" - ] -} \ No newline at end of file diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json deleted file mode 100644 index f76e2c8..0000000 --- a/frontend/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": true, - "js/ts.tsdk.path": "node_modules/typescript/lib", - "search.exclude": { - "dist/": true, - ".quasar/": true, - "/quasar.config.js.temporary.*": true - } -} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index 492d8d7..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Quasar App (frontend) - -## Install the dependencies - -```bash -pnpm install -# or: yarn/npm/bun install -``` - -### Start the app in development mode (HMR, error reporting, etc.) - -```bash -quasar dev -``` - -### Build the app for production - -```bash -quasar build -``` - -### Customize the configuration -See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js). diff --git a/frontend/env.d.ts b/frontend/env.d.ts deleted file mode 100644 index 1d82c07..0000000 --- a/frontend/env.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Add types (that are not auto-magically added by Quasar CLI already) - * for your custom variables to avoid TypeScript errors, like dynamic - * process.env variables or definitions in dotenv files configured ONLY - * for the /quasar.config file itself. - * - * https://quasar.dev/quasar-cli-vite/handling-import-meta-env#type-inference - * - * @example - * interface ImportMetaEnv { - * readonly MY_VAR: string; - * readonly MY_OTHER_VAR: string; - * } - */ -interface ImportMetaEnv {} diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index 1f7a261..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - <%= productName %> - - - - - - - - - - - - - - - - - - diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 48c20ed..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.1", - "description": "A Quasar Project", - "productName": "Quasar App", - "author": "fabio ", - "type": "module", - "private": true, - "scripts": { - "dev": "quasar dev", - "build": "quasar build", - "typecheck": "vue-tsc --noEmit", - "zod:sync": "node tools/zod-sync.mjs", - "postinstall": "quasar prepare --silent" - }, - "dependencies": { - "@quasar/extras": "^2.0.0", - "pinia": "^3.0.4", - "quasar": "^2.20.0", - "vue": "^3.5.22", - "vue-advanced-cropper": "^2.8.9", - "vue-i18n": "^11.4.6", - "vue-router": "^5.0.6", - "zod": "^4.4.3" - }, - "devDependencies": { - "@quasar/app-vite": "^3.0.0-rc.2", - "@types/node": "^22.19.11", - "autoprefixer": "^10.4.27", - "typescript": "^6.0.0", - "vue-tsc": "^3.3.3" - }, - "keywords": [ - "quasar", - "quasar-app", - "quasar-cli", - "quasar-app-vite", - "vite", - "vue", - "vuejs" - ], - "engines": { - "node": ">= 26 || ^24 || ^22.12" - } -} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml deleted file mode 100644 index 0652edd..0000000 --- a/frontend/pnpm-lock.yaml +++ /dev/null @@ -1,3035 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@quasar/extras': - specifier: ^2.0.0 - version: 2.0.1 - pinia: - specifier: ^3.0.4 - version: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) - quasar: - specifier: ^2.20.0 - version: 2.20.1 - vue: - specifier: ^3.5.22 - version: 3.5.38(typescript@6.0.3) - vue-advanced-cropper: - specifier: ^2.8.9 - version: 2.8.9(vue@3.5.38(typescript@6.0.3)) - vue-i18n: - specifier: ^11.4.6 - version: 11.4.6(vue@3.5.38(typescript@6.0.3)) - vue-router: - specifier: ^5.0.6 - version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@quasar/app-vite': - specifier: ^3.0.0-rc.2 - version: 3.0.0-rc.3(@types/node@22.19.21)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(quasar@2.20.1)(sass@1.100.0)(terser@5.48.0)(typescript@6.0.3)(vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))(yaml@2.9.0) - '@types/node': - specifier: ^22.19.11 - version: 22.19.21 - autoprefixer: - specifier: ^10.4.27 - version: 10.5.0(postcss@8.5.15) - typescript: - specifier: ^6.0.0 - version: 6.0.3 - vue-tsc: - specifier: ^3.3.3 - version: 3.3.5(typescript@6.0.3) - -packages: - - '@babel/generator@8.0.0': - resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@8.0.0': - resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@8.0.0': - resolution: {integrity: sha512-kXxQVZHNOctSJJsqzmcbPSCEkM6oHNnDIkua7g9RCO9xRHj2eCiKvRx2KPdfWR9QxcGWnK/oArrtunmie3rL9g==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/parser@8.0.0': - resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} - engines: {node: ^22.18.0 || >=24.11.0} - hasBin: true - - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} - engines: {node: '>=6.9.0'} - - '@babel/types@8.0.0': - resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} - engines: {node: ^22.18.0 || >=24.11.0} - - '@bufbuild/protobuf@2.12.0': - resolution: {integrity: sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA==} - - '@clack/core@1.4.1': - resolution: {integrity: sha512-FILJa1gGKEFTGZAJE9RpVhrjKz3c3h4ar60dSv6cGuDqufQ84YEIS3GAGvZiN+H6yaLbbvTFNejjCC4tXpZEuw==} - engines: {node: '>= 20.12.0'} - - '@clack/prompts@1.5.1': - resolution: {integrity: sha512-zccHj2z2oCCO4yrDiRSlFOxWerGqRiysP7a5jPK6uoI9URKAquwY42Dd/iUP8JWHxEzdRe4TlbvZCo8z1/mhrw==} - engines: {node: '>= 20.12.0'} - - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - - '@emnapi/core@1.11.0': - resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} - - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - - '@emnapi/runtime@1.11.0': - resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} - - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - - '@emnapi/wasi-threads@1.2.2': - resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - - '@intlify/core-base@11.4.6': - resolution: {integrity: sha512-EOeHO95XESK9IFHgHeZXunsM/WBAoCA0DlaWODvx14vKmetAuS97t+l6Xe9hTUqntPpF93vtVSjjUDafw3wXMw==} - engines: {node: '>= 22'} - - '@intlify/devtools-types@11.4.6': - resolution: {integrity: sha512-wowQPpNem56b2d43IJmqbrzG2FeBKe5f/kUGlpNuBmXs6OSqncF8m1+1lxHuW8ISZJF0ma2RkW3iLkw0g0G4VA==} - engines: {node: '>= 22'} - - '@intlify/message-compiler@11.4.6': - resolution: {integrity: sha512-5nj3jULqeTAC1WovwMs1LQWgatTa2pM/rXN9T3XW8rdOtXW9ZF6/GLSNFTKDQmPLwclhPdgUWLJ/4w3fMeeC/Q==} - engines: {node: '>= 22'} - - '@intlify/shared@11.4.6': - resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==} - engines: {node: '>= 22'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@napi-rs/wasm-runtime@1.1.5': - resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - - '@oxc-project/types@0.133.0': - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} - - '@oxc-project/types@0.135.0': - resolution: {integrity: sha512-wR+xRdFkUBMvcAjBJ2q2kcZM6d+DKu2NgoOyxZgYwZdLhmiv6+rnO8PZ/P68kMiZtIKm+pW7zyEJ4kSOs0vo+Q==} - - '@parcel/watcher-android-arm64@2.5.6': - resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.6': - resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.6': - resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.6': - resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.6': - resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm-musl@2.5.6': - resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm64-glibc@2.5.6': - resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-arm64-musl@2.5.6': - resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-x64-glibc@2.5.6': - resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-linux-x64-musl@2.5.6': - resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-win32-arm64@2.5.6': - resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.6': - resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.6': - resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.6': - resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} - engines: {node: '>= 10.0.0'} - - '@peculiar/asn1-cms@2.8.0': - resolution: {integrity: sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==} - - '@peculiar/asn1-csr@2.8.0': - resolution: {integrity: sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==} - - '@peculiar/asn1-ecc@2.8.0': - resolution: {integrity: sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==} - - '@peculiar/asn1-pfx@2.8.0': - resolution: {integrity: sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==} - - '@peculiar/asn1-pkcs8@2.8.0': - resolution: {integrity: sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==} - - '@peculiar/asn1-pkcs9@2.8.0': - resolution: {integrity: sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==} - - '@peculiar/asn1-rsa@2.8.0': - resolution: {integrity: sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==} - - '@peculiar/asn1-schema@2.8.0': - resolution: {integrity: sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==} - - '@peculiar/asn1-x509-attr@2.8.0': - resolution: {integrity: sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==} - - '@peculiar/asn1-x509@2.8.0': - resolution: {integrity: sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==} - - '@peculiar/utils@2.0.3': - resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} - - '@peculiar/x509@1.14.3': - resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} - engines: {node: '>=20.0.0'} - - '@quasar/app-vite@3.0.0-rc.3': - resolution: {integrity: sha512-aXVV2SVpnbMMf74Que6lW9MgLssxwnuuFT2024D+YdslstGP1IQIaEH6OMvtCtSgri/ToyrVG3RJ5PIFT7jCkQ==} - engines: {node: ^30 || ^28 || ^26 || ^24 || ^22.22.0} - hasBin: true - peerDependencies: - '@capacitor/cli': '>= 5' - '@electron/packager': '>= 19' - builder-util: '>= 26' - electron-builder: '>= 22' - pinia: ^2.0.0 || ^3.0.0 - quasar: ^2.16.0 - typescript: '>= 5' - vue: ^3.2.29 - vue-router: '>= 5' - workbox-build: '>= 7' - peerDependenciesMeta: - '@capacitor/cli': - optional: true - '@electron/packager': - optional: true - builder-util: - optional: true - electron-builder: - optional: true - pinia: - optional: true - typescript: - optional: true - workbox-build: - optional: true - - '@quasar/art@1.0.0': - resolution: {integrity: sha512-IAV8Qg2QM3U2alywUcCELyOlHCUNbJYmiVDRTLgcgmyBlQwo3Wk33eHAyT9cpnV5CqlmuYXe6UZt0Ybi1m0lMA==} - - '@quasar/extras@2.0.1': - resolution: {integrity: sha512-gieBBROuXkVhvqXtbcVg4pHR5FDcaK+F9PaY2K2t8+EuK8OB/0aJq306DeF5+/Bqv8ixIXT9KhpRZj0HotaOew==} - - '@quasar/render-ssr-error@2.2.3': - resolution: {integrity: sha512-tlYP5iCJXD1Yv/5JrWSgwuJiIJ9FfTexhM4Sh3kwarSp3iyr5aEk7zoHx8+jwuy9l10H6lA/TDK57CRsN1Bdtw==} - engines: {node: '>= 16'} - - '@quasar/ssl-certificate@2.0.0': - resolution: {integrity: sha512-serxn3NXqolzYLqHeBy9s8hAF0IRmRew361U2jddLFUAlatDEW2XRkINg9ZFtCTFz/SVr9/ZSFpG6ItAk6Cd6w==} - engines: {node: '>= 16'} - - '@quasar/vite-plugin@1.12.0': - resolution: {integrity: sha512-EME6h0bhsHJgpRGeJA2BvC7R5rtZGg/FWCeWAH34c0wcV77n5iIRC3jsp9Ju6gW557sOiczJXxChmhYikTrWEw==} - engines: {node: '>=20'} - peerDependencies: - '@vitejs/plugin-vue': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.5 - quasar: ^2.16.0 - vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vue: ^3.0.0 - - '@rolldown/binding-android-arm64@1.0.3': - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-android-arm64@1.1.1': - resolution: {integrity: sha512-BLf9Wak/gfwVb7NQTQW4wBgL3oAfPy7ArEkhwV543OVw/uY6B47z5xYsqPSZ9PDOorvURPinws6ThaFuNgGLgA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.3': - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-arm64@1.1.1': - resolution: {integrity: sha512-rRZRPy/Ynb+Mxu0O6tfPldHeDgAn0sRij+IOUy6sFdUlv3hArGW/DloE3GfAxtqpOJuRNgF74Nr5gM4xBeU2jQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.3': - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.1.1': - resolution: {integrity: sha512-/MtefPxhKPyWWFM8L45OWiEqRf+eSU2Qv9ZAyTaoZOoGcoPKxbbhjTJO2/U2IThv0uDZ4NWHc3/oTsR6IEOtww==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.3': - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-freebsd-x64@1.1.1': - resolution: {integrity: sha512-202K+cpIi1kx/Zn7AtxBi4LTXSY67Aszb2K9rNsuW7FeBeh0nqoNmYLOSZidV0p88VPBzMmTZcHAdPNo3kRYzQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm-gnueabihf@1.1.1': - resolution: {integrity: sha512-wl9NfeXNUwrXtUc063tddmZFUI6qiNs1CNOwni0OL4vC7MqVSYugra3ZgtDmtVy8e0DluJTENmzIv2BwqLzT4Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.3': - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.1.1': - resolution: {integrity: sha512-at2EO4o7D/PJLC4Xik16bU4CcjQE2tSv1LfqMA0TRYQYQihRm3gZeDB8xaX28A9SFedibcAk5DeMCKt4REKG0A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.3': - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.1.1': - resolution: {integrity: sha512-5PUjZx366h9tkJTPJF5eibxOlK3sGoeRiBJLLjjEB5/kLDuhr6qB3LkhqLz1smXNgsX+pBhnbcJBrPE30HznAA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@rolldown/binding-linux-ppc64-gnu@1.1.1': - resolution: {integrity: sha512-1WK84XPeio3tjP1sM/TMXiC0G1i1iq1qGZ71KfNQjEFLU1kwD+Cv5T8nGySg/JUFwLbaScu6ve9DmeXlmqpkFA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@rolldown/binding-linux-s390x-gnu@1.0.3': - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@rolldown/binding-linux-s390x-gnu@1.1.1': - resolution: {integrity: sha512-1nS1X5z1uMJ369RU25hTpKCFvUwXZp12dIzlzk4S+UxCTcSVGsAE6tzkOSufv/7jnmAtK0ZlrsJxh2fGmsnVSw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.3': - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.1.1': - resolution: {integrity: sha512-NwX/wspnq4vYyMFsqbYvzums3ki/Tk8FZbMzMAovPDp3OfLeYKby/D+9osokadXuYEV3OvpeHlwnr/bG8QMixA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.3': - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.1.1': - resolution: {integrity: sha512-+n46LhDrJFQM+229y4oXtVpj1G50U/+XuHMlpnisFTEXhrg9f/YIjp/HymX+PVJjBEr7XHRs3CFLelV464pqwA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-openharmony-arm64@1.0.3': - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-openharmony-arm64@1.1.1': - resolution: {integrity: sha512-qGwEu47zOWYo7LdRHhCWTNhzwGtxXpdY6CERs8QEOqC0PXGGics/e3vHnyEUKt8xK6YkbZXFUCeklrpB6js8ag==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.3': - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-wasm32-wasi@1.1.1': - resolution: {integrity: sha512-qczfgEH8u0wHGGOXtA7UMAybNKuQjjEXairyQaw4WzjiMztfbgatG1h4OKays/smhtwbWltpKCRGtVhU6h40Sg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.3': - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-arm64-msvc@1.1.1': - resolution: {integrity: sha512-4psXSh63mSbwJF+mB8/9yfUUEzBiHYcUjxa32EO9ZwKy0Ypwjcg4F10D8SvVXgd+isy2UUUjF9HJJnDu1T/4Gg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.3': - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.1.1': - resolution: {integrity: sha512-MUvC/HLXVjzkQkWiExdVTEEWf0py+GfWm8WKSZsekG3ih6a21iy0BHPF07X3JIf3ifoklZXTIaHTLPBgH1C3dw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} - - '@types/chrome@0.1.43': - resolution: {integrity: sha512-ukH/HhmR6ht+UTX3PLUWJxgJ/RQcK2Foj4lBzsF24SIWsXgqhGuXqjd8FFuwioPP7d/JUKLM4g8GZxw3F4HTcA==} - - '@types/cordova@11.0.3': - resolution: {integrity: sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==} - - '@types/filesystem@0.0.36': - resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} - - '@types/filewriter@0.0.33': - resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} - - '@types/har-format@1.2.16': - resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} - - '@types/jsesc@2.5.1': - resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - - '@types/node@22.19.21': - resolution: {integrity: sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==} - - '@vitejs/plugin-vue@6.0.7': - resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vue: ^3.2.25 - - '@volar/language-core@2.4.28': - resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} - - '@volar/source-map@2.4.28': - resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} - - '@volar/typescript@2.4.28': - resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - - '@vue-macros/common@3.1.2': - resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==} - engines: {node: '>=20.19.0'} - peerDependencies: - vue: ^2.7.0 || ^3.2.25 - peerDependenciesMeta: - vue: - optional: true - - '@vue/compiler-core@3.5.38': - resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==} - - '@vue/compiler-dom@3.5.38': - resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==} - - '@vue/compiler-sfc@3.5.38': - resolution: {integrity: sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==} - - '@vue/compiler-ssr@3.5.38': - resolution: {integrity: sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==} - - '@vue/devtools-api@6.6.4': - resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - - '@vue/devtools-api@7.7.9': - resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} - - '@vue/devtools-api@8.1.3': - resolution: {integrity: sha512-73NMCvxXh8Hyozc/jiwqTFWVcCMyi11U1zmrq4DoukQJnuo8JHt6FsNu4HdeUDa8SpIp5vb7Q22GWgIq0efsXg==} - - '@vue/devtools-kit@7.7.9': - resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} - - '@vue/devtools-kit@8.1.3': - resolution: {integrity: sha512-cRn7GXiCQkMYU2Z3h3pM4YO/ndbx9FY1yLDAqIqPLcmIq4H6zAOJHein6tvZU3AfPwgrodqLiPBEF+YQaS8AxA==} - - '@vue/devtools-shared@7.7.9': - resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} - - '@vue/devtools-shared@8.1.3': - resolution: {integrity: sha512-CM3uIPL+v+lrJUk33+pxspYo0MhuMWlCvf7zC9fybifvCPyM2jUbYRPwoYEJgYbwRqPikm5HozbUhp60MF2QuA==} - - '@vue/language-core@3.3.5': - resolution: {integrity: sha512-UkKu5nhX89fg4VhlG/FOeI10G3cj/7radKT/cy9BT4Q9qJmJlSTAc/dP63Xqs29aypN4f39xUV6PsLNk/dcD6g==} - - '@vue/reactivity@3.5.38': - resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==} - - '@vue/runtime-core@3.5.38': - resolution: {integrity: sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==} - - '@vue/runtime-dom@3.5.38': - resolution: {integrity: sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==} - - '@vue/server-renderer@3.5.38': - resolution: {integrity: sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==} - peerDependencies: - vue: 3.5.38 - - '@vue/shared@3.5.38': - resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==} - - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} - engines: {node: '>=0.4.0'} - hasBin: true - - alien-signals@3.2.1: - resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} - - asn1js@3.0.10: - resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} - engines: {node: '>=12.0.0'} - - ast-kit@2.2.0: - resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} - engines: {node: '>=20.19.0'} - - ast-walker-scope@0.9.0: - resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} - engines: {node: '>=20.19.0'} - - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - baseline-browser-mapping@2.10.38: - resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} - engines: {node: '>=6.0.0'} - hasBin: true - - birpc@2.9.0: - resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} - - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - bytestreamjs@2.0.1: - resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} - engines: {node: '>=6.0.0'} - - camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - - caniuse-lite@1.0.30001799: - resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} - - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - - ci-info@4.4.0: - resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} - engines: {node: '>=8'} - - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - - clean-css@5.3.3: - resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} - engines: {node: '>= 10.0'} - - clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} - - colorjs.io@0.5.2: - resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} - - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - - copy-anything@4.0.5: - resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} - engines: {node: '>=18'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - debounce@1.2.1: - resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - - dot-prop@10.1.0: - resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} - engines: {node: '>=20'} - - dotenv-expand@13.0.0: - resolution: {integrity: sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==} - engines: {node: '>=12'} - - dotenv@17.4.2: - resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} - engines: {node: '>=12'} - - easy-bem@1.1.1: - resolution: {integrity: sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - electron-to-chromium@1.5.375: - resolution: {integrity: sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==} - - elementtree@0.1.7: - resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==} - engines: {node: '>= 0.4.0'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} - - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.8.3: - resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fs-extra@11.3.5: - resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} - engines: {node: '>=14.14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - html-minifier-terser@7.2.0: - resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==} - engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - immutable@5.1.6: - resolution: {integrity: sha512-q1swsS8K7L8usSHuOqF2TAoCCkonYz0SG38wLAggaa4Wml70zixIvt2ql4coQ2C2B3hTjltJry4r6bULwgAXLQ==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-in-ssh@1.0.0: - resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} - engines: {node: '>=20'} - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - - is-what@5.5.0: - resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} - engines: {node: '>=18'} - - is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - local-pkg@1.2.1: - resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} - engines: {node: '>=14'} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - magic-string-ast@1.0.3: - resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} - engines: {node: '>=20.19.0'} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - muggle-string@0.4.1: - resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - - node-releases@2.0.48: - resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==} - engines: {node: '>=18'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - open@11.0.0: - resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} - engines: {node: '>=20'} - - param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - perfect-debounce@2.1.0: - resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pinia@3.0.4: - resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} - peerDependencies: - typescript: '>=4.5.0' - vue: ^3.5.11 - peerDependenciesMeta: - typescript: - optional: true - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.1: - resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} - - pkijs@3.4.0: - resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} - engines: {node: '>=16.0.0'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} - engines: {node: ^10 || ^12 || >=14} - - powershell-utils@0.1.0: - resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} - engines: {node: '>=20'} - - pvtsutils@1.3.6: - resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} - - pvutils@1.1.5: - resolution: {integrity: sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==} - engines: {node: '>=16.0.0'} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - quasar@2.20.1: - resolution: {integrity: sha512-C+uFyt9/D2B2Dad/c9tGSlyWLMsrz+ROJLm3FKFdY9i+vc1Mk2q/+JyHBmQnuvQCEpUrdiXqcWQbpmZhuvo60A==} - engines: {node: '>= 10.18.1', npm: '>= 6.13.4', yarn: '>= 1.21.1'} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - relateurl@0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rolldown@1.1.1: - resolution: {integrity: sha512-IN750c0p+s3jqJIsFLRZrQazmbAB1kkQDTtQjSt/gbS2ywLhlv4R5Shazer0FZKmuo/BsO3/w2UoYnUjuOZqHg==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - sass-embedded-all-unknown@1.100.0: - resolution: {integrity: sha512-auFtXY/kwYILmSVjtBDwyj0axcLbYYiffOKWoaXHnI5bsYwiRbBh3EneR1rpbX2ZIZCrwX93i5pxKLTZF/662Q==} - cpu: ['!arm', '!arm64', '!riscv64', '!x64'] - - sass-embedded-android-arm64@1.100.0: - resolution: {integrity: sha512-W+Ru9JwTnfU0UX3jSZcbqFdtKFMcYdfFwytc57h2DgnqCOIiAqI2E06mABZBZC+r3LwXCBuS5GbXAGeVgvVDkA==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [android] - - sass-embedded-android-arm@1.100.0: - resolution: {integrity: sha512-70f3HgX2pFNmzpGQ86n5e6QfWn2fP4QUQGfFQK0P1XH73ZLIzLo2YqygrGKGKeeqtc5eU2Wl1/xQzhzuKnO4kw==} - engines: {node: '>=14.0.0'} - cpu: [arm] - os: [android] - - sass-embedded-android-riscv64@1.100.0: - resolution: {integrity: sha512-icU3o0V/uCSytSpf+tX5Lf51BvyQEbLzDUJfUi9etSauYBGHpPKkdtdZH0si4v98phq11Kl8rSV1SggksxF1Hg==} - engines: {node: '>=14.0.0'} - cpu: [riscv64] - os: [android] - - sass-embedded-android-x64@1.100.0: - resolution: {integrity: sha512-mevF9VQk6gEYByy8+jusaHGmd7Usb2ytX/DsEOd0JtOGCtcf1kh575xJ6OUBDIcJ15uLnbau/0iy1eP6WVBvWA==} - engines: {node: '>=14.0.0'} - cpu: [x64] - os: [android] - - sass-embedded-darwin-arm64@1.100.0: - resolution: {integrity: sha512-1PVlYi61POo93IT/FfrG1mc1tAHxeSTyUALF2aOFmXGWjVXr3bQzEQiBGCOvQbj/ix+5hNyXFXcEMEyKvtUJJA==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [darwin] - - sass-embedded-darwin-x64@1.100.0: - resolution: {integrity: sha512-x97o3JnGyImZNCIVs9wQHJUE5QCvmVIKaH1cwrz/5dK7OT1FpeNiW+u9TUomP9hG6Ekjd8EL8NBHpxTfIhdjmg==} - engines: {node: '>=14.0.0'} - cpu: [x64] - os: [darwin] - - sass-embedded-linux-arm64@1.100.0: - resolution: {integrity: sha512-Dwjmj8Z6VRy7rAi53JAdEwIyUjpfl7PhpSc2/LpQPQx+aO5Dp7Spaipkax0ufJl1SoDUdchCsM4y/88YaluorQ==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [linux] - - sass-embedded-linux-arm@1.100.0: - resolution: {integrity: sha512-9Ul7O1eKrc5YlhwWjkp8tZPSe3UEwSZ1uwUZOQom1HL0pRlBA6F/IlGZYFTLwnHMIP1fc77MMNaBRfc05mKMpw==} - engines: {node: '>=14.0.0'} - cpu: [arm] - os: [linux] - - sass-embedded-linux-musl-arm64@1.100.0: - resolution: {integrity: sha512-XpACJB2KjSLjf2e9uuvGVdOURsoNrFqgRiihhXyUHK9W0t3LIHb7z5MA/7XGPIT9bWSOO2zyw+rH/FHtDV/Yrg==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [linux] - - sass-embedded-linux-musl-arm@1.100.0: - resolution: {integrity: sha512-sl0JgbGloPyJg66XXx5UDSDScZ0oU85DpMQU4JU/sCUCFj1Z8zZ69SJWKTCNE4/jwnce7WI2zPCV5AG+RHOZJw==} - engines: {node: '>=14.0.0'} - cpu: [arm] - os: [linux] - - sass-embedded-linux-musl-riscv64@1.100.0: - resolution: {integrity: sha512-ShvI0Kx04mwoCARwZ0UjiT97isQvzO80tAt91zmFyHLN9kelc/IrQi940farSm2xQVPCKdeVyeG0ekBsokSpYQ==} - engines: {node: '>=14.0.0'} - cpu: [riscv64] - os: [linux] - - sass-embedded-linux-musl-x64@1.100.0: - resolution: {integrity: sha512-TDBCRWNuS4RDLQXvRc1gjZlWiWTWaWGp0Bwu/IKwJxov81lsvrCs3TihTyNXtW7V5aoN4Ky3r0QOkNb3mwmBnA==} - engines: {node: '>=14.0.0'} - cpu: [x64] - os: [linux] - - sass-embedded-linux-riscv64@1.100.0: - resolution: {integrity: sha512-j4ENJGOheO+fm3j/yorLxCjBP6/XskrZx7dTLlT+lXYwN/qqCqoA/gsNLI0McS3DFM6GBwPiffzWsdWS8t6sEQ==} - engines: {node: '>=14.0.0'} - cpu: [riscv64] - os: [linux] - - sass-embedded-linux-x64@1.100.0: - resolution: {integrity: sha512-0vUSN8j0WGtCJIOPh//EmUvYGHW0QOe5iul8qyhPk50MAcw49MA0r34AhftjDdx94ILPF6vApFs0gwHPQRlpVA==} - engines: {node: '>=14.0.0'} - cpu: [x64] - os: [linux] - - sass-embedded-unknown-all@1.100.0: - resolution: {integrity: sha512-c+naBgWId4MIpToXcI0DgqetjdAkwTTAxFAuOaBz7HUXLdyG1oZRrEvSsbe41nEdQOKH0vgofVFCeSQgoXOG9A==} - os: ['!android', '!darwin', '!linux', '!win32'] - - sass-embedded-win32-arm64@1.100.0: - resolution: {integrity: sha512-iE+yxj+hUXwwbqpHkXxgAWTzeRfcWxJ7SSTQEPMk48lwq3oCrWLlz5sQuWHbuTK/i0GKQfROdP+hOmPi89yjUg==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [win32] - - sass-embedded-win32-x64@1.100.0: - resolution: {integrity: sha512-qI4F8MI7/KYoy9NdjJfhSspG42WPkADSNDvwEV7qWvCSFC83koJssRsKO2/PfY+niZz6BG65Ic/D+A11h959hw==} - engines: {node: '>=14.0.0'} - cpu: [x64] - os: [win32] - - sass-embedded@1.100.0: - resolution: {integrity: sha512-Ut8wlQSk19tm7jMK6mz6cF1+e+E7tUnW2tM02zQDPnOTcVbV8qCQG8UWxZkkNlY50+hV3hqP24OOkUlMz8xBpw==} - engines: {node: '>=16.0.0'} - hasBin: true - - sass@1.100.0: - resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} - engines: {node: '>=20.19.0'} - hasBin: true - - sax@1.1.4: - resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} - - scule@1.3.0: - resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - - selfsigned@5.5.0: - resolution: {integrity: sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==} - engines: {node: '>=18'} - - semver@7.8.4: - resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serialize-javascript@7.0.6: - resolution: {integrity: sha512-ATTK5Q4gFVg0YDp1my2vqygyvhcklD/UV5GIlYHooGTn/NogJqIzpetkD6E5kmuVULqz/S9inUL25XcAgDRJQg==} - engines: {node: '>=20.0.0'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} - - stack-trace@1.0.0: - resolution: {integrity: sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==} - engines: {node: '>=20.0.0'} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - superjson@2.2.6: - resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} - engines: {node: '>=16'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - sync-child-process@1.0.2: - resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} - engines: {node: '>=16.0.0'} - - sync-message-port@1.2.0: - resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==} - engines: {node: '>=16.0.0'} - - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} - engines: {node: '>=10'} - hasBin: true - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - ts-essentials@10.2.1: - resolution: {integrity: sha512-+Id1fRkuir+CsgK2x04/icS2b4V1hQmq7ObzIrDjhN0ozfRYivnP7aaKMVJfLApQm0trjR39A6NIMVchiB9Erw==} - peerDependencies: - typescript: '>=4.5.0' - peerDependenciesMeta: - typescript: - optional: true - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsyringe@4.10.0: - resolution: {integrity: sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==} - engines: {node: '>= 6.0.0'} - - type-fest@5.7.0: - resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} - engines: {node: '>=20'} - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unplugin-utils@0.3.1: - resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} - engines: {node: '>=20.19.0'} - - unplugin@3.0.0: - resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} - engines: {node: ^20.19.0 || >=22.12.0} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - varint@6.0.0: - resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} - - vite@8.0.16: - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - - vue-advanced-cropper@2.8.9: - resolution: {integrity: sha512-1jc5gO674kVGpJKekoaol6ZlwaF5VYDLSBwBOUpViW0IOrrRsyLw6XNszjEqgbavvqinlKNS6Kqlom3B5M72Tw==} - engines: {node: '>=8', npm: '>=5'} - peerDependencies: - vue: ^3.0.0 - - vue-i18n@11.4.6: - resolution: {integrity: sha512-l0gE7Rfy0phCa5ChKYkOq543Wgd39BCK6hkktfr1Ed4D99oRkgPK9ffShASZdeC8OJxGfdWmpYoAaAH6iLEuIg==} - engines: {node: '>= 22'} - peerDependencies: - vue: ^3.0.0 - - vue-router@5.1.0: - resolution: {integrity: sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==} - peerDependencies: - '@pinia/colada': '>=0.21.2' - '@vue/compiler-sfc': ^3.5.34 - pinia: ^3.0.4 - vite: ^7.0.0 || ^8.0.0 - vue: ^3.5.34 - peerDependenciesMeta: - '@pinia/colada': - optional: true - '@vue/compiler-sfc': - optional: true - pinia: - optional: true - vite: - optional: true - - vue-tsc@3.3.5: - resolution: {integrity: sha512-Rzh/G2MmNlMSAMTiQEjDrsb4dgB/jbtEM47rVN2NtidF1dfb/q4w4QvpQBtW5+y3y5H27Hjh7deVwk+YB02fNg==} - hasBin: true - peerDependencies: - typescript: '>=5.0.0' - - vue@3.5.38: - resolution: {integrity: sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - webpack-merge@6.0.1: - resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} - engines: {node: '>=18.0.0'} - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wildcard@2.0.1: - resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} - - wsl-utils@0.3.1: - resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} - engines: {node: '>=20'} - - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} - hasBin: true - - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - -snapshots: - - '@babel/generator@8.0.0': - dependencies: - '@babel/parser': 8.0.0 - '@babel/types': 8.0.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@types/jsesc': 2.5.1 - jsesc: 3.1.0 - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-string-parser@8.0.0': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-identifier@8.0.0': {} - - '@babel/parser@7.29.7': - dependencies: - '@babel/types': 7.29.7 - - '@babel/parser@8.0.0': - dependencies: - '@babel/types': 8.0.0 - - '@babel/types@7.29.7': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@babel/types@8.0.0': - dependencies: - '@babel/helper-string-parser': 8.0.0 - '@babel/helper-validator-identifier': 8.0.0 - - '@bufbuild/protobuf@2.12.0': {} - - '@clack/core@1.4.1': - dependencies: - fast-wrap-ansi: 0.2.2 - sisteransi: 1.0.5 - - '@clack/prompts@1.5.1': - dependencies: - '@clack/core': 1.4.1 - fast-string-width: 3.0.2 - fast-wrap-ansi: 0.2.2 - sisteransi: 1.0.5 - - '@emnapi/core@1.10.0': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - - '@emnapi/core@1.11.0': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.11.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@intlify/core-base@11.4.6': - dependencies: - '@intlify/devtools-types': 11.4.6 - '@intlify/message-compiler': 11.4.6 - '@intlify/shared': 11.4.6 - - '@intlify/devtools-types@11.4.6': - dependencies: - '@intlify/core-base': 11.4.6 - '@intlify/shared': 11.4.6 - - '@intlify/message-compiler@11.4.6': - dependencies: - '@intlify/shared': 11.4.6 - source-map-js: 1.2.1 - - '@intlify/shared@11.4.6': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 - optional: true - - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': - dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 - '@tybys/wasm-util': 0.10.2 - optional: true - - '@noble/hashes@1.4.0': {} - - '@oxc-project/types@0.133.0': {} - - '@oxc-project/types@0.135.0': {} - - '@parcel/watcher-android-arm64@2.5.6': - optional: true - - '@parcel/watcher-darwin-arm64@2.5.6': - optional: true - - '@parcel/watcher-darwin-x64@2.5.6': - optional: true - - '@parcel/watcher-freebsd-x64@2.5.6': - optional: true - - '@parcel/watcher-linux-arm-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-arm-musl@2.5.6': - optional: true - - '@parcel/watcher-linux-arm64-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-arm64-musl@2.5.6': - optional: true - - '@parcel/watcher-linux-x64-glibc@2.5.6': - optional: true - - '@parcel/watcher-linux-x64-musl@2.5.6': - optional: true - - '@parcel/watcher-win32-arm64@2.5.6': - optional: true - - '@parcel/watcher-win32-ia32@2.5.6': - optional: true - - '@parcel/watcher-win32-x64@2.5.6': - optional: true - - '@parcel/watcher@2.5.6': - dependencies: - detect-libc: 2.1.2 - is-glob: 4.0.3 - node-addon-api: 7.1.1 - picomatch: 4.0.4 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.6 - '@parcel/watcher-darwin-arm64': 2.5.6 - '@parcel/watcher-darwin-x64': 2.5.6 - '@parcel/watcher-freebsd-x64': 2.5.6 - '@parcel/watcher-linux-arm-glibc': 2.5.6 - '@parcel/watcher-linux-arm-musl': 2.5.6 - '@parcel/watcher-linux-arm64-glibc': 2.5.6 - '@parcel/watcher-linux-arm64-musl': 2.5.6 - '@parcel/watcher-linux-x64-glibc': 2.5.6 - '@parcel/watcher-linux-x64-musl': 2.5.6 - '@parcel/watcher-win32-arm64': 2.5.6 - '@parcel/watcher-win32-ia32': 2.5.6 - '@parcel/watcher-win32-x64': 2.5.6 - optional: true - - '@peculiar/asn1-cms@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-csr@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-ecc@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-pfx@2.8.0': - dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-pkcs8@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-pkcs9@2.8.0': - dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-pfx': 2.8.0 - '@peculiar/asn1-pkcs8': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - '@peculiar/asn1-x509-attr': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-rsa@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-schema@2.8.0': - dependencies: - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-x509-attr@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/asn1-x509@2.8.0': - dependencies: - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/utils': 2.0.3 - asn1js: 3.0.10 - tslib: 2.8.1 - - '@peculiar/utils@2.0.3': - dependencies: - tslib: 2.8.1 - - '@peculiar/x509@1.14.3': - dependencies: - '@peculiar/asn1-cms': 2.8.0 - '@peculiar/asn1-csr': 2.8.0 - '@peculiar/asn1-ecc': 2.8.0 - '@peculiar/asn1-pkcs9': 2.8.0 - '@peculiar/asn1-rsa': 2.8.0 - '@peculiar/asn1-schema': 2.8.0 - '@peculiar/asn1-x509': 2.8.0 - pvtsutils: 1.3.6 - reflect-metadata: 0.2.2 - tslib: 2.8.1 - tsyringe: 4.10.0 - - '@quasar/app-vite@3.0.0-rc.3(@types/node@22.19.21)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(quasar@2.20.1)(sass@1.100.0)(terser@5.48.0)(typescript@6.0.3)(vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3))(yaml@2.9.0)': - dependencies: - '@clack/prompts': 1.5.1 - '@quasar/art': 1.0.0 - '@quasar/render-ssr-error': 2.2.3 - '@quasar/ssl-certificate': 2.0.0 - '@quasar/vite-plugin': 1.12.0(@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)))(quasar@2.20.1)(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) - '@types/chrome': 0.1.43 - '@types/cordova': 11.0.3 - '@vitejs/plugin-vue': 6.0.7(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) - chokidar: 5.0.0 - ci-info: 4.4.0 - confbox: 0.2.4 - cross-spawn: 7.0.6 - dot-prop: 10.1.0 - dotenv-expand: 13.0.0 - elementtree: 0.1.7 - fflate: 0.8.3 - fs-extra: 11.3.5 - html-minifier-terser: 7.2.0 - jiti: 2.7.0 - kolorist: 1.8.0 - mlly: 1.8.2 - open: 11.0.0 - quasar: 2.20.1 - rolldown: 1.1.1 - sass-embedded: 1.100.0 - semver: 7.8.4 - serialize-javascript: 7.0.6 - serve-static: 2.2.1 - tinyglobby: 0.2.17 - ts-essentials: 10.2.1(typescript@6.0.3) - vite: 8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - vue: 3.5.38(typescript@6.0.3) - vue-router: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) - webpack-merge: 6.0.1 - optionalDependencies: - pinia: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) - typescript: 6.0.3 - transitivePeerDependencies: - - '@types/node' - - '@vitejs/devtools' - - esbuild - - less - - sass - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - '@quasar/art@1.0.0': {} - - '@quasar/extras@2.0.1': {} - - '@quasar/render-ssr-error@2.2.3': - dependencies: - stack-trace: 1.0.0 - - '@quasar/ssl-certificate@2.0.0': - dependencies: - fs-extra: 11.3.5 - selfsigned: 5.5.0 - - '@quasar/vite-plugin@1.12.0(@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)))(quasar@2.20.1)(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': - dependencies: - '@vitejs/plugin-vue': 6.0.7(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) - quasar: 2.20.1 - vite: 8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - vue: 3.5.38(typescript@6.0.3) - - '@rolldown/binding-android-arm64@1.0.3': - optional: true - - '@rolldown/binding-android-arm64@1.1.1': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.3': - optional: true - - '@rolldown/binding-darwin-arm64@1.1.1': - optional: true - - '@rolldown/binding-darwin-x64@1.0.3': - optional: true - - '@rolldown/binding-darwin-x64@1.1.1': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.3': - optional: true - - '@rolldown/binding-freebsd-x64@1.1.1': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.1.1': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.1.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.3': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.1.1': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.1.1': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.1.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.3': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.1.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.3': - optional: true - - '@rolldown/binding-linux-x64-musl@1.1.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.3': - optional: true - - '@rolldown/binding-openharmony-arm64@1.1.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.3': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@rolldown/binding-wasm32-wasi@1.1.1': - dependencies: - '@emnapi/core': 1.11.0 - '@emnapi/runtime': 1.11.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.3': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.1.1': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.3': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.1.1': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@tybys/wasm-util@0.10.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chrome@0.1.43': - dependencies: - '@types/filesystem': 0.0.36 - '@types/har-format': 1.2.16 - - '@types/cordova@11.0.3': {} - - '@types/filesystem@0.0.36': - dependencies: - '@types/filewriter': 0.0.33 - - '@types/filewriter@0.0.33': {} - - '@types/har-format@1.2.16': {} - - '@types/jsesc@2.5.1': {} - - '@types/node@22.19.21': - dependencies: - undici-types: 6.21.0 - - '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': - dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - vue: 3.5.38(typescript@6.0.3) - - '@volar/language-core@2.4.28': - dependencies: - '@volar/source-map': 2.4.28 - - '@volar/source-map@2.4.28': {} - - '@volar/typescript@2.4.28': - dependencies: - '@volar/language-core': 2.4.28 - path-browserify: 1.0.1 - vscode-uri: 3.1.0 - - '@vue-macros/common@3.1.2(vue@3.5.38(typescript@6.0.3))': - dependencies: - '@vue/compiler-sfc': 3.5.38 - ast-kit: 2.2.0 - local-pkg: 1.2.1 - magic-string-ast: 1.0.3 - unplugin-utils: 0.3.1 - optionalDependencies: - vue: 3.5.38(typescript@6.0.3) - - '@vue/compiler-core@3.5.38': - dependencies: - '@babel/parser': 7.29.7 - '@vue/shared': 3.5.38 - entities: 7.0.1 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.38': - dependencies: - '@vue/compiler-core': 3.5.38 - '@vue/shared': 3.5.38 - - '@vue/compiler-sfc@3.5.38': - dependencies: - '@babel/parser': 7.29.7 - '@vue/compiler-core': 3.5.38 - '@vue/compiler-dom': 3.5.38 - '@vue/compiler-ssr': 3.5.38 - '@vue/shared': 3.5.38 - estree-walker: 2.0.2 - magic-string: 0.30.21 - postcss: 8.5.15 - source-map-js: 1.2.1 - - '@vue/compiler-ssr@3.5.38': - dependencies: - '@vue/compiler-dom': 3.5.38 - '@vue/shared': 3.5.38 - - '@vue/devtools-api@6.6.4': {} - - '@vue/devtools-api@7.7.9': - dependencies: - '@vue/devtools-kit': 7.7.9 - - '@vue/devtools-api@8.1.3': - dependencies: - '@vue/devtools-kit': 8.1.3 - - '@vue/devtools-kit@7.7.9': - dependencies: - '@vue/devtools-shared': 7.7.9 - birpc: 2.9.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.6 - - '@vue/devtools-kit@8.1.3': - dependencies: - '@vue/devtools-shared': 8.1.3 - birpc: 2.9.0 - hookable: 5.5.3 - perfect-debounce: 2.1.0 - - '@vue/devtools-shared@7.7.9': - dependencies: - rfdc: 1.4.1 - - '@vue/devtools-shared@8.1.3': {} - - '@vue/language-core@3.3.5': - dependencies: - '@volar/language-core': 2.4.28 - '@vue/compiler-dom': 3.5.38 - '@vue/shared': 3.5.38 - alien-signals: 3.2.1 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - picomatch: 4.0.4 - - '@vue/reactivity@3.5.38': - dependencies: - '@vue/shared': 3.5.38 - - '@vue/runtime-core@3.5.38': - dependencies: - '@vue/reactivity': 3.5.38 - '@vue/shared': 3.5.38 - - '@vue/runtime-dom@3.5.38': - dependencies: - '@vue/reactivity': 3.5.38 - '@vue/runtime-core': 3.5.38 - '@vue/shared': 3.5.38 - csstype: 3.2.3 - - '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@6.0.3))': - dependencies: - '@vue/compiler-ssr': 3.5.38 - '@vue/shared': 3.5.38 - vue: 3.5.38(typescript@6.0.3) - - '@vue/shared@3.5.38': {} - - acorn@8.17.0: {} - - alien-signals@3.2.1: {} - - asn1js@3.0.10: - dependencies: - pvtsutils: 1.3.6 - pvutils: 1.1.5 - tslib: 2.8.1 - - ast-kit@2.2.0: - dependencies: - '@babel/parser': 7.29.7 - pathe: 2.0.3 - - ast-walker-scope@0.9.0: - dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 - ast-kit: 2.2.0 - - autoprefixer@10.5.0(postcss@8.5.15): - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001799 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.15 - postcss-value-parser: 4.2.0 - - baseline-browser-mapping@2.10.38: {} - - birpc@2.9.0: {} - - browserslist@4.28.2: - dependencies: - baseline-browser-mapping: 2.10.38 - caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.375 - node-releases: 2.0.48 - update-browserslist-db: 1.2.3(browserslist@4.28.2) - - buffer-from@1.1.2: {} - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - - bytestreamjs@2.0.1: {} - - camel-case@4.1.2: - dependencies: - pascal-case: 3.1.2 - tslib: 2.8.1 - - caniuse-lite@1.0.30001799: {} - - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - - ci-info@4.4.0: {} - - classnames@2.5.1: {} - - clean-css@5.3.3: - dependencies: - source-map: 0.6.1 - - clone-deep@4.0.1: - dependencies: - is-plain-object: 2.0.4 - kind-of: 6.0.3 - shallow-clone: 3.0.1 - - colorjs.io@0.5.2: {} - - commander@10.0.1: {} - - commander@2.20.3: {} - - confbox@0.1.8: {} - - confbox@0.2.4: {} - - copy-anything@4.0.5: - dependencies: - is-what: 5.5.0 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - csstype@3.2.3: {} - - debounce@1.2.1: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - default-browser-id@5.0.1: {} - - default-browser@5.5.0: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - - define-lazy-prop@3.0.0: {} - - depd@2.0.0: {} - - detect-libc@2.1.2: {} - - dot-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - dot-prop@10.1.0: - dependencies: - type-fest: 5.7.0 - - dotenv-expand@13.0.0: - dependencies: - dotenv: 17.4.2 - - dotenv@17.4.2: {} - - easy-bem@1.1.1: {} - - ee-first@1.1.1: {} - - electron-to-chromium@1.5.375: {} - - elementtree@0.1.7: - dependencies: - sax: 1.1.4 - - encodeurl@2.0.0: {} - - entities@4.5.0: {} - - entities@7.0.1: {} - - escalade@3.2.0: {} - - escape-html@1.0.3: {} - - estree-walker@2.0.2: {} - - etag@1.8.1: {} - - exsolve@1.0.8: {} - - fast-string-truncated-width@3.0.3: {} - - fast-string-width@3.0.2: - dependencies: - fast-string-truncated-width: 3.0.3 - - fast-wrap-ansi@0.2.2: - dependencies: - fast-string-width: 3.0.2 - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fflate@0.8.3: {} - - flat@5.0.2: {} - - fraction.js@5.3.4: {} - - fresh@2.0.0: {} - - fs-extra@11.3.5: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fsevents@2.3.3: - optional: true - - graceful-fs@4.2.11: {} - - has-flag@4.0.0: {} - - hookable@5.5.3: {} - - html-minifier-terser@7.2.0: - dependencies: - camel-case: 4.1.2 - clean-css: 5.3.3 - commander: 10.0.1 - entities: 4.5.0 - param-case: 3.0.4 - relateurl: 0.2.7 - terser: 5.48.0 - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - immutable@5.1.6: {} - - inherits@2.0.4: {} - - is-docker@3.0.0: {} - - is-extglob@2.1.1: - optional: true - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - optional: true - - is-in-ssh@1.0.0: {} - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - - is-what@5.5.0: {} - - is-wsl@3.1.1: - dependencies: - is-inside-container: 1.0.0 - - isexe@2.0.0: {} - - isobject@3.0.1: {} - - jiti@2.7.0: {} - - jsesc@3.1.0: {} - - json5@2.2.3: {} - - jsonfile@6.2.1: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - kind-of@6.0.3: {} - - kolorist@1.8.0: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - local-pkg@1.2.1: - dependencies: - mlly: 1.8.2 - pkg-types: 2.3.1 - quansync: 0.2.11 - - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - - magic-string-ast@1.0.3: - dependencies: - magic-string: 0.30.21 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - mitt@3.0.1: {} - - mlly@1.8.2: - dependencies: - acorn: 8.17.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 - - ms@2.1.3: {} - - muggle-string@0.4.1: {} - - nanoid@3.3.12: {} - - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - - node-addon-api@7.1.1: - optional: true - - node-releases@2.0.48: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - open@11.0.0: - dependencies: - default-browser: 5.5.0 - define-lazy-prop: 3.0.0 - is-in-ssh: 1.0.0 - is-inside-container: 1.0.0 - powershell-utils: 0.1.0 - wsl-utils: 0.3.1 - - param-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.8.1 - - parseurl@1.3.3: {} - - pascal-case@3.1.2: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - - path-browserify@1.0.1: {} - - path-key@3.1.1: {} - - pathe@2.0.3: {} - - perfect-debounce@1.0.0: {} - - perfect-debounce@2.1.0: {} - - picocolors@1.1.1: {} - - picomatch@4.0.4: {} - - pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)): - dependencies: - '@vue/devtools-api': 7.7.9 - vue: 3.5.38(typescript@6.0.3) - optionalDependencies: - typescript: 6.0.3 - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - pkg-types@2.3.1: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - - pkijs@3.4.0: - dependencies: - '@noble/hashes': 1.4.0 - asn1js: 3.0.10 - bytestreamjs: 2.0.1 - pvtsutils: 1.3.6 - pvutils: 1.1.5 - tslib: 2.8.1 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.15: - dependencies: - nanoid: 3.3.12 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - powershell-utils@0.1.0: {} - - pvtsutils@1.3.6: - dependencies: - tslib: 2.8.1 - - pvutils@1.1.5: {} - - quansync@0.2.11: {} - - quasar@2.20.1: {} - - range-parser@1.2.1: {} - - readdirp@5.0.0: {} - - reflect-metadata@0.2.2: {} - - relateurl@0.2.7: {} - - rfdc@1.4.1: {} - - rolldown@1.0.3: - dependencies: - '@oxc-project/types': 0.133.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 - - rolldown@1.1.1: - dependencies: - '@oxc-project/types': 0.135.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.1 - '@rolldown/binding-darwin-arm64': 1.1.1 - '@rolldown/binding-darwin-x64': 1.1.1 - '@rolldown/binding-freebsd-x64': 1.1.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.1 - '@rolldown/binding-linux-arm64-gnu': 1.1.1 - '@rolldown/binding-linux-arm64-musl': 1.1.1 - '@rolldown/binding-linux-ppc64-gnu': 1.1.1 - '@rolldown/binding-linux-s390x-gnu': 1.1.1 - '@rolldown/binding-linux-x64-gnu': 1.1.1 - '@rolldown/binding-linux-x64-musl': 1.1.1 - '@rolldown/binding-openharmony-arm64': 1.1.1 - '@rolldown/binding-wasm32-wasi': 1.1.1 - '@rolldown/binding-win32-arm64-msvc': 1.1.1 - '@rolldown/binding-win32-x64-msvc': 1.1.1 - - run-applescript@7.1.0: {} - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - sass-embedded-all-unknown@1.100.0: - dependencies: - sass: 1.100.0 - optional: true - - sass-embedded-android-arm64@1.100.0: - optional: true - - sass-embedded-android-arm@1.100.0: - optional: true - - sass-embedded-android-riscv64@1.100.0: - optional: true - - sass-embedded-android-x64@1.100.0: - optional: true - - sass-embedded-darwin-arm64@1.100.0: - optional: true - - sass-embedded-darwin-x64@1.100.0: - optional: true - - sass-embedded-linux-arm64@1.100.0: - optional: true - - sass-embedded-linux-arm@1.100.0: - optional: true - - sass-embedded-linux-musl-arm64@1.100.0: - optional: true - - sass-embedded-linux-musl-arm@1.100.0: - optional: true - - sass-embedded-linux-musl-riscv64@1.100.0: - optional: true - - sass-embedded-linux-musl-x64@1.100.0: - optional: true - - sass-embedded-linux-riscv64@1.100.0: - optional: true - - sass-embedded-linux-x64@1.100.0: - optional: true - - sass-embedded-unknown-all@1.100.0: - dependencies: - sass: 1.100.0 - optional: true - - sass-embedded-win32-arm64@1.100.0: - optional: true - - sass-embedded-win32-x64@1.100.0: - optional: true - - sass-embedded@1.100.0: - dependencies: - '@bufbuild/protobuf': 2.12.0 - colorjs.io: 0.5.2 - immutable: 5.1.6 - rxjs: 7.8.2 - supports-color: 8.1.1 - sync-child-process: 1.0.2 - varint: 6.0.0 - optionalDependencies: - sass-embedded-all-unknown: 1.100.0 - sass-embedded-android-arm: 1.100.0 - sass-embedded-android-arm64: 1.100.0 - sass-embedded-android-riscv64: 1.100.0 - sass-embedded-android-x64: 1.100.0 - sass-embedded-darwin-arm64: 1.100.0 - sass-embedded-darwin-x64: 1.100.0 - sass-embedded-linux-arm: 1.100.0 - sass-embedded-linux-arm64: 1.100.0 - sass-embedded-linux-musl-arm: 1.100.0 - sass-embedded-linux-musl-arm64: 1.100.0 - sass-embedded-linux-musl-riscv64: 1.100.0 - sass-embedded-linux-musl-x64: 1.100.0 - sass-embedded-linux-riscv64: 1.100.0 - sass-embedded-linux-x64: 1.100.0 - sass-embedded-unknown-all: 1.100.0 - sass-embedded-win32-arm64: 1.100.0 - sass-embedded-win32-x64: 1.100.0 - - sass@1.100.0: - dependencies: - chokidar: 5.0.0 - immutable: 5.1.6 - source-map-js: 1.2.1 - optionalDependencies: - '@parcel/watcher': 2.5.6 - optional: true - - sax@1.1.4: {} - - scule@1.3.0: {} - - selfsigned@5.5.0: - dependencies: - '@peculiar/x509': 1.14.3 - pkijs: 3.4.0 - - semver@7.8.4: {} - - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serialize-javascript@7.0.6: {} - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - - setprototypeof@1.2.0: {} - - shallow-clone@3.0.1: - dependencies: - kind-of: 6.0.3 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - sisteransi@1.0.5: {} - - source-map-js@1.2.1: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - speakingurl@14.0.1: {} - - stack-trace@1.0.0: {} - - statuses@2.0.2: {} - - superjson@2.2.6: - dependencies: - copy-anything: 4.0.5 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - - sync-child-process@1.0.2: - dependencies: - sync-message-port: 1.2.0 - - sync-message-port@1.2.0: {} - - tagged-tag@1.0.0: {} - - terser@5.48.0: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.17.0 - commander: 2.20.3 - source-map-support: 0.5.21 - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - toidentifier@1.0.1: {} - - ts-essentials@10.2.1(typescript@6.0.3): - optionalDependencies: - typescript: 6.0.3 - - tslib@1.14.1: {} - - tslib@2.8.1: {} - - tsyringe@4.10.0: - dependencies: - tslib: 1.14.1 - - type-fest@5.7.0: - dependencies: - tagged-tag: 1.0.0 - - typescript@6.0.3: {} - - ufo@1.6.4: {} - - undici-types@6.21.0: {} - - universalify@2.0.1: {} - - unplugin-utils@0.3.1: - dependencies: - pathe: 2.0.3 - picomatch: 4.0.4 - - unplugin@3.0.0: - dependencies: - '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.4 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.2.3(browserslist@4.28.2): - dependencies: - browserslist: 4.28.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - varint@6.0.0: {} - - vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.3 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 22.19.21 - fsevents: 2.3.3 - jiti: 2.7.0 - sass: 1.100.0 - sass-embedded: 1.100.0 - terser: 5.48.0 - yaml: 2.9.0 - - vscode-uri@3.1.0: {} - - vue-advanced-cropper@2.8.9(vue@3.5.38(typescript@6.0.3)): - dependencies: - classnames: 2.5.1 - debounce: 1.2.1 - easy-bem: 1.1.1 - vue: 3.5.38(typescript@6.0.3) - - vue-i18n@11.4.6(vue@3.5.38(typescript@6.0.3)): - dependencies: - '@intlify/core-base': 11.4.6 - '@intlify/devtools-types': 11.4.6 - '@intlify/shared': 11.4.6 - '@vue/devtools-api': 6.6.4 - vue: 3.5.38(typescript@6.0.3) - - vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)): - dependencies: - '@babel/generator': 8.0.0 - '@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3)) - '@vue/devtools-api': 8.1.3 - ast-walker-scope: 0.9.0 - chokidar: 5.0.0 - json5: 2.2.3 - local-pkg: 1.2.1 - magic-string: 0.30.21 - mlly: 1.8.2 - muggle-string: 0.4.1 - pathe: 2.0.3 - picomatch: 4.0.4 - scule: 1.3.0 - tinyglobby: 0.2.17 - unplugin: 3.0.0 - unplugin-utils: 0.3.1 - vue: 3.5.38(typescript@6.0.3) - yaml: 2.9.0 - optionalDependencies: - '@vue/compiler-sfc': 3.5.38 - pinia: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) - vite: 8.0.16(@types/node@22.19.21)(jiti@2.7.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.48.0)(yaml@2.9.0) - - vue-tsc@3.3.5(typescript@6.0.3): - dependencies: - '@volar/typescript': 2.4.28 - '@vue/language-core': 3.3.5 - typescript: 6.0.3 - - vue@3.5.38(typescript@6.0.3): - dependencies: - '@vue/compiler-dom': 3.5.38 - '@vue/compiler-sfc': 3.5.38 - '@vue/runtime-dom': 3.5.38 - '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@6.0.3)) - '@vue/shared': 3.5.38 - optionalDependencies: - typescript: 6.0.3 - - webpack-merge@6.0.1: - dependencies: - clone-deep: 4.0.1 - flat: 5.0.2 - wildcard: 2.0.1 - - webpack-virtual-modules@0.6.2: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wildcard@2.0.1: {} - - wsl-utils@0.3.1: - dependencies: - is-wsl: 3.1.1 - powershell-utils: 0.1.0 - - yaml@2.9.0: {} - - zod@4.4.3: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml deleted file mode 100644 index 9945010..0000000 --- a/frontend/pnpm-workspace.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# https://pnpm.io/settings - -allowBuilds: - '@parcel/watcher': true - core-js: true - electron-winstaller: true - esbuild: true - lightningcss: true - rolldown: true - unrs-resolver: true diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 25db2f4..0000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,29 +0,0 @@ -// https://github.com/michael-ciniawsky/postcss-load-config - -import autoprefixer from 'autoprefixer' -// import rtlcss from 'postcss-rtlcss' - -export default { - plugins: [ - // https://github.com/postcss/autoprefixer - autoprefixer({ - overrideBrowserslist: [ - 'last 4 Chrome versions', - 'last 4 Firefox versions', - 'last 4 Edge versions', - 'last 4 Safari versions', - 'last 4 Android versions', - 'last 4 ChromeAndroid versions', - 'last 4 FirefoxAndroid versions', - 'last 4 iOS versions' - ] - }), - - // https://github.com/elchininet/postcss-rtlcss - // If you want to support RTL css, then - // 1. yarn/pnpm/bun/npm install postcss-rtlcss - // 2. optionally set quasar.config.js > framework > lang to an RTL language - // 3. uncomment the following line (and its import statement above): - // rtlcss() - ] -} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index ae7bbdb..0000000 Binary files a/frontend/public/favicon.ico and /dev/null differ diff --git a/frontend/public/icons/favicon-128x128.png b/frontend/public/icons/favicon-128x128.png deleted file mode 100644 index 1401176..0000000 Binary files a/frontend/public/icons/favicon-128x128.png and /dev/null differ diff --git a/frontend/public/icons/favicon-16x16.png b/frontend/public/icons/favicon-16x16.png deleted file mode 100644 index 679063a..0000000 Binary files a/frontend/public/icons/favicon-16x16.png and /dev/null differ diff --git a/frontend/public/icons/favicon-32x32.png b/frontend/public/icons/favicon-32x32.png deleted file mode 100644 index fd1fbc6..0000000 Binary files a/frontend/public/icons/favicon-32x32.png and /dev/null differ diff --git a/frontend/public/icons/favicon-96x96.png b/frontend/public/icons/favicon-96x96.png deleted file mode 100644 index e93b80a..0000000 Binary files a/frontend/public/icons/favicon-96x96.png and /dev/null differ diff --git a/frontend/quasar.config.ts b/frontend/quasar.config.ts deleted file mode 100644 index 6537cd7..0000000 --- a/frontend/quasar.config.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Configuration for your app -// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file - -import { defineConfig } from '#q-app'; - -export default defineConfig((/* ctx */) => { - return { - // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature - // preFetch: true, - - // app boot file (/src/boot) - // --> boot files are part of "main.js" - // https://v2.quasar.dev/quasar-cli-vite/boot-files - boot: [ - 'i18n', - ], - - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css - css: [ - 'app.css' - ], - - // https://github.com/quasarframework/quasar/tree/dev/extras - extras: [ - // 'ionicons-v4', - // 'mdi-v7', - // 'fontawesome-v7', - // 'eva-icons', - // 'themify', - // 'line-awesome', - // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! - - 'roboto-font', // optional, you are not bound to it - 'material-icons', // optional, you are not bound to it - ], - - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build - build: { - target: { - // browser: 'baseline-widely-available', - // node: 'node22' - }, - - typescript: { - strict: true, - vueShim: true - // extendTsConfig (tsConfig) {} - }, - - // https://v2.quasar.dev/quasar-cli-vite/page-routing-with-vue-router#filename-based-routing - // filenameBasedRouting: true, - - vueRouterMode: 'hash', // available values: 'hash', 'history' - // vueRouterBase, - // vueDevtools, - - // publicPath: '/', - // define: {}, - // defineEnv: {} - // ignorePublicFolder: true, - // minify: false, - // distDir - - extendViteConf (viteConf) { - viteConf.optimizeDeps = { - ...viteConf.optimizeDeps, - include: [ - ...(viteConf.optimizeDeps?.include ?? []), - 'zod', - 'vue-i18n', - ], - }; - }, - // viteVuePluginOptions: {}, - - // vitePlugins: [ - // [ 'package-name', { ..pluginOptions.. }, { server: true, client: true } ] - // ] - }, - - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver - devServer: { - // https: true, - open: true // opens browser window automatically - }, - - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework - framework: { - config: {}, - - // iconSet: 'material-icons', // Quasar icon set - // lang: 'en-US', // Quasar language pack - - // For special cases outside of where the auto-import strategy can have an impact - // (like functional components as one of the examples), - // you can manually specify Quasar components/directives to be available everywhere: - // - // components: [], - // directives: [], - - // Quasar plugins - plugins: ['Notify'] - }, - - // animations: 'all', // --- includes all animations - // https://v2.quasar.dev/options/animations - animations: [], - - // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles - sourceFiles: { - // rootComponent: 'src/App.vue', - // router: 'src/router/index', - store: 'src/stores/index', - // pwaRegisterServiceWorker: 'src-pwa/register-sw', - // pwaServiceWorker: 'src-pwa/sw/custom-sw', - // pwaManifestFile: 'src-pwa/manifest.json', - // electronMain: 'src-electron/electron-main', - // electronPreload: 'src-electron/electron-preload' - // bexManifestFile: 'src-bex/manifest.json' - }, - - // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr - ssr: { - prodPort: 3000, // The default port that the production server should use - // (gets superseded if process.env.PORT is specified at runtime) - - middlewares: [ - 'render' // keep this as last one - ], - - // extendSSRPackageJson (pkgJson) {}, - // extendSSRWebserverConf (rolldownConf) {}, - - // manualStoreSerialization: true, - // manualStoreSsrContextInjection: true, - // manualStoreHydration: true, - // manualPostHydrationTrigger: true, - - pwa: false - // pwaOfflineHtmlFilename: 'offline.html', // do NOT use index.html as name! - - // extendSSRGenerateSWOptions (cfg) {}, - // extendSSRInjectManifestOptions (cfg) {} - }, - - // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa - pwa: { - workboxMode: 'GenerateSW' // 'GenerateSW' or 'InjectManifest' - // swFilename: 'sw.js', - // manifestFilename: 'manifest.json', - // extendPWAManifestJson (json) {}, - // useCredentialsForManifestTag: true, - // injectPWAMetaTags: false, - // extendPWACustomSWConf (rolldownConf) {}, - // extendPWAGenerateSWOptions (cfg) {}, - // extendPWAInjectManifestOptions (cfg) {}, - // extendPWASwTsConfig (tsConfig) {} - }, - - // https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova - cordova: {}, - - // https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor - capacitor: { - hideSplashscreen: true - }, - - // https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron - electron: { - // extendElectronMainConf (rolldownConf) {}, - // extendElectronPreloadConf (rolldownConf) {}, - // extendElectronPackageJson (pkgJson) {}, - - // Electron preload scripts (if any) from /src-electron, WITHOUT file extension - preloadScripts: [ 'electron-preload' ], - - // specify the debugging port to use for the Electron app when running in development mode - inspectPort: 5858, - - bundler: 'packager', // 'packager' or 'builder' - - packager: { - // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options - - // OS X / Mac App Store - // appBundleId: '', - // appCategoryType: '', - // osxSign: '', - // protocol: 'myapp://path', - - // Windows only - // win32metadata: { ... } - }, - - builder: { - // https://www.electron.build/configuration - - appId: 'frontend' - } - }, - - // https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex - bex: { - // extendBexScriptsConf (rolldownConf) {}, - // extendBexManifestJson (json) {}, - - /** - * The list of extra scripts (js/ts) not in your bex manifest that you want to - * compile and use in your browser extension. Maybe dynamic use them? - * - * Each entry in the list should be a relative filename to /src-bex/ - * - * @example [ 'my-script.ts', 'sub-folder/my-other-script.js' ] - */ - extraScripts: [] - } - } -}); diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 98240ae..0000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/frontend/src/assets/pexels-photo-4323307.jpg b/frontend/src/assets/pexels-photo-4323307.jpg deleted file mode 100644 index a38235d..0000000 Binary files a/frontend/src/assets/pexels-photo-4323307.jpg and /dev/null differ diff --git a/frontend/src/assets/quasar-logo-vertical.svg b/frontend/src/assets/quasar-logo-vertical.svg deleted file mode 100644 index 8210831..0000000 --- a/frontend/src/assets/quasar-logo-vertical.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/boot/.gitkeep b/frontend/src/boot/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/boot/i18n.ts b/frontend/src/boot/i18n.ts deleted file mode 100644 index 7d14638..0000000 --- a/frontend/src/boot/i18n.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { App } from 'vue'; -import { i18n } from '@/i18n'; - -export default ({ app }: { app: App }) => { - app.use(i18n); -}; diff --git a/frontend/src/components/AvatarUpload.vue b/frontend/src/components/AvatarUpload.vue deleted file mode 100644 index 0bd9c2d..0000000 --- a/frontend/src/components/AvatarUpload.vue +++ /dev/null @@ -1,240 +0,0 @@ - - - - - diff --git a/frontend/src/components/CropperImage.vue b/frontend/src/components/CropperImage.vue deleted file mode 100644 index e548a2e..0000000 --- a/frontend/src/components/CropperImage.vue +++ /dev/null @@ -1,480 +0,0 @@ - - - - - diff --git a/frontend/src/components/DrawerUserCard.vue b/frontend/src/components/DrawerUserCard.vue deleted file mode 100644 index 6dc31d1..0000000 --- a/frontend/src/components/DrawerUserCard.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - - - diff --git a/frontend/src/components/EssentialLink.vue b/frontend/src/components/EssentialLink.vue deleted file mode 100644 index bebad79..0000000 --- a/frontend/src/components/EssentialLink.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/frontend/src/components/ProfileStatusBadge.vue b/frontend/src/components/ProfileStatusBadge.vue deleted file mode 100644 index 958b37b..0000000 --- a/frontend/src/components/ProfileStatusBadge.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - diff --git a/frontend/src/css/app.css b/frontend/src/css/app.css deleted file mode 100644 index 5f9b55c..0000000 --- a/frontend/src/css/app.css +++ /dev/null @@ -1,19 +0,0 @@ -/* app global css */ - -.q-field input:-webkit-autofill, -.q-field input:-webkit-autofill:hover, -.q-field input:-webkit-autofill:focus, -.q-field textarea:-webkit-autofill, -.q-field textarea:-webkit-autofill:hover, -.q-field textarea:-webkit-autofill:focus { - -webkit-box-shadow: 0 0 0 1000px #ffffff inset !important; - box-shadow: 0 0 0 1000px #ffffff inset !important; - -webkit-text-fill-color: currentColor !important; - caret-color: currentColor !important; - transition: background-color 999999s ease-out; -} - -.q-field:has(input:-webkit-autofill) .q-field__control, -.q-field:has(textarea:-webkit-autofill) .q-field__control { - background-color: #ffffff !important; -} diff --git a/frontend/src/data/countries.ts b/frontend/src/data/countries.ts deleted file mode 100644 index b20073a..0000000 --- a/frontend/src/data/countries.ts +++ /dev/null @@ -1,257 +0,0 @@ -export const countries = [ - {'CH' : 'Switzerland'}, - {'IT' : 'Italy'}, - {'FR' : 'France'}, - {'DE' : 'Germany'}, - {'GB' : 'United Kingdom of Great Britain and Northern Ireland (the)'}, - {'US' : 'United States of America (the)'}, - - {'AF' : 'Afghanistan'}, - {'AL' : 'Albania'}, - {'DZ' : 'Algeria'}, - {'AS' : 'American Samoa'}, - {'AD' : 'Andorra'}, - {'AO' : 'Angola'}, - {'AI' : 'Anguilla'}, - {'AQ' : 'Antarctica'}, - {'AG' : 'Antigua and Barbuda'}, - {'AR' : 'Argentina'}, - {'AM' : 'Armenia'}, - {'AW' : 'Aruba'}, - {'AU' : 'Australia'}, - {'AT' : 'Austria'}, - {'AZ' : 'Azerbaijan'}, - {'BS' : 'Bahamas (The)'}, - {'BH' : 'Bahrain'}, - {'BD' : 'Bangladesh'}, - {'BB' : 'Barbados'}, - {'BY' : 'Belarus'}, - {'BE' : 'Belgium'}, - {'BZ' : 'Belize'}, - {'BJ' : 'Benin'}, - {'BM' : 'Bermuda'}, - {'BT' : 'Bhutan'}, - {'BO' : 'Bolivia (Plurinational State of)'}, - {'BQ' : 'Bonaire, Sint Eustatius and Saba'}, - {'BA' : 'Bosnia and Herzegovina'}, - {'BW' : 'Botswana'}, - {'BV' : 'Bouvet Island'}, - {'BR' : 'Brazil'}, - {'IO' : 'British Indian Ocean Territory (the)'}, - {'BN' : 'Brunei Darussalam'}, - {'BG' : 'Bulgaria'}, - {'BF' : 'Burkina Faso'}, - {'BI' : 'Burundi'}, - {'CV' : 'Cabo Verde'}, - {'KH' : 'Cambodia'}, - {'CM' : 'Cameroon'}, - {'CA' : 'Canada'}, - {'KY' : 'Cayman Islands (the)'}, - {'CF' : 'Central African Republic (the)'}, - {'TD' : 'Chad'}, - {'CL' : 'Chile'}, - {'CN' : 'China'}, - {'CX' : 'Christmas Island'}, - {'CC' : 'Cocos (Keeling) Islands (the)'}, - {'CO' : 'Colombia'}, - {'KM' : 'Comoros (the)'}, - {'CD' : 'Congo (the Democratic Republic of the)'}, - {'CG' : 'Congo (the)'}, - {'CK' : 'Cook Islands (the)'}, - {'CR' : 'Costa Rica'}, - {'HR' : 'Croatia'}, - {'CU' : 'Cuba'}, - {'CW' : 'Curaçao'}, - {'CY' : 'Cyprus'}, - {'CZ' : 'Czechia'}, - {'CI' : "Côte d'Ivoire"}, - {'DK' : 'Denmark'}, - {'DJ' : 'Djibouti'}, - {'DM' : 'Dominica'}, - {'DO' : 'Dominican Republic (the)'}, - {'EC' : 'Ecuador'}, - {'EG' : 'Egypt'}, - {'SV' : 'El Salvador'}, - {'GQ' : 'Equatorial Guinea'}, - {'ER' : 'Eritrea'}, - {'EE' : 'Estonia'}, - {'SZ' : 'Eswatini'}, - {'ET' : 'Ethiopia'}, - {'FK' : 'Falkland Islands (the) [Malvinas]'}, - {'FO' : 'Faroe Islands (the)'}, - {'FJ' : 'Fiji'}, - {'FI' : 'Finland'}, - {'FR' : 'France'}, - {'GF' : 'French Guiana'}, - {'PF' : 'French Polynesia'}, - {'TF' : 'French Southern Territories (the)'}, - {'GA' : 'Gabon'}, - {'GM' : 'Gambia (the)'}, - {'GE' : 'Georgia'}, - - {'GH' : 'Ghana'}, - {'GI' : 'Gibraltar'}, - {'GR' : 'Greece'}, - {'GL' : 'Greenland'}, - {'GD' : 'Grenada'}, - {'GP' : 'Guadeloupe'}, - {'GU' : 'Guam'}, - {'GT' : 'Guatemala'}, - {'GG' : 'Guernsey'}, - {'GN' : 'Guinea'}, - {'GW' : 'Guinea-Bissau'}, - {'GY' : 'Guyana'}, - {'HT' : 'Haiti'}, - {'HM' : 'Heard Island and McDonald Islands'}, - {'VA' : 'Holy See (the)'}, - {'HN' : 'Honduras'}, - {'HK' : 'Hong Kong'}, - {'HU' : 'Hungary'}, - {'IS' : 'Iceland'}, - {'IN' : 'India'}, - {'ID' : 'Indonesia'}, - {'IR' : 'Iran (Islamic Republic of)'}, - {'IQ' : 'Iraq'}, - {'IE' : 'Ireland'}, - {'IM' : 'Isle of Man'}, - {'IL' : 'Israel'}, - - {'JM' : 'Jamaica'}, - {'JP' : 'Japan'}, - {'JE' : 'Jersey'}, - {'JO' : 'Jordan'}, - {'KZ' : 'Kazakhstan'}, - {'KE' : 'Kenya'}, - {'KI' : 'Kiribati'}, - {'KP' : "Korea (the Democratic People's Republic of)"}, - {'KR' : 'Korea (the Republic of)'}, - {'KW' : 'Kuwait'}, - {'KG' : 'Kyrgyzstan'}, - {'LA' : "Lao People's Democratic Republic (the)"}, - {'LV' : 'Latvia'}, - {'LB' : 'Lebanon'}, - {'LS' : 'Lesotho'}, - {'LR' : 'Liberia'}, - {'LY' : 'Libya'}, - {'LI' : 'Liechtenstein'}, - {'LT' : 'Lithuania'}, - {'LU' : 'Luxembourg'}, - {'MO' : 'Macao'}, - {'MG' : 'Madagascar'}, - {'MW' : 'Malawi'}, - {'MY' : 'Malaysia'}, - {'MV' : 'Maldives'}, - {'ML' : 'Mali'}, - {'MT' : 'Malta'}, - {'MH' : 'Marshall Islands (the)'}, - {'MQ' : 'Martinique'}, - {'MR' : 'Mauritania'}, - {'MU' : 'Mauritius'}, - {'YT' : 'Mayotte'}, - {'MX' : 'Mexico'}, - {'FM' : 'Micronesia (Federated States of)'}, - {'MD' : 'Moldova (the Republic of)'}, - {'MC' : 'Monaco'}, - {'MN' : 'Mongolia'}, - {'ME' : 'Montenegro'}, - {'MS' : 'Montserrat'}, - {'MA' : 'Morocco'}, - {'MZ' : 'Mozambique'}, - {'MM' : 'Myanmar'}, - {'NA' : 'Namibia'}, - {'NR' : 'Nauru'}, - {'NP' : 'Nepal'}, - {'NL' : 'Netherlands (Kingdom of the)'}, - {'NC' : 'New Caledonia'}, - {'NZ' : 'New Zealand'}, - {'NI' : 'Nicaragua'}, - {'NE' : 'Niger (the)'}, - {'NG' : 'Nigeria'}, - {'NU' : 'Niue'}, - {'NF' : 'Norfolk Island'}, - {'MK' : 'North Macedonia'}, - {'MP' : 'Northern Mariana Islands (the)'}, - {'NO' : 'Norway'}, - {'OM' : 'Oman'}, - {'PK' : 'Pakistan'}, - {'PW' : 'Palau'}, - {'PS' : 'Palestine, State of'}, - {'PA' : 'Panama'}, - {'PG' : 'Papua New Guinea'}, - {'PY' : 'Paraguay'}, - {'PE' : 'Peru'}, - {'PH' : 'Philippines (the)'}, - {'PN' : 'Pitcairn'}, - {'PL' : 'Poland'}, - {'PT' : 'Portugal'}, - {'PR' : 'Puerto Rico'}, - {'QA' : 'Qatar'}, - {'RO' : 'Romania'}, - {'RU' : 'Russian Federation (the)'}, - {'RW' : 'Rwanda'}, - {'RE' : 'Réunion'}, - {'BL' : 'Saint Barthélemy'}, - {'SH' : 'Saint Helena, Ascension and Tristan da Cunha'}, - {'KN' : 'Saint Kitts and Nevis'}, - {'LC' : 'Saint Lucia'}, - {'MF' : 'Saint Martin (French part)'}, - {'PM' : 'Saint Pierre and Miquelon'}, - {'VC' : 'Saint Vincent and the Grenadines'}, - {'WS' : 'Samoa'}, - {'SM' : 'San Marino'}, - {'ST' : 'Sao Tome and Principe'}, - {'SA' : 'Saudi Arabia'}, - {'SN' : 'Senegal'}, - {'RS' : 'Serbia'}, - {'SC' : 'Seychelles'}, - {'SL' : 'Sierra Leone'}, - {'SG' : 'Singapore'}, - {'SX' : 'Sint Maarten (Dutch part)'}, - {'SK' : 'Slovakia'}, - {'SI' : 'Slovenia'}, - {'SB' : 'Solomon Islands'}, - {'SO' : 'Somalia'}, - {'ZA' : 'South Africa'}, - {'GS' : 'South Georgia and the South Sandwich Islands'}, - {'SS' : 'South Sudan'}, - {'ES' : 'Spain'}, - {'LK' : 'Sri Lanka'}, - {'SD' : 'Sudan (the)'}, - {'SR' : 'Suriname'}, - {'SJ' : 'Svalbard and Jan Mayen'}, - {'SE' : 'Sweden'}, - - {'SY' : 'Syrian Arab Republic (the)'}, - {'TW' : 'Taiwan (Province of China)'}, - {'TJ' : 'Tajikistan'}, - {'TZ' : 'Tanzania, the United Republic of'}, - {'TH' : 'Thailand'}, - {'TL' : 'Timor-Leste'}, - {'TG' : 'Togo'}, - {'TK' : 'Tokelau'}, - {'TO' : 'Tonga'}, - {'TT' : 'Trinidad and Tobago'}, - {'TN' : 'Tunisia'}, - {'TM' : 'Turkmenistan'}, - {'TC' : 'Turks and Caicos Islands (the)'}, - {'TV' : 'Tuvalu'}, - {'TR' : 'Türkiye'}, - {'UG' : 'Uganda'}, - {'UA' : 'Ukraine'}, - {'AE' : 'United Arab Emirates (the)'}, - {'GB' : 'United Kingdom of Great Britain and Northern Ireland (the)'}, - {'UM' : 'United States Minor Outlying Islands (the)'}, - - {'UY' : 'Uruguay'}, - {'UZ' : 'Uzbekistan'}, - {'VU' : 'Vanuatu'}, - {'VE' : 'Venezuela (Bolivarian Republic of)'}, - {'VN' : 'Viet Nam'}, - {'VG' : 'Virgin Islands (British)'}, - {'VI' : 'Virgin Islands (U.S.)'}, - {'WF' : 'Wallis and Futuna'}, - {'EH' : 'Western Sahara*'}, - {'YE' : 'Yemen'}, - {'ZM' : 'Zambia'}, - {'ZW' : 'Zimbabwe'}, -] diff --git a/frontend/src/encore/client.ts b/frontend/src/encore/client.ts deleted file mode 100755 index 1182e1d..0000000 --- a/frontend/src/encore/client.ts +++ /dev/null @@ -1,1419 +0,0 @@ -// Code generated by the Encore v1.57.8 client generator. DO NOT EDIT. - -// Disable eslint, jshint, and jslint for this file. -/* eslint-disable */ -/* jshint ignore:start */ -/*jslint-disable*/ - -/** - * BaseURL is the base URL for calling the Encore application's API. - */ -export type BaseURL = string - -export const Local: BaseURL = "http://localhost:4000" - -/** - * Environment returns a BaseURL for calling the cloud environment with the given name. - */ -export function Environment(name: string): BaseURL { - return `https://${name}-q55oi.encr.app` -} - -/** - * PreviewEnv returns a BaseURL for calling the preview environment with the given PR number. - */ -export function PreviewEnv(pr: number | string): BaseURL { - return Environment(`pr${pr}`) -} - -const BROWSER = typeof globalThis === "object" && ("window" in globalThis); - -/** - * Client is an API client for the q55oi Encore application. - */ -export default class Client { - public readonly admin: admin.ServiceClient - public readonly auth: auth.ServiceClient - public readonly hello: hello.ServiceClient - public readonly profiles: profiles.ServiceClient - public readonly registration: registration.ServiceClient - private readonly options: ClientOptions - private readonly target: string - - - /** - * Creates a Client for calling the public and authenticated APIs of your Encore application. - * - * @param target The target which the client should be configured to use. See Local and Environment for options. - * @param options Options for the client - */ - constructor(target: BaseURL, options?: ClientOptions) { - this.target = target - this.options = options ?? {} - const base = new BaseClient(this.target, this.options) - this.admin = new admin.ServiceClient(base) - this.auth = new auth.ServiceClient(base) - this.hello = new hello.ServiceClient(base) - this.profiles = new profiles.ServiceClient(base) - this.registration = new registration.ServiceClient(base) - } - - /** - * Creates a new Encore client with the given client options set. - * - * @param options Client options to set. They are merged with existing options. - **/ - public with(options: ClientOptions): Client { - return new Client(this.target, { - ...this.options, - ...options, - }) - } -} - -/** - * ClientOptions allows you to override any default behaviour within the generated Encore client. - */ -export interface ClientOptions { - /** - * By default the client will use the inbuilt fetch function for making the API requests. - * however you can override it with your own implementation here if you want to run custom - * code on each API request made or response received. - */ - fetcher?: Fetcher - - /** Default RequestInit to be used for the client */ - requestInit?: Omit & { headers?: Record } - - /** - * Allows you to set the authentication data to be used for each - * request either by passing in a static object or by passing in - * a function which returns a new object for each request. - */ - auth?: auth.AuthParams | AuthDataGenerator -} - -/** - * Service admin provides administrative endpoints for managing application data. - */ -export namespace admin { - export interface ListProfilesResponse { - profiles: Profile[] - } - - export interface PersonalData { - "user_id": string - "first_name": string - "last_name": string - address: string - cap: string - city: string - country: string - "created_at": string - "updated_at": string - } - - export interface PersonalDataParams { - "first_name": string - "last_name": string - address: string - cap: string - city: string - country: string - } - - export interface Profile { - "user_id": string - email: string - "display_name": string - "avatar_url": string - "is_artist": boolean - role: string - status: profiles.Status - "created_at": string - "updated_at": string - } - - export interface ProfileParams { - "display_name": string - "avatar_url": string - } - - export interface RegisterParams { - email: string - "display_name": string - "avatar_url": string - password: string - } - - export interface SystemOptionsResponse { - roles: auth.RoleOption[] - statuses: profiles.StatusOption[] - } - - export interface UpdateProfileArtistParams { - "is_artist": boolean - } - - export interface UpdateProfilePasswordParams { - password: string - } - - export interface UpdateProfileRoleParams { - role: string - } - - export interface UpdateProfileStatusParams { - status: profiles.Status - } - - export class ServiceClient { - private baseClient: BaseClient - - constructor(baseClient: BaseClient) { - this.baseClient = baseClient - this.DeleteProfile = this.DeleteProfile.bind(this) - this.GetPersonalData = this.GetPersonalData.bind(this) - this.GetProfile = this.GetProfile.bind(this) - this.GetSystemOptions = this.GetSystemOptions.bind(this) - this.InsertProfile = this.InsertProfile.bind(this) - this.ListProfiles = this.ListProfiles.bind(this) - this.UpdateProfile = this.UpdateProfile.bind(this) - this.UpdateProfileArtist = this.UpdateProfileArtist.bind(this) - this.UpdateProfilePassword = this.UpdateProfilePassword.bind(this) - this.UpdateProfileRole = this.UpdateProfileRole.bind(this) - this.UpdateProfileStatus = this.UpdateProfileStatus.bind(this) - this.UpsertPersonalData = this.UpsertPersonalData.bind(this) - } - - /** - * DeleteProfile removes any user's profile. - */ - public async DeleteProfile(userID: string): Promise { - await this.baseClient.callTypedAPI("DELETE", `/admin/profiles/${encodeURIComponent(userID)}`) - } - - /** - * GetPersonalData returns the personal data for the given user. - */ - public async GetPersonalData(userID: string): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/admin/profiles/${encodeURIComponent(userID)}/personal-data`) - return await resp.json() as PersonalData - } - - /** - * GetProfile returns the profile for the given user. - */ - public async GetProfile(userID: string): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/admin/profiles/${encodeURIComponent(userID)}`) - return await resp.json() as Profile - } - - /** - * GetSystemOptions returns the valid roles and profile statuses, each with a - * display name and underlying value, for use in admin UI dropdowns. - */ - public async GetSystemOptions(): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/admin/system/options`) - return await resp.json() as SystemOptionsResponse - } - - /** - * InsertProfile creates a new user profile with a server-generated UUID v4, - * delegating credential setup to the auth service. - */ - public async InsertProfile(params: RegisterParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/admin/profiles`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * ListProfiles returns all user profiles, ordered by user ID. - */ - public async ListProfiles(): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/admin/profiles`) - return await resp.json() as ListProfilesResponse - } - - /** - * UpdateProfile replaces the data of any user's profile. - */ - public async UpdateProfile(userID: string, params: ProfileParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * UpdateProfileArtist sets the artist flag of any user's profile. - */ - public async UpdateProfileArtist(userID: string, params: UpdateProfileArtistParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}/artist`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * UpdateProfilePassword sets the password of any user's profile. - */ - public async UpdateProfilePassword(userID: string, params: UpdateProfilePasswordParams): Promise { - await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}/password`, JSON.stringify(params)) - } - - /** - * UpdateProfileRole sets the role of any user's profile. - */ - public async UpdateProfileRole(userID: string, params: UpdateProfileRoleParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}/role`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * UpdateProfileStatus sets the status of any user's profile. - */ - public async UpdateProfileStatus(userID: string, params: UpdateProfileStatusParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}/status`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * UpsertPersonalData creates or replaces the personal data for the given user. - */ - public async UpsertPersonalData(userID: string, params: PersonalDataParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/admin/profiles/${encodeURIComponent(userID)}/personal-data`, JSON.stringify(params)) - return await resp.json() as PersonalData - } - } -} - -/** - * Service auth owns authentication: credentials, sessions, login and logout. - */ -export namespace auth { - export interface AuthParams { - Authorization: string - } - - /** - * RoleOption describes a valid role for display in admin UIs. - */ - export interface RoleOption { - name: string - value: string - } - - export interface SetPasswordParams { - "current_password": string - password: string - } - - export class ServiceClient { - private baseClient: BaseClient - - constructor(baseClient: BaseClient) { - this.baseClient = baseClient - this.Logout = this.Logout.bind(this) - this.SetPassword = this.SetPassword.bind(this) - } - - /** - * Logout revokes the session used to authenticate the current request. - */ - public async Logout(): Promise { - await this.baseClient.callTypedAPI("POST", `/auth/logout`) - } - - /** - * SetPassword changes the password for the authenticated user. - */ - public async SetPassword(params: SetPasswordParams): Promise { - await this.baseClient.callTypedAPI("PUT", `/auth/password`, JSON.stringify(params)) - } - } -} - -/** - * Service hello implements a simple hello world REST API. - */ -export namespace hello { - export interface Response { - Message: string - } - - export class ServiceClient { - private baseClient: BaseClient - - constructor(baseClient: BaseClient) { - this.baseClient = baseClient - this.Index = this.Index.bind(this) - this.World = this.World.bind(this) - } - - /** - * Landing page with usage instructions. - */ - public async Index(method: string, path: string[], body?: RequestInit["body"], options?: CallParameters): Promise { - return this.baseClient.callAPI(method, `/${path.map(encodeURIComponent).join("/")}`, body, options) - } - - /** - * This is a public REST API that responds with a personalized greeting. - * Learn more about defining APIs with Encore: - * https://encore.dev/docs/primitives/services-and-apis - * - * To call it, run in your terminal: - * - * curl http://localhost:4000/hello/World - */ - public async World(name: string): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/hello/${encodeURIComponent(name)}`) - return await resp.json() as Response - } - } -} - -/** - * Service profiles stores and serves user profile information. - */ -export namespace profiles { - export interface LoginParams { - "user_email": string - password: string - } - - export interface LoginResponse { - token: string - "expires_at": string - } - - /** - * PersonalData holds the personal details linked one-to-one to a user profile. - */ - export interface PersonalData { - "user_id": string - "first_name": string - "last_name": string - address: string - cap: string - city: string - country: string - "created_at": string - "updated_at": string - } - - /** - * PersonalDataParams are the editable fields of a user's personal data. - */ - export interface PersonalDataParams { - "first_name": string - "last_name": string - address: string - cap: string - city: string - country: string - } - - export interface Profile { - "user_id": string - email: string - "display_name": string - "avatar_url": string - "is_artist": boolean - role: string - status: Status - "created_at": string - "updated_at": string - } - - export interface ProfileParams { - "display_name": string - "avatar_url": string - } - - export interface RegisterParams { - email: string - "display_name": string - "avatar_url": string - password: string - } - - export type Status = number - - /** - * StatusOption describes a valid profile status for display in admin UIs. - */ - export interface StatusOption { - name: string - value: Status - updatable: boolean - } - - export class ServiceClient { - private baseClient: BaseClient - - constructor(baseClient: BaseClient) { - this.baseClient = baseClient - this.Delete = this.Delete.bind(this) - this.Get = this.Get.bind(this) - this.GetPersonalData = this.GetPersonalData.bind(this) - this.Insert = this.Insert.bind(this) - this.Login = this.Login.bind(this) - this.Me = this.Me.bind(this) - this.Update = this.Update.bind(this) - this.UploadAvatar = this.UploadAvatar.bind(this) - this.UpsertPersonalData = this.UpsertPersonalData.bind(this) - } - - /** - * Delete removes the authenticated user's own profile. - */ - public async Delete(): Promise { - await this.baseClient.callTypedAPI("DELETE", `/profiles`) - } - - /** - * Get returns the profile for the given user. For the caller's own profile - * (including when not authenticated), see MyProfile. - */ - public async Get(): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/profiles/profile`) - return await resp.json() as Profile - } - - /** - * GetPersonalData returns the authenticated user's own personal data. - */ - public async GetPersonalData(): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/profiles/personal-data`) - return await resp.json() as PersonalData - } - - /** - * Insert registers a new profile with a server-generated UUID v4, delegating - * credential setup to the auth service. - */ - public async Insert(params: RegisterParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/profiles`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * Login is the public entry point for authenticating a user. It first - * resolves the given email to a user ID via the local profiles database. - * The actual credential check lives in the auth service; rather than calling - * it directly, this hands the request off over Pub/Sub (publishing to - * auth.LoginRequests) and waits for the matching auth.LoginCompleted reply - * on auth.LoginResults before responding to the caller. - * - * NOTE: the wait is implemented with an in-process registry keyed by request - * ID, so the instance that publishes the request must be the one that - * receives the reply. That holds for local development (a single instance) - * but not for a horizontally scaled deployment, which would need a shared - * mechanism instead (e.g. a results table polled with retry_until). - */ - public async Login(params: LoginParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/auth/login`, JSON.stringify(params)) - return await resp.json() as LoginResponse - } - - /** - * Me returns the profile of the currently authenticated caller, or nil if - * the request is not authenticated. - */ - public async Me(): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/auth/me`) - return await resp.json() as Profile - } - - /** - * Update replaces the data of the authenticated user's own profile. - */ - public async Update(params: ProfileParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/profiles`, JSON.stringify(params)) - return await resp.json() as Profile - } - - /** - * UploadAvatar stores the request body as a new avatar image and returns its - * public URL. The caller is expected to persist the URL via the profile - * endpoints. Each upload gets a fresh key, so re-uploading never overwrites. - */ - public async UploadAvatar(method: "POST", body?: RequestInit["body"], options?: CallParameters): Promise { - return this.baseClient.callAPI(method, `/profiles/avatar`, body, options) - } - - /** - * UpsertPersonalData creates or replaces the authenticated user's own personal data. - */ - public async UpsertPersonalData(params: PersonalDataParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("PUT", `/profiles/personal-data`, JSON.stringify(params)) - return await resp.json() as PersonalData - } - } -} - -/** - * Service registration orchestrates new user registration. - */ -export namespace registration { - export interface EmailAvailabilityResponse { - email: string - available: boolean - "mx_valid": boolean - } - - export interface RegisterParams { - email: string - "display_name": string - "avatar_url": string - password: string - } - - export interface RegisterResponse { - profile: profiles.Profile - } - - export interface PasswordResetConfirmParams { - password: string - } - - export interface PasswordResetConfirmResponse { - email: string - reset: boolean - } - - export interface PasswordResetRequestParams { - email: string - } - - export interface PasswordResetRequestResponse { - "email_sent": boolean - } - - export interface WelcomeResponse { - email: string - confirmed: boolean - "used_at": string - } - - export class ServiceClient { - private baseClient: BaseClient - - constructor(baseClient: BaseClient) { - this.baseClient = baseClient - this.CheckEmail = this.CheckEmail.bind(this) - this.ConfirmPasswordReset = this.ConfirmPasswordReset.bind(this) - this.ConfirmWelcome = this.ConfirmWelcome.bind(this) - this.Register = this.Register.bind(this) - this.RequestPasswordReset = this.RequestPasswordReset.bind(this) - } - - /** - * CheckEmail reports whether an email can be used for a new registration. - */ - public async CheckEmail(email: string): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("GET", `/registration/email/${encodeURIComponent(email)}`) - return await resp.json() as EmailAvailabilityResponse - } - - /** - * ConfirmPasswordReset validates a reset token and changes the account password. - */ - public async ConfirmPasswordReset(token: string, params: PasswordResetConfirmParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/registration/password-reset/${encodeURIComponent(token)}`, JSON.stringify(params)) - return await resp.json() as PasswordResetConfirmResponse - } - - /** - * ConfirmWelcome validates the welcome email token. - */ - public async ConfirmWelcome(token: string): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/registration/welcome/${encodeURIComponent(token)}`) - return await resp.json() as WelcomeResponse - } - - /** - * Register creates a new user and records the welcome transactional email. - */ - public async Register(params: RegisterParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/registration`, JSON.stringify(params)) - return await resp.json() as RegisterResponse - } - - /** - * RequestPasswordReset creates a reset token and queues an email when the account exists. - */ - public async RequestPasswordReset(params: PasswordResetRequestParams): Promise { - // Now make the actual call to the API - const resp = await this.baseClient.callTypedAPI("POST", `/registration/password-reset`, JSON.stringify(params)) - return await resp.json() as PasswordResetRequestResponse - } - } -} - -export namespace http { - /** - * A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an - * HTTP response or the Cookie header of an HTTP request. - * - * See https://tools.ietf.org/html/rfc6265 for details. - */ - export interface Cookie { - Name: string - Value: string - /** - * indicates whether the Value was originally quoted - */ - Quoted: boolean - - /** - * optional - */ - Path: string - - /** - * optional - */ - Domain: string - - /** - * optional - */ - Expires: string - - /** - * for reading cookies only - */ - RawExpires: string - - /** - * MaxAge=0 means no 'Max-Age' attribute specified. - * MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' - * MaxAge>0 means Max-Age attribute present and given in seconds - */ - MaxAge: number - - Secure: boolean - HttpOnly: boolean - SameSite: SameSite - Partitioned: boolean - Raw: string - /** - * Raw text of unparsed attribute-value pairs - */ - Unparsed: string[] - } - - /** - * SameSite allows a server to define a cookie attribute making it impossible for - * the browser to send this cookie along with cross-site requests. The main - * goal is to mitigate the risk of cross-origin information leakage, and provide - * some protection against cross-site request forgery attacks. - * - * See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. - */ - export type SameSite = number -} - - - -function encodeQuery(parts: Record): string { - const pairs: string[] = [] - for (const key in parts) { - const val = (Array.isArray(parts[key]) ? parts[key] : [parts[key]]) as string[] - for (const v of val) { - pairs.push(`${key}=${encodeURIComponent(v)}`) - } - } - return pairs.join("&") -} - -// makeRecord takes a record and strips any undefined values from it, -// and returns the same record with a narrower type. -// @ts-ignore - TS ignore because makeRecord is not always used -function makeRecord(record: Record): Record { - for (const key in record) { - if (record[key] === undefined) { - delete record[key] - } - } - return record as Record -} - -function encodeWebSocketHeaders(headers: Record) { - // url safe, no pad - const base64encoded = btoa(JSON.stringify(headers)) - .replaceAll("=", "") - .replaceAll("+", "-") - .replaceAll("/", "_"); - return "encore.dev.headers." + base64encoded; -} - -class WebSocketConnection { - public ws: WebSocket; - - private hasUpdateHandlers: (() => void)[] = []; - - constructor(url: string, headers?: Record) { - let protocols = ["encore-ws"]; - if (headers) { - protocols.push(encodeWebSocketHeaders(headers)) - } - - this.ws = new WebSocket(url, protocols) - - this.on("error", () => { - this.resolveHasUpdateHandlers(); - }); - - this.on("close", () => { - this.resolveHasUpdateHandlers(); - }); - } - - resolveHasUpdateHandlers() { - const handlers = this.hasUpdateHandlers; - this.hasUpdateHandlers = []; - - for (const handler of handlers) { - handler() - } - } - - async hasUpdate() { - // await until a new message have been received, or the socket is closed - await new Promise((resolve) => { - this.hasUpdateHandlers.push(() => resolve(null)) - }); - } - - on(type: "error" | "close" | "message" | "open", handler: (event: any) => void) { - this.ws.addEventListener(type, handler); - } - - off(type: "error" | "close" | "message" | "open", handler: (event: any) => void) { - this.ws.removeEventListener(type, handler); - } - - close() { - this.ws.close(); - } -} - -export class StreamInOut { - public socket: WebSocketConnection; - private buffer: Response[] = []; - - constructor(url: string, headers?: Record) { - this.socket = new WebSocketConnection(url, headers); - this.socket.on("message", (event: any) => { - this.buffer.push(JSON.parse(event.data)); - this.socket.resolveHasUpdateHandlers(); - }); - } - - close() { - this.socket.close(); - } - - async send(msg: Request) { - if (this.socket.ws.readyState === WebSocket.CONNECTING) { - // await that the socket is opened - await new Promise((resolve) => { - this.socket.ws.addEventListener("open", resolve, { once: true }); - }); - } - - return this.socket.ws.send(JSON.stringify(msg)); - } - - async next(): Promise { - for await (const next of this) return next; - return undefined; - } - - async *[Symbol.asyncIterator](): AsyncGenerator { - while (true) { - if (this.buffer.length > 0) { - yield this.buffer.shift() as Response; - } else { - if (this.socket.ws.readyState === WebSocket.CLOSED) return; - await this.socket.hasUpdate(); - } - } - } -} - -export class StreamIn { - public socket: WebSocketConnection; - private buffer: Response[] = []; - - constructor(url: string, headers?: Record) { - this.socket = new WebSocketConnection(url, headers); - this.socket.on("message", (event: any) => { - this.buffer.push(JSON.parse(event.data)); - this.socket.resolveHasUpdateHandlers(); - }); - } - - close() { - this.socket.close(); - } - - async next(): Promise { - for await (const next of this) return next; - return undefined; - } - - async *[Symbol.asyncIterator](): AsyncGenerator { - while (true) { - if (this.buffer.length > 0) { - yield this.buffer.shift() as Response; - } else { - if (this.socket.ws.readyState === WebSocket.CLOSED) return; - await this.socket.hasUpdate(); - } - } - } -} - -export class StreamOut { - public socket: WebSocketConnection; - private responseValue: Promise; - - constructor(url: string, headers?: Record) { - let responseResolver: (_: any) => void; - this.responseValue = new Promise((resolve) => responseResolver = resolve); - - this.socket = new WebSocketConnection(url, headers); - this.socket.on("message", (event: any) => { - responseResolver(JSON.parse(event.data)) - }); - } - - async response(): Promise { - return this.responseValue; - } - - close() { - this.socket.close(); - } - - async send(msg: Request) { - if (this.socket.ws.readyState === WebSocket.CONNECTING) { - // await that the socket is opened - await new Promise((resolve) => { - this.socket.ws.addEventListener("open", resolve, { once: true }); - }); - } - - return this.socket.ws.send(JSON.stringify(msg)); - } -} -// CallParameters is the type of the parameters to a method call, but require headers to be a Record type -type CallParameters = Omit & { - /** Headers to be sent with the request */ - headers?: Record - - /** Query parameters to be sent with the request */ - query?: Record -} - -// AuthDataGenerator is a function that returns a new instance of the authentication data required by this API -export type AuthDataGenerator = () => - | auth.AuthParams - | Promise - | undefined; - -// A fetcher is the prototype for the inbuilt Fetch function -export type Fetcher = typeof fetch; - -const boundFetch = fetch.bind(this); - -class BaseClient { - readonly baseURL: string - readonly fetcher: Fetcher - readonly headers: Record - readonly requestInit: Omit & { headers?: Record } - readonly authGenerator?: AuthDataGenerator - - constructor(baseURL: string, options: ClientOptions) { - this.baseURL = baseURL - this.headers = {} - - // Add User-Agent header if the script is running in the server - // because browsers do not allow setting User-Agent headers to requests - if (!BROWSER) { - this.headers["User-Agent"] = "q55oi-Generated-TS-Client (Encore/v1.57.8)"; - } - - this.requestInit = options.requestInit ?? {}; - - // Setup what fetch function we'll be using in the base client - if (options.fetcher !== undefined) { - this.fetcher = options.fetcher - } else { - this.fetcher = boundFetch - } - - // Setup an authentication data generator using the auth data token option - if (options.auth !== undefined) { - const auth = options.auth - if (typeof auth === "function") { - this.authGenerator = auth - } else { - this.authGenerator = () => auth - } - } - } - - async getAuthData(): Promise { - let authData: auth.AuthParams | undefined; - - // If authorization data generator is present, call it and add the returned data to the request - if (this.authGenerator) { - const mayBePromise = this.authGenerator(); - if (mayBePromise instanceof Promise) { - authData = await mayBePromise; - } else { - authData = mayBePromise; - } - } - - if (authData) { - const data: CallParameters = {}; - - data.headers = makeRecord({ - authorization: authData.Authorization, - }); - - return data; - } - - return undefined; - } - - // createStreamInOut sets up a stream to a streaming API endpoint. - async createStreamInOut(path: string, params?: CallParameters): Promise> { - let { query, headers } = params ?? {}; - - // Fetch auth data if there is any - const authData = await this.getAuthData(); - - // If we now have authentication data, add it to the request - if (authData) { - if (authData.query) { - query = {...query, ...authData.query}; - } - if (authData.headers) { - headers = {...headers, ...authData.headers}; - } - } - - const queryString = query ? '?' + encodeQuery(query) : '' - return new StreamInOut(this.baseURL + path + queryString, headers); - } - - // createStreamIn sets up a stream to a streaming API endpoint. - async createStreamIn(path: string, params?: CallParameters): Promise> { - let { query, headers } = params ?? {}; - - // Fetch auth data if there is any - const authData = await this.getAuthData(); - - // If we now have authentication data, add it to the request - if (authData) { - if (authData.query) { - query = {...query, ...authData.query}; - } - if (authData.headers) { - headers = {...headers, ...authData.headers}; - } - } - - const queryString = query ? '?' + encodeQuery(query) : '' - return new StreamIn(this.baseURL + path + queryString, headers); - } - - // createStreamOut sets up a stream to a streaming API endpoint. - async createStreamOut(path: string, params?: CallParameters): Promise> { - let { query, headers } = params ?? {}; - - // Fetch auth data if there is any - const authData = await this.getAuthData(); - - // If we now have authentication data, add it to the request - if (authData) { - if (authData.query) { - query = {...query, ...authData.query}; - } - if (authData.headers) { - headers = {...headers, ...authData.headers}; - } - } - - const queryString = query ? '?' + encodeQuery(query) : '' - return new StreamOut(this.baseURL + path + queryString, headers); - } - - // callTypedAPI makes an API call, defaulting content type to "application/json" - public async callTypedAPI(method: string, path: string, body?: RequestInit["body"], params?: CallParameters): Promise { - return this.callAPI(method, path, body, { - ...params, - headers: { "Content-Type": "application/json", ...params?.headers } - }); - } - - // callAPI is used by each generated API method to actually make the request - public async callAPI(method: string, path: string, body?: RequestInit["body"], params?: CallParameters): Promise { - let { query, headers, ...rest } = params ?? {} - const init = { - ...this.requestInit, - ...rest, - method, - body: body ?? null, - } - - // Merge our headers with any predefined headers - init.headers = {...this.headers, ...init.headers, ...headers} - - // Fetch auth data if there is any - const authData = await this.getAuthData(); - - // If we now have authentication data, add it to the request - if (authData) { - if (authData.query) { - query = {...query, ...authData.query}; - } - if (authData.headers) { - init.headers = {...init.headers, ...authData.headers}; - } - } - - // Make the actual request - const queryString = query ? '?' + encodeQuery(query) : '' - const response = await this.fetcher(this.baseURL+path+queryString, init) - - // handle any error responses - if (!response.ok) { - // try and get the error message from the response body - let body: APIErrorResponse = { code: ErrCode.Unknown, message: `request failed: status ${response.status}` } - - // if we can get the structured error we should, otherwise give a best effort - try { - const text = await response.text() - - try { - const jsonBody = JSON.parse(text) - if (isAPIErrorResponse(jsonBody)) { - body = jsonBody - } else { - body.message += ": " + JSON.stringify(jsonBody) - } - } catch { - body.message += ": " + text - } - } catch (e) { - // otherwise we just append the text to the error message - body.message += ": " + String(e) - } - - throw new APIError(response.status, body) - } - - return response - } -} - -/** - * APIErrorDetails represents the response from an Encore API in the case of an error - */ -interface APIErrorResponse { - code: ErrCode - message: string - details?: any -} - -function isAPIErrorResponse(err: any): err is APIErrorResponse { - return ( - err !== undefined && err !== null && - isErrCode(err.code) && - typeof(err.message) === "string" && - (err.details === undefined || err.details === null || typeof(err.details) === "object") - ) -} - -function isErrCode(code: any): code is ErrCode { - return code !== undefined && Object.values(ErrCode).includes(code) -} - -/** - * APIError represents a structured error as returned from an Encore application. - */ -export class APIError extends Error { - /** - * The HTTP status code associated with the error. - */ - public readonly status: number - - /** - * The Encore error code - */ - public readonly code: ErrCode - - /** - * The error details - */ - public readonly details?: any - - constructor(status: number, response: APIErrorResponse) { - // extending errors causes issues after you construct them, unless you apply the following fixes - super(response.message); - - // set error name as constructor name, make it not enumerable to keep native Error behavior - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target#new.target_in_constructors - Object.defineProperty(this, 'name', { - value: 'APIError', - enumerable: false, - configurable: true, - }) - - // fix the prototype chain - if ((Object as any).setPrototypeOf == undefined) { - (this as any).__proto__ = APIError.prototype - } else { - Object.setPrototypeOf(this, APIError.prototype); - } - - // capture a stack trace - if ((Error as any).captureStackTrace !== undefined) { - (Error as any).captureStackTrace(this, this.constructor); - } - - this.status = status - this.code = response.code - this.details = response.details - } -} - -/** - * Typeguard allowing use of an APIError's fields' - */ -export function isAPIError(err: any): err is APIError { - return err instanceof APIError; -} - -export enum ErrCode { - /** - * OK indicates the operation was successful. - */ - OK = "ok", - - /** - * Canceled indicates the operation was canceled (typically by the caller). - * - * Encore will generate this error code when cancellation is requested. - */ - Canceled = "canceled", - - /** - * Unknown error. An example of where this error may be returned is - * if a Status value received from another address space belongs to - * an error-space that is not known in this address space. Also - * errors raised by APIs that do not return enough error information - * may be converted to this error. - * - * Encore will generate this error code in the above two mentioned cases. - */ - Unknown = "unknown", - - /** - * InvalidArgument indicates client specified an invalid argument. - * Note that this differs from FailedPrecondition. It indicates arguments - * that are problematic regardless of the state of the system - * (e.g., a malformed file name). - * - * This error code will not be generated by the gRPC framework. - */ - InvalidArgument = "invalid_argument", - - /** - * DeadlineExceeded means operation expired before completion. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For - * example, a successful response from a server could have been delayed - * long enough for the deadline to expire. - * - * The gRPC framework will generate this error code when the deadline is - * exceeded. - */ - DeadlineExceeded = "deadline_exceeded", - - /** - * NotFound means some requested entity (e.g., file or directory) was - * not found. - * - * This error code will not be generated by the gRPC framework. - */ - NotFound = "not_found", - - /** - * AlreadyExists means an attempt to create an entity failed because one - * already exists. - * - * This error code will not be generated by the gRPC framework. - */ - AlreadyExists = "already_exists", - - /** - * PermissionDenied indicates the caller does not have permission to - * execute the specified operation. It must not be used for rejections - * caused by exhausting some resource (use ResourceExhausted - * instead for those errors). It must not be - * used if the caller cannot be identified (use Unauthenticated - * instead for those errors). - * - * This error code will not be generated by the gRPC core framework, - * but expect authentication middleware to use it. - */ - PermissionDenied = "permission_denied", - - /** - * ResourceExhausted indicates some resource has been exhausted, perhaps - * a per-user quota, or perhaps the entire file system is out of space. - * - * This error code will be generated by the gRPC framework in - * out-of-memory and server overload situations, or when a message is - * larger than the configured maximum size. - */ - ResourceExhausted = "resource_exhausted", - - /** - * FailedPrecondition indicates operation was rejected because the - * system is not in a state required for the operation's execution. - * For example, directory to be deleted may be non-empty, an rmdir - * operation is applied to a non-directory, etc. - * - * A litmus test that may help a service implementor in deciding - * between FailedPrecondition, Aborted, and Unavailable: - * (a) Use Unavailable if the client can retry just the failing call. - * (b) Use Aborted if the client should retry at a higher-level - * (e.g., restarting a read-modify-write sequence). - * (c) Use FailedPrecondition if the client should not retry until - * the system state has been explicitly fixed. E.g., if an "rmdir" - * fails because the directory is non-empty, FailedPrecondition - * should be returned since the client should not retry unless - * they have first fixed up the directory by deleting files from it. - * (d) Use FailedPrecondition if the client performs conditional - * REST Get/Update/Delete on a resource and the resource on the - * server does not match the condition. E.g., conflicting - * read-modify-write on the same resource. - * - * This error code will not be generated by the gRPC framework. - */ - FailedPrecondition = "failed_precondition", - - /** - * Aborted indicates the operation was aborted, typically due to a - * concurrency issue like sequencer check failures, transaction aborts, - * etc. - * - * See litmus test above for deciding between FailedPrecondition, - * Aborted, and Unavailable. - */ - Aborted = "aborted", - - /** - * OutOfRange means operation was attempted past the valid range. - * E.g., seeking or reading past end of file. - * - * Unlike InvalidArgument, this error indicates a problem that may - * be fixed if the system state changes. For example, a 32-bit file - * system will generate InvalidArgument if asked to read at an - * offset that is not in the range [0,2^32-1], but it will generate - * OutOfRange if asked to read from an offset past the current - * file size. - * - * There is a fair bit of overlap between FailedPrecondition and - * OutOfRange. We recommend using OutOfRange (the more specific - * error) when it applies so that callers who are iterating through - * a space can easily look for an OutOfRange error to detect when - * they are done. - * - * This error code will not be generated by the gRPC framework. - */ - OutOfRange = "out_of_range", - - /** - * Unimplemented indicates operation is not implemented or not - * supported/enabled in this service. - * - * This error code will be generated by the gRPC framework. Most - * commonly, you will see this error code when a method implementation - * is missing on the server. It can also be generated for unknown - * compression algorithms or a disagreement as to whether an RPC should - * be streaming. - */ - Unimplemented = "unimplemented", - - /** - * Internal errors. Means some invariants expected by underlying - * system has been broken. If you see one of these errors, - * something is very broken. - * - * This error code will be generated by the gRPC framework in several - * internal error conditions. - */ - Internal = "internal", - - /** - * Unavailable indicates the service is currently unavailable. - * This is a most likely a transient condition and may be corrected - * by retrying with a backoff. Note that it is not always safe to retry - * non-idempotent operations. - * - * See litmus test above for deciding between FailedPrecondition, - * Aborted, and Unavailable. - * - * This error code will be generated by the gRPC framework during - * abrupt shutdown of a server process or network connection. - */ - Unavailable = "unavailable", - - /** - * DataLoss indicates unrecoverable data loss or corruption. - * - * This error code will not be generated by the gRPC framework. - */ - DataLoss = "data_loss", - - /** - * Unauthenticated indicates the request does not have valid - * authentication credentials for the operation. - * - * The gRPC framework will generate this error code when the - * authentication metadata is invalid or a Credentials callback fails, - * but also expect authentication middleware to generate it. - */ - Unauthenticated = "unauthenticated", -} diff --git a/frontend/src/encore/zod.ts b/frontend/src/encore/zod.ts deleted file mode 100644 index 06c0c58..0000000 --- a/frontend/src/encore/zod.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Code synced from src/encore/client.ts by frontend/tools/zod-sync.mjs. -// Existing field validators are preserved when this file is synced again. -import { z } from 'zod'; - -export const AdminPersonalDataParamsSchema = z.object({ - first_name: z.string().min(2).max(32), - last_name: z.string().min(2).max(32), - address: z.string().min(5).max(32), - cap: z.string(), - city: z.string().min(2).max(32), - country: z.string(), -}); - -export const AdminProfileParamsSchema = z.object({ - display_name: z.string().min(2).max(32), - avatar_url: z.string(), -}); - -export const AdminRegisterParamsSchema = z.object({ - email: z.string(), - display_name: z.string(), - avatar_url: z.string(), - password: z.string(), -}); - -export const AdminUpdateProfileArtistParamsSchema = z.object({ - is_artist: z.boolean(), -}); - -export const AdminUpdateProfilePasswordParamsSchema = z.object({ - password: z.string(), -}); - -export const AdminUpdateProfileRoleParamsSchema = z.object({ - role: z.string(), -}); - -export const AdminUpdateProfileStatusParamsSchema = z.object({ - status: z.number(), -}); - -export const AuthSetPasswordParamsSchema = z.object({ - current_password: z.string(), - password: z.string(), -}); - -export const ProfilesLoginParamsSchema = z.object({ - user_email: z.string(), - password: z.string(), -}); - -export const ProfilesPersonalDataParamsSchema = z.object({ - first_name: z.string(), - last_name: z.string(), - address: z.string(), - cap: z.string(), - city: z.string(), - country: z.string(), -}); - -export const ProfilesProfileParamsSchema = z.object({ - display_name: z.string(), - avatar_url: z.string(), -}); - -export const ProfilesRegisterParamsSchema = z.object({ - email: z.string(), - display_name: z.string(), - avatar_url: z.string(), - password: z.string(), -}); - -export const RegistrationRegisterParamsSchema = z.object({ - email: z.string().email(), - display_name: z.string().min(2).max(32), - avatar_url: z.string(), - password: z.string().min(8), -}); - -export const RegistrationPasswordResetConfirmParamsSchema = z.object({ - password: z.string(), -}); - -export const RegistrationPasswordResetRequestParamsSchema = z.object({ - email: z.string(), -}); diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts deleted file mode 100644 index e8f85e7..0000000 --- a/frontend/src/i18n/index.ts +++ /dev/null @@ -1,688 +0,0 @@ -import { createI18n } from 'vue-i18n'; - -export type SupportedLocale = 'en' | 'it' | 'fr' | 'de' | 'es'; - -export const localeOptions: { label: string; value: SupportedLocale }[] = [ - { label: 'English', value: 'en' }, - { label: 'Italiano', value: 'it' }, - { label: 'Français', value: 'fr' }, - { label: 'Deutsch', value: 'de' }, - { label: 'Español', value: 'es' }, -]; - -const fallbackLocale: SupportedLocale = 'en'; -const storageKey = 'app.locale'; - -const messages = { - en: { - app: { title: 'Encore Profiles' }, - nav: { - menu: 'Menu', - profiles: 'Profiles', - myProfile: 'My profile', - login: 'Log in', - register: 'Register', - logout: 'Log out ({name})', - logoutAction: 'Log out', - language: 'Language', - }, - actions: { - cancel: 'Cancel', - save: 'Save', - create: 'Create', - remove: 'Remove', - chooseFile: 'Choose file', - camera: 'Camera', - takePhoto: 'Take photo', - crop: 'Crop', - newProfile: 'New profile', - register: 'Register', - editProfile: 'Edit profile', - updateRole: 'Update role', - updateStatus: 'Update status', - updatePassword: 'Update password', - }, - fields: { - email: 'Email', - password: 'Password', - displayName: 'Display name', - firstName: 'First name', - lastName: 'Last name', - address: 'Address', - cap: 'Postcode', - city: 'City', - country: 'Country', - role: 'Role', - status: 'Status', - }, - avatar: { - cropAvatar: 'Crop avatar', - cameraUnavailable: 'Camera not available in this browser.', - }, - admin: { - dashboard: 'Dashboard', - profiles: 'Profiles', - personalData: 'Personal data', - profile: 'Profile', - notArtist: 'Not an artist', - artist: 'Artist', - columns: { - name: 'Name', - email: 'Email', - role: 'Role', - status: 'Status', - artist: 'Artist', - created: 'Created', - updated: 'Updated', - }, - notifications: { - profileCreated: 'Profile created.', - profileUpdated: 'Profile updated.', - personalDataUpdated: 'Personal data updated.', - roleUpdated: 'Role updated.', - statusUpdated: 'Status updated.', - passwordUpdated: 'Password updated.', - }, - }, - profile: { - title: 'My profile', - avatar: 'Avatar', - password: 'Password', - currentPassword: 'Current password', - newPassword: 'New password', - confirmNewPassword: 'Confirm new password', - passwordMismatch: 'Passwords do not match.', - loginRequired: 'You must be logged in to view your profile.', - }, - login: { - title: 'Log in', - needAccount: 'Create an account', - forgotPassword: 'Forgot password', - }, - passwordReset: { - title: 'Password reset', - newPasswordTitle: 'Choose a new password', - sendLink: 'Send reset link', - updatePassword: 'Update password', - backToLogin: 'Back to log in', - requestSent: 'If the email exists, we sent a password reset link.', - resetDone: 'Password updated. You can now log in.', - }, - register: { - title: 'Create account', - welcomeTitle: 'Welcome', - haveAccount: 'I already have an account', - success: 'Registration completed. You can now log in.', - confirmationSent: 'We sent you a confirmation request. Please check your mailbox.', - emailUnavailable: 'This email is already registered.', - emailDomainInvalid: 'This email domain cannot receive email.', - }, - welcome: { - title: 'Email confirmation', - success: 'Email confirmed for {email}.', - missingToken: 'Missing confirmation token.', - }, - pages: { - home: 'Home', - goHome: 'Go Home', - goToIndex: 'Go to Index Page', - goToSecond: 'Go to Second Page', - cropperExample: 'Cropper example', - notFound: 'Oops. Nothing here...', - unauthorized: 'Unauthorized', - unauthorizedHint: 'You need to log in to access this page.', - }, - status: { - 0: 'Active', - 1: 'Inactive', - 2: 'Suspended', - 3: 'Deleted', - 4: 'Pending', - 5: 'Waiting deletion', - 6: 'Banned', - unknown: 'Unknown ({status})', - }, - }, - it: { - app: { title: 'Profili Encore' }, - nav: { - menu: 'Menu', - profiles: 'Profili', - myProfile: 'Il mio profilo', - login: 'Accedi', - register: 'Registrati', - logout: 'Esci ({name})', - logoutAction: 'Esci', - language: 'Lingua', - }, - actions: { - cancel: 'Annulla', - save: 'Salva', - create: 'Crea', - remove: 'Rimuovi', - chooseFile: 'Scegli file', - camera: 'Fotocamera', - takePhoto: 'Scatta foto', - crop: 'Ritaglia', - newProfile: 'Nuovo profilo', - register: 'Registrati', - editProfile: 'Modifica profilo', - updateRole: 'Aggiorna ruolo', - updateStatus: 'Aggiorna stato', - updatePassword: 'Aggiorna password', - }, - fields: { - email: 'Email', - password: 'Password', - displayName: 'Nome visualizzato', - firstName: 'Nome', - lastName: 'Cognome', - address: 'Indirizzo', - cap: 'CAP', - city: 'Città', - country: 'Paese', - role: 'Ruolo', - status: 'Stato', - }, - avatar: { - cropAvatar: 'Ritaglia avatar', - cameraUnavailable: 'Fotocamera non disponibile in questo browser.', - }, - admin: { - dashboard: 'Dashboard', - profiles: 'Profili', - personalData: 'Dati personali', - profile: 'Profilo', - notArtist: 'Non artista', - artist: 'Artista', - columns: { - name: 'Nome', - email: 'Email', - role: 'Ruolo', - status: 'Stato', - artist: 'Artista', - created: 'Creato', - updated: 'Aggiornato', - }, - notifications: { - profileCreated: 'Profilo creato.', - profileUpdated: 'Profilo aggiornato.', - personalDataUpdated: 'Dati personali aggiornati.', - roleUpdated: 'Ruolo aggiornato.', - statusUpdated: 'Stato aggiornato.', - passwordUpdated: 'Password aggiornata.', - }, - }, - profile: { - title: 'Il mio profilo', - avatar: 'Avatar', - password: 'Password', - currentPassword: 'Password corrente', - newPassword: 'Nuova password', - confirmNewPassword: 'Conferma nuova password', - passwordMismatch: 'Le password non coincidono.', - loginRequired: 'Devi effettuare l’accesso per visualizzare il profilo.', - }, - login: { - title: 'Accedi', - needAccount: 'Crea un account', - forgotPassword: 'Password dimenticata', - }, - passwordReset: { - title: 'Recupero password', - newPasswordTitle: 'Scegli una nuova password', - sendLink: 'Invia link di recupero', - updatePassword: 'Aggiorna password', - backToLogin: 'Torna al login', - requestSent: 'Se l’email esiste, abbiamo inviato un link per reimpostare la password.', - resetDone: 'Password aggiornata. Ora puoi accedere.', - }, - register: { - title: 'Crea account', - welcomeTitle: 'Benvenuto', - haveAccount: 'Ho già un account', - success: 'Registrazione completata. Ora puoi accedere.', - confirmationSent: 'Ti abbiamo inviato una richieta di conferma, consulta la tua casella di posta.', - emailUnavailable: 'Questa email è già registrata.', - emailDomainInvalid: 'Il dominio di questa email non può ricevere posta.', - }, - welcome: { - title: 'Conferma email', - success: 'Email confermata per {email}.', - missingToken: 'Token di conferma mancante.', - }, - pages: { - home: 'Home', - goHome: 'Vai alla home', - goToIndex: 'Vai alla pagina iniziale', - goToSecond: 'Vai alla seconda pagina', - cropperExample: 'Esempio cropper', - notFound: 'Ops. Qui non c’è niente...', - unauthorized: 'Non autorizzato', - unauthorizedHint: 'Devi effettuare l’accesso per visualizzare questa pagina.', - }, - status: { - 0: 'Attivo', - 1: 'Inattivo', - 2: 'Sospeso', - 3: 'Eliminato', - 4: 'In attesa', - 5: 'In attesa di eliminazione', - 6: 'Bannato', - unknown: 'Sconosciuto ({status})', - }, - }, - fr: { - app: { title: 'Profils Encore' }, - nav: { - menu: 'Menu', - profiles: 'Profils', - myProfile: 'Mon profil', - login: 'Connexion', - register: 'Inscription', - logout: 'Déconnexion ({name})', - logoutAction: 'Déconnexion', - language: 'Langue', - }, - actions: { - cancel: 'Annuler', - save: 'Enregistrer', - create: 'Créer', - remove: 'Supprimer', - chooseFile: 'Choisir un fichier', - camera: 'Caméra', - takePhoto: 'Prendre une photo', - crop: 'Recadrer', - newProfile: 'Nouveau profil', - register: 'S’inscrire', - editProfile: 'Modifier le profil', - updateRole: 'Modifier le rôle', - updateStatus: 'Modifier le statut', - updatePassword: 'Modifier le mot de passe', - }, - fields: { - email: 'Email', - password: 'Mot de passe', - displayName: 'Nom affiché', - firstName: 'Prénom', - lastName: 'Nom', - address: 'Adresse', - cap: 'Code postal', - city: 'Ville', - country: 'Pays', - role: 'Rôle', - status: 'Statut', - }, - avatar: { - cropAvatar: 'Recadrer l’avatar', - cameraUnavailable: 'Caméra non disponible dans ce navigateur.', - }, - admin: { - dashboard: 'Tableau de bord', - profiles: 'Profils', - personalData: 'Données personnelles', - profile: 'Profil', - notArtist: 'Pas artiste', - artist: 'Artiste', - columns: { - name: 'Nom', - email: 'Email', - role: 'Rôle', - status: 'Statut', - artist: 'Artiste', - created: 'Créé', - updated: 'Mis à jour', - }, - notifications: { - profileCreated: 'Profil créé.', - profileUpdated: 'Profil mis à jour.', - personalDataUpdated: 'Données personnelles mises à jour.', - roleUpdated: 'Rôle mis à jour.', - statusUpdated: 'Statut mis à jour.', - passwordUpdated: 'Mot de passe mis à jour.', - }, - }, - profile: { - title: 'Mon profil', - avatar: 'Avatar', - password: 'Mot de passe', - currentPassword: 'Mot de passe actuel', - newPassword: 'Nouveau mot de passe', - confirmNewPassword: 'Confirmer le nouveau mot de passe', - passwordMismatch: 'Les mots de passe ne correspondent pas.', - loginRequired: 'Vous devez être connecté pour voir votre profil.', - }, - login: { - title: 'Connexion', - needAccount: 'Créer un compte', - forgotPassword: 'Mot de passe oublié', - }, - passwordReset: { - title: 'Réinitialisation du mot de passe', - newPasswordTitle: 'Choisir un nouveau mot de passe', - sendLink: 'Envoyer le lien', - updatePassword: 'Mettre à jour le mot de passe', - backToLogin: 'Retour à la connexion', - requestSent: 'Si l’email existe, nous avons envoyé un lien de réinitialisation.', - resetDone: 'Mot de passe mis à jour. Vous pouvez maintenant vous connecter.', - }, - register: { - title: 'Créer un compte', - welcomeTitle: 'Bienvenue', - haveAccount: 'J’ai déjà un compte', - success: 'Inscription terminée. Vous pouvez maintenant vous connecter.', - confirmationSent: 'Nous vous avons envoyé une demande de confirmation. Veuillez consulter votre boîte mail.', - emailUnavailable: 'Cet email est déjà enregistré.', - emailDomainInvalid: 'Ce domaine email ne peut pas recevoir d’e-mails.', - }, - welcome: { - title: 'Confirmation email', - success: 'Email confirmée pour {email}.', - missingToken: 'Jeton de confirmation manquant.', - }, - pages: { - home: 'Accueil', - goHome: 'Accueil', - goToIndex: 'Aller à la page d’accueil', - goToSecond: 'Aller à la deuxième page', - cropperExample: 'Exemple de recadrage', - notFound: 'Oups. Rien ici...', - unauthorized: 'Non autorisé', - unauthorizedHint: 'Vous devez vous connecter pour accéder à cette page.', - }, - status: { - 0: 'Actif', - 1: 'Inactif', - 2: 'Suspendu', - 3: 'Supprimé', - 4: 'En attente', - 5: 'Suppression en attente', - 6: 'Banni', - unknown: 'Inconnu ({status})', - }, - }, - de: { - app: { title: 'Encore Profile' }, - nav: { - menu: 'Menü', - profiles: 'Profile', - myProfile: 'Mein Profil', - login: 'Anmelden', - register: 'Registrieren', - logout: 'Abmelden ({name})', - logoutAction: 'Abmelden', - language: 'Sprache', - }, - actions: { - cancel: 'Abbrechen', - save: 'Speichern', - create: 'Erstellen', - remove: 'Entfernen', - chooseFile: 'Datei wählen', - camera: 'Kamera', - takePhoto: 'Foto aufnehmen', - crop: 'Zuschneiden', - newProfile: 'Neues Profil', - register: 'Registrieren', - editProfile: 'Profil bearbeiten', - updateRole: 'Rolle ändern', - updateStatus: 'Status ändern', - updatePassword: 'Passwort ändern', - }, - fields: { - email: 'E-Mail', - password: 'Passwort', - displayName: 'Anzeigename', - firstName: 'Vorname', - lastName: 'Nachname', - address: 'Adresse', - cap: 'Postleitzahl', - city: 'Stadt', - country: 'Land', - role: 'Rolle', - status: 'Status', - }, - avatar: { - cropAvatar: 'Avatar zuschneiden', - cameraUnavailable: 'Kamera ist in diesem Browser nicht verfügbar.', - }, - admin: { - dashboard: 'Dashboard', - profiles: 'Profile', - personalData: 'Persönliche Daten', - profile: 'Profil', - notArtist: 'Kein Künstler', - artist: 'Künstler', - columns: { - name: 'Name', - email: 'E-Mail', - role: 'Rolle', - status: 'Status', - artist: 'Künstler', - created: 'Erstellt', - updated: 'Aktualisiert', - }, - notifications: { - profileCreated: 'Profil erstellt.', - profileUpdated: 'Profil aktualisiert.', - personalDataUpdated: 'Persönliche Daten aktualisiert.', - roleUpdated: 'Rolle aktualisiert.', - statusUpdated: 'Status aktualisiert.', - passwordUpdated: 'Passwort aktualisiert.', - }, - }, - profile: { - title: 'Mein Profil', - avatar: 'Avatar', - password: 'Passwort', - currentPassword: 'Aktuelles Passwort', - newPassword: 'Neues Passwort', - confirmNewPassword: 'Neues Passwort bestätigen', - passwordMismatch: 'Die Passwörter stimmen nicht überein.', - loginRequired: 'Sie müssen angemeldet sein, um Ihr Profil zu sehen.', - }, - login: { - title: 'Anmelden', - needAccount: 'Konto erstellen', - forgotPassword: 'Passwort vergessen', - }, - passwordReset: { - title: 'Passwort zurücksetzen', - newPasswordTitle: 'Neues Passwort wählen', - sendLink: 'Link senden', - updatePassword: 'Passwort aktualisieren', - backToLogin: 'Zur Anmeldung', - requestSent: 'Wenn die E-Mail existiert, haben wir einen Link zum Zurücksetzen gesendet.', - resetDone: 'Passwort aktualisiert. Sie können sich jetzt anmelden.', - }, - register: { - title: 'Konto erstellen', - welcomeTitle: 'Willkommen', - haveAccount: 'Ich habe bereits ein Konto', - success: 'Registrierung abgeschlossen. Sie können sich jetzt anmelden.', - confirmationSent: 'Wir haben Ihnen eine Bestätigungsanfrage gesendet. Bitte prüfen Sie Ihr Postfach.', - emailUnavailable: 'Diese E-Mail ist bereits registriert.', - emailDomainInvalid: 'Diese E-Mail-Domain kann keine E-Mails empfangen.', - }, - welcome: { - title: 'E-Mail-Bestätigung', - success: 'E-Mail für {email} bestätigt.', - missingToken: 'Bestätigungstoken fehlt.', - }, - pages: { - home: 'Startseite', - goHome: 'Zur Startseite', - goToIndex: 'Zur Startseite', - goToSecond: 'Zur zweiten Seite', - cropperExample: 'Cropper-Beispiel', - notFound: 'Hoppla. Hier ist nichts...', - unauthorized: 'Nicht autorisiert', - unauthorizedHint: 'Sie müssen angemeldet sein, um diese Seite aufzurufen.', - }, - status: { - 0: 'Aktiv', - 1: 'Inaktiv', - 2: 'Gesperrt', - 3: 'Gelöscht', - 4: 'Ausstehend', - 5: 'Löschung ausstehend', - 6: 'Gebannt', - unknown: 'Unbekannt ({status})', - }, - }, - es: { - app: { title: 'Perfiles Encore' }, - nav: { - menu: 'Menú', - profiles: 'Perfiles', - myProfile: 'Mi perfil', - login: 'Iniciar sesión', - register: 'Registrarse', - logout: 'Cerrar sesión ({name})', - logoutAction: 'Cerrar sesión', - language: 'Idioma', - }, - actions: { - cancel: 'Cancelar', - save: 'Guardar', - create: 'Crear', - remove: 'Eliminar', - chooseFile: 'Elegir archivo', - camera: 'Cámara', - takePhoto: 'Tomar foto', - crop: 'Recortar', - newProfile: 'Nuevo perfil', - register: 'Registrarse', - editProfile: 'Editar perfil', - updateRole: 'Actualizar rol', - updateStatus: 'Actualizar estado', - updatePassword: 'Actualizar contraseña', - }, - fields: { - email: 'Email', - password: 'Contraseña', - displayName: 'Nombre visible', - firstName: 'Nombre', - lastName: 'Apellido', - address: 'Dirección', - cap: 'Código postal', - city: 'Ciudad', - country: 'País', - role: 'Rol', - status: 'Estado', - }, - avatar: { - cropAvatar: 'Recortar avatar', - cameraUnavailable: 'La cámara no está disponible en este navegador.', - }, - admin: { - dashboard: 'Panel', - profiles: 'Perfiles', - personalData: 'Datos personales', - profile: 'Perfil', - notArtist: 'No artista', - artist: 'Artista', - columns: { - name: 'Nombre', - email: 'Email', - role: 'Rol', - status: 'Estado', - artist: 'Artista', - created: 'Creado', - updated: 'Actualizado', - }, - notifications: { - profileCreated: 'Perfil creado.', - profileUpdated: 'Perfil actualizado.', - personalDataUpdated: 'Datos personales actualizados.', - roleUpdated: 'Rol actualizado.', - statusUpdated: 'Estado actualizado.', - passwordUpdated: 'Contraseña actualizada.', - }, - }, - profile: { - title: 'Mi perfil', - avatar: 'Avatar', - password: 'Contraseña', - currentPassword: 'Contraseña actual', - newPassword: 'Nueva contraseña', - confirmNewPassword: 'Confirmar nueva contraseña', - passwordMismatch: 'Las contraseñas no coinciden.', - loginRequired: 'Debes iniciar sesión para ver tu perfil.', - }, - login: { - title: 'Iniciar sesión', - needAccount: 'Crear una cuenta', - forgotPassword: 'Contraseña olvidada', - }, - passwordReset: { - title: 'Recuperar contraseña', - newPasswordTitle: 'Elige una nueva contraseña', - sendLink: 'Enviar enlace', - updatePassword: 'Actualizar contraseña', - backToLogin: 'Volver al inicio de sesión', - requestSent: 'Si el email existe, enviamos un enlace para restablecer la contraseña.', - resetDone: 'Contraseña actualizada. Ahora puedes iniciar sesión.', - }, - register: { - title: 'Crear cuenta', - welcomeTitle: 'Bienvenido', - haveAccount: 'Ya tengo una cuenta', - success: 'Registro completado. Ahora puedes iniciar sesión.', - confirmationSent: 'Te hemos enviado una solicitud de confirmación. Revisa tu buzón de correo.', - emailUnavailable: 'Este email ya está registrado.', - emailDomainInvalid: 'Este dominio de email no puede recibir correo.', - }, - welcome: { - title: 'Confirmación de email', - success: 'Email confirmado para {email}.', - missingToken: 'Falta el token de confirmación.', - }, - pages: { - home: 'Inicio', - goHome: 'Ir al inicio', - goToIndex: 'Ir a la página inicial', - goToSecond: 'Ir a la segunda página', - cropperExample: 'Ejemplo de recorte', - notFound: 'Vaya. Aquí no hay nada...', - unauthorized: 'No autorizado', - unauthorizedHint: 'Debes iniciar sesión para acceder a esta página.', - }, - status: { - 0: 'Activo', - 1: 'Inactivo', - 2: 'Suspendido', - 3: 'Eliminado', - 4: 'Pendiente', - 5: 'Esperando eliminación', - 6: 'Bloqueado', - unknown: 'Desconocido ({status})', - }, - }, -}; - -function detectLocale(): SupportedLocale { - const stored = window.localStorage.getItem(storageKey); - if (isSupportedLocale(stored)) return stored; - - const browserLocale = navigator.language.split('-')[0] ?? null; - if (isSupportedLocale(browserLocale)) return browserLocale; - - return fallbackLocale; -} - -function isSupportedLocale(locale: string | null): locale is SupportedLocale { - return localeOptions.some((option) => option.value === locale); -} - -export const i18n = createI18n({ - legacy: false, - locale: detectLocale(), - fallbackLocale, - messages, -}); - -export function setLocale(locale: SupportedLocale) { - i18n.global.locale.value = locale; - window.localStorage.setItem(storageKey, locale); -} diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue deleted file mode 100644 index f7a5979..0000000 --- a/frontend/src/layouts/AdminLayout.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - - - diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue deleted file mode 100644 index 996a049..0000000 --- a/frontend/src/layouts/MainLayout.vue +++ /dev/null @@ -1,126 +0,0 @@ - - - - - diff --git a/frontend/src/pages/ErrorNotFound.vue b/frontend/src/pages/ErrorNotFound.vue deleted file mode 100644 index e0ed15e..0000000 --- a/frontend/src/pages/ErrorNotFound.vue +++ /dev/null @@ -1,23 +0,0 @@ - diff --git a/frontend/src/pages/ErrorUnauthorized.vue b/frontend/src/pages/ErrorUnauthorized.vue deleted file mode 100644 index ac332ba..0000000 --- a/frontend/src/pages/ErrorUnauthorized.vue +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/frontend/src/pages/IndexPage.vue b/frontend/src/pages/IndexPage.vue deleted file mode 100644 index 858fa66..0000000 --- a/frontend/src/pages/IndexPage.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/frontend/src/pages/LoginPage.vue b/frontend/src/pages/LoginPage.vue deleted file mode 100644 index 777a033..0000000 --- a/frontend/src/pages/LoginPage.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - diff --git a/frontend/src/pages/PasswordResetPage.vue b/frontend/src/pages/PasswordResetPage.vue deleted file mode 100644 index ef560c6..0000000 --- a/frontend/src/pages/PasswordResetPage.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - diff --git a/frontend/src/pages/ProfilePage.vue b/frontend/src/pages/ProfilePage.vue deleted file mode 100644 index 64e3ea8..0000000 --- a/frontend/src/pages/ProfilePage.vue +++ /dev/null @@ -1,482 +0,0 @@ - - - diff --git a/frontend/src/pages/RegisterPage.vue b/frontend/src/pages/RegisterPage.vue deleted file mode 100644 index 6259564..0000000 --- a/frontend/src/pages/RegisterPage.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - diff --git a/frontend/src/pages/SecondPage.vue b/frontend/src/pages/SecondPage.vue deleted file mode 100644 index 87f4ce4..0000000 --- a/frontend/src/pages/SecondPage.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/frontend/src/pages/WelcomePage.vue b/frontend/src/pages/WelcomePage.vue deleted file mode 100644 index 690c50f..0000000 --- a/frontend/src/pages/WelcomePage.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/DashboardPage.vue b/frontend/src/pages/admin/DashboardPage.vue deleted file mode 100644 index cb4e93c..0000000 --- a/frontend/src/pages/admin/DashboardPage.vue +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/ProfilesPage.vue b/frontend/src/pages/admin/ProfilesPage.vue deleted file mode 100644 index a2298e9..0000000 --- a/frontend/src/pages/admin/ProfilesPage.vue +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - diff --git a/frontend/src/pages/admin/dialogs/CreateProfileDialog.vue b/frontend/src/pages/admin/dialogs/CreateProfileDialog.vue deleted file mode 100644 index d512772..0000000 --- a/frontend/src/pages/admin/dialogs/CreateProfileDialog.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/dialogs/EditProfileDialog.vue b/frontend/src/pages/admin/dialogs/EditProfileDialog.vue deleted file mode 100644 index c9f3493..0000000 --- a/frontend/src/pages/admin/dialogs/EditProfileDialog.vue +++ /dev/null @@ -1,261 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/dialogs/UpdatePasswordDialog.vue b/frontend/src/pages/admin/dialogs/UpdatePasswordDialog.vue deleted file mode 100644 index d661024..0000000 --- a/frontend/src/pages/admin/dialogs/UpdatePasswordDialog.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/dialogs/UpdateRoleDialog.vue b/frontend/src/pages/admin/dialogs/UpdateRoleDialog.vue deleted file mode 100644 index c3f88ac..0000000 --- a/frontend/src/pages/admin/dialogs/UpdateRoleDialog.vue +++ /dev/null @@ -1,78 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/dialogs/UpdateStatusDialog.vue b/frontend/src/pages/admin/dialogs/UpdateStatusDialog.vue deleted file mode 100644 index 63e0691..0000000 --- a/frontend/src/pages/admin/dialogs/UpdateStatusDialog.vue +++ /dev/null @@ -1,79 +0,0 @@ - - - diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts deleted file mode 100644 index c1f0816..0000000 --- a/frontend/src/router/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { defineRouter } from '#q-app'; -import { - createMemoryHistory, - createRouter, - createWebHashHistory, - createWebHistory, -} from 'vue-router'; - -import routes from './routes'; - -/* - * If not building with SSR mode, you can - * directly export the Router instantiation; - * - * The function below can be async too; either use - * async/await or return a Promise which resolves - * with the Router instance. - */ - -export default defineRouter((/* { store, ssrContext } */) => { - const createHistory = import.meta.env.QUASAR_SERVER - ? createMemoryHistory - : (import.meta.env.QUASAR_VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); - - const Router = createRouter({ - scrollBehavior: () => ({ left: 0, top: 0 }), - routes, - - // Leave this as is and make changes in quasar.conf.js instead! - // quasar.conf.js -> build -> vueRouterMode - // quasar.conf.js -> build -> publicPath - history: createHistory(import.meta.env.QUASAR_VUE_ROUTER_BASE) - }); - - return Router; -}); diff --git a/frontend/src/router/routes.ts b/frontend/src/router/routes.ts deleted file mode 100644 index a8992df..0000000 --- a/frontend/src/router/routes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -const routes: RouteRecordRaw[] = [ - { - path: '/', - component: () => import('@/layouts/MainLayout.vue'), - children: [ - { path: '', component: () => import('@/pages/IndexPage.vue') }, - { path: 'second', component: () => import('@/pages/SecondPage.vue') }, - { path: 'login', component: () => import('@/pages/LoginPage.vue') }, - { path: 'password-reset', component: () => import('@/pages/PasswordResetPage.vue') }, - { path: 'register', component: () => import('@/pages/RegisterPage.vue') }, - { path: 'welcome', component: () => import('@/pages/WelcomePage.vue') }, - { path: 'profile', component: () => import('@/pages/ProfilePage.vue') }, - { path: '401', component: () => import('@/pages/ErrorUnauthorized.vue') }, - ], - }, - { - path: '/admin', - component: () => import('@/layouts/AdminLayout.vue'), - children: [ - { path: 'dashboard', component: () => import('@/pages/admin/DashboardPage.vue') }, - { path: 'profiles', component: () => import('@/pages/admin/ProfilesPage.vue') } - ], - }, - // Always leave this as last one, - // but you can also remove it - { - path: '/:catchAll(.*)*', - component: () => import('@/pages/ErrorNotFound.vue'), - }, -]; - -export default routes; diff --git a/frontend/src/stores/admin-store.ts b/frontend/src/stores/admin-store.ts deleted file mode 100644 index 54b614a..0000000 --- a/frontend/src/stores/admin-store.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref, computed } from 'vue'; -import Client, { Local, admin as AdminNS, auth as AuthNS, profiles as ProfilesNS } from '@/encore/client'; -import { useProfilesStore } from '@/stores/profiles-store'; - -export type Profile = AdminNS.Profile; -export type ProfileParams = AdminNS.ProfileParams; -export type RegisterParams = AdminNS.RegisterParams; -export type PersonalData = AdminNS.PersonalData; -export type PersonalDataParams = AdminNS.PersonalDataParams; -export type UpdateProfilePasswordParams = AdminNS.UpdateProfilePasswordParams; -export type RoleOption = AuthNS.RoleOption; -export type StatusOption = ProfilesNS.StatusOption; - -export const useAdminStore = defineStore('admin', () => { - const profiles = ref([]); - const roles = ref([]); - const statuses = ref([]); - const loading = ref(false); - const error = ref(null); - - const profilesStore = useProfilesStore(); - - const client = new Client(Local, { - auth: () => (profilesStore.token ? { Authorization: `Bearer ${profilesStore.token}` } : undefined), - }); - - async function withLoading(fn: () => Promise): Promise { - loading.value = true; - error.value = null; - try { - return await fn(); - } catch (err) { - error.value = err instanceof Error ? err.message : String(err); - throw err; - } finally { - loading.value = false; - } - } - - /** Fetches all profiles into `profiles`. Requires the caller to be logged in as an admin. */ - async function listProfiles(): Promise { - return withLoading(async () => { - const res = await client.admin.ListProfiles(); - profiles.value = res.profiles; - return res.profiles; - }); - } - - /** Creates a new user profile with credentials. Requires the caller to be logged in as an admin. */ - async function insertProfile(params: RegisterParams): Promise { - return withLoading(async () => { - const res = await client.admin.InsertProfile(params); - profiles.value.push(res); - return res; - }); - } - - /** Fetches the valid roles and profile statuses into `roles` and `statuses`. */ - async function fetchSystemOptions(): Promise { - return withLoading(async () => { - const res = await client.admin.GetSystemOptions(); - roles.value = res.roles; - statuses.value = res.statuses; - }); - } - - /** Statuses that an admin can manually set on a profile. */ - const updatableStatuses = computed(() => statuses.value.filter((s) => s.updatable)); - - function replaceProfile(updated: Profile) { - const idx = profiles.value.findIndex((p) => p.user_id === updated.user_id); - if (idx !== -1) { - profiles.value[idx] = updated; - } - } - - /** Updates any user's profile. Requires the caller to be logged in as an admin. */ - async function updateProfile(userID: string, params: ProfileParams): Promise { - return withLoading(async () => { - const res = await client.admin.UpdateProfile(userID, params); - replaceProfile(res); - return res; - }); - } - - /** Updates the role of any user's profile. */ - async function updateProfileRole(userID: string, role: string): Promise { - return withLoading(async () => { - const res = await client.admin.UpdateProfileRole(userID, { role }); - replaceProfile(res); - return res; - }); - } - - /** Updates the status of any user's profile. */ - async function updateProfileStatus(userID: string, status: ProfilesNS.Status): Promise { - return withLoading(async () => { - const res = await client.admin.UpdateProfileStatus(userID, { status }); - replaceProfile(res); - return res; - }); - } - - /** Updates the password of any user's profile. */ - async function updateProfilePassword(userID: string, params: UpdateProfilePasswordParams): Promise { - return withLoading(async () => { - await client.admin.UpdateProfilePassword(userID, params); - }); - } - - /** Fetches the personal data of any user. Requires the caller to be logged in as an admin. */ - async function getPersonalData(userID: string): Promise { - return withLoading(async () => { - return await client.admin.GetPersonalData(userID); - }); - } - - /** Creates or replaces the personal data of any user. */ - async function upsertPersonalData(userID: string, params: PersonalDataParams): Promise { - return withLoading(async () => { - return await client.admin.UpsertPersonalData(userID, params); - }); - } - - /** Uploads an avatar image and returns its public URL. */ - async function uploadAvatar(image: Blob): Promise { - return withLoading(async () => { - const resp = await client.profiles.UploadAvatar('POST', image); - if (!resp.ok) { - throw new Error(`avatar upload failed (${resp.status})`); - } - const data = (await resp.json()) as { url: string }; - return data.url; - }); - } - - return { - // state - profiles, - roles, - statuses, - loading, - error, - // getters - updatableStatuses, - // actions - listProfiles, - fetchSystemOptions, - insertProfile, - updateProfile, - updateProfileRole, - updateProfileStatus, - updateProfilePassword, - getPersonalData, - upsertPersonalData, - uploadAvatar, - }; -}); diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts deleted file mode 100644 index 14c3df4..0000000 --- a/frontend/src/stores/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { defineStore } from '#q-app'; -import { createPinia } from 'pinia'; - -/* - * When adding new properties to stores, you should also - * extend the `PiniaCustomProperties` interface. - * @see https://pinia.vuejs.org/core-concepts/plugins.html#Typing-new-store-properties - */ -declare module 'pinia' { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - export interface PiniaCustomProperties { - // add your custom properties here, if any - } -} - -/* - * If not building with SSR mode, you can - * directly export the Store instantiation; - * - * The function below can be async too; either use - * async/await or return a Promise which resolves - * with the Store instance. - */ - -export default defineStore((/* { ssrContext } */) => { - const pinia = createPinia(); - - // You can add Pinia plugins here - // pinia.use(SomePiniaPlugin) - - return pinia; -}); diff --git a/frontend/src/stores/layout-store.ts b/frontend/src/stores/layout-store.ts deleted file mode 100644 index 021b990..0000000 --- a/frontend/src/stores/layout-store.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; - -export const useLayoutStore = defineStore('layout', () => { - const bodyWidth = ref(0); - const bodyHeight = ref(0); - - let bodyResizeObserver: ResizeObserver | null = null; - let listeners = 0; - - function updateBodySize() { - if (typeof document === 'undefined') { - return; - } - - const rect = document.body.getBoundingClientRect(); - bodyWidth.value = Math.floor(rect.width); - bodyHeight.value = Math.floor(rect.height); - } - - function startBodyResizeListener() { - listeners += 1; - - if (typeof window === 'undefined' || typeof document === 'undefined') { - return; - } - - updateBodySize(); - - if (bodyResizeObserver) { - return; - } - - bodyResizeObserver = new ResizeObserver(() => { - updateBodySize(); - }); - bodyResizeObserver.observe(document.body); - window.addEventListener('resize', updateBodySize); - } - - function stopBodyResizeListener() { - listeners = Math.max(0, listeners - 1); - - if (listeners > 0) { - return; - } - - bodyResizeObserver?.disconnect(); - bodyResizeObserver = null; - - if (typeof window !== 'undefined') { - window.removeEventListener('resize', updateBodySize); - } - } - - return { - bodyWidth, - bodyHeight, - startBodyResizeListener, - stopBodyResizeListener, - }; -}); diff --git a/frontend/src/stores/profiles-store.ts b/frontend/src/stores/profiles-store.ts deleted file mode 100644 index b4f9d68..0000000 --- a/frontend/src/stores/profiles-store.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { defineStore } from 'pinia'; -import { ref, computed } from 'vue'; -import { LocalStorage } from 'quasar'; -import Client, { Local, auth as AuthNS, profiles as ProfilesNS } from '@/encore/client'; - -export type Profile = ProfilesNS.Profile; -export type ProfileParams = ProfilesNS.ProfileParams; -export type RegisterParams = ProfilesNS.RegisterParams; -export type LoginParams = ProfilesNS.LoginParams; -export type LoginResponse = ProfilesNS.LoginResponse; -export type Status = ProfilesNS.Status; -export type PersonalData = ProfilesNS.PersonalData; -export type PersonalDataParams = ProfilesNS.PersonalDataParams; -export type SetPasswordParams = AuthNS.SetPasswordParams; - -/** Display info for each Status value, mirroring profiles/status.go. */ -export const STATUS_INFO: { label: string; color: string }[] = [ - { label: 'Active', color: 'positive' }, - { label: 'Inactive', color: 'grey' }, - { label: 'Suspended', color: 'warning' }, - { label: 'Deleted', color: 'negative' }, - { label: 'Pending', color: 'info' }, - { label: 'Waiting deletion', color: 'warning' }, - { label: 'Banned', color: 'negative' }, -]; - -export function statusInfo(status: Status): { label: string; color: string } { - return STATUS_INFO[status] ?? { label: `Unknown (${status})`, color: 'grey' }; -} - -const TOKEN_STORAGE_KEY = 'profiles.token'; - -export const useProfilesStore = defineStore('profiles', () => { - const token = ref(LocalStorage.getItem(TOKEN_STORAGE_KEY)); - const profile = ref(null); - const personalData = ref(null); - const loading = ref(false); - const error = ref(null); - - const isAuthenticated = computed(() => token.value !== null); - - const client = new Client(Local, { - auth: () => (token.value ? { Authorization: `Bearer ${token.value}` } : undefined), - }); - - async function withLoading(fn: () => Promise): Promise { - loading.value = true; - error.value = null; - try { - return await fn(); - } catch (err) { - error.value = err instanceof Error ? err.message : String(err); - throw err; - } finally { - loading.value = false; - } - } - - function setToken(newToken: string | null) { - token.value = newToken; - if (newToken) { - LocalStorage.set(TOKEN_STORAGE_KEY, newToken); - } else { - LocalStorage.remove(TOKEN_STORAGE_KEY); - } - } - - /** Logs in and fetches the resulting profile into `profile`. */ - async function login(params: LoginParams): Promise { - return withLoading(async () => { - const res = await client.profiles.Login(params); - setToken(res.token); - await me(); - return res; - }); - } - - /** Clears the local session. Does not call the auth service's Logout endpoint. */ - function logout() { - setToken(null); - profile.value = null; - personalData.value = null; - } - - /** Fetches the authenticated caller's profile, or null if not authenticated. */ - async function me(): Promise { - return withLoading(async () => { - const res = await client.profiles.Me(); - profile.value = res; - return res; - }); - } - - /** Registers a new profile (and credentials) and returns it. */ - async function register(params: RegisterParams): Promise { - return withLoading(async () => { - return await client.profiles.Insert(params); - }); - } - - /** Updates the authenticated caller's own profile. */ - async function update(params: ProfileParams): Promise { - return withLoading(async () => { - const res = await client.profiles.Update(params); - profile.value = res; - return res; - }); - } - - /** Deletes the authenticated caller's own profile and clears the session. */ - async function remove(): Promise { - return withLoading(async () => { - await client.profiles.Delete(); - logout(); - }); - } - - /** Fetches the authenticated caller's own personal data into `personalData`. */ - async function fetchPersonalData(): Promise { - return withLoading(async () => { - const res = await client.profiles.GetPersonalData(); - personalData.value = res; - return res; - }); - } - - /** Creates or replaces the authenticated caller's own personal data. */ - async function savePersonalData(params: PersonalDataParams): Promise { - return withLoading(async () => { - const res = await client.profiles.UpsertPersonalData(params); - personalData.value = res; - return res; - }); - } - - /** Changes the authenticated caller's password after verifying the current one. */ - async function setPassword(params: SetPasswordParams): Promise { - return withLoading(async () => { - await client.auth.SetPassword(params); - }); - } - - /** Uploads an avatar image and returns its public URL. */ - async function uploadAvatar(image: Blob): Promise { - return withLoading(async () => { - const resp = await client.profiles.UploadAvatar('POST', image); - if (!resp.ok) { - throw new Error(`avatar upload failed (${resp.status})`); - } - const data = (await resp.json()) as { url: string }; - return data.url; - }); - } - - return { - // state - token, - profile, - personalData, - loading, - error, - // getters - isAuthenticated, - // actions - login, - logout, - me, - register, - update, - remove, - fetchPersonalData, - savePersonalData, - setPassword, - uploadAvatar, - }; -}); diff --git a/frontend/tools/zod-sync.mjs b/frontend/tools/zod-sync.mjs deleted file mode 100644 index 2e4812e..0000000 --- a/frontend/tools/zod-sync.mjs +++ /dev/null @@ -1,327 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import ts from 'typescript'; - -const rootDir = process.cwd(); -const clientPath = path.join(rootDir, 'src/encore/client.ts'); -const outputPath = path.join(rootDir, 'src/encore/zod.ts'); - -const clientSource = fs.readFileSync(clientPath, 'utf8'); -const clientFile = ts.createSourceFile(clientPath, clientSource, ts.ScriptTarget.Latest, true); -const existingSource = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf8') : ''; -const existingFile = ts.createSourceFile(outputPath, existingSource, ts.ScriptTarget.Latest, true); - -const schemas = collectEncoreSchemas(clientFile); -const existingSchemas = collectExistingSchemas(existingFile, existingSource); -const generated = renderOutput(schemas, existingSchemas); - -fs.writeFileSync(outputPath, generated); -console.log(`Synced ${schemas.length} Zod schemas to ${path.relative(rootDir, outputPath)}`); - -function collectEncoreSchemas(sourceFile) { - const found = []; - const typeAliases = new Map(); - const writableParamTypes = collectWritableParamTypes(sourceFile); - - for (const statement of sourceFile.statements) { - if (!isExportedNamespace(statement)) continue; - const namespaceName = statement.name.text; - const body = statement.body; - if (!body || !ts.isModuleBlock(body)) continue; - - for (const child of body.statements) { - if (ts.isTypeAliasDeclaration(child) && isExported(child)) { - typeAliases.set(`${namespaceName}.${child.name.text}`, child.type); - } - } - } - - for (const statement of sourceFile.statements) { - if (!isExportedNamespace(statement)) continue; - const namespaceName = statement.name.text; - const body = statement.body; - if (!body || !ts.isModuleBlock(body)) continue; - - for (const child of body.statements) { - const qualifiedName = `${namespaceName}.${child.name?.text ?? ''}`; - if (!writableParamTypes.has(qualifiedName)) continue; - - if (ts.isInterfaceDeclaration(child) && isExported(child)) { - found.push({ - kind: 'object', - namespaceName, - typeName: child.name.text, - schemaName: schemaName(namespaceName, child.name.text), - fields: child.members - .filter(ts.isPropertySignature) - .map((member) => ({ - name: propertyName(member.name), - zod: zodForType(member.type, namespaceName, typeAliases), - })) - .filter((field) => field.name), - }); - } - - if (ts.isTypeAliasDeclaration(child) && isExported(child)) { - found.push({ - kind: 'alias', - namespaceName, - typeName: child.name.text, - schemaName: schemaName(namespaceName, child.name.text), - zod: zodForType(child.type, namespaceName, typeAliases), - }); - } - } - } - - return found; -} - -function collectWritableParamTypes(sourceFile) { - const found = new Set(); - - for (const statement of sourceFile.statements) { - if (!isExportedNamespace(statement)) continue; - const namespaceName = statement.name.text; - const body = statement.body; - if (!body || !ts.isModuleBlock(body)) continue; - - for (const child of body.statements) { - if (!ts.isClassDeclaration(child) || child.name?.text !== 'ServiceClient') continue; - - for (const member of child.members) { - if (!ts.isMethodDeclaration(member)) continue; - const httpMethod = writableHttpMethod(member); - if (!httpMethod) continue; - - const bodyParamName = jsonStringifiedParamName(member); - if (!bodyParamName) continue; - - const param = member.parameters.find((candidate) => propertyName(candidate.name) === bodyParamName); - if (!param?.type || !ts.isTypeReferenceNode(param.type)) continue; - - const ref = typeNameText(param.type.typeName); - const qualifiedRef = ref.includes('.') ? ref : `${namespaceName}.${ref}`; - found.add(qualifiedRef); - } - } - } - - return found; -} - -function writableHttpMethod(method) { - let result = ''; - - visit(method.body); - return result; - - function visit(node) { - if (result || !node) return; - - if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { - const callName = node.expression.name.text; - if (callName === 'callTypedAPI' || callName === 'callAPI') { - const [methodArg] = node.arguments; - if (methodArg && ts.isStringLiteral(methodArg) && ['POST', 'PUT'].includes(methodArg.text)) { - result = methodArg.text; - return; - } - } - } - - ts.forEachChild(node, visit); - } -} - -function jsonStringifiedParamName(method) { - let result = ''; - - visit(method.body); - return result; - - function visit(node) { - if (result || !node) return; - - if ( - ts.isCallExpression(node) && - ts.isPropertyAccessExpression(node.expression) && - ts.isIdentifier(node.expression.expression) && - node.expression.expression.text === 'JSON' && - node.expression.name.text === 'stringify' - ) { - const [arg] = node.arguments; - if (arg && ts.isIdentifier(arg)) { - result = arg.text; - return; - } - } - - ts.forEachChild(node, visit); - } -} - -function collectExistingSchemas(sourceFile, sourceText) { - const result = new Map(); - - for (const statement of sourceFile.statements) { - if (!ts.isVariableStatement(statement) || !isExported(statement)) continue; - - for (const declaration of statement.declarationList.declarations) { - if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue; - if (!declaration.name.text.endsWith('Schema')) continue; - - const objectLiteral = zodObjectLiteral(declaration.initializer); - if (!objectLiteral) continue; - - const fields = new Map(); - for (const prop of objectLiteral.properties) { - if (!ts.isPropertyAssignment(prop)) continue; - const name = propertyName(prop.name); - if (!name) continue; - fields.set(name, prop.initializer.getText(sourceFile)); - } - - result.set(declaration.name.text, { fields }); - } - } - - if (!sourceText.trim()) return result; - return result; -} - -function renderOutput(schemas, existingSchemas) { - const lines = [ - '// Code synced from src/encore/client.ts by frontend/tools/zod-sync.mjs.', - '// Existing field validators are preserved when this file is synced again.', - "import { z } from 'zod';", - '', - ]; - - for (const schema of schemas) { - if (schema.kind === 'alias') { - lines.push(`export const ${schema.schemaName} = ${schema.zod};`); - lines.push(''); - continue; - } - - const existing = existingSchemas.get(schema.schemaName); - const currentNames = new Set(schema.fields.map((field) => field.name)); - lines.push(`export const ${schema.schemaName} = z.object({`); - for (const field of schema.fields) { - const expression = existing?.fields.get(field.name) ?? field.zod; - lines.push(` ${quoteKey(field.name)}: ${expression},`); - } - for (const [name, expression] of existing?.fields ?? []) { - if (currentNames.has(name)) continue; - lines.push(` // TODO: no longer present in Encore type ${schema.namespaceName}.${schema.typeName}`); - lines.push(` ${quoteKey(name)}: ${expression},`); - } - lines.push('});'); - lines.push(''); - } - - return `${lines.join('\n').trimEnd()}\n`; -} - -function zodObjectLiteral(initializer) { - if (!ts.isCallExpression(initializer)) return null; - if (!ts.isPropertyAccessExpression(initializer.expression)) return null; - if (initializer.expression.name.text !== 'object') return null; - if (!ts.isIdentifier(initializer.expression.expression)) return null; - if (initializer.expression.expression.text !== 'z') return null; - const [arg] = initializer.arguments; - return arg && ts.isObjectLiteralExpression(arg) ? arg : null; -} - -function zodForType(type, namespaceName, typeAliases) { - if (!type) return 'z.unknown()'; - - if (type.kind === ts.SyntaxKind.StringKeyword) return 'z.string()'; - if (type.kind === ts.SyntaxKind.NumberKeyword) return 'z.number()'; - if (type.kind === ts.SyntaxKind.BooleanKeyword) return 'z.boolean()'; - if (type.kind === ts.SyntaxKind.AnyKeyword) return 'z.any()'; - if (type.kind === ts.SyntaxKind.UnknownKeyword) return 'z.unknown()'; - - if (ts.isArrayTypeNode(type)) { - return `z.array(${zodForType(type.elementType, namespaceName, typeAliases)})`; - } - - if (ts.isUnionTypeNode(type)) { - const literals = type.types.filter(ts.isLiteralTypeNode); - if (literals.length === type.types.length && literals.length > 0) { - return `z.union([${literals.map((literal) => zodLiteral(literal)).join(', ')}])`; - } - return 'z.unknown()'; - } - - if (ts.isTypeLiteralNode(type)) { - const fields = type.members - .filter(ts.isPropertySignature) - .map((member) => `${quoteKey(propertyName(member.name))}: ${zodForType(member.type, namespaceName, typeAliases)}`); - return `z.object({ ${fields.join(', ')} })`; - } - - if (ts.isTypeReferenceNode(type)) { - const ref = typeNameText(type.typeName); - const qualifiedRef = ref.includes('.') ? ref : `${namespaceName}.${ref}`; - const aliasType = typeAliases.get(qualifiedRef); - if (aliasType) return zodForType(aliasType, namespaceName, typeAliases); - return `z.lazy(() => ${schemaNameFromReference(ref, namespaceName)})`; - } - - return 'z.unknown()'; -} - -function zodLiteral(literal) { - const node = literal.literal; - if (ts.isStringLiteral(node)) return `z.literal(${JSON.stringify(node.text)})`; - if (ts.isNumericLiteral(node)) return `z.literal(${node.text})`; - if (node.kind === ts.SyntaxKind.TrueKeyword) return 'z.literal(true)'; - if (node.kind === ts.SyntaxKind.FalseKeyword) return 'z.literal(false)'; - return 'z.unknown()'; -} - -function schemaNameFromReference(ref, namespaceName) { - if (ref.includes('.')) { - const [ns, name] = ref.split('.'); - return schemaName(ns, name); - } - return schemaName(namespaceName, ref); -} - -function schemaName(namespaceName, typeName) { - return `${pascal(namespaceName)}${pascal(typeName)}Schema`; -} - -function propertyName(name) { - if (!name) return ''; - if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text; - return ''; -} - -function quoteKey(key) { - return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key); -} - -function typeNameText(name) { - if (ts.isIdentifier(name)) return name.text; - if (ts.isQualifiedName(name)) return `${typeNameText(name.left)}.${name.right.text}`; - return 'unknown'; -} - -function pascal(value) { - return value - .split(/[^A-Za-z0-9]+/) - .filter(Boolean) - .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`) - .join(''); -} - -function isExportedNamespace(node) { - return ts.isModuleDeclaration(node) && isExported(node) && ts.isIdentifier(node.name); -} - -function isExported(node) { - return Boolean(node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)); -} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json deleted file mode 100644 index f154205..0000000 --- a/frontend/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./.quasar/tsconfig.json" -} \ No newline at end of file diff --git a/profiles/login.go b/profiles/login.go index 045b3fb..f3e0761 100644 --- a/profiles/login.go +++ b/profiles/login.go @@ -3,7 +3,6 @@ package profiles import ( "context" "errors" - "sync" "time" "encore.app/auth" @@ -34,12 +33,6 @@ var loginRequests = pubsub.TopicRef[pubsub.Publisher[*auth.LoginRequested]](auth // auth.LoginRequests) and waits for the matching auth.LoginCompleted reply // on auth.LoginResults before responding to the caller. // -// NOTE: the wait is implemented with an in-process registry keyed by request -// ID, so the instance that publishes the request must be the one that -// receives the reply. That holds for local development (a single instance) -// but not for a horizontally scaled deployment, which would need a shared -// mechanism instead (e.g. a results table polled with retry_until). -// //encore:api public method=POST path=/auth/login func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) { var userID uuid.UUID @@ -58,9 +51,6 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) { return nil, errs.WrapCode(err, errs.Internal, "failed to generate request id") } - wait := loginWaiters.register(requestID) - defer loginWaiters.cancel(requestID) - if _, err := loginRequests.Publish(ctx, &auth.LoginRequested{ RequestID: requestID, UserID: userID, @@ -69,64 +59,47 @@ func Login(ctx context.Context, p *LoginParams) (*LoginResponse, error) { return nil, errs.WrapCode(err, errs.Internal, "failed to publish login request") } - select { - case result := <-wait: - if !result.OK { - return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid credentials"} + result, err := pollLoginResult(ctx, func(ctx context.Context) (*auth.LoginCompleted, error) { + var ok bool + var token uuid.UUID + var expiresAt time.Time + err := authDB.QueryRow(ctx, ` + SELECT ok, token, expires_at FROM login_results WHERE request_id = $1 + `, requestID).Scan(&ok, &token, &expiresAt) + if errors.Is(err, sqldb.ErrNoRows) { + return nil, sqldb.ErrNoRows + } + if err != nil { + return nil, errs.WrapCode(err, errs.Internal, "failed to read login result") + } + return &auth.LoginCompleted{RequestID: requestID, OK: ok, Token: token, ExpiresAt: expiresAt}, nil + }, loginTimeout, 50*time.Millisecond) + if err != nil { + return nil, err + } + if !result.OK { + return nil, &errs.Error{Code: errs.Unauthenticated, Message: "invalid credentials"} + } + return &LoginResponse{Token: result.Token, ExpiresAt: result.ExpiresAt}, nil +} + +func pollLoginResult(ctx context.Context, fetch func(context.Context) (*auth.LoginCompleted, error), timeout, interval time.Duration) (*auth.LoginCompleted, error) { + deadline := time.Now().Add(timeout) + for { + result, err := fetch(ctx) + if err == nil { + return result, nil + } + if !errors.Is(err, sqldb.ErrNoRows) { + return nil, err + } + if time.Now().After(deadline) { + return nil, &errs.Error{Code: errs.DeadlineExceeded, Message: "login timed out"} + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(interval): } - return &LoginResponse{Token: result.Token, ExpiresAt: result.ExpiresAt}, nil - case <-time.After(loginTimeout): - return nil, &errs.Error{Code: errs.DeadlineExceeded, Message: "login timed out"} - case <-ctx.Done(): - return nil, ctx.Err() - } -} - -var _ = pubsub.NewSubscription( - auth.LoginResults, "deliver-to-waiting-login", - pubsub.SubscriptionConfig[*auth.LoginCompleted]{ - Handler: func(ctx context.Context, ev *auth.LoginCompleted) error { - loginWaiters.deliver(ev) - return nil - }, - }, -) - -// loginWaiters lets the synchronous Login handler above wait for the -// asynchronous LoginCompleted reply that matches its RequestID. -var loginWaiters = newWaiterRegistry() - -type waiterRegistry struct { - mu sync.Mutex - waiting map[uuid.UUID]chan *auth.LoginCompleted -} - -func newWaiterRegistry() *waiterRegistry { - return &waiterRegistry{waiting: make(map[uuid.UUID]chan *auth.LoginCompleted)} -} - -func (r *waiterRegistry) register(id uuid.UUID) <-chan *auth.LoginCompleted { - ch := make(chan *auth.LoginCompleted, 1) - r.mu.Lock() - r.waiting[id] = ch - r.mu.Unlock() - return ch -} - -func (r *waiterRegistry) cancel(id uuid.UUID) { - r.mu.Lock() - delete(r.waiting, id) - r.mu.Unlock() -} - -func (r *waiterRegistry) deliver(ev *auth.LoginCompleted) { - r.mu.Lock() - ch, ok := r.waiting[ev.RequestID] - if ok { - delete(r.waiting, ev.RequestID) - } - r.mu.Unlock() - if ok { - ch <- ev } } diff --git a/profiles/login_test.go b/profiles/login_test.go new file mode 100644 index 0000000..fd785a7 --- /dev/null +++ b/profiles/login_test.go @@ -0,0 +1,65 @@ +package profiles + +import ( + "context" + "testing" + "time" + + "encore.app/auth" + "encore.dev/beta/errs" + "encore.dev/storage/sqldb" + "encore.dev/types/uuid" +) + +func TestPollLoginResultReturnsValueWhenReady(t *testing.T) { + requestID, err := uuid.NewV4() + if err != nil { + t.Fatalf("uuid.NewV4() failed: %v", err) + } + token, err := uuid.NewV4() + if err != nil { + t.Fatalf("uuid.NewV4() failed: %v", err) + } + + calls := 0 + result, err := pollLoginResult(context.Background(), func(context.Context) (*auth.LoginCompleted, error) { + calls++ + if calls < 3 { + return nil, sqldb.ErrNoRows + } + return &auth.LoginCompleted{ + RequestID: requestID, + OK: true, + Token: token, + ExpiresAt: time.Now().Add(time.Minute), + }, nil + }, 200*time.Millisecond, 10*time.Millisecond) + if err != nil { + t.Fatalf("pollLoginResult() returned error: %v", err) + } + if !result.OK { + t.Fatal("expected successful login result") + } + if result.RequestID != requestID { + t.Fatalf("request ID mismatch: got %v, want %v", result.RequestID, requestID) + } + if result.Token != token { + t.Fatalf("token mismatch: got %v, want %v", result.Token, token) + } +} + +func TestPollLoginResultTimesOut(t *testing.T) { + _, err := pollLoginResult(context.Background(), func(context.Context) (*auth.LoginCompleted, error) { + return nil, sqldb.ErrNoRows + }, 50*time.Millisecond, 10*time.Millisecond) + if err == nil { + t.Fatal("expected timeout error") + } + e, ok := err.(*errs.Error) + if !ok { + t.Fatalf("expected *errs.Error, got %T", err) + } + if e.Code != errs.DeadlineExceeded { + t.Fatalf("expected deadline exceeded error, got %v", e.Code) + } +} diff --git a/profiles/personal_data.go b/profiles/personal_data.go index e0bee9c..45d678d 100644 --- a/profiles/personal_data.go +++ b/profiles/personal_data.go @@ -3,6 +3,7 @@ package profiles import ( "context" "errors" + "strings" "time" "encore.dev/beta/auth" @@ -35,6 +36,28 @@ type PersonalDataParams struct { Country string `json:"country"` } +func (p *PersonalDataParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "personal data params are required"} + } + p.FirstName = normalizeText(p.FirstName) + p.LastName = normalizeText(p.LastName) + p.Address = normalizeText(p.Address) + p.City = normalizeText(p.City) + p.Country = strings.ToUpper(strings.TrimSpace(p.Country)) + p.Cap = strings.TrimSpace(p.Cap) + if p.FirstName == "" || p.LastName == "" || p.Address == "" || p.City == "" || p.Country == "" { + return &errs.Error{Code: errs.InvalidArgument, Message: "all personal data fields are required"} + } + if len(p.Cap) < 4 || len(p.Cap) > 10 { + return &errs.Error{Code: errs.InvalidArgument, Message: "postal code is invalid"} + } + if len(p.Country) != 2 { + return &errs.Error{Code: errs.InvalidArgument, Message: "country must be a 2-letter code"} + } + return nil +} + // GetPersonalData returns the authenticated user's own personal data. // //encore:api auth method=GET path=/profiles/personal-data @@ -61,6 +84,9 @@ func GetPersonalData(ctx context.Context) (*PersonalData, error) { // //encore:api auth method=PUT path=/profiles/personal-data func UpsertPersonalData(ctx context.Context, p *PersonalDataParams) (*PersonalData, error) { + if err := p.Validate(); err != nil { + return nil, err + } userID, err := authedUserID() if err != nil { return nil, err diff --git a/profiles/profiles.go b/profiles/profiles.go index b7cb852..fd536f3 100644 --- a/profiles/profiles.go +++ b/profiles/profiles.go @@ -4,6 +4,9 @@ package profiles import ( "context" "errors" + "net/mail" + "net/url" + "strings" "time" authsvc "encore.app/auth" @@ -18,6 +21,8 @@ var db = sqldb.NewDatabase("profiles", sqldb.DatabaseConfig{ Migrations: "./migrations", }) +var authDB = sqldb.Named("auth") + type Profile struct { UserID uuid.UUID `json:"user_id"` Email string `json:"email"` @@ -42,11 +47,74 @@ type RegisterParams struct { Password string `json:"password" encore:"sensitive"` } +func (p *RegisterParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "register params are required"} + } + email, err := normalizeEmailValue(p.Email) + if err != nil { + return err + } + p.Email = email + name := normalizeText(p.DisplayName) + if name == "" { + return &errs.Error{Code: errs.InvalidArgument, Message: "display name is required"} + } + p.DisplayName = name + if p.AvatarURL != "" { + if _, err := url.ParseRequestURI(p.AvatarURL); err != nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "avatar url is invalid"} + } + } + return authsvc.ValidatePassword(p.Password) +} + +func (p *ProfileParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "profile params are required"} + } + name := normalizeText(p.DisplayName) + if name == "" { + return &errs.Error{Code: errs.InvalidArgument, Message: "display name is required"} + } + if p.AvatarURL != "" { + if _, err := url.ParseRequestURI(p.AvatarURL); err != nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "avatar url is invalid"} + } + } + p.DisplayName = name + return nil +} + +func normalizeEmailValue(email string) (string, error) { + email = strings.TrimSpace(strings.ToLower(email)) + if email == "" { + return "", &errs.Error{Code: errs.InvalidArgument, Message: "email is required"} + } + parsed, err := mail.ParseAddress(email) + if err != nil { + return "", &errs.Error{Code: errs.InvalidArgument, Message: "invalid email"} + } + return strings.TrimSpace(strings.ToLower(parsed.Address)), nil +} + +func normalizeText(value string) string { + value = strings.TrimSpace(value) + if value == "" { + return "" + } + parts := strings.Fields(value) + return strings.Join(parts, " ") +} + // Insert registers a new profile with a server-generated UUID v4, delegating // credential setup to the auth service. // //encore:api public method=POST path=/profiles func Insert(ctx context.Context, p *RegisterParams) (*Profile, error) { + if err := p.Validate(); err != nil { + return nil, err + } userID, err := uuid.NewV4() if err != nil { return nil, errs.WrapCode(err, errs.Internal, "failed to generate profile id") @@ -73,6 +141,9 @@ func Insert(ctx context.Context, p *RegisterParams) (*Profile, error) { // //encore:api auth method=PUT path=/profiles func Update(ctx context.Context, p *ProfileParams) (*Profile, error) { + if err := p.Validate(); err != nil { + return nil, err + } uid, ok := auth.UserID() if !ok { return nil, &errs.Error{Code: errs.PermissionDenied, Message: "cannot act on another user's profile"} @@ -161,6 +232,10 @@ func requireSelf(userID uuid.UUID) error { return nil } +func normalizeProfileName(value string) string { + return normalizeText(value) +} + // isUniqueViolation reports whether err is a Postgres unique constraint violation. func isUniqueViolation(err error) bool { var pgErr *pgconn.PgError diff --git a/profiles/validation_test.go b/profiles/validation_test.go new file mode 100644 index 0000000..ef9a3b3 --- /dev/null +++ b/profiles/validation_test.go @@ -0,0 +1,36 @@ +package profiles + +import "testing" + +func TestProfileParamsValidate(t *testing.T) { + if err := (&ProfileParams{DisplayName: "", AvatarURL: ""}).Validate(); err == nil { + t.Fatal("Validate() should reject empty display name") + } + if err := (&ProfileParams{DisplayName: " Alice ", AvatarURL: "https://example.com/avatar.png"}).Validate(); err != nil { + t.Fatalf("Validate() returned unexpected error: %v", err) + } +} + +func TestRegisterParamsValidate(t *testing.T) { + if err := (&RegisterParams{Email: "", DisplayName: "Alice", Password: "validpass"}).Validate(); err == nil { + t.Fatal("Validate() should reject empty email") + } + if err := (&RegisterParams{Email: "alice@example.com", DisplayName: "Alice", Password: "weak"}).Validate(); err == nil { + t.Fatal("Validate() should reject weak password") + } + if err := (&RegisterParams{Email: " Alice@Example.com ", DisplayName: " Alice ", Password: "validpass"}).Validate(); err != nil { + t.Fatalf("Validate() returned unexpected error: %v", err) + } +} + +func TestPersonalDataParamsValidate(t *testing.T) { + if err := (&PersonalDataParams{FirstName: "", LastName: "Rossi", Address: "Main 1", Cap: "20100", City: "Milan", Country: "IT"}).Validate(); err == nil { + t.Fatal("Validate() should reject empty first name") + } + if err := (&PersonalDataParams{FirstName: "Mario", LastName: "Rossi", Address: "Main 1", Cap: "20", City: "Milan", Country: "IT"}).Validate(); err == nil { + t.Fatal("Validate() should reject invalid cap") + } + if err := (&PersonalDataParams{FirstName: " Mario ", LastName: " Rossi ", Address: " Main 1 ", Cap: "20100", City: " Milan ", Country: " IT "}).Validate(); err != nil { + t.Fatalf("Validate() returned unexpected error: %v", err) + } +} diff --git a/registration/registration.go b/registration/registration.go index dc47129..6a6ac12 100644 --- a/registration/registration.go +++ b/registration/registration.go @@ -9,6 +9,7 @@ import ( htmltemplate "html/template" "net" "net/mail" + "os" "strings" texttemplate "text/template" "time" @@ -31,11 +32,19 @@ var db = sqldb.NewDatabase("registration", sqldb.DatabaseConfig{ var profilesDB = sqldb.Named("profiles") const ( - welcomeTokenTTL = 24 * time.Hour - passwordResetTokenTTL = time.Hour - frontendBaseURL = "http://localhost:9000/#" + welcomeTokenTTL = 24 * time.Hour + passwordResetTokenTTL = time.Hour + defaultFrontendBaseURL = "http://localhost:9000/#" ) +var frontendBaseURL = defaultFrontendBaseURL + +func init() { + if value := strings.TrimSpace(os.Getenv("FRONTEND_BASE_URL")); value != "" { + frontendBaseURL = value + } +} + type RegisterParams struct { Email string `json:"email"` DisplayName string `json:"display_name"` @@ -57,6 +66,14 @@ type PasswordResetRequestParams struct { Email string `json:"email"` } +func (p *PasswordResetRequestParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "password reset request is required"} + } + _, err := normalizeEmail(p.Email) + return err +} + type PasswordResetRequestResponse struct { EmailSent bool `json:"email_sent"` } @@ -65,6 +82,13 @@ type PasswordResetConfirmParams struct { Password string `json:"password" encore:"sensitive"` } +func (p *PasswordResetConfirmParams) Validate() error { + if p == nil { + return &errs.Error{Code: errs.InvalidArgument, Message: "password reset confirm params are required"} + } + return authsvc.ValidatePassword(p.Password) +} + type PasswordResetConfirmResponse struct { Email string `json:"email"` Reset bool `json:"reset"` @@ -114,6 +138,18 @@ func CheckEmail(ctx context.Context, email string) (*EmailAvailabilityResponse, // //encore:api public method=POST path=/registration func Register(ctx context.Context, p *RegisterParams) (*RegisterResponse, error) { + if p == nil { + return nil, &errs.Error{Code: errs.InvalidArgument, Message: "register params are required"} + } + if err := (&profilessvc.RegisterParams{ + Email: p.Email, + DisplayName: p.DisplayName, + AvatarURL: p.AvatarURL, + Password: p.Password, + }).Validate(); err != nil { + return nil, err + } + pruneExpiredTokens(ctx) profile, err := profilessvc.Insert(ctx, &profilessvc.RegisterParams{ Email: p.Email, DisplayName: p.DisplayName, @@ -178,10 +214,14 @@ func ConfirmWelcome(ctx context.Context, token uuid.UUID) (*WelcomeResponse, err // //encore:api public method=POST path=/registration/password-reset func RequestPasswordReset(ctx context.Context, p *PasswordResetRequestParams) (*PasswordResetRequestResponse, error) { + if err := p.Validate(); err != nil { + return nil, err + } email, err := normalizeEmail(p.Email) if err != nil { return nil, err } + pruneExpiredTokens(ctx) var profile profilessvc.Profile err = profilesDB.QueryRow(ctx, ` @@ -226,6 +266,9 @@ func RequestPasswordReset(ctx context.Context, p *PasswordResetRequestParams) (* // //encore:api public method=POST path=/registration/password-reset/:token func ConfirmPasswordReset(ctx context.Context, token uuid.UUID, p *PasswordResetConfirmParams) (*PasswordResetConfirmResponse, error) { + if err := p.Validate(); err != nil { + return nil, err + } var userID uuid.UUID var email string var expiresAt time.Time @@ -285,7 +328,7 @@ func createPasswordResetToken(ctx context.Context, profile *profilessvc.Profile) } func normalizeEmail(email string) (string, error) { - email = strings.TrimSpace(email) + email = strings.TrimSpace(strings.ToLower(email)) if email == "" { return "", &errs.Error{Code: errs.InvalidArgument, Message: "email is required"} } @@ -293,7 +336,12 @@ func normalizeEmail(email string) (string, error) { if err != nil { return "", &errs.Error{Code: errs.InvalidArgument, Message: "invalid email"} } - return parsed.Address, nil + return strings.TrimSpace(strings.ToLower(parsed.Address)), nil +} + +func pruneExpiredTokens(ctx context.Context) { + _, _ = db.Exec(ctx, `DELETE FROM welcome_tokens WHERE expires_at < NOW() OR used_at IS NOT NULL`) + _, _ = db.Exec(ctx, `DELETE FROM password_reset_tokens WHERE expires_at < NOW() OR used_at IS NOT NULL`) } func emailDomain(email string) (string, bool) { diff --git a/registration/validation_test.go b/registration/validation_test.go new file mode 100644 index 0000000..bdfd264 --- /dev/null +++ b/registration/validation_test.go @@ -0,0 +1,26 @@ +package registration + +import "testing" + +func TestNormalizeEmail(t *testing.T) { + got, err := normalizeEmail(" Alice@Example.com ") + if err != nil { + t.Fatalf("normalizeEmail() returned unexpected error: %v", err) + } + if got != "alice@example.com" { + t.Fatalf("normalizeEmail() = %q, want %q", got, "alice@example.com") + } + + if _, err := normalizeEmail(" "); err == nil { + t.Fatal("normalizeEmail() should reject empty email") + } +} + +func TestPasswordResetConfirmParamsValidate(t *testing.T) { + if err := (&PasswordResetConfirmParams{Password: "weak"}).Validate(); err == nil { + t.Fatal("Validate() should reject weak password") + } + if err := (&PasswordResetConfirmParams{Password: "validpass"}).Validate(); err != nil { + t.Fatalf("Validate() returned unexpected error: %v", err) + } +}