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 @@
-
-
-
-
-
-
-
-
-
- {{ t('actions.chooseFile') }}
-
-
- {{ t('actions.camera') }}
-
-
- {{ t('actions.remove') }}
-
-
-
-
-
-
-
-
- {{ t('actions.camera') }}
-
-
-
-
-
- {{ cameraError }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('avatar.cropAvatar') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
![Crop source]()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ profileName }}
-
-
- {{ profile.email }}
-
-
-
-
-
-
- {{ t('actions.editProfile') }}
-
-
- {{ t('nav.logoutAction') }}
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
- {{ label }}
- {{ caption }}
-
-
-
-
-
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 @@
-
-
- {{ statusLabel }}
-
-
-
-
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 @@
-
-
-
-
-
-
-
- {{ t('app.title') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('pages.home') }}
-
-
-
-
-
-
-
- {{ t('admin.dashboard') }}
-
-
-
-
-
-
-
- {{ t('nav.profiles') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
- {{ t('app.title') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('admin.dashboard') }}
-
-
-
-
- Essential Links
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- 404
-
-
-
- {{ $t('pages.notFound') }}
-
-
-
-
-
-
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 @@
-
-
-
-
- 401
-
-
-
- {{ $t('pages.unauthorized') }}
-
-
-
- {{ $t('pages.unauthorizedHint') }}
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('login.title') }}
-
-
-
-
-
- {{ t('register.success') }}
-
-
-
-
-
-
-
- {{ t('login.forgotPassword') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ store.error }}
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ pageTitle }}
-
-
-
-
-
- {{ t('passwordReset.requestSent') }}
-
-
-
-
-
- {{ error }}
-
-
-
-
-
-
-
-
-
- {{ t('passwordReset.resetDone') }}
-
-
-
-
-
-
-
-
-
- {{ error }}
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
- {{ t('profile.title') }}
-
-
- {{ profilesStore.error }}
-
-
-
-
-
-
-
-
-
-
{{ profilesStore.profile.email }}
-
{{ displayValue(profileForm.display_name) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ profileFormErrors.avatar_url }}
-
-
-
-
-
-
-
- {{ t('fields.displayName') }}
- {{ displayValue(profileForm.display_name) }}
-
-
-
-
- {{ t('fields.email') }}
- {{ profilesStore.profile.email }}
-
-
-
-
- {{ t('fields.role') }}
- {{ profilesStore.profile.role }}
-
-
-
-
- {{ t('fields.status') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('fields.firstName') }}
- {{ displayValue(personalForm.first_name) }}
-
-
-
-
- {{ t('fields.lastName') }}
- {{ displayValue(personalForm.last_name) }}
-
-
-
-
- {{ t('fields.address') }}
- {{ displayValue(personalForm.address) }}
-
-
-
-
- {{ t('fields.cap') }}
- {{ displayValue(personalForm.cap) }}
-
-
-
-
- {{ t('fields.city') }}
- {{ displayValue(personalForm.city) }}
-
-
-
-
- {{ t('fields.country') }}
- {{ displayValue(countryLabel) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('profile.loginRequired') }}
-
-
-
-
-
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 @@
-
-
-
-
-
- {{ t('register.welcomeTitle') }}
-
-
-
-
- {{ t('register.confirmationSent') }}
-
-
-
-
-
-
- {{ t('register.title') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ error }}
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('welcome.title') }}
-
-
-
-
-
-
- {{ t('welcome.success', { email }) }}
-
-
-
- {{ error }}
-
-
-
- {{ t('welcome.missingToken') }}
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
- {{ t('admin.dashboard') }}
-
-
-
-
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 @@
-
-
-
-
{{ t('admin.profiles') }}
-
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('actions.newProfile') }}
-
-
-
-
-
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
{{ profile.email }}
-
{{ profile.role }} · {{ profile.user_id }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ editFormErrors.avatar_url }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('actions.updatePassword') }}
-
-
-
- {{ profile.email }}
-
-
-
-
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('actions.updateRole') }}
-
-
-
- {{ profile.email }}
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
-
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 @@
-
-
-
-
- {{ t('actions.updateStatus') }}
-
-
-
- {{ profile.email }}
-
-
-
-
- {{ adminStore.error }}
-
-
-
-
-
-
-
-
-
-
-
-
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)
+ }
+}